[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH V1 4/8] x86/nestedvmx: Reflect EPT holes hit during L2 emulation to L1


  • To: xen-devel@xxxxxxxxxxxxxxxxxxxx
  • From: chunjie.zhu@xxxxxxxxxx
  • Date: Mon, 26 Oct 2026 15:17:50 +0800
  • Arc-authentication-results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=citrix.com; dmarc=pass action=none header.from=citrix.com; dkim=pass header.d=citrix.com; arc=none
  • Arc-message-signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=+Qm6MOAPPFM5h91qqxn9EYsE3cBuyr47YCYET4IV7cU=; b=d0wQ3QqQ5g/S7uMZf658oWfRYeUJSs7/vKWo4nTBgxQsxYYTGwQ0NXzS6j4yZaT8yFlqCuqHSmsZZGR/E9uFHU3CyolyDyVdRSKcRTeoFpuVR3Opm/JYsURpmcoa1TzMuKILIwr3mcdMkLnH0RaE5QuHZ+BPSTPBPACnbMK/mVkUEW5Wy33PoEf3nubMeFFQ22Z+HEHwGZzmlYuBkKSH4C9suV+01Sy9UMh6J7QgkslJK40gJKy44YnZ+7tNj4WsX4PM07c6HdhMg6Xhyo+lb6Tp5lIT2Cs7A7Sf510rgjWe/OoAbLa6gxfPwNsArFED808YeG6c1oSSMCQ8L+U/qw==
  • Arc-seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=saGmxlO/jijHtQxfsdP0nnqaWw2U0j0xnwwZnjwtC+gzRkaz1r3Ja9fCey2cSwT79WuJnGYKi5DD0gcSJgqGzU1qWMia9F08jTko8aCdrKw+I0pLonkVlyMzIF6+yceuip/vffaNptCb5oLdO/NUvg4OkDqQmc520siHSWCq0sKHjn84dW6y4cGWoRIt5NED0GYTYkER5Vyk0Bz5VmjxEliYb/5QsZYGwLD1qeNTeYOx5Rfk+TSKHHteTEwf6clDh6/LVzspgewieyrySuFOXwtEpBOLXkppvhVH2nNJLD/4y8XmlROO4K1baQApDA9dmO5Z9L3SsXfKVRAJ8VezyQ==
  • Authentication-results: eu.smtp.expurgate.cloud; dkim=pass header.s=selector1 header.d=citrix.com header.i="@citrix.com" header.h="From:Date:Subject:Message-Id:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck"
  • Authentication-results: mx.microsoft.com 1; dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=citrix.com;
  • Cc: andrew.cooper3@xxxxxxxxxx, anthony.perard@xxxxxxxxxx, michal.orzel@xxxxxxx, jbeulich@xxxxxxxx, julien@xxxxxxx, roger@xxxxxxxxxxxxxx, sstabellini@xxxxxxxxxx, dpsmith@xxxxxxxxxxxxxxxxxxxx, Chunjie Zhu <chunjie.zhu@xxxxxxxxxx>
  • Delivery-date: Mon, 26 Oct 2026 07:18:48 +0000
  • List-id: Xen developer discussion <xen-devel.lists.xenproject.org>

From: Chunjie Zhu <chunjie.zhu@xxxxxxxxxx>

When L0 emulates an L2 instruction, e.g. a `movs` from MMIO, it
translates the other operand with paging_gva_to_gfn().  For an n2 vCPU
this walks L2's pagetables and then L1's EPT.  If L1's EPT has no
mapping for the resulting L2 GPA, the walk returns
NESTEDHVM_PAGEFAULT_INJECT and paging_gva_to_gfn() returns INVALID_GFN.
The emulator then injects #PF into L2, although L2's own pagetables map
the address.  L2 is blamed for a fault that is L1's to handle.

Reflect the hole to L1 as an EPT violation instead:

 * paging_gva_to_gfn() calls nvmx_reflect_emul_ept_violation() on
   INJECT.  If the current L2 exit is an EPT violation, it records the
   missing GPA and sets nv_vmexit_pending.
 * sync_vvmcs_ro() reports that GPA to L1 as the guest-physical address
   of the violation, together with the exit qualification computed by
   the L1 EPT walk.
 * hvm_inject_event() drops events for L2 while a virtual vmexit to L1
   is pending, so the emulator's #PF does not stay in vmcs02.

L1 can then fix its EPT and resume L2, which re-executes the
instruction.

Signed-off-by: Chunjie Zhu <chunjie.zhu@xxxxxxxxxx>
Assisted-by: Claude:claude-sonnet-5
---
 xen/arch/x86/hvm/hvm.c                  |  6 +++++
 xen/arch/x86/hvm/vmx/vvmx.c             | 30 +++++++++++++++++++++++++
 xen/arch/x86/include/asm/hvm/vmx/vvmx.h |  4 ++++
 xen/arch/x86/mm/p2m.c                   |  6 ++++-
 4 files changed, 45 insertions(+), 1 deletion(-)

diff --git a/xen/arch/x86/hvm/hvm.c b/xen/arch/x86/hvm/hvm.c
index ef9c7cd05777..b38f8dd68733 100644
--- a/xen/arch/x86/hvm/hvm.c
+++ b/xen/arch/x86/hvm/hvm.c
@@ -1801,6 +1801,12 @@ void hvm_inject_event(const struct x86_event *event)
         }
     }
 
+    /* L2 is about to exit to L1; an event for L2 would only be lost. */
+    if ( nestedhvm_enabled(curr->domain) &&
+         nestedhvm_vcpu_in_guestmode(curr) &&
+         vcpu_nestedhvm(curr).nv_vmexit_pending )
+        return;
+
     alternative_vcall(hvm_funcs.inject_event, event);
 }
 
