[PATCH v2 10/36] KVM: nVMX: Supports VMX tertiary controls and GUEST_APIC_TIMER bit

From: isaku . yamahata

Date: Thu Mar 05 2026 - 12:50:39 EST


From: Isaku Yamahata <isaku.yamahata@xxxxxxxxx>

Emulate MSR_IA32_VMX_PROCBASED_CTLS3 to advertise APIC timer virtualization
feature to the L2 guest.

Reported-by: syzbot+ci66a37fb2e2f8de71@xxxxxxxxxxxxxxxxxxxxxxxxx
Closes: https://lore.kernel.org/kvm/6982f952.050a0220.3b3015.0012.GAE@xxxxxxxxxx/
Signed-off-by: Isaku Yamahata <isaku.yamahata@xxxxxxxxx>
---
Changes:
v1 -> v2:
- When in-kernel lapic is disabled, disable nested apic timer
virtualization.
- disable apic timer virtualization bit in nested tertiary vm exec control
when in-kernel lapic is disabled.
---
arch/x86/kvm/vmx/capabilities.h | 1 +
arch/x86/kvm/vmx/hyperv.c | 7 +++++
arch/x86/kvm/vmx/nested.c | 48 +++++++++++++++++++++++++++++++++
arch/x86/kvm/vmx/nested.h | 11 ++++++++
arch/x86/kvm/vmx/vmx.c | 6 ++++-
arch/x86/kvm/x86.h | 2 +-
6 files changed, 73 insertions(+), 2 deletions(-)

diff --git a/arch/x86/kvm/vmx/capabilities.h b/arch/x86/kvm/vmx/capabilities.h
index c5cb098f579b..8d67be77f02c 100644
--- a/arch/x86/kvm/vmx/capabilities.h
+++ b/arch/x86/kvm/vmx/capabilities.h
@@ -47,6 +47,7 @@ struct nested_vmx_msrs {
u64 cr4_fixed1;
u64 vmcs_enum;
u64 vmfunc_controls;
+ u64 tertiary_ctls;
};

