[PATCH v6 1/2] ntdll: Add emulation for UMIP instructions.

Brendan Shanks bshanks at codeweavers.com
Mon Feb 17 14:26:46 CST 2020


Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=47571
Signed-off-by: Brendan Shanks <bshanks at codeweavers.com>
---
 dlls/ntdll/signal_i386.c   | 333 ++++++++++++++++++++++++++++++++-
 dlls/ntdll/signal_x86_64.c | 374 ++++++++++++++++++++++++++++++++++++-
 2 files changed, 691 insertions(+), 16 deletions(-)

diff --git a/dlls/ntdll/signal_i386.c b/dlls/ntdll/signal_i386.c
index e9dd0de2fc..e999ae8c20 100644
--- a/dlls/ntdll/signal_i386.c
+++ b/dlls/ntdll/signal_i386.c
@@ -1521,37 +1521,301 @@ __ASM_STDCALL_FUNC( NtGetContextThread, 8,
                     __ASM_CFI(".cfi_same_value %ebp\n\t")
                     "ret $8" )
 
+/***********************************************************************
+ * User-Mode Instruction Prevention (UMIP) is an x86 feature that prevents user-space
+ * code (CPL > 0) from executing the sgdt, sidt, sldt, smsw, and str instructions.
+ * If one of these instructions is executed, a general protection fault is issued.
+ *
+ * UMIP was first implemented by the AMD Zen2 and Intel Sunny Cove microarchitectures
+ * (both released in 2019).
+ *
+ * On Linux:
+ * The kernel traps the GPF and emulates sgdt, sidt, and smsw.
+ * sldt and str are not emulated.
+ *
+ * When the kernel doesn't emulate an instruction, the process receives a SIGSEGV.
+ * emulate_umip_instr() emulates all instructions not emulated by the Linux kernel.
+ */
+
+/* Dummy LDT, matches what I see on Windows and Linux */
+#define UMIP_DUMMY_LDT 0
+
+/* Dummy task register, matches what I see on Windows and Linux */
+#define UMIP_DUMMY_TR 0x40
+
+static void *get_reg_address( CONTEXT *context, BYTE rm )
+{
+    switch (rm & 7)
+    {
+    case 0: return &context->Eax;
+    case 1: return &context->Ecx;
+    case 2: return &context->Edx;
+    case 3: return &context->Ebx;
+    case 4: return &context->Esp;
+    case 5: return &context->Ebp;
+    case 6: return &context->Esi;
+    case 7: return &context->Edi;
+    }
+    return NULL;
+}
+
+/***********************************************************************
+ *           INSTR_GetOperandAddr
+ *
+ * Return the address of an instruction operand (from the mod/rm byte).
+ */
+static int INSTR_GetOperandAddr( CONTEXT *context, BYTE *instr, unsigned int instr_len,
+                                 int long_addr, int segprefix, int *len, void **addr )
+{
+    int mod, rm, base = 0, index = 0, ss = 0, off;
+    unsigned int i = 0;
+
+#define GET_VAL( val, type ) \
+    { if (sizeof(type) > (instr_len - i)) return 0; \
+      *val = *(type *)&instr[i]; i += sizeof(type); *len += sizeof(type); }
+
+    *len = 0;
+    GET_VAL( &mod, BYTE );
+    rm = mod & 7;
+    mod >>= 6;
+
+    if (mod == 3)
+    {
+        *addr = get_reg_address( context, rm );
+        return 1;
+    }
+
+    if (long_addr)
+    {
+        if (rm == 4)
+        {
+            BYTE sib;
+            GET_VAL( &sib, BYTE );
+            rm = sib & 7;
+            ss = sib >> 6;
+            switch((sib >> 3) & 7)
+            {
+            case 0: index = context->Eax; break;
+            case 1: index = context->Ecx; break;
+            case 2: index = context->Edx; break;
+            case 3: index = context->Ebx; break;
+            case 4: index = 0; break;
+            case 5: index = context->Ebp; break;
+            case 6: index = context->Esi; break;
+            case 7: index = context->Edi; break;
+            }
+        }
+
+        switch(rm)
+        {
+        case 0: base = context->Eax; break;
+        case 1: base = context->Ecx; break;
+        case 2: base = context->Edx; break;
+        case 3: base = context->Ebx; break;
+        case 4: base = context->Esp; break;
+        case 5: base = context->Ebp; break;
+        case 6: base = context->Esi; break;
+        case 7: base = context->Edi; break;
+        }
+        switch (mod)
+        {
+        case 0:
+            if (rm == 5)  /* special case: ds:(disp32) */
+            {
+                GET_VAL( &base, DWORD );
+            }
+            break;
+
+        case 1:  /* 8-bit disp */
+            GET_VAL( &off, BYTE );
+            base += (signed char)off;
+            break;
+
+        case 2:  /* 32-bit disp */
+            GET_VAL( &off, DWORD );
+            base += (signed long)off;
+            break;
+        }
+    }
+    else  /* short address */
+    {
+        switch(rm)
+        {
+        case 0:  /* ds:(bx,si) */
+            base = LOWORD(context->Ebx) + LOWORD(context->Esi);
+            break;
+        case 1:  /* ds:(bx,di) */
+            base = LOWORD(context->Ebx) + LOWORD(context->Edi);
+            break;
+        case 2:  /* ss:(bp,si) */
+            base = LOWORD(context->Ebp) + LOWORD(context->Esi);
+            break;
+        case 3:  /* ss:(bp,di) */
+            base = LOWORD(context->Ebp) + LOWORD(context->Edi);
+            break;
+        case 4:  /* ds:(si) */
+            base = LOWORD(context->Esi);
+            break;
+        case 5:  /* ds:(di) */
+            base = LOWORD(context->Edi);
+            break;
+        case 6:  /* ss:(bp) */
+            base = LOWORD(context->Ebp);
+            break;
+        case 7:  /* ds:(bx) */
+            base = LOWORD(context->Ebx);
+            break;
+        }
+
+        switch(mod)
+        {
+        case 0:
+            if (rm == 6)  /* special case: ds:(disp16) */
+            {
+                GET_VAL( &base, WORD );
+            }
+            break;
+
+        case 1:  /* 8-bit disp */
+            GET_VAL( &off, BYTE );
+            base += (signed char)off;
+            break;
+
+        case 2:  /* 16-bit disp */
+            GET_VAL( &off, WORD );
+            base += (signed short)off;
+            break;
+        }
+        base &= 0xffff;
+    }
+    /* FIXME: we assume that all segments have a base of 0 */
+    *addr = (void *)(base + (index << ss));
+    return 1;
+#undef GET_VAL
+}
+
+/***********************************************************************
+ *           emulate_umip_instr
+ *
+ * Emulate a UMIP-protected instruction.
+ * Returns: 0 if no instruction emulated,
+ *          1 if instruction's write to memory failed,
+ *          2 if instruction emulated successfully.
+ */
+static int emulate_umip_instr( CONTEXT *context, BYTE *instr, unsigned int len, void **err_addr,
+                               int segprefix, unsigned int prefixlen, int long_op, int long_addr )
+{
+    if (len < 2) return 0;
+
+    if (instr[0] == 0x00)    /* sldt/str */
+    {
+        int reg = (instr[1] >> 3) & 7;
+        switch (reg)
+        {
+        case 0: /* sldt */
+        case 1: /* str */
+        {
+            int instr_len;
+            int mod = instr[1] >> 6;
+            void *data;
+            UINT16 dummy_value;
+
+            if (!INSTR_GetOperandAddr( context, &instr[1], len - 1, long_addr,
+                                       segprefix, &instr_len, &data ))
+                return 0;
+
+            if (reg == 0)
+            {
+                /* sldt */
+                dummy_value = UMIP_DUMMY_LDT;
+                TRACE( "sldt at 0x%08x\n", context->Eip );
+            }
+            else
+            {
+                /* str */
+                dummy_value = UMIP_DUMMY_TR;
+                TRACE( "str at 0x%08x\n", context->Eip );
+            }
+
+            if (mod == 3)
+            {
+                /* Destination operand is a register.
+                 * Zero-extend the dummy value and store to the register. */
+                UINT32 dummy_value_32 = dummy_value;
+                memcpy( data, &dummy_value_32, long_op ? 4 : 2 );
+            }
+            else
+            {
+                /* Destination operand is a memory location.
+                 * Only copy 16 bits regardless of operand size. */
+                if (virtual_uninterrupted_write_memory( data, &dummy_value, sizeof(dummy_value) ))
+                {
+                    TRACE( "memory write by 0x%08x to %p failed\n", context->Eip, data );
+                    *err_addr = data;
+                    return 1;
+                }
+            }
+            context->Eip += prefixlen + instr_len + 2;
+            return 2;
+        }
+        }
+    }
+    else if (instr[0] == 0x01)  /* sgdt/sidt/str */
+    {
+        /* The Linux kernel already emulates these instructions for 32-bit processes,
+         * Wine should never get an exception for them.
+         * However, it may be necessary in the future to emulate these instructions
+         * for other OSes if they don't do any emulation.
+         */
+    }
+
+    return 0;
+}
 
 /***********************************************************************
  *           is_privileged_instr
  *
- * Check if the fault location is a privileged instruction.
+ * Check if the fault location is a privileged instruction, and emulate a UMIP-protected instruction if found.
  * Based on the instruction emulation code in dlls/kernel/instr.c.
+ * Returns: 0 if no privileged instruction found or emulation failed,
+ *          1 if instruction found and ExceptionCode has been set,
+ *          2 if instruction was successfully emulated.
  */
