diff mbox

[2/2] x86emul: support LAR/LSL/VERR/VERW

Message ID 584957810200007800126B28@prv-mh.provo.novell.com (mailing list archive)
State New, archived
Headers show

Commit Message

Jan Beulich Dec. 8, 2016, 11:52 a.m. UTC
Signed-off-by: Jan Beulich <jbeulich@suse.com>
x86emul: support LAR/LSL/VERR/VERW

Signed-off-by: Jan Beulich <jbeulich@suse.com>

--- a/tools/tests/x86_emulator/test_x86_emulator.c
+++ b/tools/tests/x86_emulator/test_x86_emulator.c
@@ -46,7 +46,47 @@ static int read(
     if ( verbose )
         printf("** %s(%u, %p,, %u,)\n", __func__, seg, (void *)offset, bytes);
 
-    bytes_read += bytes;
+    switch ( seg )
+    {
+        uint64_t value;
+
+    case x86_seg_gdtr:
+        /* Fake system segment type matching table index. */
+        if ( (offset & 7) || (bytes > 8) )
+            return X86EMUL_UNHANDLEABLE;
+#ifdef __x86_64__
+        if ( !(offset & 8) )
+        {
+            memset(p_data, 0, bytes);
+            return X86EMUL_OKAY;
+        }
+        value = (offset - 8) >> 4;
+#else
+        value = (offset - 8) >> 3;
+#endif
+        if ( value >= 0x10 )
+            return X86EMUL_UNHANDLEABLE;
+        value |= value << 40;
+        memcpy(p_data, &value, bytes);
+        return X86EMUL_OKAY;
+
+    case x86_seg_ldtr:
+        /* Fake user segment type matching table index. */
+        if ( (offset & 7) || (bytes > 8) )
+            return X86EMUL_UNHANDLEABLE;
+        value = offset >> 3;
+        if ( value >= 0x10 )
+            return X86EMUL_UNHANDLEABLE;
+        value |= (value | 0x10) << 40;
+        memcpy(p_data, &value, bytes);
+        return X86EMUL_OKAY;
+
+    default:
+        if ( !is_x86_user_segment(seg) )
+            return X86EMUL_UNHANDLEABLE;
+        bytes_read += bytes;
+        break;
+    }
     memcpy(p_data, (void *)offset, bytes);
     return X86EMUL_OKAY;
 }
@@ -75,6 +115,8 @@ static int write(
     if ( verbose )
         printf("** %s(%u, %p,, %u,)\n", __func__, seg, (void *)offset, bytes);
 
+    if ( !is_x86_user_segment(seg) )
+        return X86EMUL_UNHANDLEABLE;
     memcpy((void *)offset, p_data, bytes);
     return X86EMUL_OKAY;
 }
@@ -90,10 +132,24 @@ static int cmpxchg(
     if ( verbose )
         printf("** %s(%u, %p,, %u,)\n", __func__, seg, (void *)offset, bytes);
 
+    if ( !is_x86_user_segment(seg) )
+        return X86EMUL_UNHANDLEABLE;
     memcpy((void *)offset, new, bytes);
     return X86EMUL_OKAY;
 }
 
