This patch adds emulation for instructions protected by User-Mode Instruction Prevention (currently implemented by AMD Ryzen 3000 CPUs and some rare Intel CPUs).
Changes from previous patch: an access violation exception is now raised if the emulated instruction tries to write to an invalid memory address, tests were added for this behavior, and all tests are now in exception.c.
Brendan Shanks (2): ntdll: Add emulation for UMIP instructions. ntdll/tests: Add tests for UMIP instructions.
dlls/ntdll/Makefile.in | 1 + dlls/ntdll/signal_i386.c | 19 + dlls/ntdll/signal_x86_64.c | 19 + dlls/ntdll/tests/exception.c | 375 +++++++++++++++++ dlls/ntdll/umip.c | 780 +++++++++++++++++++++++++++++++++++ dlls/ntdll/umip.h | 21 + 6 files changed, 1215 insertions(+) create mode 100644 dlls/ntdll/umip.c create mode 100644 dlls/ntdll/umip.h
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=47571 Signed-off-by: Brendan Shanks bshanks@codeweavers.com --- dlls/ntdll/Makefile.in | 1 + dlls/ntdll/signal_i386.c | 19 + dlls/ntdll/signal_x86_64.c | 19 + dlls/ntdll/umip.c | 780 +++++++++++++++++++++++++++++++++++++ dlls/ntdll/umip.h | 21 + 5 files changed, 840 insertions(+) create mode 100644 dlls/ntdll/umip.c create mode 100644 dlls/ntdll/umip.h
diff --git a/dlls/ntdll/Makefile.in b/dlls/ntdll/Makefile.in index 9b4d70f6e8..999fc1abf5 100644 --- a/dlls/ntdll/Makefile.in +++ b/dlls/ntdll/Makefile.in @@ -50,6 +50,7 @@ C_SRCS = \ thread.c \ threadpool.c \ time.c \ + umip.c \ version.c \ virtual.c \ wcstring.c diff --git a/dlls/ntdll/signal_i386.c b/dlls/ntdll/signal_i386.c index e9dd0de2fc..7b294e9d91 100644 --- a/dlls/ntdll/signal_i386.c +++ b/dlls/ntdll/signal_i386.c @@ -59,6 +59,7 @@ #include "ntdll_misc.h" #include "wine/exception.h" #include "wine/debug.h" +#include "umip.h"
#ifdef HAVE_VALGRIND_MEMCHECK_H #include <valgrind/memcheck.h> @@ -2016,8 +2017,26 @@ static void segv_handler( int signal, siginfo_t *siginfo, void *sigcontext ) case TRAP_x86_UNKNOWN: /* Unknown fault code */ { WORD err = get_error_code(context); + int result; + void *err_addr; if (!err && (stack->rec.ExceptionCode = is_privileged_instr( &stack->context ))) break; if ((err & 7) == 2 && handle_interrupt( err >> 3, context, stack )) return; + if (!err && (result = umip_emulate_instruction( &stack->context, &err_addr ))) + { + if (result == 1) + { + restore_context( &stack->context, sigcontext ); + return; + } + else + { + stack->rec.ExceptionCode = EXCEPTION_ACCESS_VIOLATION; + stack->rec.NumberParameters = 2; + stack->rec.ExceptionInformation[0] = 1; + stack->rec.ExceptionInformation[1] = err_addr; + break; + } + } stack->rec.ExceptionCode = EXCEPTION_ACCESS_VIOLATION; stack->rec.NumberParameters = 2; stack->rec.ExceptionInformation[0] = 0; diff --git a/dlls/ntdll/signal_x86_64.c b/dlls/ntdll/signal_x86_64.c index c372f65b34..e6dfa01c01 100644 --- a/dlls/ntdll/signal_x86_64.c +++ b/dlls/ntdll/signal_x86_64.c @@ -70,6 +70,7 @@ #include "wine/list.h" #include "ntdll_misc.h" #include "wine/debug.h" +#include "umip.h"
#ifdef HAVE_VALGRIND_MEMCHECK_H #include <valgrind/memcheck.h> @@ -2926,8 +2927,26 @@ static void segv_handler( int signal, siginfo_t *siginfo, void *sigcontext ) case TRAP_x86_UNKNOWN: /* Unknown fault code */ { WORD err = ERROR_sig(ucontext); + int result; + void *err_addr; if (!err && (stack->rec.ExceptionCode = is_privileged_instr( &stack->context ))) break; if ((err & 7) == 2 && handle_interrupt( ucontext, stack )) return; + if (!err && (result = umip_emulate_instruction( &stack->context, &err_addr ))) + { + if (result == 1) + { + restore_context( &stack->context, sigcontext ); + return; + } + else + { + stack->rec.ExceptionCode = EXCEPTION_ACCESS_VIOLATION; + stack->rec.NumberParameters = 2; + stack->rec.ExceptionInformation[0] = 1; + stack->rec.ExceptionInformation[1] = (ULONG_PTR)err_addr; + break; + } + } stack->rec.ExceptionCode = EXCEPTION_ACCESS_VIOLATION; stack->rec.NumberParameters = 2; stack->rec.ExceptionInformation[0] = 0; diff --git a/dlls/ntdll/umip.c b/dlls/ntdll/umip.c new file mode 100644 index 0000000000..9f153170ff --- /dev/null +++ b/dlls/ntdll/umip.c @@ -0,0 +1,780 @@ +/* + * Emulation for instructions covered by User-Mode Instruction Prevention + * + * Copyright (C) 2019 Brendan Shanks for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/* + * 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: + * 32-bit processes: The kernel traps the GPF and emulates sgdt, sidt, and smsw. + * sldt and str are not emulated. + * + * 64-bit processes: 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. + * This file contains functions used by the 32- and 64-bit signal handlers to emulate all + * instructions not emulated by the Linux kernel. + */ + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#define NONAMELESSUNION +#include "wine/asm.h" +#include "wine/debug.h" +#include "ntdll_misc.h" + +WINE_DEFAULT_DEBUG_CHANNEL(seh); + +/* 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 + +#if defined(__i386__) && defined(linux) + +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_instruction + * + * Emulate a UMIP-protected instruction. + * Returns: 0 if no instruction emulated, 1 if instruction emulated successfully, + * 2 if instruction's write to memory failed. + */ +static int emulate_instruction( CONTEXT *context, void **err_addr ) +{ + int prefix, segprefix, prefixlen, long_op, long_addr; + unsigned int i, len; + BYTE instr[16]; + + long_op = long_addr = 1; + len = virtual_uninterrupted_read_memory( (BYTE *)context->Eip, instr, sizeof(instr) ); + + /* First handle any possible prefix */ + + segprefix = -1; /* no prefix */ + prefix = 1; + for (i = 0; i < len; i++) + { + switch(instr[i]) + { + case 0x2e: + segprefix = context->SegCs; + break; + case 0x36: + segprefix = context->SegSs; + break; + case 0x3e: + segprefix = context->SegDs; + break; + case 0x26: + segprefix = context->SegEs; + break; + case 0x64: + segprefix = context->SegFs; + break; + case 0x65: + segprefix = context->SegGs; + break; + case 0x66: + long_op = !long_op; /* opcode size prefix */ + break; + case 0x67: + long_addr = !long_addr; /* addr size prefix */ + break; + case 0xf0: /* lock */ + break; + case 0xf2: /* repne */ + break; + case 0xf3: /* repe */ + break; + default: + prefix = 0; /* no more prefixes */ + break; + } + + if (!prefix) + break; + } + prefixlen = i; + + /* Now look at the actual instruction */ + + if (len - i < 3) + return 0; + + switch(instr[i++]) + { + case 0x0f: /* extended instruction */ + switch(instr[i++]) + { + case 0x00: /* sldt/str */ + { + int reg = (instr[i] >> 3) & 7; + switch (reg) + { + case 0: /* sldt */ + case 1: /* str */ + { + int instr_len; + int mod = instr[i] >> 6; + void *data; + UINT16 dummy_value; + + if (!INSTR_GetOperandAddr( context, &instr[i], len - i + 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 2; + } + } + context->Eip += prefixlen + instr_len + 2; + return 1; + } + } + } + case 0x01: /* sgdt/sidt/smsw */ + { + /* 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. + */ + break; + } + } + break; + } + return 0; /* Unable to emulate it */ +} + +#elif defined(__x86_64__) && defined(linux) /* __i386__ && linux */ + +#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( 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, + 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( 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( context, id ) : 0; + if (!long_addr) index &= 0xffffffff; + have_sib = 1; + } + + base = *get_int_reg( 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; + } + 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_instruction + * + * Emulate a UMIP-protected instruction. + * Returns: 0 if no instruction emulated, 1 if instruction emulated successfully, + * 2 if instruction's write to memory failed. + */ +static int emulate_instruction( CONTEXT *context, void **err_addr ) +{ + int prefix, segprefix, prefixlen, long_op, long_addr, rex; + unsigned int i, len; + BYTE instr[16]; + + long_op = long_addr = 1; + len = virtual_uninterrupted_read_memory( (BYTE *)context->Rip, instr, sizeof(instr) ); + + /* First handle any possible prefix */ + + segprefix = -1; /* no seg prefix */ + rex = 0; /* no rex prefix */ + prefix = 1; + for (i = 0; i < len; i++) + { + switch(instr[i]) + { + case 0x2e: + segprefix = context->SegCs; + break; + case 0x36: + segprefix = context->SegSs; + break; + case 0x3e: + segprefix = context->SegDs; + break; + case 0x26: + segprefix = context->SegEs; + break; + case 0x64: + segprefix = context->SegFs; + break; + case 0x65: + segprefix = context->SegGs; + break; + case 0x66: + long_op = !long_op; /* opcode size prefix */ + break; + case 0x67: + long_addr = !long_addr; /* addr size prefix */ + break; + case 0x40: /* rex */ + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4a: + case 0x4b: + case 0x4c: + case 0x4d: + case 0x4e: + case 0x4f: + rex = instr[i]; + break; + case 0xf0: /* lock */ + break; + case 0xf2: /* repne */ + break; + case 0xf3: /* repe */ + break; + default: + prefix = 0; /* no more prefixes */ + break; + } + + if (!prefix) + break; + } + prefixlen = i; + + /* Now look at the actual instruction */ + + if (len - i < 3) + return 0; + + switch(instr[i++]) + { + case 0x0f: /* extended instruction */ + switch(instr[i++]) + { + case 0x00: /* sldt/str */ + { + int reg = REGMODRM_REG( instr[i], rex ); + switch (reg) + { + case 0: /* sldt */ + case 1: /* str */ + { + int instr_len; + int rm = REGMODRM_RM( instr[i], rex ); + BYTE *data; + UINT16 dummy_value; + + if (!INSTR_GetOperandAddr( context, &instr[i], len - i + 1, 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[i], 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 2; + } + } + context->Rip += prefixlen + instr_len + 2; + return 1; + } + default: break; + } + } + case 0x01: /* sgdt/sidt/smsw */ + { + int instr_len; + int reg = REGMODRM_REG( instr[i], 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[i], rex ) == 3) + return 0; + + if (!INSTR_GetOperandAddr( context, &instr[i], len - i + 1, 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 2; + } + + context->Rip += prefixlen + instr_len + 2; + return 1; + } + case 4: /* smsw */ + { + BYTE *data; + int rm = REGMODRM_RM( instr[i], rex ); + + if (!INSTR_GetOperandAddr( context, &instr[i], len - i + 1, long_addr, + rex, segprefix, &instr_len, &data )) + return 0; + + if (REGMODRM_MOD( instr[i], 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 2; + } + } + context->Rip += prefixlen + instr_len + 2; + return 1; + } + default: break; + } + } + } + break; /* Unable to emulate it */ + } + return 0; +} + +#endif /* __x86_64 && linux */ + +#if (defined(__i386__) || defined(__x86_64__)) && defined(linux) +extern int have_cpuid (void ); + +extern void do_cpuid_cx( unsigned int ax, unsigned int cx, unsigned int *p ); +#ifdef __i386__ +__ASM_GLOBAL_FUNC( do_cpuid_cx, + "pushl %esi\n\t" + "pushl %ebx\n\t" + "movl 12(%esp),%eax\n\t" + "movl 16(%esp),%ecx\n\t" + "movl 20(%esp),%esi\n\t" + "cpuid\n\t" + "movl %eax,(%esi)\n\t" + "movl %ebx,4(%esi)\n\t" + "movl %ecx,8(%esi)\n\t" + "movl %edx,12(%esi)\n\t" + "popl %ebx\n\t" + "popl %esi\n\t" + "ret" ) +#else +__ASM_GLOBAL_FUNC( do_cpuid_cx, + "pushq %rbx\n\t" + "movq %rdx,%r8\n\t" + "movl %edi,%eax\n\t" + "movl %esi,%ecx\n\t" + "cpuid\n\t" + "movl %eax,(%r8)\n\t" + "movl %ebx,4(%r8)\n\t" + "movl %ecx,8(%r8)\n\t" + "movl %edx,12(%r8)\n\t" + "popq %rbx\n\t" + "ret" ) +#endif + +static int umip_enabled( void ) +{ + /* Check cpuid to see if UMIP is supported. + * UMIP bit is EAX=0x07,ECX=0x0, ECX bit 2 + * (CPUID.07H.0H:ECX:UMIP[bit 2] in Intel syntax) + * + * It would be preferable to check if UMIP is actually enabled + * (CR4.UMIP), but that can't be done from userspace. + */ + unsigned int regs[4]; + +#ifdef __i386__ + if (!have_cpuid()) return 0; +#endif + + do_cpuid_cx( 0x00000000, 0, regs ); /* get standard cpuid level and vendor name */ + if (regs[0] >= 0x00000007) /* Check for supported cpuid version */ + { + do_cpuid_cx( 0x00000007, 0, regs ); + if (regs[2] & (1 << 2)) + return 1; + } + + return 0; +} +#endif /* (__i386__ || __x86_64__) && linux */ + +int umip_emulate_instruction( CONTEXT *context, void **err_addr ) +{ +#if (defined(__i386__) || defined(__x86_64__)) && defined(linux) + static int umip_enabled_cache = -1; + if (umip_enabled_cache == -1) umip_enabled_cache = umip_enabled(); + + if (umip_enabled_cache) + return emulate_instruction( context, err_addr ); + else + return 0; +#else + return 0; +#endif +} diff --git a/dlls/ntdll/umip.h b/dlls/ntdll/umip.h new file mode 100644 index 0000000000..87dce7d707 --- /dev/null +++ b/dlls/ntdll/umip.h @@ -0,0 +1,21 @@ +/* + * Emulation for instructions covered by User-Mode Instruction Prevention + * + * Copyright (C) 2019 Brendan Shanks for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +extern int umip_emulate_instruction(CONTEXT *context, void **err_addr) DECLSPEC_HIDDEN;
On Thu, Nov 14, 2019 at 7:29 AM Brendan Shanks bshanks@codeweavers.com wrote:
... +/***********************************************************************
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 )
+{ ...
- /* FIXME: we assume that all segments have a base of 0 */
- *addr = (void *)(base + (index << ss));
- return 1;
Does this FIXME need to be resolved? I don't have an easy way to test UMIP but would the code example below give the correct output with this patch? (This is not based on any real program, so it may be a moot point.)
#include <stdio.h> #include <stdlib.h>
int main() { unsigned int ldt = 0x4141; unsigned short ldt2 = 0x4141; unsigned short ldt3 = 0x4141; asm volatile ("sldt %0" : "=a" (ldt)); asm volatile ("sldt (%0)" : : "r" (&ldt2)); #if defined(__x86_64__) || defined(__amd64__) asm volatile ("sldt %%gs:(%0)" :: "r" (0x1250)); asm volatile ("movw %%gs:0x1250, %0" : "=r" (ldt3)); #elif defined(__i386__) asm volatile ("sldt %%fs:(%0)" :: "r" (0xBF4)); asm volatile ("movw %%fs:0xBF4, %0" : "=r" (ldt3)); #endif printf("ldt = %x\n", ldt); printf("ldt2 = %x\n", ldt2); printf("ldt3 = %x\n", ldt3); return 0; }
-Andrew
On Nov 13, 2019, at 7:51 PM, Andrew Wesie awesie@gmail.com wrote:
On Thu, Nov 14, 2019 at 7:29 AM Brendan Shanks bshanks@codeweavers.com wrote:
... +/***********************************************************************
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 )
+{ ...
- /* FIXME: we assume that all segments have a base of 0 */
- *addr = (void *)(base + (index << ss));
- return 1;
Does this FIXME need to be resolved? I don't have an easy way to test UMIP but would the code example below give the correct output with this patch? (This is not based on any real program, so it may be a moot point.)
#include <stdio.h> #include <stdlib.h>
int main() { unsigned int ldt = 0x4141; unsigned short ldt2 = 0x4141; unsigned short ldt3 = 0x4141; asm volatile ("sldt %0" : "=a" (ldt)); asm volatile ("sldt (%0)" : : "r" (&ldt2)); #if defined(__x86_64__) || defined(__amd64__) asm volatile ("sldt %%gs:(%0)" :: "r" (0x1250)); asm volatile ("movw %%gs:0x1250, %0" : "=r" (ldt3)); #elif defined(__i386__) asm volatile ("sldt %%fs:(%0)" :: "r" (0xBF4)); asm volatile ("movw %%fs:0xBF4, %0" : "=r" (ldt3)); #endif printf("ldt = %x\n", ldt); printf("ldt2 = %x\n", ldt2); printf("ldt3 = %x\n", ldt3); return 0; }
Thanks for spotting that, I tried out your test app and it does crash when run with the emulation. I believe you’re right that this usage won’t show up in real programs though, especially on x86_64. I’m inclined to leave the FIXME unresolved.
I also investigated the history: I copied INSTR_GetOperandAddr() from ntoskrnl.exe/instr.c, and the FIXME has been present there since it was copied out of krnl386.exe16/instr.c 10 years ago. The original copy in krnl386.exe/instr.c still contains the segment code which could probably be re-used if necessary.
Brendan
Brendan Shanks bshanks@codeweavers.com writes:
On Nov 13, 2019, at 7:51 PM, Andrew Wesie awesie@gmail.com wrote:
On Thu, Nov 14, 2019 at 7:29 AM Brendan Shanks bshanks@codeweavers.com wrote:
... +/***********************************************************************
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 )
+{ ...
- /* FIXME: we assume that all segments have a base of 0 */
- *addr = (void *)(base + (index << ss));
- return 1;
Does this FIXME need to be resolved? I don't have an easy way to test UMIP but would the code example below give the correct output with this patch? (This is not based on any real program, so it may be a moot point.)
#include <stdio.h> #include <stdlib.h>
int main() { unsigned int ldt = 0x4141; unsigned short ldt2 = 0x4141; unsigned short ldt3 = 0x4141; asm volatile ("sldt %0" : "=a" (ldt)); asm volatile ("sldt (%0)" : : "r" (&ldt2)); #if defined(__x86_64__) || defined(__amd64__) asm volatile ("sldt %%gs:(%0)" :: "r" (0x1250)); asm volatile ("movw %%gs:0x1250, %0" : "=r" (ldt3)); #elif defined(__i386__) asm volatile ("sldt %%fs:(%0)" :: "r" (0xBF4)); asm volatile ("movw %%fs:0xBF4, %0" : "=r" (ldt3)); #endif printf("ldt = %x\n", ldt); printf("ldt2 = %x\n", ldt2); printf("ldt3 = %x\n", ldt3); return 0; }
Thanks for spotting that, I tried out your test app and it does crash when run with the emulation. I believe you’re right that this usage won’t show up in real programs though, especially on x86_64. I’m inclined to leave the FIXME unresolved.
I also investigated the history: I copied INSTR_GetOperandAddr() from ntoskrnl.exe/instr.c, and the FIXME has been present there since it was copied out of krnl386.exe16/instr.c 10 years ago. The original copy in krnl386.exe/instr.c still contains the segment code which could probably be re-used if necessary.
Outside of 16-bit code, these would in general be GDT selectors, and there's no easy way of getting their base address. Hopefully it won't become necessary.
Brendan Shanks bshanks@codeweavers.com writes:
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=47571 Signed-off-by: Brendan Shanks bshanks@codeweavers.com
dlls/ntdll/Makefile.in | 1 + dlls/ntdll/signal_i386.c | 19 + dlls/ntdll/signal_x86_64.c | 19 + dlls/ntdll/umip.c | 780 +++++++++++++++++++++++++++++++++++++ dlls/ntdll/umip.h | 21 + 5 files changed, 840 insertions(+) create mode 100644 dlls/ntdll/umip.c create mode 100644 dlls/ntdll/umip.h
Please also merge this with the existing is_privileged_instr() functions in the respective signal_*.c files, we don't need to parse the code multiple times for the same exception.
On Nov 14, 2019, at 12:31 AM, Alexandre Julliard julliard@winehq.org wrote:
Brendan Shanks bshanks@codeweavers.com writes:
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=47571 Signed-off-by: Brendan Shanks bshanks@codeweavers.com
dlls/ntdll/Makefile.in | 1 + dlls/ntdll/signal_i386.c | 19 + dlls/ntdll/signal_x86_64.c | 19 + dlls/ntdll/umip.c | 780 +++++++++++++++++++++++++++++++++++++ dlls/ntdll/umip.h | 21 + 5 files changed, 840 insertions(+) create mode 100644 dlls/ntdll/umip.c create mode 100644 dlls/ntdll/umip.h
Please also merge this with the existing is_privileged_instr() functions in the respective signal_*.c files, we don't need to parse the code multiple times for the same exception.
Sure, I can do that. Did you want all the parsing/emulation code merged into the signal_*.c files, so there would be no separate umip.c file? Or I can have is_privileged_instr() just detect the UMIP instructions and then call into the additional parsing/emulation in umip.c, this would remove the redundant virtual_uninterrupted_read_memory() call from umip.c.
Brendan
Brendan Shanks bshanks@codeweavers.com writes:
On Nov 14, 2019, at 12:31 AM, Alexandre Julliard julliard@winehq.org wrote:
Brendan Shanks bshanks@codeweavers.com writes:
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=47571 Signed-off-by: Brendan Shanks bshanks@codeweavers.com
dlls/ntdll/Makefile.in | 1 + dlls/ntdll/signal_i386.c | 19 + dlls/ntdll/signal_x86_64.c | 19 + dlls/ntdll/umip.c | 780 +++++++++++++++++++++++++++++++++++++ dlls/ntdll/umip.h | 21 + 5 files changed, 840 insertions(+) create mode 100644 dlls/ntdll/umip.c create mode 100644 dlls/ntdll/umip.h
Please also merge this with the existing is_privileged_instr() functions in the respective signal_*.c files, we don't need to parse the code multiple times for the same exception.
Sure, I can do that. Did you want all the parsing/emulation code merged into the signal_*.c files, so there would be no separate umip.c file? Or I can have is_privileged_instr() just detect the UMIP instructions and then call into the additional parsing/emulation in umip.c, this would remove the redundant virtual_uninterrupted_read_memory() call from umip.c.
It can all be merged, I don't think this requires a separate file.
Signed-off-by: Brendan Shanks bshanks@codeweavers.com --- dlls/ntdll/tests/exception.c | 375 +++++++++++++++++++++++++++++++++++ 1 file changed, 375 insertions(+)
diff --git a/dlls/ntdll/tests/exception.c b/dlls/ntdll/tests/exception.c index fb58c0ee7a..0d63731b56 100644 --- a/dlls/ntdll/tests/exception.c +++ b/dlls/ntdll/tests/exception.c @@ -327,6 +327,19 @@ static const struct exception 0xba, 0xba, 0xba, 0xba, 0xba, /* mov $0xbabababa, %edx */ 0xcd, 0x2d, 0xc3 }, /* int $0x2d; ret */ 17, 0, FALSE, STATUS_BREAKPOINT, 3, { 0xb8b8b8b8, 0xb9b9b9b9, 0xbabababa } }, + + /* test UMIP-covered instructions storing to a NULL pointer */ + { { 0x31, 0xc0, 0x0f, 0x01, 0x00, 0xc3 }, /* xor %eax,%eax; sgdt (%eax); ret */ + 2, 3, FALSE, STATUS_ACCESS_VIOLATION, 2, { 1, 0 } }, +/* 45 */ + { { 0x31, 0xc0, 0x0f, 0x01, 0x08, 0xc3 }, /* xor %eax,%eax; sidt (%eax); ret */ + 2, 3, FALSE, STATUS_ACCESS_VIOLATION, 2, { 1, 0 } }, + { { 0x31, 0xc0, 0x0f, 0x00, 0x00, 0xc3 }, /* xor %eax,%eax; sldt (%eax); ret */ + 2, 3, FALSE, STATUS_ACCESS_VIOLATION, 2, { 1, 0 } }, + { { 0x31, 0xc0, 0x0f, 0x01, 0x20, 0xc3 }, /* xor %eax,%eax; smsw (%eax); ret */ + 2, 3, FALSE, STATUS_ACCESS_VIOLATION, 2, { 1, 0 } }, + { { 0x31, 0xc0, 0x0f, 0x00, 0x08, 0xc3 }, /* xor %eax,%eax; str (%eax); ret */ + 2, 3, FALSE, STATUS_ACCESS_VIOLATION, 2, { 1, 0 } }, };
static int got_exception; @@ -2420,6 +2433,19 @@ static const struct exception 1, 1, STATUS_SINGLE_STEP, 0 }, { { 0xcd, 0x2c, 0xc3 }, 0, 2, STATUS_ASSERTION_FAILURE, 0 }, + + /* test UMIP-covered instructions storing to a NULL pointer */ + { { 0x48, 0x31, 0xc0, 0x0f, 0x01, 0x00, 0xc3 }, /* xor %rax,%rax; sgdt (%rax); ret */ + 3, 3, STATUS_ACCESS_VIOLATION, 2, { 1, 0 } }, + { { 0x48, 0x31, 0xc0, 0x0f, 0x01, 0x08, 0xc3 }, /* xor %rax,%rax; sidt (%rax); ret */ + 3, 3, STATUS_ACCESS_VIOLATION, 2, { 1, 0 } }, +/* 40 */ + { { 0x48, 0x31, 0xc0, 0x0f, 0x00, 0x00, 0xc3 }, /* xor %rax,%rax; sldt (%rax); ret */ + 3, 3, STATUS_ACCESS_VIOLATION, 2, { 1, 0 } }, + { { 0x48, 0x31, 0xc0, 0x0f, 0x01, 0x20, 0xc3 }, /* xor %rax,%rax; smsw (%rax); ret */ + 3, 3, STATUS_ACCESS_VIOLATION, 2, { 1, 0 } }, + { { 0x48, 0x31, 0xc0, 0x0f, 0x00, 0x08, 0xc3 }, /* xor %rax,%rax; str (%rax); ret */ + 3, 3, STATUS_ACCESS_VIOLATION, 2, { 1, 0 } }, };
static int got_exception; @@ -3383,6 +3409,353 @@ static void test_unload_trace(void) ok(found, "Unloaded module wasn't found.\n"); }
+#if defined(__x86_64__) +static DWORD WINAPI umip_handler( EXCEPTION_RECORD *rec, ULONG64 frame, + CONTEXT *context, DISPATCHER_CONTEXT *dispatcher ) +{ + const UINT *umip_insn_len = *(const UINT **)(dispatcher->HandlerData); + + got_exception++; + trace( "umip_handler exception: %x flags:%x addr:%p\n", + rec->ExceptionCode, rec->ExceptionFlags, rec->ExceptionAddress ); + + context->Rip += *umip_insn_len; + return ExceptionContinueExecution; +} +#else +static DWORD umip_handler( EXCEPTION_RECORD *rec, EXCEPTION_REGISTRATION_RECORD *frame, + CONTEXT *context, EXCEPTION_REGISTRATION_RECORD **dispatcher ) +{ + const UINT *umip_insn_len = *(const UINT **)(frame + 1); + + got_exception++; + trace( "umip_handler exception: %x flags:%x addr:%p\n", + rec->ExceptionCode, rec->ExceptionFlags, rec->ExceptionAddress ); + + context->Eip += *umip_insn_len; + return ExceptionContinueExecution; +} +#endif + +/* Each UMIP test is run first using run_exception_test() in case it throws an exception. If it doesn't, the + * test is run again to check the returned value. + * Tests writing to memory have two versions: one used for run_exception_test() that writes to the stack, and the + * other writing to a pointer passed as an argument. + */ +static void umip_test_reg_mem_16(const char *insn_name, + const BYTE *reg16_code, UINT reg16_code_size, UINT reg16_insn_len, + const BYTE *reg32_code, UINT reg32_code_size, UINT reg32_insn_len, + const BYTE *reg64_code, UINT reg64_code_size, UINT reg64_insn_len, + const BYTE *mem16_arg_code, UINT mem16_arg_code_size, + const BYTE *mem16_stack_code, UINT mem16_stack_code_size, UINT mem16_stack_insn_len) +{ + /* sldt, str, smsw all store a 16-bit value to a register or memory. + * When the destination is a register, the 16-bit value is zero-extended to the destination register size. + * When the destination is memory, only the 16-bit value is stored. + */ + UINT16 value16; + UINT32 value32; + + UINT16 (*reg16_func)(void) = code_mem; + UINT32 (*reg32_func)(void) = code_mem; + void (*mem16_func)(UINT16 *value) = code_mem; + + /* Destination is 16-bit register */ + got_exception = 0; + run_exception_test(umip_handler, ®16_insn_len, reg16_code, reg16_code_size, 0); + ok(!got_exception, "%s reg16 caused exception\n", insn_name); + if (!got_exception) + { + memset(&value16, 0xcc, sizeof(value16)); + memcpy(code_mem, reg16_code, reg16_code_size); + value16 = reg16_func(); + trace("%s reg16: 0x%x\n", insn_name, value16); + } + + /* Destination is 32-bit register. */ + got_exception = 0; + run_exception_test(umip_handler, ®32_insn_len, reg32_code, reg32_code_size, 0); + ok(!got_exception, "%s reg32 caused exception\n", insn_name); + if (!got_exception) + { + memset(&value32, 0xcc, sizeof(value32)); + memcpy(code_mem, reg32_code, reg32_code_size); + value32 = reg32_func(); + trace("%s reg32: 0x%x\n", insn_name, value32); +#if defined(__x86_64__) + /* For sldt/str in 64-bit mode, the upper 16 bits is defined to be zero */ + if (!strcmp(insn_name, "sldt") || !strcmp(insn_name, "str")) + ok(value32 >> 16 == 0, "%s expected upper 16 bits = 0, got 0x%x\n", insn_name, value32 >> 16); +#endif + } + +#if defined(__x86_64__) + /* Destination is 64-bit register */ + got_exception = 0; + run_exception_test(umip_handler, ®64_insn_len, reg64_code, reg64_code_size, 0); + ok(!got_exception, "%s reg64 caused exception\n", insn_name); + if (!got_exception) + { + UINT64 value64; + UINT64 (*reg64_func)(void) = code_mem; + + memset(&value64, 0xcc, sizeof(value64)); + memcpy(code_mem, reg64_code, reg64_code_size); + value64 = reg64_func(); + trace("%s reg64: 0x%llx\n", insn_name, value64); + + /* For sldt/str the upper 48 bits is defined to be zero */ + if (!strcmp(insn_name, "sldt") || !strcmp(insn_name, "str")) + ok(value64 >> 16 == 0, "%s expected upper 48 bits = 0, got 0x%llx\n", insn_name, value64 >> 16); + } +#endif + + /* Destination is memory (only the low 16 bits are defined to be written) */ + got_exception = 0; + run_exception_test(umip_handler, &mem16_stack_insn_len, mem16_stack_code, mem16_stack_code_size, 0); + ok(!got_exception, "%s mem16_stack caused exception\n", insn_name); + if (!got_exception) + { + memset(&value32, 0xcc, sizeof(value32)); + memcpy(code_mem, mem16_arg_code, mem16_arg_code_size); + mem16_func((UINT16 *)&value32); + trace("%s mem: 0x%x\n", insn_name, value32); + ok(value32 >> 16 == 0xcccc, "%s expected upper 16 bits = 0xcccc, got 0x%x\n", insn_name, value32 >> 16); + } +} + +static void umip_test_mem_descriptor(const char *insn_name, + const BYTE *code_arg, UINT code_arg_size, + const BYTE *code_stack, UINT code_stack_size, UINT code_stack_insn_len) +{ + /* sgdt and sidt write the descriptor table register to memory. + * The descriptor consists of a 2-byte limit field, and a base field which + * is 4 bytes in 32-bit mode and 8 bytes in 64-bit mode. + */ + BYTE descriptor[10]; + void (*func)(BYTE *value) = code_mem; + + got_exception = 0; + run_exception_test(umip_handler, &code_stack_insn_len, code_stack, code_stack_size, 0); + ok(!got_exception, "%s caused exception\n", insn_name); + if (!got_exception) + { + memset(descriptor, 0xcc, sizeof(descriptor)); + memcpy(code_mem, code_arg, code_arg_size); + func(descriptor); + trace("%s limit: 0x%x\n", insn_name, *(UINT16 *)&descriptor[0]); + trace("%s base: 0x%p\n", insn_name, (void *) *(UINT_PTR *)&descriptor[2]); + } +} + +static void umip_test_sldt(void) +{ + static const BYTE reg16[] = { + 0x66, 0x0f, 0x00, 0xc0, /* sldtw %ax */ + 0xc3, /* ret */ + }; + static const BYTE reg32[] = { + 0x0f, 0x00, 0xc0, /* sldtl %eax */ + 0xc3, /* ret */ + }; + static const BYTE reg64[] = { + 0x48, 0x0f, 0x00, 0xc0, /* sldtq %rax */ + 0xc3, /* ret */ + }; +#if defined(__x86_64__) + static const BYTE mem16_arg[] = { + 0x0f, 0x00, 0x01, /* sldtw (%rcx) */ + 0xc3, /* ret */ + }; + static const BYTE mem16_stack[] = { + 0x0f, 0x00, 0x44, 0x24, 0x08, /* sldtw 0x8(%rsp) */ + 0xc3, /* ret */ + }; +#else + static const BYTE mem16_arg[] = { + 0x8b, 0x4c, 0x24, 0x04, /* movl 0x4(%esp), %ecx */ + 0x0f, 0x00, 0x01, /* sldtw (%ecx) */ + 0xc3, /* ret */ + }; + static const BYTE mem16_stack[] = { + 0x83, 0xec, 0x02, /* subl $0x2,%esp */ + 0x0f, 0x00, 0x04, 0x24, /* sldtw (%esp) */ + 0x90, /* nop (padding so mem16_stack_insn_len is same for x86_64 and i386) */ + 0x83, 0xc4, 0x02, /* addl $0x2,%esp */ + 0xc3, /* ret */ + }; +#endif + + umip_test_reg_mem_16("sldt", + reg16, sizeof(reg16), 4, + reg32, sizeof(reg32), 3, + reg64, sizeof(reg64), 4, + mem16_arg, sizeof(mem16_arg), + mem16_stack, sizeof(mem16_stack), 5); +} + +static void umip_test_str(void) +{ + static const BYTE reg16[] = { + 0x66, 0x0f, 0x00, 0xc8, /* strw %ax */ + 0xc3, /* ret */ + }; + static const BYTE reg32[] = { + 0x0f, 0x00, 0xc8, /* strl %eax */ + 0xc3, /* ret */ + }; + static const BYTE reg64[] = { + 0x48, 0x0f, 0x00, 0xc8, /* strq %rax */ + 0xc3, /* ret */ + }; +#if defined(__x86_64__) + static const BYTE mem16_arg[] = { + 0x0f, 0x00, 0x09, /* strw (%rcx) */ + 0xc3, /* ret */ + }; + static const BYTE mem16_stack[] = { + 0x0f, 0x00, 0x4c, 0x24, 0x08, /* strw 0x8(%rsp) */ + 0xc3, /* ret */ + }; +#else + static const BYTE mem16_arg[] = { + 0x8b, 0x4c, 0x24, 0x04, /* movl 0x4(%esp), %ecx */ + 0x0f, 0x00, 0x09, /* strw (%ecx) */ + 0xc3, /* ret */ + }; + static const BYTE mem16_stack[] = { + 0x83, 0xec, 0x02, /* subl $0x2,%esp */ + 0x0f, 0x00, 0x0c, 0x24, /* strw (%esp) */ + 0x90, /* nop (padding so mem16_stack_insn_len is same for x86_64 and i386) */ + 0x83, 0xc4, 0x02, /* addl $0x2,%esp */ + 0xc3, /* ret */ + }; +#endif + + umip_test_reg_mem_16("str", + reg16, sizeof(reg16), 4, + reg32, sizeof(reg32), 3, + reg64, sizeof(reg64), 4, + mem16_arg, sizeof(mem16_arg), + mem16_stack, sizeof(mem16_stack), 5); +} + +static void umip_test_sgdt(void) +{ + /* sgdt destination must be memory */ +#if defined(__x86_64__) + static const BYTE mem_arg[] = { + 0x0f, 0x01, 0x01, /* sgdtt (%rcx) */ + 0xc3, /* ret */ + }; + static const BYTE mem_stack[] = { + 0x0f, 0x01, 0x44, 0x24, 0x08, /* sgdtt 0x8(%rsp) */ + 0xc3, /* ret */ + }; +#else + static const BYTE mem_arg[] = { + 0x8b, 0x4c, 0x24, 0x04, /* movl 0x4(%esp), %ecx */ + 0x0f, 0x01, 0x01, /* sgdt (%ecx) */ + 0xc3, /* ret */ + }; + static const BYTE mem_stack[] = { + 0x83, 0xec, 0x06, /* subl $0x6,%esp */ + 0x0f, 0x01, 0x04, 0x24, /* sgdt (%esp) */ + 0x90, /* nop (padding so mem16_stack_insn_len is same for x86_64 and i386) */ + 0x83, 0xc4, 0x06, /* addl $0x6,%esp */ + 0xc3, /* ret */ + }; +#endif + + umip_test_mem_descriptor("sgdt", mem_arg, sizeof(mem_arg), mem_stack, sizeof(mem_stack), 5); +} + +static void umip_test_sidt(void) +{ + /* sidt destination must be memory */ +#if defined(__x86_64__) + static const BYTE mem_arg[] = { + 0x0f, 0x01, 0x09, /* sidtt (%rcx) */ + 0xc3, /* ret */ + }; + static const BYTE mem_stack[] = { + 0x0f, 0x01, 0x4c, 0x24, 0x08, /* sidtt 0x8(%rsp) */ + 0xc3, /* ret */ + }; +#else + static const BYTE mem_arg[] = { + 0x8b, 0x4c, 0x24, 0x04, /* movl 0x4(%esp), %ecx */ + 0x0f, 0x01, 0x09, /* sidt (%ecx) */ + 0xc3, /* ret */ + }; + static const BYTE mem_stack[] = { + 0x83, 0xec, 0x06, /* subl $0x6,%esp */ + 0x0f, 0x01, 0x0c, 0x24, /* sidt (%esp) */ + 0x90, /* nop (padding so mem16_stack_insn_len is same for x86_64 and i386) */ + 0x83, 0xc4, 0x06, /* addl $0x6,%esp */ + 0xc3, /* ret */ + }; +#endif + + umip_test_mem_descriptor("sidt", mem_arg, sizeof(mem_arg), mem_stack, sizeof(mem_stack), 5); +} + +static void umip_test_smsw(void) +{ + static const BYTE reg16[] = { + 0x66, 0x0f, 0x01, 0xe0, /* smsww %ax */ + 0xc3, /* ret */ + }; + static const BYTE reg32[] = { + 0x0f, 0x01, 0xe0, /* smswl %eax */ + 0xc3, /* ret */ + }; + static const BYTE reg64[] = { + 0x48, 0x0f, 0x01, 0xe0, /* smswq %rax */ + 0xc3, /* ret */ + }; + +#if defined(__x86_64__) + static const BYTE mem16_arg[] = { + 0x0f, 0x01, 0x21, /* smsww (%rcx) */ + 0xc3, /* ret */ + }; + static const BYTE mem16_stack[] = { + 0x0f, 0x01, 0x64, 0x24, 0x08, /* smsww 0x8(%rsp) */ + 0xc3, /* ret */ + }; +#else + static const BYTE mem16_arg[] = { + 0x8b, 0x4c, 0x24, 0x04, /* movl 0x4(%esp), %ecx */ + 0x0f, 0x01, 0x21, /* smsww (%ecx) */ + 0xc3, /* ret */ + }; + static const BYTE mem16_stack[] = { + 0x83, 0xec, 0x02, /* subl $0x2,%esp */ + 0x0f, 0x01, 0x24, 0x24, /* smsww (%esp) */ + 0x90, /* nop (padding so mem16_stack_insn_len is same for x86_64 and i386) */ + 0x83, 0xc4, 0x02, /* addl $0x2,%esp */ + 0xc3, /* ret */ + }; +#endif + + umip_test_reg_mem_16("smsw", + reg16, sizeof(reg16), 4, + reg32, sizeof(reg32), 3, + reg64, sizeof(reg64), 4, + mem16_arg, sizeof(mem16_arg), + mem16_stack, sizeof(mem16_stack), 5); +} + +static void test_umip(void) +{ + umip_test_sldt(); + umip_test_str(); + umip_test_sgdt(); + umip_test_sidt(); + umip_test_smsw(); +} + START_TEST(exception) { HMODULE hntdll = GetModuleHandleA("ntdll.dll"); @@ -3513,6 +3886,7 @@ START_TEST(exception) test_suspend_thread(); test_suspend_process(); test_unload_trace(); + test_umip();
#elif defined(__x86_64__)
@@ -3551,6 +3925,7 @@ START_TEST(exception) test_suspend_thread(); test_suspend_process(); test_unload_trace(); + test_umip();
if (pRtlAddFunctionTable && pRtlDeleteFunctionTable && pRtlInstallFunctionTableCallback && pRtlLookupFunctionEntry) test_dynamic_unwind();
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=60097
Your paranoid android.
=== w2008s64 (32 bit report) ===
ntdll: exception.c:382: Test failed: ExceptionAddress at 002D0000 instead of 002D000B exception.c:418: Test failed: ExceptionAddress at 002D0000 instead of 002D000B exception.c:382: Test failed: ExceptionAddress at 002D0000 instead of 002D000B exception.c:418: Test failed: ExceptionAddress at 002D0000 instead of 002D000B exception.c:382: Test failed: ExceptionAddress at 002D0000 instead of 002D000B exception.c:418: Test failed: ExceptionAddress at 002D0000 instead of 002D000B exception.c:382: Test failed: ExceptionAddress at 00000000 instead of 002D000B exception.c:418: Test failed: ExceptionAddress at 00000000 instead of 002D000B exception.c:382: Test failed: ExceptionAddress at 00000000 instead of 002D000B exception.c:418: Test failed: ExceptionAddress at 00000000 instead of 002D000B exception.c:382: Test failed: ExceptionAddress at 00000000 instead of 002D000B exception.c:418: Test failed: ExceptionAddress at 00000000 instead of 002D000B exception.c:382: Test failed: ExceptionAddress at 00000000 instead of 002D000B exception.c:418: Test failed: ExceptionAddress at 00000000 instead of 002D000B exception.c:382: Test failed: ExceptionAddress at 00000000 instead of 002D000B exception.c:418: Test failed: ExceptionAddress at 00000000 instead of 002D000B exception.c:382: Test failed: ExceptionAddress at 00000000 instead of 002D000B exception.c:418: Test failed: ExceptionAddress at 00000000 instead of 002D000B