-static inline DWORD is_privileged_instr( CONTEXT *context )
+static inline int is_privileged_instr( CONTEXT *context, EXCEPTION_RECORD *rec )
 {
     BYTE instr[16];
     unsigned int i, len, prefix_count = 0;
+    int long_op = 1, long_addr = 1;
+    int segprefix = -1; /* no prefix */
 
     if (!wine_ldt_is_system( context->SegCs )) return 0;
     len = virtual_uninterrupted_read_memory( (BYTE *)context->Eip, instr, sizeof(instr) );
 
-    for (i = 0; i < len; i++) switch (instr[i])
+    for (i = 0; i < len; i++)
+    {
+    switch (instr[i])
     {
     /* instruction prefixes */
     case 0x2e:  /* %cs: */
+        segprefix = context->SegCs; prefix_count++; break;
     case 0x36:  /* %ss: */
+        segprefix = context->SegSs; prefix_count++; break;
     case 0x3e:  /* %ds: */
+        segprefix = context->SegDs; prefix_count++; break;
     case 0x26:  /* %es: */
+        segprefix = context->SegEs; prefix_count++; break;
     case 0x64:  /* %fs: */
+        segprefix = context->SegFs; prefix_count++; break;
     case 0x65:  /* %gs: */
+        segprefix = context->SegGs; prefix_count++; break;
     case 0x66:  /* opcode size */
+        long_op = !long_op; prefix_count++; break;
     case 0x67:  /* addr size */
+        long_addr = !long_addr; prefix_count++; break;
     case 0xf0:  /* lock */
     case 0xf2:  /* repne */
     case 0xf3:  /* repe */
-        if (++prefix_count >= 15) return EXCEPTION_ILLEGAL_INSTRUCTION;
-        continue;
+        prefix_count++; break;
 
     case 0x0f: /* extended instruction */
         if (i == len - 1) return 0;
@@ -1561,7 +1825,24 @@ static inline DWORD is_privileged_instr( CONTEXT *context )
         case 0x21: /* mov drX, reg */
         case 0x22: /* mov reg, crX */
         case 0x23: /* mov reg drX */
-            return EXCEPTION_PRIV_INSTRUCTION;
+            rec->ExceptionCode = EXCEPTION_PRIV_INSTRUCTION;
+            return 1;
+
+        case 0x00: /* sldt/str */
+        case 0x01: /* sgdt/sidt/smsw */
+        {
+            void *err_addr;
+            int result = emulate_umip_instr( context, &instr[i+1], len - i - 1, &err_addr,
+                                             segprefix, prefix_count, long_op, long_addr );
+            if (result == 1)
+            {
+                rec->ExceptionCode = EXCEPTION_ACCESS_VIOLATION;
+                rec->NumberParameters = 2;
+                rec->ExceptionInformation[0] = 1;
+                rec->ExceptionInformation[1] = (ULONG_PTR)err_addr;
+            }
+            return result;
+        }
         }
         return 0;
     case 0x6c: /* insb (%dx) */
@@ -1580,10 +1861,14 @@ static inline DWORD is_privileged_instr( CONTEXT *context )
     case 0xf4: /* hlt */
     case 0xfa: /* cli */
     case 0xfb: /* sti */
-        return EXCEPTION_PRIV_INSTRUCTION;
+        rec->ExceptionCode = EXCEPTION_PRIV_INSTRUCTION;
+        return 1;
     default:
         return 0;
     }
+
+    if (prefix_count >= 15) { rec->ExceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION; return 1; }
+    }
     return 0;
 }
 
