* [PATCH] KVM: nVMX: Synthesize advanced VM-Exit info for emulated EPT Violations
@ 2026-10-29 2:40 Kevin Cheng
0 siblings, 0 replies; only message in thread
From: Kevin Cheng @ 2026-10-29 2:40 UTC (permalink / raw)
To: seanjc, pbonzini; +Cc: kvm, linux-kernel, yosry, Kevin Cheng
When synthesizing an EPT Violation to inject into L1 in response to a
fault detected while emulating an L2 GVA access, populate bits 11:9 of
EXIT_QUALIFICATION, i.e. the "advanced VM-Exit information", with the U/S,
R/W, and X/NX attributes of the L2 linear address' translation.
KVM has advertised VMX_EPT_ADVANCED_VMEXIT_INFO_BIT to L1 since
commit a0efd8cb221f ("KVM: nVMX: pass advanced EPT violation vmexit info
to guest"), but has never correctly populated bits 11:9 for synthesized
faults. Originally the bits were pulled from vmcs02.EXIT_QUALIFICATION,
which is stale and/or holds unrelated data when the fault didn't come
from an EPT Violation VM-Exit. Commit 96b067b59ad9 ("KVM: VMX:
Synthesize nested EPT violation GVA_IS_VALID/GVA_TRANSLATED bits")
correctly stopped consuming the stale vmcs02 data for synthesized faults,
but nothing in the synthesized path sets bits 11:9, and so L1 now always
observes a supervisor, read-only, executable translation for every
synthesized EPT Violation. Per the SDM, bits 11:9 are valid whenever
bits 7 and 8 (GVA_IS_VALID and GVA_TRANSLATED) are set and advanced
VM-Exit information is supported.
Fill in the bits from vmx_translate_nested_gpa(), which already receives
the effective L2 pte_access (for MBEC), rather than from the EPT walker
in paging_tmpl.h: the L2 page attributes are not EPT properties and so
are not visible to the EPT walker, and the nonpaging L2 case (CR0.PG=0,
where every linear address is architecturally user-mode, writable, and
executable) is handled for free as nonpaging_gva_to_gpa() passes ACC_ALL.
Skip the synthesis for EPT Misconfigs, as the walker doesn't initialize
exit_qualification for misconfigs and the injection path ignores it, and
leave bits 11:9 clear when GVA_TRANSLATED is clear, as the bits are
architecturally undefined in that case.
Explicitly strip bits 11:9 from the synthesized fault's exit qualification
in nested_ept_inject_page_fault() when L1 isn't advertised advanced
VM-Exit information. Previously that was implicitly guaranteed because
nothing set the bits in the synthesized fault, but now that
vmx_translate_nested_gpa() does, relying on the bits being outside the
"take from the fault" mask would leak them to L1.
Fixes: a0efd8cb221f ("KVM: nVMX: pass advanced EPT violation vmexit info to guest")
Signed-off-by: Kevin Cheng <chengkev@google.com>
Assisted-by: LLM
---
Tested on hardware with advanced VM-Exit information support by running
the vmx kvm-unit-tests, including a new ept_access_test_adv_info test
(will be sent separately) that exercises bits 11:9 for both native
accesses, where the bits are propagated from vmcs02, and emulated accesses
(string I/O), where KVM must synthesize the bits. The emulated cases fail
without this patch and pass with it.
Per Documentation/process/generated-content.rst: an LLM-based coding
assistant was used to help analyze the nested EPT fault-injection paths
and review the EXIT_QUALIFICATION bit handling. The code and changelog
were written, reviewed, and tested by the author.
arch/x86/kvm/mmu/paging_tmpl.h | 3 ++-
arch/x86/kvm/vmx/nested.c | 49 ++++++++++++++++++++++++++++++----
2 files changed, 46 insertions(+), 6 deletions(-)
diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h
index 8e350095508c..7023872b5ebb 100644
--- a/arch/x86/kvm/mmu/paging_tmpl.h
+++ b/arch/x86/kvm/mmu/paging_tmpl.h
@@ -508,7 +508,8 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
* out of date if it is serving an EPT misconfiguration.
* [5:3] - Calculated by the page walk of the guest EPT page tables
* [7:8] - Derived from "fault stage" access bits
- * [9:11] - Derived from [9:11] of real exit_qualification
+ * [9:11] - Filled in by the caller (vmx_translate_nested_gpa()) based
+ * on the L2 page table walk, if the fault is synthesized.
*
* The other bits are set to 0.
*/
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 9b0bfa2f854c..f7cee9b3a7dd 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -452,15 +452,22 @@ static void nested_ept_inject_page_fault(struct kvm_vcpu *vcpu,
vm_exit_reason = EXIT_REASON_EPT_MISCONFIG;
exit_qualification = 0;
} else {
+ const u64 adv_info = EPT_VIOLATION_GVA_USER |
+ EPT_VIOLATION_GVA_WRITABLE |
+ EPT_VIOLATION_GVA_NX;
u64 mask = EPT_VIOLATION_GVA_IS_VALID |
EPT_VIOLATION_GVA_TRANSLATED;
if (vmx->nested.msrs.ept_caps & VMX_EPT_ADVANCED_VMEXIT_INFO_BIT)
- mask |= EPT_VIOLATION_GVA_USER |
- EPT_VIOLATION_GVA_WRITABLE |
- EPT_VIOLATION_GVA_NX;
+ mask |= adv_info;
- exit_qualification = fault->exit_qualification & ~mask;
+ /*
+ * Always drop the advanced VM-Exit information bits from
+ * the synthesized fault, as they are undefined unless L1
+ * is advertised support, in which case they'll be filled
+ * in below from the appropriate source.
+ */
+ exit_qualification = fault->exit_qualification & ~(mask | adv_info);
/*
* Use the EXIT_QUALIFICATION from the VMCS if and only
@@ -7499,7 +7506,39 @@ static gpa_t vmx_translate_nested_gpa(struct kvm_vcpu *vcpu, gpa_t gpa,
if ((pte_access & ACC_USER_MASK) && (access & PFERR_GUEST_FINAL_MASK))
access |= PFERR_USER_MASK;
- return w->gva_to_gpa(vcpu, w, gpa, access, exception);
+ gpa = w->gva_to_gpa(vcpu, w, gpa, access, exception);
+ if (likely(gpa != INVALID_GPA) || !exception)
+ return gpa;
+
+ /*
+ * Synthesize the "advanced VM-Exit information" bits for EPT
+ * Violations that are the result of translating a linear address.
+ * Bits 11:9 of EXIT_QUALIFICATION report the U/S, R/W, and X/NX
+ * attributes of the linear address' translation, which are known only
+ * to the caller walking L2's page tables, i.e. are not visible to the
+ * EPT walker (nor should they be, they aren't EPT properties).
+ *
+ * Note, bits 11:9 are architecturally undefined if bit 8 is '0', i.e.
+ * if the access wasn't to the final translation, and must be '0' if
+ * L1 doesn't support advanced VM-Exit information (handled when the
+ * fault is injected into L1). Note #2, when L2 has paging disabled,
+ * every linear address is a user-mode, writable, and executable
+ * address, which is exactly what the nonpaging MMU passes in. Lastly,
+ * EPT Misconfigs don't have an EXIT_QUALIFICATION, don't touch it.
+ */
+ if ((exception->error_code & PFERR_RSVD_MASK) ||
+ !(exception->exit_qualification & EPT_VIOLATION_GVA_IS_VALID) ||
+ !(exception->exit_qualification & EPT_VIOLATION_GVA_TRANSLATED))
+ return gpa;
+
+ if (pte_access & ACC_USER_MASK)
+ exception->exit_qualification |= EPT_VIOLATION_GVA_USER;
+ if (pte_access & ACC_WRITE_MASK)
+ exception->exit_qualification |= EPT_VIOLATION_GVA_WRITABLE;
+ if (!(pte_access & ACC_EXEC_MASK))
+ exception->exit_qualification |= EPT_VIOLATION_GVA_NX;
+
+ return gpa;
}
struct kvm_x86_nested_ops vmx_nested_ops __initdata = {
--
2.56.0.313.g2479322e68-goog
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-10-29 2:40 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-29 2:40 [PATCH] KVM: nVMX: Synthesize advanced VM-Exit info for emulated EPT Violations Kevin Cheng
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®