diff --git a/xen/arch/x86/hvm/vmx/vvmx.c b/xen/arch/x86/hvm/vmx/vvmx.c
index 8d6e0c66f38e..dc8bcbeea033 100644
--- a/xen/arch/x86/hvm/vmx/vvmx.c
+++ b/xen/arch/x86/hvm/vmx/vvmx.c
@@ -1290,7 +1290,12 @@ static void sync_vvmcs_ro(struct vcpu *v)
     {
         set_vvmcs(v, EXIT_QUALIFICATION, nvmx->ept.exit_qual);
         set_vvmcs(v, VM_EXIT_REASON, nvmx->ept.exit_reason);
+
+        if ( nvmx->ept.gpa_valid )
+            set_vvmcs(v, GUEST_PHYSICAL_ADDRESS, nvmx->ept.gpa);
     }
+
+    nvmx->ept.gpa_valid = false;
 }
 
 static void load_vvmcs_host_state(struct vcpu *v)
@@ -2384,6 +2389,31 @@ int cf_check nvmx_hap_walk_L1_p2m(
     return rc;
 }
 
+/*
+ * L0 is emulating an L2 instruction triggered by an EPT violation, and L1's
+ * EPT has a hole for another address the instruction accesses.  Report that
+ * hole to L1 as the EPT violation, instead of blaming L2 with a #PF.  Called
+ * after nvmx_hap_walk_L1_p2m() returned NESTEDHVM_PAGEFAULT_INJECT.
+ */
+bool nvmx_reflect_emul_ept_violation(struct vcpu *v, paddr_t L2_gpa)
+{
+    struct nestedvmx *nvmx = &vcpu_2_nvmx(v);
+    unsigned long reason;
+
+    if ( v != current || !nestedhvm_vcpu_in_guestmode(v) )
+        return false;
+
+    __vmread(VM_EXIT_REASON, &reason);
+    if ( (uint16_t)reason != EXIT_REASON_EPT_VIOLATION )
+        return false;
+
+    nvmx->ept.gpa = L2_gpa;
+    nvmx->ept.gpa_valid = true;
+    vcpu_nestedhvm(v).nv_vmexit_pending = 1;
+
+    return true;
+}
+
 /*
  * As nvmx_hap_walk_L1_p2m(), for a walk of a specific nested p2m that is not
  * tied to a VM exit (e.g. L0 emulation reading L2's pagetables).  There is
diff --git a/xen/arch/x86/include/asm/hvm/vmx/vvmx.h 
b/xen/arch/x86/include/asm/hvm/vmx/vvmx.h
index 2e0ac5798d5d..92c8a8144527 100644
--- a/xen/arch/x86/include/asm/hvm/vmx/vvmx.h
+++ b/xen/arch/x86/include/asm/hvm/vmx/vvmx.h
@@ -38,6 +38,9 @@ struct nestedvmx {
         bool     enabled;
         uint32_t exit_reason;
         uint32_t exit_qual;
+        /* Violation raised by L0's emulation, not by the L2 access itself */
+        bool     gpa_valid;
+        paddr_t  gpa;
     } ept;
     uint32_t guest_vpid;
     struct list_head launched_list;
@@ -94,6 +97,7 @@ struct p2m_domain;
 int cf_check nvmx_hap_walk_L1_p2m(
     struct vcpu *v, paddr_t L2_gpa, paddr_t *L1_gpa, unsigned int *page_order,
     uint8_t *p2m_acc, struct npfec npfec);
+bool nvmx_reflect_emul_ept_violation(struct vcpu *v, paddr_t L2_gpa);
 int cf_check nvmx_hap_walk_L1_p2m2(
     struct p2m_domain *nested_p2m, paddr_t L2_gpa, paddr_t *L1_gpa,
     unsigned int *page_order, uint8_t *p2m_acc, struct npfec npfec);
diff --git a/xen/arch/x86/mm/p2m.c b/xen/arch/x86/mm/p2m.c
index 61b2d3fbd4df..ae13d36fdab7 100644
--- a/xen/arch/x86/mm/p2m.c
+++ b/xen/arch/x86/mm/p2m.c
@@ -25,6 +25,8 @@
 #include <asm/p2m.h>
 #include <asm/mem_sharing.h>
 #include <asm/hvm/nestedhvm.h>
+#include <asm/hvm/vmx/vmcs.h>
+#include <asm/hvm/vmx/vvmx.h>
 #include <asm/altp2m.h>
 #include <asm/vm_event.h>
 #include <xsm/xsm.h>
@@ -1675,7 +1677,7 @@ unsigned long paging_gva_to_gfn(struct vcpu *v,
             .write_access = *pfec & PFEC_write_access,
             .insn_fetch   = *pfec & PFEC_insn_fetch,
         };
-        int rv;
+        int rv = NESTEDHVM_PAGEFAULT_DONE;
         uint64_t base, gen;
 
         /* translate l2 guest va into l2 guest gfn */
@@ -1700,6 +1702,8 @@ unsigned long paging_gva_to_gfn(struct vcpu *v,
         {
             if ( base != p2m->np2m_base || gen != p2m->np2m_generation )
                 goto again;
+            if ( rv == NESTEDHVM_PAGEFAULT_INJECT && cpu_has_vmx )
+                nvmx_reflect_emul_ept_violation(v, pfn_to_paddr(l2_gfn));
             return gfn_x(INVALID_GFN);
         }
 
-- 
2.34.1




 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.