struct vmcs_config {
diff --git a/arch/x86/kvm/vmx/hyperv.c b/arch/x86/kvm/vmx/hyperv.c
index fa41d036acd4..2731c2e4b0e5 100644
--- a/arch/x86/kvm/vmx/hyperv.c
+++ b/arch/x86/kvm/vmx/hyperv.c
@@ -141,6 +141,13 @@ void nested_evmcs_filter_control_msr(struct kvm_vcpu *vcpu, u32 msr_index, u64 *
case MSR_IA32_VMX_PROCBASED_CTLS2:
ctl_high &= evmcs_get_supported_ctls(EVMCS_2NDEXEC);
break;
+ case MSR_IA32_VMX_PROCBASED_CTLS3:
+ /*
+ * tertiary procbased controls are 64-bit. 0 means unsupported,
+ * 1 supported.
+ */
+ *pdata &= evmcs_get_supported_ctls(EVMCS_3RDEXEC);
+ return;
case MSR_IA32_VMX_TRUE_PINBASED_CTLS:
case MSR_IA32_VMX_PINBASED_CTLS:
ctl_high &= evmcs_get_supported_ctls(EVMCS_PINCTRL);
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 5f0ac8acd768..be6b92b3c66a 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -218,6 +218,11 @@ static inline bool vmx_control_verify(u32 control, u32 low, u32 high)
return fixed_bits_valid(control, low, high);
}

+static inline bool vmx_control64_verify(u64 control, u64 msr)
+{
+ return !(control & ~msr);
+}
+
static inline u64 vmx_control_msr(u32 low, u32 high)
{
return low | ((u64)high << 32);
@@ -1511,6 +1516,25 @@ int vmx_set_vmx_msr(struct kvm_vcpu *vcpu, struct msr_data *msr_info)
case MSR_IA32_VMX_TRUE_ENTRY_CTLS:
case MSR_IA32_VMX_PROCBASED_CTLS2:
return vmx_restore_control_msr(vmx, msr_index, data);
+ case MSR_IA32_VMX_PROCBASED_CTLS3: {
+ u64 ctls3;
+
+ if (!__nested_cpu_supports_tertiary_ctls(&vmcs_config.nested))
+ return -EINVAL;
+
+ /* read-only for guest. */
+ if (!msr_info->host_initiated)
+ return -EINVAL;
+
+ ctls3 = vmcs_config.nested.tertiary_ctls;
+ if (!nested_cpu_can_support_apic_virt_timer(vcpu))
+ ctls3 &= ~TERTIARY_EXEC_GUEST_APIC_TIMER;
+
+ if (!vmx_control64_verify(data, ctls3))
+ return -EINVAL;
+ vmx->nested.msrs.tertiary_ctls = data;
+ return 0;
+ }
case MSR_IA32_VMX_MISC:
return vmx_restore_vmx_misc(vmx, data);
case MSR_IA32_VMX_CR0_FIXED0:
@@ -1608,6 +1632,16 @@ int vmx_get_vmx_msr(struct nested_vmx_msrs *msrs, struct msr_data *msr_info)
msrs->secondary_ctls_low,
msrs->secondary_ctls_high);
break;
+ case MSR_IA32_VMX_PROCBASED_CTLS3:
+ if (!__nested_cpu_supports_tertiary_ctls(&vmcs_config.nested))
+ return KVM_MSR_RET_UNSUPPORTED;
+
+ if (!msr_info->host_initiated &&
+ !__nested_cpu_supports_tertiary_ctls(msrs))
+ return -EINVAL;
+
+ *pdata = msrs->tertiary_ctls;
+ break;
case MSR_IA32_VMX_EPT_VPID_CAP:
*pdata = msrs->ept_caps |
((u64)msrs->vpid_caps << 32);
@@ -7285,6 +7319,18 @@ static void nested_vmx_setup_secondary_ctls(u32 ept_caps,
msrs->secondary_ctls_high |= SECONDARY_EXEC_ENCLS_EXITING;
}

+static void nested_vmx_setup_tertiary_ctls(struct vmcs_config *vmcs_conf,
+ struct nested_vmx_msrs *msrs)
+{
+ msrs->tertiary_ctls = vmcs_conf->cpu_based_3rd_exec_ctrl;
+
+ msrs->tertiary_ctls &= TERTIARY_EXEC_GUEST_APIC_TIMER;
+
+ if (msrs->tertiary_ctls)
+ msrs->procbased_ctls_high |=
+ CPU_BASED_ACTIVATE_TERTIARY_CONTROLS;
+}
+
static void nested_vmx_setup_misc_data(struct vmcs_config *vmcs_conf,
struct nested_vmx_msrs *msrs)
{
@@ -7373,6 +7419,8 @@ void nested_vmx_setup_ctls_msrs(struct vmcs_config *vmcs_conf, u32 ept_caps)

nested_vmx_setup_secondary_ctls(ept_caps, vmcs_conf, msrs);

+ nested_vmx_setup_tertiary_ctls(vmcs_conf, msrs);
+
nested_vmx_setup_misc_data(vmcs_conf, msrs);

nested_vmx_setup_basic(msrs);
diff --git a/arch/x86/kvm/vmx/nested.h b/arch/x86/kvm/vmx/nested.h
index d0257447b7cb..8c25054a710e 100644
--- a/arch/x86/kvm/vmx/nested.h
+++ b/arch/x86/kvm/vmx/nested.h
@@ -152,6 +152,17 @@ static inline bool nested_cpu_has_vmx_shadow_vmcs(struct kvm_vcpu *vcpu)
SECONDARY_EXEC_SHADOW_VMCS;
}

+static inline bool __nested_cpu_supports_tertiary_ctls(struct nested_vmx_msrs *msrs)
+{
+ return msrs->procbased_ctls_high & CPU_BASED_ACTIVATE_TERTIARY_CONTROLS;
+}
+
+/* APIC TIMER VIRTUALIZATION requires in-kernel lapic. */
+static inline bool nested_cpu_can_support_apic_virt_timer(struct kvm_vcpu *vcpu)
+{
+ return cpu_has_vmx_apic_timer_virt() && lapic_in_kernel(vcpu);
+}
+
static inline bool nested_cpu_has(struct vmcs12 *vmcs12, u32 bit)
{
return vmcs12->cpu_based_vm_exec_control & bit;
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 9177b693df1b..25f31103cb21 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -4985,9 +4985,13 @@ static void __vmx_vcpu_reset(struct kvm_vcpu *vcpu)
init_vmcs(vmx);

if (nested &&
- kvm_check_has_quirk(vcpu->kvm, KVM_X86_QUIRK_STUFF_FEATURE_MSRS))
+ kvm_check_has_quirk(vcpu->kvm, KVM_X86_QUIRK_STUFF_FEATURE_MSRS)) {
memcpy(&vmx->nested.msrs, &vmcs_config.nested, sizeof(vmx->nested.msrs));

+ if (!nested_cpu_can_support_apic_virt_timer(vcpu))
+ vmx->nested.msrs.tertiary_ctls &= ~TERTIARY_EXEC_GUEST_APIC_TIMER;
+ }
+
vcpu_setup_sgx_lepubkeyhash(vcpu);

vmx->nested.posted_intr_nv = -1;
diff --git a/arch/x86/kvm/x86.h b/arch/x86/kvm/x86.h
index 44a28d343d40..1de31aae9c6c 100644
--- a/arch/x86/kvm/x86.h
+++ b/arch/x86/kvm/x86.h
@@ -92,7 +92,7 @@ do { \
* associated feature that KVM supports for nested virtualization.
*/
#define KVM_FIRST_EMULATED_VMX_MSR MSR_IA32_VMX_BASIC
-#define KVM_LAST_EMULATED_VMX_MSR MSR_IA32_VMX_VMFUNC
+#define KVM_LAST_EMULATED_VMX_MSR MSR_IA32_VMX_PROCBASED_CTLS3

#define KVM_DEFAULT_PLE_GAP 128
#define KVM_VMX_DEFAULT_PLE_WINDOW 4096
--
2.45.2