This patch adds emulation for instructions protected by User-Mode Instruction Prevention (currently only implemented by Ryzen 3000 CPUs). I'm still working on tests but wanted to get some feedback, especially on these topics:
- I'm currently adding the exception handler in signal_init_process() but this doesn't feel like the right place to do it. Is there a better place for this to go?
- the instruction emulation helper functions are copied from ntoskrnl.exe/instr.c, would it be better to move them somewhere so they can be shared?
Brendan Shanks (1): ntdll: Add emulation for UMIP instructions
dlls/ntdll/Makefile.in | 1 + dlls/ntdll/signal_i386.c | 2 + dlls/ntdll/signal_x86_64.c | 3 + dlls/ntdll/umip.c | 787 +++++++++++++++++++++++++++++++++++++ dlls/ntdll/umip.h | 21 + 5 files changed, 814 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 | 2 + dlls/ntdll/signal_x86_64.c | 3 + dlls/ntdll/umip.c | 787 +++++++++++++++++++++++++++++++++++++ dlls/ntdll/umip.h | 21 + 5 files changed, 814 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 a43599e375..94a828dcdf 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> @@ -2440,6 +2441,7 @@ void signal_init_process(void) #endif
wine_ldt_init_locking( ldt_lock, ldt_unlock ); + umip_add_handler(); return;
error: diff --git a/dlls/ntdll/signal_x86_64.c b/dlls/ntdll/signal_x86_64.c index feb2e69d3f..ebfef1c685 100644 --- a/dlls/ntdll/signal_x86_64.c +++ b/dlls/ntdll/signal_x86_64.c @@ -67,6 +67,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> @@ -3350,6 +3351,8 @@ void signal_init_process(void) sig_act.sa_sigaction = trap_handler; if (sigaction( SIGTRAP, &sig_act, NULL ) == -1) goto error; #endif + + umip_add_handler(); return;
error: diff --git a/dlls/ntdll/umip.c b/dlls/ntdll/umip.c new file mode 100644 index 0000000000..c05211568b --- /dev/null +++ b/dlls/ntdll/umip.c @@ -0,0 +1,787 @@ +/* + * 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: no emulation is done for any instructions. + * A kernel patch to add emulation for sgdt, sidt, and smsw + * has been accepted and should be in v5.4. + * + * When the kernel doesn't emulate an instruction, the process receives a SIGSEGV. + * This file contains exception handlers for 32- and 64-bit code to emulate all + * instructions not emulated by the Linux kernel. + */ + +#include <stdarg.h> +#include <string.h> + +#define NONAMELESSUNION +#include "windef.h" +#include "winbase.h" +#include "winternl.h" +#define WIN32_NO_STATUS +#include "ddk/wdm.h" +#include "excpt.h" +#include "wine/asm.h" +#include "wine/debug.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 DWORD *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 void *INSTR_GetOperandAddr( CONTEXT *context, BYTE *instr, + int long_addr, int segprefix, int *len ) +{ + int mod, rm, base = 0, index = 0, ss = 0, off; + +#define GET_VAL(val,type) \ + { *val = *(type *)instr; instr += sizeof(type); *len += sizeof(type); } + + *len = 0; + GET_VAL( &mod, BYTE ); + rm = mod & 7; + mod >>= 6; + + if (mod == 3) return get_reg_address( context, rm ); + + 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 */ + return (void *)(base + (index << ss)); +#undef GET_VAL +} + + +/*********************************************************************** + * emulate_instruction + * + * Emulate a privileged instruction. + * Returns exception continuation status. + */ +static DWORD emulate_instruction( EXCEPTION_RECORD *rec, CONTEXT *context ) +{ + int prefix, segprefix, prefixlen, len, long_op, long_addr; + BYTE *instr; + + long_op = long_addr = 1; + instr = (BYTE *)context->Eip; + if (!instr) return ExceptionContinueSearch; + + /* First handle any possible prefix */ + + segprefix = -1; /* no prefix */ + prefix = 1; + prefixlen = 0; + while(prefix) + { + switch(*instr) + { + 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) + { + instr++; + prefixlen++; + } + } + + /* Now look at the actual instruction */ + + switch(*instr) + { + case 0x0f: /* extended instruction */ + switch(instr[1]) + { + case 0x00: /* sldt/str */ + { + int reg = (instr[2] >> 3) & 7; + switch (reg) + { + case 0: /* sldt */ + case 1: /* str */ + { + BYTE *data = INSTR_GetOperandAddr( context, instr + 2, long_addr, + segprefix, &len ); + int mod = instr[2] >> 6; + UINT16 dummy_value; + + 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. */ + memcpy( data, &dummy_value, sizeof(dummy_value) ); + } + context->Eip += prefixlen + len + 2; + return ExceptionContinueExecution; + } + } + } + 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 ExceptionContinueSearch; /* Unable to emulate it */ +} + + +/*********************************************************************** + * vectored_handler + * + * Vectored exception handler used to emulate protected instructions + * from 32-bit code. + */ +static LONG CALLBACK vectored_handler( EXCEPTION_POINTERS *ptrs ) +{ + EXCEPTION_RECORD *record = ptrs->ExceptionRecord; + CONTEXT *context = ptrs->ContextRecord; + + if (record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && + record->ExceptionInformation[0] == 0 && + record->ExceptionInformation[1] == 0xffffffff) + { + if (emulate_instruction( record, context ) == ExceptionContinueExecution) + return EXCEPTION_CONTINUE_EXECUTION; + } + return EXCEPTION_CONTINUE_SEARCH; +} + +#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 BYTE *INSTR_GetOperandAddr( CONTEXT *context, BYTE *instr, + int long_addr, int rex, int segprefix, int *len ) +{ + int mod, rm, ss = 0, off, have_sib = 0; + DWORD64 base = 0, index = 0; + +#define GET_VAL( val, type ) \ + { *val = *(type *)instr; instr += sizeof(type); *len += sizeof(type); } + + *len = 0; + GET_VAL( &mod, BYTE ); + rm = REGMODRM_RM( mod, rex ); + mod = REGMODRM_MOD( mod, rex ); + + if (mod == 3) + return (BYTE *)get_int_reg( context, rm ); + + 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 */ + return (BYTE *)(base + (index << ss)); +#undef GET_VAL +} + + +/*********************************************************************** + * emulate_instruction + * + * Emulate a privileged instruction. + * Returns exception continuation status. + */ +static DWORD emulate_instruction( EXCEPTION_RECORD *rec, CONTEXT *context ) +{ + int prefix, segprefix, prefixlen, len, long_op, long_addr, rex; + BYTE *instr; + + long_op = long_addr = 1; + instr = (BYTE *)context->Rip; + if (!instr) return ExceptionContinueSearch; + + /* First handle any possible prefix */ + + segprefix = -1; /* no seg prefix */ + rex = 0; /* no rex prefix */ + prefix = 1; + prefixlen = 0; + while(prefix) + { + switch(*instr) + { + 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; + break; + case 0xf0: /* lock */ + break; + case 0xf2: /* repne */ + break; + case 0xf3: /* repe */ + break; + default: + prefix = 0; /* no more prefixes */ + break; + } + if (prefix) + { + instr++; + prefixlen++; + } + } + + /* Now look at the actual instruction */ + + switch(*instr) + { + case 0x0f: /* extended instruction */ + switch(instr[1]) + { + case 0x00: /* sldt/str */ + { + int reg = REGMODRM_REG( instr[2], rex ); + switch (reg) + { + case 0: /* sldt */ + case 1: /* str */ + { + BYTE *data = INSTR_GetOperandAddr( context, instr + 2, long_addr, + rex, segprefix, &len ); + int rm = REGMODRM_RM( instr[2], rex ); + UINT16 dummy_value; + + 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[2], 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, get_op_size( long_op, rex ) ); + } + else + { + /* Destination operand is a memory location. + * Only copy 16 bits regardless of operand size. */ + memcpy( data, &dummy_value, sizeof(dummy_value) ); + } + context->Rip += prefixlen + len + 2; + return ExceptionContinueExecution; + } + default: break; + } + } + case 0x01: /* sgdt/sidt/smsw */ + { + int reg = REGMODRM_REG( instr[2], 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[2], rex ) == 3) + return ExceptionContinueSearch; + + data = INSTR_GetOperandAddr( context, instr + 2, long_addr, + rex, segprefix, &len ); + + 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 ); + } + memcpy( data, &dummy_limit, UMIP_GDT_IDT_LIMIT_SIZE ); + memcpy( data + UMIP_GDT_IDT_LIMIT_SIZE, &dummy_base_addr, UMIP_GDT_IDT_BASE_SIZE_64BIT ); + + context->Rip += prefixlen + len + 2; + return ExceptionContinueExecution; + } + case 4: /* smsw */ + { + BYTE *data = INSTR_GetOperandAddr( context, instr + 2, long_addr, + rex, segprefix, &len ); + int rm = REGMODRM_RM( instr[2], rex ); + + if (REGMODRM_MOD( instr[2], 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 ); + memcpy( data, &dummy_msw, sizeof(dummy_msw) ); + } + context->Rip += prefixlen + len + 2; + return ExceptionContinueExecution; + } + default: break; + } + } + } + break; /* Unable to emulate it */ + } + return ExceptionContinueSearch; /* Unable to emulate it */ +} + + +/*********************************************************************** + * vectored_handler + * + * Vectored exception handler used to emulate UMIP instructions + * from 64-bit code. + */ +static LONG CALLBACK vectored_handler( EXCEPTION_POINTERS *ptrs ) +{ + EXCEPTION_RECORD *record = ptrs->ExceptionRecord; + CONTEXT *context = ptrs->ContextRecord; + + if (record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && + record->ExceptionInformation[0] == 0 && + record->ExceptionInformation[1] == 0xffffffffffffffff) + { + if (emulate_instruction( record, context ) == ExceptionContinueExecution) + { + TRACE( "next instruction rip=%lx\n", context->Rip ); + TRACE( " rax=%016lx rbx=%016lx rcx=%016lx rdx=%016lx\n", + context->Rax, context->Rbx, context->Rcx, context->Rdx ); + TRACE( " rsi=%016lx rdi=%016lx rbp=%016lx rsp=%016lx\n", + context->Rsi, context->Rdi, context->Rbp, context->Rsp ); + TRACE( " r8=%016lx r9=%016lx r10=%016lx r11=%016lx\n", + context->R8, context->R9, context->R10, context->R11 ); + TRACE( " r12=%016lx r13=%016lx r14=%016lx r15=%016lx\n", + context->R12, context->R13, context->R14, context->R15 ); + + return EXCEPTION_CONTINUE_EXECUTION; + } + } + return EXCEPTION_CONTINUE_SEARCH; +} +#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 */ + +void umip_add_handler( void ) +{ +#if (defined(__i386__) || defined(__x86_64__)) && defined(linux) + if (umip_enabled()) + RtlAddVectoredExceptionHandler( TRUE, vectored_handler ); +#endif +} diff --git a/dlls/ntdll/umip.h b/dlls/ntdll/umip.h new file mode 100644 index 0000000000..704d0a25d5 --- /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 void umip_add_handler(void) DECLSPEC_HIDDEN;
Brendan Shanks bshanks@codeweavers.com writes:
This patch adds emulation for instructions protected by User-Mode Instruction Prevention (currently only implemented by Ryzen 3000 CPUs). I'm still working on tests but wanted to get some feedback, especially on these topics:
- I'm currently adding the exception handler in signal_init_process() but this doesn't feel like the right place to do it. Is there a better place for this to go?
This shouldn't use an exception handler, it should be integrated into the existing signal handlers for the respective platforms, most likely as part of the is_privileged_instr() functions.