+static int read_segment(
+    enum x86_segment seg,
+    struct segment_register *reg,
+    struct x86_emulate_ctxt *ctxt)
+{
+    if ( !is_x86_user_segment(seg) )
+        return X86EMUL_UNHANDLEABLE;
+    memset(reg, 0, sizeof(*reg));
+    reg->attr.fields.p = 1;
+    return X86EMUL_OKAY;
+}
+
 static int cpuid(
     unsigned int *eax,
     unsigned int *ebx,
@@ -193,6 +249,21 @@ static int read_cr(
     return X86EMUL_UNHANDLEABLE;
 }
 
+static int read_msr(
+    unsigned int reg,
+    uint64_t *val,
+    struct x86_emulate_ctxt *ctxt)
+{
+    switch ( reg )
+    {
+    case 0xc0000080: /* EFER */
+        *val = ctxt->addr_size > 32 ? 0x500 /* LME|LMA */ : 0;
+        return X86EMUL_OKAY;
+    }
+
+    return X86EMUL_UNHANDLEABLE;
+}
+
 int get_fpu(
     void (*exception_callback)(void *, struct cpu_user_regs *),
     void *exception_callback_arg,
@@ -223,8 +294,10 @@ static struct x86_emulate_ops emulops =
     .insn_fetch = fetch,
     .write      = write,
     .cmpxchg    = cmpxchg,
+    .read_segment = read_segment,
     .cpuid      = cpuid,
     .read_cr    = read_cr,
+    .read_msr   = read_msr,
     .get_fpu    = get_fpu,
 };
 
@@ -726,6 +799,156 @@ int main(int argc, char **argv)
         goto fail;
     printf("okay\n");
 
+    printf("%-40s", "Testing lar (null selector)...");
+    instr[0] = 0x0f; instr[1] = 0x02; instr[2] = 0xc1;
+    regs.eflags = 0x240;
+    regs.eip    = (unsigned long)&instr[0];
+    regs.ecx    = 0;
+    regs.eax    = 0x11111111;
+    rc = x86_emulate(&ctxt, &emulops);
+    if ( (rc != X86EMUL_OKAY) ||
+         (regs.eax != 0x11111111) ||
+         (regs.eflags != 0x200) ||
+         (regs.eip != (unsigned long)&instr[3]) )
+        goto fail;
+    printf("okay\n");
+
+    printf("%-40s", "Testing lsl (null selector)...");
+    instr[0] = 0x0f; instr[1] = 0x03; instr[2] = 0xca;
+    regs.eflags = 0x240;
+    regs.eip    = (unsigned long)&instr[0];
+    regs.edx    = 0;
+    regs.ecx    = 0x11111111;
+    rc = x86_emulate(&ctxt, &emulops);
+    if ( (rc != X86EMUL_OKAY) ||
+         (regs.ecx != 0x11111111) ||
+         (regs.eflags != 0x200) ||
+         (regs.eip != (unsigned long)&instr[3]) )
+        goto fail;
+    printf("okay\n");
+
+    printf("%-40s", "Testing verr (null selector)...");
+    instr[0] = 0x0f; instr[1] = 0x00; instr[2] = 0x21;
+    regs.eflags = 0x240;
+    regs.eip    = (unsigned long)&instr[0];
+    regs.ecx    = (unsigned long)res;
+    *res        = 0;
+    rc = x86_emulate(&ctxt, &emulops);
+    if ( (rc != X86EMUL_OKAY) ||
+         (regs.eflags != 0x200) ||
+         (regs.eip != (unsigned long)&instr[3]) )
+        goto fail;
+    printf("okay\n");
+
+    printf("%-40s", "Testing verw (null selector)...");
+    instr[0] = 0x0f; instr[1] = 0x00; instr[2] = 0x2a;
+    regs.eflags = 0x240;
+    regs.eip    = (unsigned long)&instr[0];
+    regs.ecx    = 0;
+    regs.edx    = (unsigned long)res;
+    rc = x86_emulate(&ctxt, &emulops);
+    if ( (rc != X86EMUL_OKAY) ||
+         (regs.eflags != 0x200) ||
+         (regs.eip != (unsigned long)&instr[3]) )
+        goto fail;
+    printf("okay\n");
+
+    printf("%-40s", "Testing lar/lsl/verr/verw (all types)...");
+    for ( i = 0; i < 0x20; ++i )
+    {
+        unsigned int sel = i < 0x10 ?
+#ifndef __x86_64__
+                                      (i << 3) + 8
+#else
+                                      (i << 4) + 8
+#endif
+                                    : ((i - 0x10) << 3) | 4;
+        bool failed;
+
+#ifndef __x86_64__
+# define LAR_VALID 0xffff1a3eU
+# define LSL_VALID 0xffff0a0eU
+#else
+# define LAR_VALID 0xffff1a04U
+# define LSL_VALID 0xffff0a04U
+#endif
+#define VERR_VALID 0xccff0000U
+#define VERW_VALID 0x00cc0000U
+
+        instr[0] = 0x0f; instr[1] = 0x02; instr[2] = 0xc2;
+        regs.eflags = (LAR_VALID >> i) & 1 ? 0x200 : 0x240;
+        regs.eip    = (unsigned long)&instr[0];
+        regs.edx    = sel;
+        regs.eax    = 0x11111111;
+        rc = x86_emulate(&ctxt, &emulops);
+        if ( (rc != X86EMUL_OKAY) ||
+             (regs.eip != (unsigned long)&instr[3]) )
+            goto fail;
+        if ( (LAR_VALID >> i) & 1 )
+            failed = (regs.eflags != 0x240) ||
+                     ((regs.eax & 0xf0ff00) != (i << 8));
+        else
+            failed = (regs.eflags != 0x200) ||
+                     (regs.eax != 0x11111111);
+        if ( failed )
+        {
+            printf("LAR %04x (type %02x) ", sel, i);
+            goto fail;
+        }
+
+        instr[0] = 0x0f; instr[1] = 0x03; instr[2] = 0xd1;
+        regs.eflags = (LSL_VALID >> i) & 1 ? 0x200 : 0x240;
+        regs.eip    = (unsigned long)&instr[0];
+        regs.ecx    = sel;
+        regs.edx    = 0x11111111;
+        rc = x86_emulate(&ctxt, &emulops);
+        if ( (rc != X86EMUL_OKAY) ||
+             (regs.eip != (unsigned long)&instr[3]) )
+            goto fail;
+        if ( (LSL_VALID >> i) & 1 )
+            failed = (regs.eflags != 0x240) ||
+                     (regs.edx != (i & 0xf));
+        else
+            failed = (regs.eflags != 0x200) ||
+                     (regs.edx != 0x11111111);
+        if ( failed )
+        {
+            printf("LSL %04x (type %02x) ", sel, i);
+            goto fail;
+        }
+
+        instr[0] = 0x0f; instr[1] = 0x00; instr[2] = 0xe2;
+        regs.eflags = (VERR_VALID >> i) & 1 ? 0x200 : 0x240;
+        regs.eip    = (unsigned long)&instr[0];
+        regs.ecx    = 0;
+        regs.edx    = sel;
+        rc = x86_emulate(&ctxt, &emulops);
+        if ( (rc != X86EMUL_OKAY) ||
+             (regs.eip != (unsigned long)&instr[3]) )
+            goto fail;
+        if ( regs.eflags != ((VERR_VALID >> i) & 1 ? 0x240 : 0x200) )
+        {
+            printf("VERR %04x (type %02x) ", sel, i);
+            goto fail;
+        }
+
+        instr[0] = 0x0f; instr[1] = 0x00; instr[2] = 0xe9;
+        regs.eflags = (VERW_VALID >> i) & 1 ? 0x200 : 0x240;
+        regs.eip    = (unsigned long)&instr[0];
+        regs.ecx    = sel;
+        regs.edx    = 0;
+        rc = x86_emulate(&ctxt, &emulops);
+        if ( (rc != X86EMUL_OKAY) ||
+             (regs.eip != (unsigned long)&instr[3]) )
+            goto fail;
+        if ( regs.eflags != ((VERW_VALID >> i) & 1 ? 0x240 : 0x200) )
+        {
+            printf("VERW %04x (type %02x) ", sel, i);
+            goto fail;
+        }
+    }
+    printf("okay\n");
+
 #define decl_insn(which) extern const unsigned char which[], which##_len[]
 #define put_insn(which, insn) ".pushsection .test, \"ax\", @progbits\n" \
                               #which ": " insn "\n"                     \
--- a/xen/arch/x86/x86_emulate/x86_emulate.c
+++ b/xen/arch/x86/x86_emulate/x86_emulate.c
@@ -182,7 +182,7 @@ static const opcode_desc_t opcode_table[
 
 static const opcode_desc_t twobyte_table[256] = {
     /* 0x00 - 0x07 */
-    ModRM, ImplicitOps|ModRM, ModRM, ModRM,
+    ModRM, ImplicitOps|ModRM, DstReg|SrcMem16|ModRM, DstReg|SrcMem16|ModRM,
     0, ImplicitOps, ImplicitOps, ImplicitOps,
     /* 0x08 - 0x0F */
     ImplicitOps, ImplicitOps, 0, ImplicitOps,
@@ -1384,7 +1384,7 @@ protmode_load_seg(
     }
 
     /* System segment descriptors must reside in the GDT. */
-    if ( !is_x86_user_segment(seg) && (sel & 4) )
+    if ( is_x86_system_segment(seg) && (sel & 4) )
         goto raise_exn;
 
     switch ( rc = ops->read(sel_seg, sel & 0xfff8, &desc, sizeof(desc), ctxt) )
@@ -1401,14 +1401,11 @@ protmode_load_seg(
         return rc;
     }
 
-    if ( !is_x86_user_segment(seg) )
-    {
-        /* System segments must have S flag == 0. */
-        if ( desc.b & (1u << 12) )
-            goto raise_exn;
-    }
+    /* System segments must have S flag == 0. */
+    if ( is_x86_system_segment(seg) && (desc.b & (1u << 12)) )
+        goto raise_exn;
     /* User segments must have S flag == 1. */
-    else if ( !(desc.b & (1u << 12)) )
+    if ( is_x86_user_segment(seg) && !(desc.b & (1u << 12)) )
         goto raise_exn;
 
     dpl = (desc.b >> 13) & 3;
@@ -1470,10 +1467,17 @@ protmode_load_seg(
              ((dpl < cpl) || (dpl < rpl)) )
             goto raise_exn;
         break;
+    case x86_seg_none:
+        /* Non-conforming segment: check DPL against RPL and CPL. */
+        if ( ((desc.b & (0x1c << 8)) != (0x1c << 8)) &&
+             ((dpl < cpl) || (dpl < rpl)) )
+            return X86EMUL_EXCEPTION;
+        a_flag = 0;
+        break;
     }
 
     /* Segment present in memory? */
-    if ( !(desc.b & (1 << 15)) )
+    if ( !(desc.b & (1 << 15)) && seg != x86_seg_none )
     {
         fault_type = seg != x86_seg_ss ? EXC_NP : EXC_SS;
         goto raise_exn;
@@ -1481,7 +1485,7 @@ protmode_load_seg(
 
     if ( !is_x86_user_segment(seg) )
     {
-        int lm = in_longmode(ctxt, ops);
+        int lm = (desc.b & (1u << 12)) ? 0 : in_longmode(ctxt, ops);
 
         if ( lm < 0 )
             return X86EMUL_UNHANDLEABLE;
@@ -1501,7 +1505,8 @@ protmode_load_seg(
                 return rc;
             }
             if ( (desc_hi.b & 0x00001f00) ||
-                 !is_canonical_address((uint64_t)desc_hi.a << 32) )
+                 (seg != x86_seg_none &&
+                  !is_canonical_address((uint64_t)desc_hi.a << 32)) )
                 goto raise_exn;
         }
     }
@@ -1544,7 +1549,8 @@ protmode_load_seg(
     return X86EMUL_OKAY;
 
  raise_exn:
-    generate_exception(fault_type, sel & 0xfffc);
+    generate_exception_if(seg != x86_seg_none, fault_type, sel & 0xfffc);
+    rc = X86EMUL_EXCEPTION;
  done:
     return rc;
 }
@@ -4424,6 +4430,28 @@ x86_emulate(
             if ( (rc = load_seg(seg, src.val, 0, NULL, ctxt, ops)) != 0 )
                 goto done;
             break;
+        case 4: /* verr / verw */
+            _regs.eflags &= ~EFLG_ZF;
+            switch ( rc = protmode_load_seg(x86_seg_none, src.val, false,
+                                            &sreg, ctxt, ops) )
+            {
+            case X86EMUL_OKAY:
+                if ( sreg.attr.fields.s &&
+                     ((modrm_reg & 1) ? ((sreg.attr.fields.type & 0xa) == 0x2)
+                                      : ((sreg.attr.fields.type & 0xa) != 0x8)) )
+                    _regs.eflags |= EFLG_ZF;
+                break;
+            case X86EMUL_EXCEPTION:
+                if ( ctxt->event_pending )
+                {
+            default:
+                    goto done;
+                }
+                /* Instead of the exception, ZF remains cleared. */
+                rc = X86EMUL_OKAY;
+                break;
+            }
+            break;
         default:
             generate_exception_if(true, EXC_UD);
             break;
@@ -4621,6 +4649,96 @@ x86_emulate(
         break;
     }
 
+    case X86EMUL_OPC(0x0f, 0x02): /* lar */
+        generate_exception_if(!in_protmode(ctxt, ops), EXC_UD);
+        _regs.eflags &= ~EFLG_ZF;
+        switch ( rc = protmode_load_seg(x86_seg_none, src.val, false, &sreg,
+                                        ctxt, ops) )
+        {
+        case X86EMUL_OKAY:
+            if ( !sreg.attr.fields.s )
+            {
+                switch ( sreg.attr.fields.type )
+                {
+                case 0x01: /* available 16-bit TSS */
+                case 0x03: /* busy 16-bit TSS */
+                case 0x04: /* 16-bit call gate */
+                case 0x05: /* 16/32-bit task gate */
+                    if ( in_longmode(ctxt, ops) )
+                        break;
+                    /* fall through */
+                case 0x02: /* LDT */
+                case 0x09: /* available 32/64-bit TSS */
+                case 0x0b: /* busy 32/64-bit TSS */
+                case 0x0c: /* 32/64-bit call gate */
+                    _regs.eflags |= EFLG_ZF;
+                    break;
+                }
+            }
+            else
+                _regs.eflags |= EFLG_ZF;
+            break;
+        case X86EMUL_EXCEPTION:
+            if ( ctxt->event_pending )
+            {
+        default:
+                goto done;
+            }
+            /* Instead of the exception, ZF remains cleared. */
+            rc = X86EMUL_OKAY;
+            break;
+        }
+        if ( _regs.eflags & EFLG_ZF )
+            dst.val = ((sreg.attr.bytes & 0xff) << 8) |
+                      ((sreg.limit >> (sreg.attr.fields.g ? 12 : 0)) &
+                       0xf0000) |
+                      ((sreg.attr.bytes & 0xf00) << 12);
+        else
+            dst.type = OP_NONE;
+        break;
+
+    case X86EMUL_OPC(0x0f, 0x03): /* lsl */
+        generate_exception_if(!in_protmode(ctxt, ops), EXC_UD);
+        _regs.eflags &= ~EFLG_ZF;
+        switch ( rc = protmode_load_seg(x86_seg_none, src.val, false, &sreg,
+                                        ctxt, ops) )
+        {
+        case X86EMUL_OKAY:
+            if ( !sreg.attr.fields.s )
+            {
+                switch ( sreg.attr.fields.type )
+                {
+                case 0x01: /* available 16-bit TSS */
+                case 0x03: /* busy 16-bit TSS */
+                    if ( in_longmode(ctxt, ops) )
+                        break;
+                    /* fall through */
+                case 0x02: /* LDT */
+                case 0x09: /* available 32/64-bit TSS */
+                case 0x0b: /* busy 32/64-bit TSS */
+                    _regs.eflags |= EFLG_ZF;
+                    break;
+                }
+            }
+            else
+                _regs.eflags |= EFLG_ZF;
+            break;
+        case X86EMUL_EXCEPTION:
+            if ( ctxt->event_pending )
+            {
+        default:
+                goto done;
+            }
+            /* Instead of the exception, ZF remains cleared. */
+            rc = X86EMUL_OKAY;
+            break;
+        }
+        if ( _regs.eflags & EFLG_ZF )
+            dst.val = sreg.limit;
+        else
+            dst.type = OP_NONE;
+        break;
+
     case X86EMUL_OPC(0x0f, 0x05): /* syscall */ {
         uint64_t msr_content;

Comments

Andrew Cooper Dec. 8, 2016, 4:24 p.m. UTC | #1
On 08/12/16 11:52, Jan Beulich wrote:
> --- a/xen/arch/x86/x86_emulate/x86_emulate.c
> +++ b/xen/arch/x86/x86_emulate/x86_emulate.c
> @@ -182,7 +182,7 @@ static const opcode_desc_t opcode_table[
>  
>  static const opcode_desc_t twobyte_table[256] = {
>      /* 0x00 - 0x07 */
> -    ModRM, ImplicitOps|ModRM, ModRM, ModRM,
> +    ModRM, ImplicitOps|ModRM, DstReg|SrcMem16|ModRM, DstReg|SrcMem16|ModRM,
>      0, ImplicitOps, ImplicitOps, ImplicitOps,
>      /* 0x08 - 0x0F */
>      ImplicitOps, ImplicitOps, 0, ImplicitOps,
> @@ -1384,7 +1384,7 @@ protmode_load_seg(
>      }

A number of the following changes were very confusing to follow until I
realised that you are introducing a meaning, where
protmode_load_seg(x86_seg_none, ...) means "please do all the checks,
but don't commit any state or raise any exceptions".

It would be helpful to point this out in the commit message and in a
comment at the head of protmode_load_seg().

>  
>      /* System segment descriptors must reside in the GDT. */
> -    if ( !is_x86_user_segment(seg) && (sel & 4) )
> +    if ( is_x86_system_segment(seg) && (sel & 4) )
>          goto raise_exn;
>  
>      switch ( rc = ops->read(sel_seg, sel & 0xfff8, &desc, sizeof(desc), ctxt) )
> @@ -1401,14 +1401,11 @@ protmode_load_seg(
>          return rc;
>      }
>  
> -    if ( !is_x86_user_segment(seg) )
> -    {
> -        /* System segments must have S flag == 0. */
> -        if ( desc.b & (1u << 12) )
> -            goto raise_exn;
> -    }
> +    /* System segments must have S flag == 0. */
> +    if ( is_x86_system_segment(seg) && (desc.b & (1u << 12)) )
> +        goto raise_exn;
>      /* User segments must have S flag == 1. */
> -    else if ( !(desc.b & (1u << 12)) )
> +    if ( is_x86_user_segment(seg) && !(desc.b & (1u << 12)) )
>          goto raise_exn;

This might be clearer as (and is definitely shorter as)

/* Check .S is correct for the type of segment. */
if ( seg != x86_seg_none &&
     is_x86_user_segment(seg) != (desc.b & (1u << 12)) )
    goto raise_exn;

>  
>      dpl = (desc.b >> 13) & 3;
> @@ -1470,10 +1467,17 @@ protmode_load_seg(
>               ((dpl < cpl) || (dpl < rpl)) )
>              goto raise_exn;
>          break;
> +    case x86_seg_none:
> +        /* Non-conforming segment: check DPL against RPL and CPL. */
> +        if ( ((desc.b & (0x1c << 8)) != (0x1c << 8)) &&
> +             ((dpl < cpl) || (dpl < rpl)) )
> +            return X86EMUL_EXCEPTION;

I realise it is no functional change, but it would be cleaner to have
this as a goto raise_exn, to avoid having one spurious early-exit in a
fucntion which otherwise always goes to raise_exn or done.

> +        a_flag = 0;
> +        break;
>      }
>  
>      /* Segment present in memory? */
> -    if ( !(desc.b & (1 << 15)) )
> +    if ( !(desc.b & (1 << 15)) && seg != x86_seg_none )

What is the purpose of this change, given that the raise_exn case is
always conditional on seg != x86_seg_none ?

>      {
>          fault_type = seg != x86_seg_ss ? EXC_NP : EXC_SS;
>          goto raise_exn;
> @@ -1481,7 +1485,7 @@ protmode_load_seg(
>  
>      if ( !is_x86_user_segment(seg) )
>      {
> -        int lm = in_longmode(ctxt, ops);
> +        int lm = (desc.b & (1u << 12)) ? 0 : in_longmode(ctxt, ops);
>  
>          if ( lm < 0 )
>              return X86EMUL_UNHANDLEABLE;
> @@ -1501,7 +1505,8 @@ protmode_load_seg(
>                  return rc;
>              }
>              if ( (desc_hi.b & 0x00001f00) ||
> -                 !is_canonical_address((uint64_t)desc_hi.a << 32) )
> +                 (seg != x86_seg_none &&
> +                  !is_canonical_address((uint64_t)desc_hi.a << 32)) )
>                  goto raise_exn;
>          }
>      }
> @@ -1544,7 +1549,8 @@ protmode_load_seg(
>      return X86EMUL_OKAY;
>  
>   raise_exn:
> -    generate_exception(fault_type, sel & 0xfffc);
> +    generate_exception_if(seg != x86_seg_none, fault_type, sel & 0xfffc);
> +    rc = X86EMUL_EXCEPTION;
>   done:
>      return rc;
>  }
> @@ -4424,6 +4430,28 @@ x86_emulate(
>              if ( (rc = load_seg(seg, src.val, 0, NULL, ctxt, ops)) != 0 )
>                  goto done;
>              break;
> +        case 4: /* verr / verw */

This looks wrong, but I see it isn't actually.  Whether this patch or a
subsequent one, it would be clearer to alter the switch statement not to
mask off the bottom bit, and have individual case labels for the
instructions.

> +            _regs.eflags &= ~EFLG_ZF;
> +            switch ( rc = protmode_load_seg(x86_seg_none, src.val, false,
> +                                            &sreg, ctxt, ops) )
> +            {
> +            case X86EMUL_OKAY:
> +                if ( sreg.attr.fields.s &&
> +                     ((modrm_reg & 1) ? ((sreg.attr.fields.type & 0xa) == 0x2)
> +                                      : ((sreg.attr.fields.type & 0xa) != 0x8)) )
> +                    _regs.eflags |= EFLG_ZF;
> +                break;
> +            case X86EMUL_EXCEPTION:

Could we please annotate the areas where we selectively passing
exceptions?  I can see this pattern getting confusing if we don't. 
Something like:

/* #PF needs to escape.  #GP should have been squashed already. */

> +                if ( ctxt->event_pending )
> +                {
> +            default:
> +                    goto done;
> +                }
> +                /* Instead of the exception, ZF remains cleared. */
> +                rc = X86EMUL_OKAY;
> +                break;
> +            }
> +            break;
>          default:
>              generate_exception_if(true, EXC_UD);
>              break;
> @@ -4621,6 +4649,96 @@ x86_emulate(
>          break;
>      }
>  
> +    case X86EMUL_OPC(0x0f, 0x02): /* lar */
> +        generate_exception_if(!in_protmode(ctxt, ops), EXC_UD);

As a tangential point, the distinction between the various in_*()
predicates is sufficiently subtle that I keep on having to look it up to
check.

What do you think about replacing the current predicates with a single
mode_is() predicate which takes an or-able set of REAL|VM86|PROT|LONG
flags ?

> +        _regs.eflags &= ~EFLG_ZF;
> +        switch ( rc = protmode_load_seg(x86_seg_none, src.val, false, &sreg,
> +                                        ctxt, ops) )
> +        {
> +        case X86EMUL_OKAY:
> +            if ( !sreg.attr.fields.s )
> +            {
> +                switch ( sreg.attr.fields.type )
> +                {
> +                case 0x01: /* available 16-bit TSS */
> +                case 0x03: /* busy 16-bit TSS */
> +                case 0x04: /* 16-bit call gate */
> +                case 0x05: /* 16/32-bit task gate */
> +                    if ( in_longmode(ctxt, ops) )
> +                        break;
> +                    /* fall through */
> +                case 0x02: /* LDT */

According to the Intel manual, LDT is valid in protected mode, but not
valid in long mode.

This appears to be a functional difference from AMD, who permit querying
LDT in long mode.

I haven't checked what real hardware behaviour is.  Given that Intel
documents LDT as valid for LSL, I wonder whether this is a documentation
error.

> +                case 0x09: /* available 32/64-bit TSS */
> +                case 0x0b: /* busy 32/64-bit TSS */
> +                case 0x0c: /* 32/64-bit call gate */
> +                    _regs.eflags |= EFLG_ZF;
> +                    break;
> +                }
> +            }
> +            else
> +                _regs.eflags |= EFLG_ZF;
> +            break;
> +        case X86EMUL_EXCEPTION:
> +            if ( ctxt->event_pending )
> +            {
> +        default:
> +                goto done;
> +            }
> +            /* Instead of the exception, ZF remains cleared. */
> +            rc = X86EMUL_OKAY;
> +            break;
> +        }
> +        if ( _regs.eflags & EFLG_ZF )
> +            dst.val = ((sreg.attr.bytes & 0xff) << 8) |
> +                      ((sreg.limit >> (sreg.attr.fields.g ? 12 : 0)) &
> +                       0xf0000) |
> +                      ((sreg.attr.bytes & 0xf00) << 12);

Is this correct?  The AMD manuals says for 16bit, attr & 0xff00, and for
32 or 64bit, attr & 0xffff00.

The Intel manual describes this in a far more complicated way, but still
looks compatible.

~Andrew
Jan Beulich Dec. 9, 2016, 10:40 a.m. UTC | #2
>>> On 08.12.16 at 17:24, <andrew.cooper3@citrix.com> wrote:
> On 08/12/16 11:52, Jan Beulich wrote:
>> @@ -1401,14 +1401,11 @@ protmode_load_seg(
>>          return rc;
>>      }
>>  
>> -    if ( !is_x86_user_segment(seg) )
>> -    {
>> -        /* System segments must have S flag == 0. */
>> -        if ( desc.b & (1u << 12) )
>> -            goto raise_exn;
>> -    }
>> +    /* System segments must have S flag == 0. */
>> +    if ( is_x86_system_segment(seg) && (desc.b & (1u << 12)) )
>> +        goto raise_exn;
>>      /* User segments must have S flag == 1. */
>> -    else if ( !(desc.b & (1u << 12)) )
>> +    if ( is_x86_user_segment(seg) && !(desc.b & (1u << 12)) )
>>          goto raise_exn;
> 
> This might be clearer as (and is definitely shorter as)
> 
> /* Check .S is correct for the type of segment. */
> if ( seg != x86_seg_none &&
>      is_x86_user_segment(seg) != (desc.b & (1u << 12)) )
>     goto raise_exn;

I had it that way first, and then thought the opposite (the explicit
two if()-s being more clear). I'd prefer to keep it as is, but if you
feel strongly about the other variant being better, I can change it.

>> @@ -1470,10 +1467,17 @@ protmode_load_seg(
>>               ((dpl < cpl) || (dpl < rpl)) )
>>              goto raise_exn;
>>          break;
>> +    case x86_seg_none:
>> +        /* Non-conforming segment: check DPL against RPL and CPL. */
>> +        if ( ((desc.b & (0x1c << 8)) != (0x1c << 8)) &&
>> +             ((dpl < cpl) || (dpl < rpl)) )
>> +            return X86EMUL_EXCEPTION;
> 
> I realise it is no functional change, but it would be cleaner to have
> this as a goto raise_exn, to avoid having one spurious early-exit in a
> fucntion which otherwise always goes to raise_exn or done.

That's a matter of taste I think - to me it seems better to make
immediately obvious that no exception will be raised in this case
(which "goto raise_exn" would suggest).

>>      /* Segment present in memory? */
>> -    if ( !(desc.b & (1 << 15)) )
>> +    if ( !(desc.b & (1 << 15)) && seg != x86_seg_none )
> 
> What is the purpose of this change, given that the raise_exn case is
> always conditional on seg != x86_seg_none ?

Well - we don't want to reach raise_exn in this case, i.e. we
want to take the implicit "else" path. After all a clear P bit does
not result in the instructions to fail.

>> @@ -4424,6 +4430,28 @@ x86_emulate(
>>              if ( (rc = load_seg(seg, src.val, 0, NULL, ctxt, ops)) != 0 )
>>                  goto done;
>>              break;
>> +        case 4: /* verr / verw */
> 
> This looks wrong, but I see it isn't actually.  Whether this patch or a
> subsequent one, it would be clearer to alter the switch statement not to
> mask off the bottom bit, and have individual case labels for the
> instructions.

Again a case where I think the masking off of the low bit is the
better approach. I wouldn't object to a patch altering it, but I'm
not convinced enough to put one together myself. And no, I
don't think this should be done in this patch.

>> +            _regs.eflags &= ~EFLG_ZF;
>> +            switch ( rc = protmode_load_seg(x86_seg_none, src.val, false,
>> +                                            &sreg, ctxt, ops) )
>> +            {
>> +            case X86EMUL_OKAY:
>> +                if ( sreg.attr.fields.s &&
>> +                     ((modrm_reg & 1) ? ((sreg.attr.fields.type & 0xa) == 0x2)
>> +                                      : ((sreg.attr.fields.type & 0xa) != 0x8)) )
>> +                    _regs.eflags |= EFLG_ZF;
>> +                break;
>> +            case X86EMUL_EXCEPTION:
> 
> Could we please annotate the areas where we selectively passing
> exceptions?  I can see this pattern getting confusing if we don't. 
> Something like:
> 
> /* #PF needs to escape.  #GP should have been squashed already. */

Hmm, we're not selectively passing exceptions here; we pass any
which have got raised, and #GP/#SS/#NP aren't among them. So
I think the comment may rather want to be "Only #PF can come
here", which then we could as well ASSERT() ...

>> +                if ( ctxt->event_pending )
>> +                {

... here (and that would then serve as a de-facto comment).
What do you think?

>> @@ -4621,6 +4649,96 @@ x86_emulate(
>>          break;
>>      }
>>  
>> +    case X86EMUL_OPC(0x0f, 0x02): /* lar */
>> +        generate_exception_if(!in_protmode(ctxt, ops), EXC_UD);
> 
> As a tangential point, the distinction between the various in_*()
> predicates is sufficiently subtle that I keep on having to look it up to
> check.
> 
> What do you think about replacing the current predicates with a single
> mode_is() predicate which takes an or-able set of REAL|VM86|PROT|LONG
> flags ?

And you mean x86_decode() to set them once at its beginning?
That would make e.g. read_msr() a required hook, which I don't
think is a good idea (the more that x86_decode(), which in
particular gets passed almost no hooks at all from
x86_decode_insn(), doesn't need to know whether we're
emulating long mode).

If anything this would therefore need to be another input coming
from the original callers (complementing addr_size and sp_size).

>> +        _regs.eflags &= ~EFLG_ZF;
>> +        switch ( rc = protmode_load_seg(x86_seg_none, src.val, false, &sreg,
>> +                                        ctxt, ops) )
>> +        {
>> +        case X86EMUL_OKAY:
>> +            if ( !sreg.attr.fields.s )
>> +            {
>> +                switch ( sreg.attr.fields.type )
>> +                {
>> +                case 0x01: /* available 16-bit TSS */
>> +                case 0x03: /* busy 16-bit TSS */
>> +                case 0x04: /* 16-bit call gate */
>> +                case 0x05: /* 16/32-bit task gate */
>> +                    if ( in_longmode(ctxt, ops) )
>> +                        break;
>> +                    /* fall through */
>> +                case 0x02: /* LDT */
> 
> According to the Intel manual, LDT is valid in protected mode, but not
> valid in long mode.
> 
> This appears to be a functional difference from AMD, who permit querying
> LDT in long mode.
> 
> I haven't checked what real hardware behaviour is.  Given that Intel
> documents LDT as valid for LSL, I wonder whether this is a documentation
> error.

I did check all this on hardware, so yes, looks to be a doc error.

>> +                case 0x09: /* available 32/64-bit TSS */
>> +                case 0x0b: /* busy 32/64-bit TSS */
>> +                case 0x0c: /* 32/64-bit call gate */
>> +                    _regs.eflags |= EFLG_ZF;
>> +                    break;
>> +                }
>> +            }
>> +            else
>> +                _regs.eflags |= EFLG_ZF;
>> +            break;
>> +        case X86EMUL_EXCEPTION:
>> +            if ( ctxt->event_pending )
>> +            {
>> +        default:
>> +                goto done;
>> +            }
>> +            /* Instead of the exception, ZF remains cleared. */
>> +            rc = X86EMUL_OKAY;
>> +            break;
>> +        }
>> +        if ( _regs.eflags & EFLG_ZF )
>> +            dst.val = ((sreg.attr.bytes & 0xff) << 8) |
>> +                      ((sreg.limit >> (sreg.attr.fields.g ? 12 : 0)) &
>> +                       0xf0000) |
>> +                      ((sreg.attr.bytes & 0xf00) << 12);
> 
> Is this correct?  The AMD manuals says for 16bit, attr & 0xff00, and for
> 32 or 64bit, attr & 0xffff00.

Well - as for all operations truncation to operand size will occur
during writeback of dst.val to the destination register.

Jan
diff mbox

Patch

--- a/tools/tests/x86_emulator/test_x86_emulator.c
+++ b/tools/tests/x86_emulator/test_x86_emulator.c
@@ -46,7 +46,47 @@  static int read(
     if ( verbose )
         printf("** %s(%u, %p,, %u,)\n", __func__, seg, (void *)offset, bytes);
 
-    bytes_read += bytes;
+    switch ( seg )
+    {
+        uint64_t value;
+
+    case x86_seg_gdtr:
+        /* Fake system segment type matching table index. */
+        if ( (offset & 7) || (bytes > 8) )
+            return X86EMUL_UNHANDLEABLE;
+#ifdef __x86_64__
+        if ( !(offset & 8) )
+        {
+            memset(p_data, 0, bytes);
+            return X86EMUL_OKAY;
+        }
+        value = (offset - 8) >> 4;
+#else
+        value = (offset - 8) >> 3;
+#endif
+        if ( value >= 0x10 )
+            return X86EMUL_UNHANDLEABLE;
+        value |= value << 40;
+        memcpy(p_data, &value, bytes);
+        return X86EMUL_OKAY;
+
+    case x86_seg_ldtr:
+        /* Fake user segment type matching table index. */
+        if ( (offset & 7) || (bytes > 8) )
+            return X86EMUL_UNHANDLEABLE;
+        value = offset >> 3;
+        if ( value >= 0x10 )
+            return X86EMUL_UNHANDLEABLE;
+        value |= (value | 0x10) << 40;
+        memcpy(p_data, &value, bytes);
+        return X86EMUL_OKAY;
+
+    default:
+        if ( !is_x86_user_segment(seg) )
+            return X86EMUL_UNHANDLEABLE;
+        bytes_read += bytes;
+        break;
+    }
     memcpy(p_data, (void *)offset, bytes);
     return X86EMUL_OKAY;
 }
@@ -75,6 +115,8 @@  static int write(
     if ( verbose )
         printf("** %s(%u, %p,, %u,)\n", __func__, seg, (void *)offset, bytes);
 
+    if ( !is_x86_user_segment(seg) )
+        return X86EMUL_UNHANDLEABLE;
     memcpy((void *)offset, p_data, bytes);
     return X86EMUL_OKAY;
 }
@@ -90,10 +132,24 @@  static int cmpxchg(
     if ( verbose )
         printf("** %s(%u, %p,, %u,)\n", __func__, seg, (void *)offset, bytes);
 
+    if ( !is_x86_user_segment(seg) )
+        return X86EMUL_UNHANDLEABLE;
     memcpy((void *)offset, new, bytes);
     return X86EMUL_OKAY;
 }
 
+static int read_segment(
+    enum x86_segment seg,
+    struct segment_register *reg,
+    struct x86_emulate_ctxt *ctxt)
+{
+    if ( !is_x86_user_segment(seg) )
+        return X86EMUL_UNHANDLEABLE;
+    memset(reg, 0, sizeof(*reg));
+    reg->attr.fields.p = 1;
+    return X86EMUL_OKAY;
+}
+
 static int cpuid(
     unsigned int *eax,
     unsigned int *ebx,
@@ -193,6 +249,21 @@  static int read_cr(
     return X86EMUL_UNHANDLEABLE;
 }
 
+static int read_msr(
+    unsigned int reg,
+    uint64_t *val,
+    struct x86_emulate_ctxt *ctxt)
+{
+    switch ( reg )
+    {
+    case 0xc0000080: /* EFER */
+        *val = ctxt->addr_size > 32 ? 0x500 /* LME|LMA */ : 0;
+        return X86EMUL_OKAY;
+    }
+
+    return X86EMUL_UNHANDLEABLE;
+}
+
 int get_fpu(
     void (*exception_callback)(void *, struct cpu_user_regs *),
     void *exception_callback_arg,
@@ -223,8 +294,10 @@  static struct x86_emulate_ops emulops =
     .insn_fetch = fetch,
     .write      = write,
     .cmpxchg    = cmpxchg,
+    .read_segment = read_segment,
     .cpuid      = cpuid,
     .read_cr    = read_cr,
+    .read_msr   = read_msr,
     .get_fpu    = get_fpu,
 };
 
@@ -726,6 +799,156 @@  int main(int argc, char **argv)
         goto fail;
     printf("okay\n");
 
+    printf("%-40s", "Testing lar (null selector)...");
+    instr[0] = 0x0f; instr[1] = 0x02; instr[2] = 0xc1;
+    regs.eflags = 0x240;
+    regs.eip    = (unsigned long)&instr[0];
+    regs.ecx    = 0;
+    regs.eax    = 0x11111111;
+    rc = x86_emulate(&ctxt, &emulops);
+    if ( (rc != X86EMUL_OKAY) ||
+         (regs.eax != 0x11111111) ||
+         (regs.eflags != 0x200) ||
+         (regs.eip != (unsigned long)&instr[3]) )
+        goto fail;
+    printf("okay\n");
+
+    printf("%-40s", "Testing lsl (null selector)...");
+    instr[0] = 0x0f; instr[1] = 0x03; instr[2] = 0xca;
+    regs.eflags = 0x240;
+    regs.eip    = (unsigned long)&instr[0];
+    regs.edx    = 0;
+    regs.ecx    = 0x11111111;
+    rc = x86_emulate(&ctxt, &emulops);
+    if ( (rc != X86EMUL_OKAY) ||
+         (regs.ecx != 0x11111111) ||
+         (regs.eflags != 0x200) ||
+         (regs.eip != (unsigned long)&instr[3]) )
+        goto fail;
+    printf("okay\n");
+
+    printf("%-40s", "Testing verr (null selector)...");
+    instr[0] = 0x0f; instr[1] = 0x00; instr[2] = 0x21;
+    regs.eflags = 0x240;
+    regs.eip    = (unsigned long)&instr[0];
+    regs.ecx    = (unsigned long)res;
+    *res        = 0;
+    rc = x86_emulate(&ctxt, &emulops);
+    if ( (rc != X86EMUL_OKAY) ||
+         (regs.eflags != 0x200) ||
+         (regs.eip != (unsigned long)&instr[3]) )
+        goto fail;
+    printf("okay\n");
+
+    printf("%-40s", "Testing verw (null selector)...");
+    instr[0] = 0x0f; instr[1] = 0x00; instr[2] = 0x2a;
+    regs.eflags = 0x240;
+    regs.eip    = (unsigned long)&instr[0];
+    regs.ecx    = 0;
+    regs.edx    = (unsigned long)res;
+    rc = x86_emulate(&ctxt, &emulops);
+    if ( (rc != X86EMUL_OKAY) ||
+         (regs.eflags != 0x200) ||
+         (regs.eip != (unsigned long)&instr[3]) )
+        goto fail;
+    printf("okay\n");
+
+    printf("%-40s", "Testing lar/lsl/verr/verw (all types)...");
+    for ( i = 0; i < 0x20; ++i )
+    {
+        unsigned int sel = i < 0x10 ?
+#ifndef __x86_64__
+                                      (i << 3) + 8
+#else
+                                      (i << 4) + 8
+#endif
+                                    : ((i - 0x10) << 3) | 4;
+        bool failed;
+
+#ifndef __x86_64__
+# define LAR_VALID 0xffff1a3eU
+# define LSL_VALID 0xffff0a0eU
+#else
+# define LAR_VALID 0xffff1a04U
+# define LSL_VALID 0xffff0a04U
+#endif
+#define VERR_VALID 0xccff0000U
+#define VERW_VALID 0x00cc0000U
+
+        instr[0] = 0x0f; instr[1] = 0x02; instr[2] = 0xc2;
+        regs.eflags = (LAR_VALID >> i) & 1 ? 0x200 : 0x240;
+        regs.eip    = (unsigned long)&instr[0];
+        regs.edx    = sel;
+        regs.eax    = 0x11111111;
+        rc = x86_emulate(&ctxt, &emulops);
+        if ( (rc != X86EMUL_OKAY) ||
+             (regs.eip != (unsigned long)&instr[3]) )
+            goto fail;
+        if ( (LAR_VALID >> i) & 1 )
+            failed = (regs.eflags != 0x240) ||
+                     ((regs.eax & 0xf0ff00) != (i << 8));
+        else
+            failed = (regs.eflags != 0x200) ||
+                     (regs.eax != 0x11111111);
+        if ( failed )
+        {
+            printf("LAR %04x (type %02x) ", sel, i);
+            goto fail;
+        }
+
+        instr[0] = 0x0f; instr[1] = 0x03; instr[2] = 0xd1;
+        regs.eflags = (LSL_VALID >> i) & 1 ? 0x200 : 0x240;
+        regs.eip    = (unsigned long)&instr[0];
+        regs.ecx    = sel;
+        regs.edx    = 0x11111111;
+        rc = x86_emulate(&ctxt, &emulops);
+        if ( (rc != X86EMUL_OKAY) ||
+             (regs.eip != (unsigned long)&instr[3]) )
+            goto fail;
+        if ( (LSL_VALID >> i) & 1 )
+            failed = (regs.eflags != 0x240) ||
+                     (regs.edx != (i & 0xf));
+        else
+            failed = (regs.eflags != 0x200) ||
+                     (regs.edx != 0x11111111);
+        if ( failed )
+        {
+            printf("LSL %04x (type %02x) ", sel, i);
+            goto fail;
+        }
+
+        instr[0] = 0x0f; instr[1] = 0x00; instr[2] = 0xe2;
+        regs.eflags = (VERR_VALID >> i) & 1 ? 0x200 : 0x240;
+        regs.eip    = (unsigned long)&instr[0];
+        regs.ecx    = 0;
+        regs.edx    = sel;
+        rc = x86_emulate(&ctxt, &emulops);
+        if ( (rc != X86EMUL_OKAY) ||
+             (regs.eip != (unsigned long)&instr[3]) )
+            goto fail;
+        if ( regs.eflags != ((VERR_VALID >> i) & 1 ? 0x240 : 0x200) )
+        {
+            printf("VERR %04x (type %02x) ", sel, i);
+            goto fail;
+        }
+
+        instr[0] = 0x0f; instr[1] = 0x00; instr[2] = 0xe9;
+        regs.eflags = (VERW_VALID >> i) & 1 ? 0x200 : 0x240;
+        regs.eip    = (unsigned long)&instr[0];
+        regs.ecx    = sel;
+        regs.edx    = 0;
+        rc = x86_emulate(&ctxt, &emulops);
+        if ( (rc != X86EMUL_OKAY) ||
+             (regs.eip != (unsigned long)&instr[3]) )
+            goto fail;
+        if ( regs.eflags != ((VERW_VALID >> i) & 1 ? 0x240 : 0x200) )
+        {
+            printf("VERW %04x (type %02x) ", sel, i);
+            goto fail;
+        }
+    }
+    printf("okay\n");
+
 #define decl_insn(which) extern const unsigned char which[], which##_len[]
 #define put_insn(which, insn) ".pushsection .test, \"ax\", @progbits\n" \
                               #which ": " insn "\n"                     \
--- a/xen/arch/x86/x86_emulate/x86_emulate.c
+++ b/xen/arch/x86/x86_emulate/x86_emulate.c
@@ -182,7 +182,7 @@  static const opcode_desc_t opcode_table[
 
 static const opcode_desc_t twobyte_table[256] = {
     /* 0x00 - 0x07 */
-    ModRM, ImplicitOps|ModRM, ModRM, ModRM,
+    ModRM, ImplicitOps|ModRM, DstReg|SrcMem16|ModRM, DstReg|SrcMem16|ModRM,
     0, ImplicitOps, ImplicitOps, ImplicitOps,
     /* 0x08 - 0x0F */
     ImplicitOps, ImplicitOps, 0, ImplicitOps,
@@ -1384,7 +1384,7 @@  protmode_load_seg(
     }
 
     /* System segment descriptors must reside in the GDT. */
-    if ( !is_x86_user_segment(seg) && (sel & 4) )
+    if ( is_x86_system_segment(seg) && (sel & 4) )
         goto raise_exn;
 
     switch ( rc = ops->read(sel_seg, sel & 0xfff8, &desc, sizeof(desc), ctxt) )
@@ -1401,14 +1401,11 @@  protmode_load_seg(
         return rc;
     }
 
-    if ( !is_x86_user_segment(seg) )
-    {
-        /* System segments must have S flag == 0. */
-        if ( desc.b & (1u << 12) )
-            goto raise_exn;
-    }
+    /* System segments must have S flag == 0. */
+    if ( is_x86_system_segment(seg) && (desc.b & (1u << 12)) )
+        goto raise_exn;
     /* User segments must have S flag == 1. */
-    else if ( !(desc.b & (1u << 12)) )
+    if ( is_x86_user_segment(seg) && !(desc.b & (1u << 12)) )
         goto raise_exn;
 
     dpl = (desc.b >> 13) & 3;
@@ -1470,10 +1467,17 @@  protmode_load_seg(
              ((dpl < cpl) || (dpl < rpl)) )
             goto raise_exn;
         break;
+    case x86_seg_none:
+        /* Non-conforming segment: check DPL against RPL and CPL. */
+        if ( ((desc.b & (0x1c << 8)) != (0x1c << 8)) &&
+             ((dpl < cpl) || (dpl < rpl)) )
+            return X86EMUL_EXCEPTION;
+        a_flag = 0;
+        break;
     }
 
     /* Segment present in memory? */
-    if ( !(desc.b & (1 << 15)) )
+    if ( !(desc.b & (1 << 15)) && seg != x86_seg_none )
     {
         fault_type = seg != x86_seg_ss ? EXC_NP : EXC_SS;
         goto raise_exn;
@@ -1481,7 +1485,7 @@  protmode_load_seg(
 
     if ( !is_x86_user_segment(seg) )
     {
-        int lm = in_longmode(ctxt, ops);
+        int lm = (desc.b & (1u << 12)) ? 0 : in_longmode(ctxt, ops);
 
         if ( lm < 0 )
             return X86EMUL_UNHANDLEABLE;
@@ -1501,7 +1505,8 @@  protmode_load_seg(
                 return rc;
             }
             if ( (desc_hi.b & 0x00001f00) ||
-                 !is_canonical_address((uint64_t)desc_hi.a << 32) )
+                 (seg != x86_seg_none &&
+                  !is_canonical_address((uint64_t)desc_hi.a << 32)) )
                 goto raise_exn;
         }
     }
@@ -1544,7 +1549,8 @@  protmode_load_seg(
     return X86EMUL_OKAY;
 
  raise_exn:
-    generate_exception(fault_type, sel & 0xfffc);
+    generate_exception_if(seg != x86_seg_none, fault_type, sel & 0xfffc);
+    rc = X86EMUL_EXCEPTION;
  done:
     return rc;
 }
@@ -4424,6 +4430,28 @@  x86_emulate(
             if ( (rc = load_seg(seg, src.val, 0, NULL, ctxt, ops)) != 0 )
                 goto done;
             break;
+        case 4: /* verr / verw */
+            _regs.eflags &= ~EFLG_ZF;
+            switch ( rc = protmode_load_seg(x86_seg_none, src.val, false,
+                                            &sreg, ctxt, ops) )
+            {
+            case X86EMUL_OKAY:
+                if ( sreg.attr.fields.s &&
+                     ((modrm_reg & 1) ? ((sreg.attr.fields.type & 0xa) == 0x2)
+                                      : ((sreg.attr.fields.type & 0xa) != 0x8)) )
+                    _regs.eflags |= EFLG_ZF;
+                break;
+            case X86EMUL_EXCEPTION:
+                if ( ctxt->event_pending )
+                {
+            default:
+                    goto done;
+                }
+                /* Instead of the exception, ZF remains cleared. */
+                rc = X86EMUL_OKAY;
+                break;
+            }
+            break;
         default:
             generate_exception_if(true, EXC_UD);
             break;
@@ -4621,6 +4649,96 @@  x86_emulate(
         break;
     }
 
+    case X86EMUL_OPC(0x0f, 0x02): /* lar */
+        generate_exception_if(!in_protmode(ctxt, ops), EXC_UD);
+        _regs.eflags &= ~EFLG_ZF;
+        switch ( rc = protmode_load_seg(x86_seg_none, src.val, false, &sreg,
+                                        ctxt, ops) )
+        {
+        case X86EMUL_OKAY:
+            if ( !sreg.attr.fields.s )
+            {
+                switch ( sreg.attr.fields.type )
+                {
+                case 0x01: /* available 16-bit TSS */
+                case 0x03: /* busy 16-bit TSS */
+                case 0x04: /* 16-bit call gate */
+                case 0x05: /* 16/32-bit task gate */
+                    if ( in_longmode(ctxt, ops) )
+                        break;
+                    /* fall through */
+                case 0x02: /* LDT */
+                case 0x09: /* available 32/64-bit TSS */
+                case 0x0b: /* busy 32/64-bit TSS */
+                case 0x0c: /* 32/64-bit call gate */
+                    _regs.eflags |= EFLG_ZF;
+                    break;
+                }
+            }
+            else
+                _regs.eflags |= EFLG_ZF;
+            break;
+        case X86EMUL_EXCEPTION:
+            if ( ctxt->event_pending )
+            {
+        default:
+                goto done;
+            }
+            /* Instead of the exception, ZF remains cleared. */
+            rc = X86EMUL_OKAY;
+            break;
+        }
+        if ( _regs.eflags & EFLG_ZF )
+            dst.val = ((sreg.attr.bytes & 0xff) << 8) |
+                      ((sreg.limit >> (sreg.attr.fields.g ? 12 : 0)) &
+                       0xf0000) |
+                      ((sreg.attr.bytes & 0xf00) << 12);
+        else
+            dst.type = OP_NONE;
+        break;
+
+    case X86EMUL_OPC(0x0f, 0x03): /* lsl */
+        generate_exception_if(!in_protmode(ctxt, ops), EXC_UD);
+        _regs.eflags &= ~EFLG_ZF;
+        switch ( rc = protmode_load_seg(x86_seg_none, src.val, false, &sreg,
+                                        ctxt, ops) )
+        {
+        case X86EMUL_OKAY:
+            if ( !sreg.attr.fields.s )
+            {
+                switch ( sreg.attr.fields.type )
+                {
+                case 0x01: /* available 16-bit TSS */
+                case 0x03: /* busy 16-bit TSS */
+                    if ( in_longmode(ctxt, ops) )
+                        break;
+                    /* fall through */
+                case 0x02: /* LDT */
+                case 0x09: /* available 32/64-bit TSS */
+                case 0x0b: /* busy 32/64-bit TSS */
+                    _regs.eflags |= EFLG_ZF;
+                    break;
+                }
+            }
+            else
+                _regs.eflags |= EFLG_ZF;
+            break;
+        case X86EMUL_EXCEPTION:
+            if ( ctxt->event_pending )
+            {
+        default:
+                goto done;
+            }
+            /* Instead of the exception, ZF remains cleared. */
+            rc = X86EMUL_OKAY;
+            break;
+        }
+        if ( _regs.eflags & EFLG_ZF )
+            dst.val = sreg.limit;
+        else
+            dst.type = OP_NONE;
+        break;
+
     case X86EMUL_OPC(0x0f, 0x05): /* syscall */ {
         uint64_t msr_content;