@@ -1994,6 +2279,39 @@ static void segv_handler( int signal, siginfo_t *siginfo, void *sigcontext )
         }
     }
 
+    /* is_privileged_instr() needs to run before setup_exception_record(). In case an instruction is
+     * successfully emulated, creating a stack frame may corrupt data.
+     */
+    if (get_trap_code(context) == TRAP_x86_PROTFLT)
+    {
+        int result;
+        CONTEXT localcontext;
+        EXCEPTION_RECORD rec;
+
+        save_context( &localcontext, sigcontext, fs, gs );
+        rec.NumberParameters = 0;
+
+        if (!get_error_code(context) && (result = is_privileged_instr( &localcontext, &rec )))
+        {
+            if (result == 1)
+            {
+                stack = setup_exception_record( context, stack_ptr, fs, gs );
+                if (stack->rec.ExceptionCode == EXCEPTION_STACK_OVERFLOW) goto done;
+
+                stack->context = localcontext;
+                stack->rec.ExceptionCode = rec.ExceptionCode;
+                if (rec.NumberParameters)
+                {
+                    stack->rec.NumberParameters = rec.NumberParameters;
+                    stack->rec.ExceptionInformation[0] = rec.ExceptionInformation[0];
+                    stack->rec.ExceptionInformation[1] = rec.ExceptionInformation[1];
+                }
+                goto done;
+            }
+            else { restore_context( &localcontext, sigcontext ); return; }
+        }
+    }
+
     stack = setup_exception_record( context, stack_ptr, fs, gs );
     if (stack->rec.ExceptionCode == EXCEPTION_STACK_OVERFLOW) goto done;
 
