|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [PATCH v2 4/7] x86/nestedvmx: Load L2's EFER on VM entry and restore L1's on VM exit
From: Chunjie Zhu <chunjie.zhu@xxxxxxxxxx>
nvmx_update_entry_control() strips VM_ENTRY_LOAD_GUEST_EFER from the vmcs02
entry controls, and virtual_vmentry() only derives EFER.LMA/LME from
VM_ENTRY_IA32E_MODE. As a result:
* While L2 runs, hardware EFER is L0's own value (LME set), and since
vmx_update_guest_efer() clears the RDMSR intercept when guest_efer matches
the value it believes is loaded, L2's RDMSR of EFER is passed through and
returns LME=1 even though L2 never set it.
* L0's v->arch.hvm.guest_efer has LME cleared for a non-64bit L2, so L2 never
sets LME in L0's view
L2 Linux AP trampolines read EFER and skip the WRMSR if LME is already set.
L0 therefore never sees LME, and when L2 enables CR0.PG it is treated as
legacy PAE paging. PDPTEs are loaded from what is really a PML4, and VM
entry fails:
vmentry failure (reason 0x80000021): Invalid guest state (2)
PDPTE0 = 0x0000000023604063 EFER(VMCS) = 0
which crashes the L1 domain ("Unhandled nested vmexit: reason 2147483681").
Fix this by:
* Forcing VM_ENTRY_LOAD_GUEST_EFER in vmcs02 (when the CPU supports it), so
hardware EFER, and hence passthrough RDMSR, matches guest_efer.
* In virtual_vmentry(), taking guest_efer from vmcs12 GUEST_EFER when L1 set
VM_ENTRY_LOAD_GUEST_EFER, preserving LME for an unpaged L2, with LMA still
following IA32E_MODE. Then push it to hardware with
hvm_update_guest_efer().
* In virtual_vmexit(), restoring guest_efer from vmcs01 GUEST_EFER so that
L2's SCE/NXE/LME bits don't leak into L1.
Signed-off-by: Chunjie Zhu <chunjie.zhu@xxxxxxxxxx>
Assisted-by: Claude:claude-sonnet-5
---
xen/arch/x86/hvm/vmx/vvmx.c | 30 ++++++++++++++++++++++++++++--
1 file changed, 28 insertions(+), 2 deletions(-)
diff --git a/xen/arch/x86/hvm/vmx/vvmx.c b/xen/arch/x86/hvm/vmx/vvmx.c
index 0ec9eb6be450..e37f59bea7ce 100644
--- a/xen/arch/x86/hvm/vmx/vvmx.c
+++ b/xen/arch/x86/hvm/vmx/vvmx.c
@@ -695,6 +695,12 @@ static void nvmx_update_entry_control(struct vcpu *v)
shadow_cntrl &= ~(VM_ENTRY_LOAD_GUEST_PAT
| VM_ENTRY_LOAD_GUEST_EFER
| VM_ENTRY_LOAD_PERF_GLOBAL_CTRL);
+ /*
+ * Always load L2's EFER from v->arch.hvm.guest_efer, so hardware (and
+ * passthrough RDMSR) agrees with it rather than showing L0's own EFER.
+ */
+ if ( cpu_has_vmx_efer )
+ shadow_cntrl |= VM_ENTRY_LOAD_GUEST_EFER;
__vmwrite(VM_ENTRY_CONTROLS, shadow_cntrl);
}
@@ -1220,13 +1226,24 @@ static void virtual_vmentry(struct cpu_user_regs *regs)
lm_l1 = hvm_long_mode_active(v);
lm_l2 = !!(get_vvmcs(v, VM_ENTRY_CONTROLS) & VM_ENTRY_IA32E_MODE);
- if ( lm_l2 )
- v->arch.hvm.guest_efer |= EFER_LMA | EFER_LME;
+ if ( get_vvmcs(v, VM_ENTRY_CONTROLS) & VM_ENTRY_LOAD_GUEST_EFER )
+ {
+ /*
+ * L2 may have EFER.LME set while still unpaged (LMA clear), e.g. an
+ * AP trampoline which skips the WRMSR because it reads LME already
+ * set. Keep it, or enabling paging later is treated as legacy PAE.
+ */
+ v->arch.hvm.guest_efer = get_vvmcs(v, GUEST_EFER) & ~EFER_LMA;
+ }
else
v->arch.hvm.guest_efer &= ~(EFER_LMA | EFER_LME);
+ if ( lm_l2 )
+ v->arch.hvm.guest_efer |= EFER_LMA | EFER_LME;
+
load_shadow_control(v);
load_shadow_guest_state(v);
+ hvm_update_guest_efer(v);
if ( lm_l1 != lm_l2 )
paging_update_paging_modes(v);
@@ -1452,6 +1469,15 @@ static void virtual_vmexit(struct cpu_user_regs *regs)
lm_l2 = hvm_long_mode_active(v);
lm_l1 = !!(get_vvmcs(v, VM_EXIT_CONTROLS) & VM_EXIT_IA32E_MODE);
+ /* Don't leak L2's EFER (SCE, NXE, LME) into L1. */
+ if ( cpu_has_vmx_efer )
+ {
+ unsigned long efer;
+
+ __vmread(GUEST_EFER, &efer);
+ v->arch.hvm.guest_efer = efer;
+ }
+
if ( lm_l1 )
v->arch.hvm.guest_efer |= EFER_LMA | EFER_LME;
else
--
2.34.1
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |