From: Brendan Shanks Subject: [PATCH v4 2/2] ntdll/tests: Add tests for UMIP instructions. Message-Id: <20191204002108.25903-3-bshanks@codeweavers.com> Date: Tue, 3 Dec 2019 16:21:08 -0800 In-Reply-To: <20191204002108.25903-1-bshanks@codeweavers.com> References: <20191204002108.25903-1-bshanks@codeweavers.com> Signed-off-by: Brendan Shanks --- 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(); -- 2.23.0