@@ -2016,7 +2334,6 @@ static void segv_handler( int signal, siginfo_t *siginfo, void *sigcontext )
     case TRAP_x86_UNKNOWN:   /* Unknown fault code */
         {
             WORD err = get_error_code(context);
-            if (!err && (stack->rec.ExceptionCode = is_privileged_instr( &stack->context ))) break;
             if ((err & 7) == 2 && handle_interrupt( err >> 3, context, stack )) return;
             stack->rec.ExceptionCode = EXCEPTION_ACCESS_VIOLATION;
             stack->rec.NumberParameters = 2;
diff --git a/dlls/ntdll/signal_x86_64.c b/dlls/ntdll/signal_x86_64.c
index 04f3854388..be743f8178 100644
--- a/dlls/ntdll/signal_x86_64.c
+++ b/dlls/ntdll/signal_x86_64.c
@@ -2752,25 +2752,326 @@ static void setup_raise_exception( ucontext_t *sigcontext, struct stack_layout *
     EFL_sig(sigcontext) &= ~(0x100|0x400|0x40000);
 }
 
+/***********************************************************************
+ * User-Mode Instruction Prevention (UMIP) is an x86 feature that prevents user-space
+ * code (CPL > 0) from executing the sgdt, sidt, sldt, smsw, and str instructions.
+ * If one of these instructions is executed, a general protection fault is issued.
+ *
+ * UMIP was first implemented by the AMD Zen2 and Intel Sunny Cove microarchitectures
+ * (both released in 2019).
+ *
+ * On Linux:
+ * Kernel 5.4 and newer emulate sgdt, sidt, and smsw. sldt and str are not emulated.
+ * Kernels older than v5.4 do not emulate any instructions.
+ *
+ * When the kernel doesn't emulate an instruction, the process receives a SIGSEGV.
+ * emulate_umip_instr() emulates all instructions not emulated by the Linux kernel.
+ */
+
+/* Dummy base addresses are the same as used by the kernel */
+#define UMIP_DUMMY_GDT_BASE 0xfffffffffffe0000ULL
+#define UMIP_DUMMY_IDT_BASE 0xffffffffffff0000ULL
+#define UMIP_DUMMY_GDT_IDT_LIMIT 0
+
+#define UMIP_GDT_IDT_BASE_SIZE_64BIT 8
+#define UMIP_GDT_IDT_LIMIT_SIZE 2
+
+/* Dummy LDT, matches what I see on Windows and Linux */
+#define UMIP_DUMMY_LDT 0
+
+/* Dummy task register, matches what I see on Windows and Linux */
+#define UMIP_DUMMY_TR 0x40
+
+/* Dummy MSW is same value used by kernel, defined as:
+ * (X86_CR0_PE | X86_CR0_MP | X86_CR0_ET |
+ *  X86_CR0_NE | X86_CR0_WP | X86_CR0_AM |
+ *  X86_CR0_PG)
+ */
+#define UMIP_DUMMY_MSW 0x33
+
+#define REX_B   1
+#define REX_X   2
+#define REX_R   4
+#define REX_W   8
+
+#define REGMODRM_MOD( regmodrm, rex )   ((regmodrm) >> 6)
+#define REGMODRM_REG( regmodrm, rex )   (((regmodrm) >> 3) & 7) | (((rex) & REX_R) ? 8 : 0)
+#define REGMODRM_RM( regmodrm, rex )    (((regmodrm) & 7) | (((rex) & REX_B) ? 8 : 0))
+
+#define SIB_SS( sib, rex )      ((sib) >> 6)
+#define SIB_INDEX( sib, rex )   (((sib) >> 3) & 7) | (((rex) & REX_X) ? 8 : 0)
+#define SIB_BASE( sib, rex )    (((sib) & 7) | (((rex) & REX_B) ? 8 : 0))
+
+static inline DWORD64 *get_int_reg_ptr( CONTEXT *context, int index )
+{
+    return &context->Rax + index; /* index should be in range 0 .. 15 */
+}
+
+static inline int get_op_size( int long_op, int rex )
+{
+    if (rex & REX_W)
+        return sizeof(DWORD64);
+    else if (long_op)
+        return sizeof(DWORD);
+    else
+        return sizeof(WORD);
+}
+
+/***********************************************************************
+ *           INSTR_GetOperandAddr
+ *
+ * Return the address of an instruction operand (from the mod/rm byte).
+ */
+static int INSTR_GetOperandAddr( CONTEXT *context, BYTE *instr, unsigned int instr_len,
+                                 unsigned int addl_instr_len, int long_addr, int rex, int segprefix,
+                                 int *len, BYTE **addr )
+{
+    int mod, rm, ss = 0, off, have_sib = 0;
+    unsigned int i = 0;
+    DWORD64 base = 0, index = 0;
+
+#define GET_VAL( val, type ) \
+    { if (sizeof(type) > (instr_len - i)) return 0; \
+      *val = *(type *)&instr[i]; i += sizeof(type); *len += sizeof(type); }
+
+    *len = 0;
+    GET_VAL( &mod, BYTE );
+    rm  = REGMODRM_RM( mod, rex );
+    mod = REGMODRM_MOD( mod, rex );
+
+    if (mod == 3)
+    {
+        *addr = (BYTE *)get_int_reg_ptr( context, rm );
+        return 1;
+    }
+
+    if ((rm & 7) == 4)
+    {
+        BYTE sib;
+        int id;
+
+        GET_VAL( &sib, BYTE );
+        rm = SIB_BASE( sib, rex );
+        id = SIB_INDEX( sib, rex );
+        ss = SIB_SS( sib, rex );
+
+        index = (id != 4) ? *get_int_reg_ptr( context, id ) : 0;
+        if (!long_addr) index &= 0xffffffff;
+        have_sib = 1;
+    }
+
+    base = *get_int_reg_ptr( context, rm );
+    if (!long_addr) base &= 0xffffffff;
+
+    switch (mod)
+    {
+    case 0:
+        if (rm == 5)  /* special case */
+        {
+            base = have_sib ? 0 : context->Rip;
+            if (!long_addr) base &= 0xffffffff;
+            GET_VAL( &off, DWORD );
+            base += (signed long)off;
+            base += (signed long)*len + (signed long)addl_instr_len;
+        }
+        break;
+
+    case 1:  /* 8-bit disp */
+        GET_VAL( &off, BYTE );
+        base += (signed char)off;
+        break;
+
+    case 2:  /* 32-bit disp */
+        GET_VAL( &off, DWORD );
+        base += (signed long)off;
+        break;
+    }
+
+    /* FIXME: we assume that all segments have a base of 0 */
+    *addr = (BYTE *)(base + (index << ss));
+    return 1;
+#undef GET_VAL
+}
+
+/***********************************************************************
+ *           emulate_umip_instr
+ *
+ * Emulate a UMIP-protected instruction.
+ * Returns: 0 if no instruction emulated,
+ *          1 if instruction's write to memory failed,
+ *          2 if instruction emulated successfully.
+ */
+static int emulate_umip_instr( CONTEXT *context, BYTE *instr, unsigned int len, void **err_addr,
+                               int segprefix, unsigned int prefixlen, int long_op, int long_addr, int rex )
+{
+    if (len < 2) return 0;
+
+    if (instr[0] == 0x00)    /* sldt/str */
+    {
+        int reg = REGMODRM_REG( instr[1], rex );
+        switch (reg)
+        {
+        case 0: /* sldt */
+        case 1: /* str */
+        {
+            int instr_len;
+            BYTE *data;
+            UINT16 dummy_value;
+
+            if (!INSTR_GetOperandAddr( context, &instr[1], len - 1, prefixlen + 2, long_addr,
+                                       rex, segprefix, &instr_len, &data ))
+                return 0;
+
+            if (reg == 0)
+            {
+                /* sldt */
+                dummy_value = UMIP_DUMMY_LDT;
+                TRACE( "sldt at 0x%lx\n", context->Rip );
+            }
+            else
+            {
+                /* str */
+                dummy_value = UMIP_DUMMY_TR;
+                TRACE( "str at 0x%lx\n", context->Rip );
+            }
+
+            if (REGMODRM_MOD( instr[1], rex ) == 3)
+            {
+                /* Destination operand is a register.
+                 * Zero-extend the dummy LDT and store to the register. */
+                UINT64 dummy_value_64 = dummy_value;
+                memcpy( data, &dummy_value_64, get_op_size( long_op, rex ) );
+            }
+            else
+            {
+                /* Destination operand is a memory location.
+                 * Only copy 16 bits regardless of operand size. */
+                if (virtual_uninterrupted_write_memory( data, &dummy_value, sizeof(dummy_value) ))
+                {
+                    TRACE( "memory write by 0x%lx to %p failed\n", context->Rip, data );
+                    *err_addr = data;
+                    return 1;
+                }
+            }
+            context->Rip += prefixlen + instr_len + 2;
+            return 2;
+        }
+        default: break;
+        }
+    }
+    else if (instr[0] == 0x01)  /* sgdt/sidt/str */
+    {
+        int instr_len;
+        int reg = REGMODRM_REG( instr[1], rex );
+        switch (reg)
+        {
+        case 0: /* sgdt */
+        case 1: /* sidt */
+        {
+            BYTE *data;
+            UINT16 dummy_limit = UMIP_DUMMY_GDT_IDT_LIMIT;
+            UINT64 dummy_base_addr;
+
+            /* sgdt/sidt cannot use a register as the destination operand */
+            if (REGMODRM_MOD( instr[1], rex ) == 3)
+                return 0;
+
+            if (!INSTR_GetOperandAddr( context, &instr[1], len - 1, prefixlen + 2, long_addr,
+                                       rex, segprefix, &instr_len, &data ))
+                return 0;
+
+            if (reg == 0)
+            {
+                /* sgdt */
+                dummy_base_addr = UMIP_DUMMY_GDT_BASE;
+                TRACE( "sgdt at %lx\n", context->Rip );
+            }
+            else if (reg == 1)
+            {
+                /* sidt */
+                dummy_base_addr = UMIP_DUMMY_IDT_BASE;
+                TRACE( "sidt at %lx\n", context->Rip );
+            }
+            if (virtual_uninterrupted_write_memory( data, &dummy_limit, UMIP_GDT_IDT_LIMIT_SIZE ) ||
+                virtual_uninterrupted_write_memory( data + UMIP_GDT_IDT_LIMIT_SIZE, &dummy_base_addr, UMIP_GDT_IDT_BASE_SIZE_64BIT ))
+            {
+                TRACE( "memory write by 0x%lx to %p failed\n", context->Rip, data );
+                *err_addr = data;
+                return 1;
+            }
+
+            context->Rip += prefixlen + instr_len + 2;
+            return 2;
+        }
+        case 4: /* smsw */
+        {
+            BYTE *data;
+
+            if (!INSTR_GetOperandAddr( context, &instr[1], len - 1, prefixlen + 2, long_addr,
+                                       rex, segprefix, &instr_len, &data ))
+                return 0;
+
+            if (REGMODRM_MOD( instr[1], rex ) == 3)
+            {
+                /* Destination operand is a register.
+                 * Zero-extend the dummy MSW and store to the register. */
+                UINT64 dummy_msw = UMIP_DUMMY_MSW;
+                TRACE( "smsw at %lx\n", context->Rip );
+                memcpy( data, &dummy_msw, get_op_size( long_op, rex ) );
+            }
+            else
+            {
+                /* Destination operand is a memory location.
+                 * Only copy 16 bits regardless of operand size. */
+                UINT16 dummy_msw = UMIP_DUMMY_MSW;
+                TRACE( "smsw at %lx\n", context->Rip );
+                if (virtual_uninterrupted_write_memory( data, &dummy_msw, sizeof(dummy_msw) ))
+                {
+                    TRACE( "memory write by 0x%lx to %p failed\n", context->Rip, data );
+                    *err_addr = data;
+                    return 1;
+                }
+            }
+            context->Rip += prefixlen + instr_len + 2;
+            return 2;
+        }
+        default: break;
+        }
+    }
+
+    return 0;
+}
 
 /***********************************************************************
  *           is_privileged_instr
  *
- * Check if the fault location is a privileged instruction.
+ * Check if the fault location is a privileged instruction, and emulate a UMIP-protected instruction if found.
+ * Returns: 0 if no privileged instruction found or emulation failed,
+ *          1 if instruction found and ExceptionCode has been set,
+ *          2 if instruction was successfully emulated.
  */
-static inline DWORD is_privileged_instr( CONTEXT *context )
+static inline int is_privileged_instr( CONTEXT *context, EXCEPTION_RECORD *rec )
 {
     BYTE instr[16];
+    int segprefix, long_op = 1, long_addr = 1, rex;
     unsigned int i, prefix_count = 0;
     unsigned int len = virtual_uninterrupted_read_memory( (BYTE *)context->Rip, instr, sizeof(instr) );
 
-    for (i = 0; i < len; i++) switch (instr[i])
+    segprefix = -1;  /* no seg prefix */
+    rex = 0;  /* no rex prefix */
+    for (i = 0; i < len; i++)
+    {
+    switch (instr[i])
     {
     /* instruction prefixes */
     case 0x2e:  /* %cs: */
+        segprefix = context->SegCs; prefix_count++; break;
     case 0x36:  /* %ss: */
+        segprefix = context->SegSs; prefix_count++; break;
     case 0x3e:  /* %ds: */
+        segprefix = context->SegDs; prefix_count++; break;
     case 0x26:  /* %es: */
+        segprefix = context->SegEs; prefix_count++; break;
     case 0x40:  /* rex */
     case 0x41:  /* rex */
     case 0x42:  /* rex */
@@ -2787,15 +3088,19 @@ static inline DWORD is_privileged_instr( CONTEXT *context )
     case 0x4d:  /* rex */
     case 0x4e:  /* rex */
     case 0x4f:  /* rex */
+        rex = instr[i]; prefix_count++; break;
     case 0x64:  /* %fs: */
+        segprefix = context->SegFs; prefix_count++; break;
     case 0x65:  /* %gs: */
+        segprefix = context->SegGs; prefix_count++; break;
     case 0x66:  /* opcode size */
+        long_op = !long_op; prefix_count++; break;
     case 0x67:  /* addr size */
+        long_addr = !long_addr; prefix_count++; break;
     case 0xf0:  /* lock */
     case 0xf2:  /* repne */
     case 0xf3:  /* repe */
-        if (++prefix_count >= 15) return EXCEPTION_ILLEGAL_INSTRUCTION;
-        continue;
+        prefix_count++; break;
 
     case 0x0f: /* extended instruction */
         if (i == len - 1) return 0;
@@ -2808,7 +3113,24 @@ static inline DWORD is_privileged_instr( CONTEXT *context )
         case 0x21: /* mov drX, reg */
         case 0x22: /* mov reg, crX */
         case 0x23: /* mov reg drX */
-            return EXCEPTION_PRIV_INSTRUCTION;
+            rec->ExceptionCode = EXCEPTION_PRIV_INSTRUCTION;
+            return 1;
+
+        case 0x00: /* sldt/str */
+        case 0x01: /* sgdt/sidt/smsw */
+        {
+            void *err_addr;
+            int result = emulate_umip_instr( context, &instr[i+1], len - i - 1, &err_addr,
+                                             segprefix, prefix_count, long_op, long_addr, rex );
+            if (result == 1)
+            {
+                rec->ExceptionCode = EXCEPTION_ACCESS_VIOLATION;
+                rec->NumberParameters = 2;
+                rec->ExceptionInformation[0] = 1;
+                rec->ExceptionInformation[1] = (ULONG_PTR)err_addr;
+            }
+            return result;
+        }
         }
         return 0;
     case 0x6c: /* insb (%dx) */
@@ -2827,10 +3149,14 @@ static inline DWORD is_privileged_instr( CONTEXT *context )
     case 0xf4: /* hlt */
     case 0xfa: /* cli */
     case 0xfb: /* sti */
-        return EXCEPTION_PRIV_INSTRUCTION;
+        rec->ExceptionCode = EXCEPTION_PRIV_INSTRUCTION;
+        return 1;
     default:
         return 0;
     }
+
+    if (prefix_count >= 15) { rec->ExceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION; return 1; }
+    }
     return 0;
 }
 
