]> xenbits.xensource.com Git - xen.git/commitdiff
x86/PV: check GDT/LDT limits during emulation
authorJan Beulich <jbeulich@suse.com>
Mon, 4 Nov 2019 14:22:29 +0000 (15:22 +0100)
committerJan Beulich <jbeulich@suse.com>
Mon, 4 Nov 2019 14:22:29 +0000 (15:22 +0100)
Accesses beyond the LDT limit originating from emulation would trigger
the ASSERT() in pv_map_ldt_shadow_page(). On production builds such
accesses would cause an attempt to promote the touched page (offset from
the present LDT base address) to a segment descriptor one. If this
happens to succeed, guest user mode would be able to elevate its
privileges to that of the guest kernel. This is particularly easy when
there's no LDT at all, in which case the LDT base stored internally to
Xen is simply zero.

Also adjust the ASSERT() that was triggering: It was off by one to
begin with, and for production builds we also better use
ASSERT_UNREACHABLE() instead with suitable recovery code afterwards.

This is XSA-298.

Reported-by: Andrew Cooper <andrew.cooper3@citrix.com>
Signed-off-by: Jan Beulich <jbeulich@suse.com>
Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>
master commit: 93021cbe880a8013691a48d0febef8ed7d3e3ebd
master date: 2019-10-31 16:08:16 +0100

xen/arch/x86/mm.c
xen/arch/x86/traps.c

index 7e51109e41123710d8308480e0a81050ed20aff3..5c9db3f89822cc4366fb80eb09f917755f59482f 100644 (file)
@@ -699,6 +699,18 @@ int map_ldt_shadow_page(unsigned int off)
 
     BUG_ON(unlikely(in_irq()));
 
+    /*
+     * Prior limit checking should guarantee this property.  NB. This is
+     * safe as updates to the LDT can only be made by MMUEXT_SET_LDT to the
+     * current vcpu, and vcpu_reset() will block until this vcpu has been
+     * descheduled before continuing.
+     */
+    if ( unlikely((off >> 3) >= v->arch.pv_vcpu.ldt_ents) )
+    {
+        ASSERT_UNREACHABLE();
+        return 0;
+    }
+
     if ( is_pv_32bit_domain(d) )
         gva = (u32)gva;
     guest_get_eff_kern_l1e(v, gva, &l1e);
index 232d1b05d4a168428fddcee0fdc0a0d522e3234a..3c1c4e2c2d169f007a7dffab990c1406dd5b8ae2 100644 (file)
@@ -1934,7 +1934,14 @@ static int read_descriptor(unsigned int sel,
 {
     struct desc_struct desc;
 
-    if ( sel < 4)
+    if ( sel < 4 ||
+         /*
+          * Don't apply the GDT limit here, as the selector may be a Xen
+          * provided one. __get_user() will fail (without taking further
+          * action) for ones falling in the gap between guest populated
+          * and Xen ones.
+          */
+         ((sel & 4) && (sel >> 3) >= v->arch.pv_vcpu.ldt_ents) )
         desc.b = desc.a = 0;
     else if ( __get_user(desc,
                          (const struct desc_struct *)(!(sel & 4)
@@ -1993,7 +2000,13 @@ static int read_gate_descriptor(unsigned int gate_sel,
         (!(gate_sel & 4) ? GDT_VIRT_START(v) : LDT_VIRT_START(v))
         + (gate_sel >> 3);
     if ( (gate_sel < 4) ||
-         ((gate_sel >= FIRST_RESERVED_GDT_BYTE) && !(gate_sel & 4)) ||
+         /*
+          * We're interested in call gates only, which occupy a single
+          * seg_desc_t for 32-bit and a consecutive pair of them for 64-bit.
+          */
+         ((gate_sel >> 3) + !is_pv_32bit_vcpu(v) >=
+          (gate_sel & 4 ? v->arch.pv_vcpu.ldt_ents
+                        : v->arch.pv_vcpu.gdt_ents)) ||
          __get_user(desc, pdesc) )
         return 0;
 
@@ -2012,7 +2025,7 @@ static int read_gate_descriptor(unsigned int gate_sel,
     if ( !is_pv_32bit_vcpu(v) )
     {
         if ( (*ar & 0x1f00) != 0x0c00 ||
-             (gate_sel >= FIRST_RESERVED_GDT_BYTE - 8 && !(gate_sel & 4)) ||
+             /* Limit check done above already. */
              __get_user(desc, pdesc + 1) ||
              (desc.b & 0x1f00) )
             return 0;