@@ -2904,6 +3230,39 @@ static void segv_handler( int signal, siginfo_t *siginfo, void *sigcontext )
         }
     }
 
+    /* is_privileged_instr() needs to run before setup_exception(). In case an instruction is
+     * successfully emulated, creating a stack frame may corrupt data.
+     */
+    if (TRAP_sig(ucontext) == TRAP_x86_PROTFLT)
+    {
+        int result;
+        CONTEXT context;
+        EXCEPTION_RECORD rec;
+
+        save_context( &context, sigcontext );
+        rec.NumberParameters = 0;
+
+        if (!ERROR_sig(ucontext) && (result = is_privileged_instr( &context, &rec )))
+        {
+            if (result == 1)
+            {
+                stack = setup_exception( sigcontext );
+                if (stack->rec.ExceptionCode == EXCEPTION_STACK_OVERFLOW) goto done;
+
+                stack->context = context;
+                stack->rec.ExceptionCode = rec.ExceptionCode;
+                if (rec.NumberParameters)
+                {
+                    stack->rec.NumberParameters = rec.NumberParameters;
+                    stack->rec.ExceptionInformation[0] = rec.ExceptionInformation[0];
+                    stack->rec.ExceptionInformation[1] = rec.ExceptionInformation[1];
+                }
+                goto done;
+            }
+            else { restore_context( &context, sigcontext ); return; }
+        }
+    }
+
     stack = setup_exception( sigcontext );
     if (stack->rec.ExceptionCode == EXCEPTION_STACK_OVERFLOW) goto done;
 
@@ -2926,7 +3285,6 @@ static void segv_handler( int signal, siginfo_t *siginfo, void *sigcontext )
     case TRAP_x86_UNKNOWN:   /* Unknown fault code */
         {
             WORD err = ERROR_sig(ucontext);
-            if (!err && (stack->rec.ExceptionCode = is_privileged_instr( &stack->context ))) break;
             if ((err & 7) == 2 && handle_interrupt( ucontext, stack )) return;
             stack->rec.ExceptionCode = EXCEPTION_ACCESS_VIOLATION;
             stack->rec.NumberParameters = 2;
-- 
2.24.1




More information about the wine-devel mailing list