Skip to content

Commit

Permalink
i#5195 win2019: Fix except-execution test
Browse files Browse the repository at this point in the history
Updates the security-win32.except-execution test to not make a raw
thread with a custom stack, which fails on GA CI win2019.  Running the
test code on the initial thread as-is works fine, for regular DR
instrumentation modes.

To avoid headaches with tabs and trailing spaces, updates the context
dumping to avoid those, and updates other templates that use that.

Issue: #5195
  • Loading branch information
derekbruening committed Mar 10, 2022
1 parent fde0e15 commit 86e40f9
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 327 deletions.
242 changes: 7 additions & 235 deletions suite/tests/security-win32/except-execution.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* **********************************************************
* Copyright (c) 2014 Google, Inc. All rights reserved.
* Copyright (c) 2014-2022 Google, Inc. All rights reserved.
* Copyright (c) 2003-2010 VMware, Inc. All rights reserved.
* **********************************************************/

Expand Down Expand Up @@ -36,231 +36,12 @@

#include "except.h"

/* for repeatability we need a consistent stack */
#define STACK_BASE ((void *)0x14000000)

/***************************************************************************/
/* This is all copied from ntdll.h and from share/detach.c */

#include <AccCtrl.h> /* for SE_KERNEL_OBJECT */
#include <Aclapi.h>

typedef struct _UNICODE_STRING {
/* Length field is size in bytes not counting final 0 */
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;
typedef UNICODE_STRING *PUNICODE_STRING;

typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor; // Points to type SECURITY_DESCRIPTOR
PVOID SecurityQualityOfService; // Points to type SECURITY_QUALITY_OF_SERVICE
} OBJECT_ATTRIBUTES;
typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES;

#define InitializeObjectAttributes(p, n, a, r, s) \
{ \
(p)->Length = sizeof(OBJECT_ATTRIBUTES); \
(p)->RootDirectory = r; \
(p)->Attributes = a; \
(p)->ObjectName = n; \
(p)->SecurityDescriptor = s; \
(p)->SecurityQualityOfService = NULL; \
}

#define OBJ_CASE_INSENSITIVE 0x00000040L
/* N.B.: this is an invalid parameter on NT4! */
#define OBJ_KERNEL_HANDLE 0x00000200L
typedef ULONG ACCESS_MASK;

typedef struct _CLIENT_ID {
HANDLE UniqueProcess;
HANDLE UniqueThread;
} CLIENT_ID;
typedef CLIENT_ID *PCLIENT_ID;

typedef struct _USER_STACK {
PVOID FixedStackBase;
PVOID FixedStackLimit;
PVOID ExpandableStackBase;
PVOID ExpandableStackLimit;
PVOID ExpandableStackBottom;
} USER_STACK, *PUSER_STACK;

/* 64kb, same as allocation granularity so is as small as we can get */
#define STACK_RESERVE 0x10000
/* 12kb, matches current core stack size, note can expand to
* STACK_RESERVE - (5 * PAGE_SIZE), i.e. 44kb */
#define STACK_COMMIT 0x3000

typedef unsigned int(__stdcall *threadfunc_t)(void *);

/* returns NULL on error */
/* FIXME - is similar to core create_thread, but uses API routines where
* possible, could try to share. */
/* stack_reserve and stack commit must be multiples of PAGE_SIZE and reserve
* should be at least 5 pages larger then commit */
/* NOTE - For !target_kernel32 :
* target thread routine can't exit by by returning, instead it must call
* ExitThread or the like
* caller or target thread routine is responsible for informing csrss (if
* necessary) and freeing the the thread stack
/* We used to have a custom stack allocated at a particular address.
* We used a raw NtCreateThread with associated code borrowed from the core
* that used this stack. But this doesn't work on modern Windows, and at
* least for instrumentation modes we actively support today, there seems to
* be no need for all of that: so we just run the code on the initial stack.
*/
static HANDLE
nt_create_thread(HANDLE hProcess, PTHREAD_START_ROUTINE start_addr, void *arg,
uint stack_reserve, uint stack_commit, bool suspended, uint *tid,
bool target_kernel32)
{
HANDLE hThread = NULL;
USER_STACK stack = { 0 };
OBJECT_ATTRIBUTES oa;
CLIENT_ID cid;
CONTEXT context = { 0 };
uint num_commit_bytes, code;
unsigned long old_prot;
void *p;
SECURITY_DESCRIPTOR *sd = NULL;

GET_NTDLL(NtCreateThread,
(OUT PHANDLE ThreadHandle, IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes, IN HANDLE ProcessHandle,
OUT PCLIENT_ID ClientId, IN PCONTEXT ThreadContext,
IN PUSER_STACK UserStack, IN BOOLEAN CreateSuspended));

/* both stack size and stack reserve must be multiples of PAGE_SIZE */
assert((stack_reserve & (PAGE_SIZE - 1)) == 0);
assert((stack_commit & (PAGE_SIZE - 1)) == 0);
/* We stick a non-committed page on each end just to be safe and windows
* needs three pages at the end to properly handle end of expandable stack
* case (wants to pass exception back to the app on overflow, so needs some
* stack for that). */
assert(stack_reserve >= stack_commit + (5 * PAGE_SIZE));

/* Use the security descriptor from the target process for creating the
* thread so that once created the thread will be able to open a full
* access handle to itself (xref case 2096). */
/* NOTES - tried many ways to impersonate based on target process token
* so we could just use the default and was unable to get anywhere with
* that. Easiest thing to do here is just create a new security descriptor
* with a NULL (not empty) DACL [just InitializeSecurityDescriptor();
* SetSecurityDescriptorDacl()], but that's a privilege escalation
* problem (allows anybody full access to the thread)]. If we instead get
* the full security descriptor from the target process and try to use that
* the kernel complains that its a bad choice of owner. What we do instead
* is get just the DACL and leave the rest empty (will be filled in with
* defaults during create thread). Thus the security descriptor for the
* thread will end up having the owner, group, and SACL from this
* process and the DACL from the target. Upshot is the the thread pseudo
* handle will have full permissions (from the DACL), but the owner will be
* us and, even though the handle we get back from CreateThread will be
* fully permissioned as we request, any subsequent attempts by us to
* OpenThread will fail since we aren't on the DACL. We could always add
* ourselves to the DACL later or we can use the SE_DEBUG_PRIVILEGE to
* allow us to open it anyways. Note if for some reason we want to view the
* SACL we need to enable the ACCESS_SYSTEM_SECURITY privilege when opening
* the handle.
* FIXME - we could instead build our own DACL combining the two, we could
* also try setting the owner/group after the thread is created if we
* really wanted to look like the target process thread, and could also
* start with a NULL sd and set the DACL later if want to match
* CreateThread as closely as possible. If we do anything post system
* call should be sure to always create the thread suspended.
*/
code = GetSecurityInfo(hProcess, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL,
NULL, NULL, NULL, &sd);
assert(code == ERROR_SUCCESS);

InitializeObjectAttributes(&oa, NULL, OBJ_CASE_INSENSITIVE, NULL, sd);

stack.ExpandableStackBottom = VirtualAllocEx(
hProcess, STACK_BASE, stack_reserve - PAGE_SIZE, MEM_RESERVE, PAGE_READWRITE);
if (stack.ExpandableStackBottom == NULL)
goto error;

/* We provide non-committed boundary page on each side of the stack just to
* be safe (note we will get a stack overflow exception if stack grows to
* 3rd to last page of this region (xpsp2)). */
stack.ExpandableStackBottom = ((byte *)stack.ExpandableStackBottom) + PAGE_SIZE;
stack.ExpandableStackBase =
((byte *)stack.ExpandableStackBottom) + stack_reserve - (2 * PAGE_SIZE);

stack.ExpandableStackLimit = ((byte *)stack.ExpandableStackBase) - stack_commit;
num_commit_bytes = stack_commit + PAGE_SIZE;
p = ((byte *)stack.ExpandableStackBase) - num_commit_bytes;
p = VirtualAllocEx(hProcess, p, num_commit_bytes, MEM_COMMIT, PAGE_READWRITE);
if (p == NULL)
goto error;
if (!VirtualProtectEx(hProcess, p, PAGE_SIZE, PAGE_READWRITE | PAGE_GUARD, &old_prot))
goto error;

/* set the context: initialize with our own */
context.ContextFlags = CONTEXT_FULL;
GetThreadContext(GetCurrentThread(), &context);
if (target_kernel32) {
assert(false);
#if 0 /* not implemented here */
/* For kernel32!BaseThreadStartThunk CXT_XAX contains the address of the
* thread routine and CXT_XBX the arg */
context.CXT_XSP = (ptr_uint_t)stack.ExpandableStackBase;
context.CXT_XIP = (ptr_uint_t)get_kernel_thread_start_thunk();
context.CXT_XAX = (ptr_uint_t)start_addr;
context.CXT_XBX = (ptr_uint_t)arg;
#endif
} else {
ptr_uint_t buf[2];
bool res;
SIZE_T written;
/* directly targeting the start_address */
context.CXT_XSP = ((ptr_uint_t)stack.ExpandableStackBase) - sizeof(buf);
context.CXT_XIP = (ptr_uint_t)start_addr;
/* set up arg on stack, give NULL return address */
buf[0] = (ptr_uint_t)arg;
buf[1] = 0;
res = WriteProcessMemory(hProcess, (void *)context.CXT_XSP, &buf, sizeof(buf),
&written);
if (!res || written != sizeof(buf)) {
goto error;
}
}
if (context.CXT_XIP == 0) {
goto error;
}

/* NOTE - CreateThread passes NULL for object attributes so despite Nebbet
* must be optional (checked NTsp6a, XPsp2). We don't pass NULL so we can
* specify the security descriptor. */
if (!NT_SUCCESS(NtCreateThread(&hThread, THREAD_ALL_ACCESS, &oa, hProcess, &cid,
&context, &stack, (byte)(suspended ? TRUE : FALSE)))) {
goto error;
}

if (tid != NULL)
*tid = (ptr_uint_t)cid.UniqueThread;

exit:
if (sd != NULL) {
/* Free the security descriptor. */
LocalFree(sd);
}

return hThread;

error:
if (stack.ExpandableStackBottom != NULL) {
/* Free remote stack on error. */
VirtualFreeEx(hProcess, stack.ExpandableStackBottom, 0, MEM_RELEASE);
}
assert(hThread == NULL);
hThread = NULL; /* just to be safe */
goto exit;
}

/***************************************************************************/

typedef void (*funcptr)();

Expand Down Expand Up @@ -424,16 +205,7 @@ thread_func()
int
main()
{
HANDLE hThread;

/* I tried just making a new stack and swapping to it but had trouble
* w/ exception unwinding walking off the stack even though I put 0
* in fs:0. In any case, making a raw thread does the trick.
*/
hThread =
(HANDLE)nt_create_thread(GetCurrentProcess(), (threadfunc_t)thread_func, NULL,
STACK_RESERVE, STACK_COMMIT, false, NULL, false);
WaitForSingleObject(hThread, INFINITE);
thread_func();

return 0;
}
96 changes: 48 additions & 48 deletions suite/tests/security-win32/except-execution.templatex
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,36 @@
In RaiseException filter
In RaiseException handler
Inside first filter eax=badcdef0
exception code = 0xc0000005, ExceptionFlags=0x00000000
record=00000000, params=2
PC 0x00401... tried to read address 0xbadcdef0
pc=0x00401... eax=0xbadcdef0
ContextFlags=0x000100.f
Edi=0xeecdcdcd Esi=0xffcdcdcd Ebx=0xbbcdcdcd
Edx=0xddcdcdcd Ecx=0xcccdcdcd
Eax=0xbadcdef0@&
Ebp=0x1400ef.. Eip=0x00401... SegCs=0x000000..
EFlags & 0xFFFF=0x00000202 Esp=0x1400eb.. SegSs=0x000000..
exception code = 0xc0000005, ExceptionFlags=0x00000000
record=00000000, params=2
PC 0x00401... tried to read address 0xbadcdef0
pc=0x00401... eax=0xbadcdef0
ContextFlags=0x000100.f
Edi=0xeecdcdcd Esi=0xffcdcdcd Ebx=0xbbcdcdcd
Edx=0xddcdcdcd Ecx=0xcccdcdcd
@@
Eax=0xbadcdef0
Ebp=0x........ Eip=0x00401... SegCs=0x000000..
EFlags & 0xFFFF=0x00000202 Esp=0x........ SegSs=0x000000..
<floating point area>
SegGs=0x000000.. SegFs=0x000000.. SegEs=0x000000.. SegDs=0x000000..
SegGs=0x000000.. SegFs=0x000000.. SegEs=0x000000.. SegDs=0x000000..
Inside first handler
At statement after 1st try-except
Inside 2nd filter
Inside 3rd filter
exception code = 0xc0000005, ExceptionFlags=0x00000000
record=00000000, params=2
PC 0xdeadbeef tried to read address 0xdeadbeef
pc=0xdeadbeef eax=0x00000000
ContextFlags=0x000100.f
Edi=0xeecdcdcd Esi=0xffcdcdcd Ebx=0xbbcdcdcd
Edx=0xdeadbeef Ecx=0xcccdcdcd
Eax=0x00000000@&
Ebp=0x1400ef.. Eip=0xdeadbeef SegCs=0x000000..
EFlags & 0xFFFF=0x00000202 Esp=0x1400eb.. SegSs=0x000000..
exception code = 0xc0000005, ExceptionFlags=0x00000000
record=00000000, params=2
PC 0xdeadbeef tried to read address 0xdeadbeef
pc=0xdeadbeef eax=0x00000000
ContextFlags=0x000100.f
Edi=0xeecdcdcd Esi=0xffcdcdcd Ebx=0xbbcdcdcd
Edx=0xdeadbeef Ecx=0xcccdcdcd
@@
Eax=0x00000000
Ebp=0x........ Eip=0xdeadbeef SegCs=0x000000..
EFlags & 0xFFFF=0x00000202 Esp=0x........ SegSs=0x000000..
<floating point area>
SegGs=0x000000.. SegFs=0x000000.. SegEs=0x000000.. SegDs=0x000000..
SegGs=0x000000.. SegFs=0x000000.. SegEs=0x000000.. SegDs=0x000000..
Finally!
Expected memory access violation, ignoring it!
After exception handler
Expand All @@ -54,19 +54,19 @@ Attempting execution of badfunc
#if defined(PROGRAM_SHEPHERDING) && defined(security) && !defined(detect_mode) && defined(throw_exception) && !defined(low) && !defined(client) && !defined(thin_client)
SEC_VIO_EXCEPTION
DATA VIOLATION: Inside first filter eax=0
exception code = 0xc0000005, ExceptionFlags=0x00000000
record=00000000, params=2
PC 0x1400ec.. tried to read address 0x1400ec..
pc=0x1400ec.. eax=0x00000000
ContextFlags=0x000100.f
Edi=0xeecdcdcd Esi=0xffcdcdcd Ebx=0xbbcdcdcd
Edx=0xddcdcdcd Ecx=0xcccdcdcd
Eax=0x00000000@&
Ebp=0x1400ef.. Eip=0x1400ec.. SegCs=0x000000..
EFlags & 0xFFFF=0x00000202 Esp=0x1400eb.. SegSs=0x000000..
exception code = 0xc0000005, ExceptionFlags=0x00000000
record=00000000, params=2
PC 0x........ tried to read address 0x........
pc=0x........ eax=0x00000000
ContextFlags=0x000100.f
Edi=0xeecdcdcd Esi=0xffcdcdcd Ebx=0xbbcdcdcd
Edx=0xddcdcdcd Ecx=0xcccdcdcd
@@
Eax=0x00000000
Ebp=0x........ Eip=0x........ SegCs=0x000000..
EFlags & 0xFFFF=0x00000202 Esp=0x........ SegSs=0x000000..
<floating point area>
SegGs=0x000000.. SegFs=0x000000.. SegEs=0x000000.. SegDs=0x000000..
SegGs=0x000000.. SegFs=0x000000.. SegEs=0x000000.. SegDs=0x000000..
DATA VIOLATION: Inside first handler
DATA: At statement after 1st try-except
# if defined(PROGRAM_SHEPHERDING) && defined(security) && defined(throw_exception) && defined(throw_exception_max) && defined(1)
Expand All @@ -79,19 +79,19 @@ DATA: At statement after 1st try-except
SEC_VIO_EXCEPTION
DATA: Finally!
DATA: Expected execution violation!
exception code = 0xc0000005, ExceptionFlags=0x00000000
record=00000000, params=2
PC 0x1400ec.. tried to read address 0x1400ec..
pc=0x1400ec.. eax=0x00000000
ContextFlags=0x000100.f
Edi=0xeecdcdcd Esi=0xffcdcdcd Ebx=0xbbcdcdcd
Edx=0xddcdcdcd Ecx=0xcccdcdcd
Eax=0x00000000@&
Ebp=0x1400ef.. Eip=0x1400ec.. SegCs=0x000000..
EFlags & 0xFFFF=0x00000202 Esp=0x1400eb.. SegSs=0x000000..
exception code = 0xc0000005, ExceptionFlags=0x00000000
record=00000000, params=2
PC 0x........ tried to read address 0x........
pc=0x........ eax=0x00000000
ContextFlags=0x000100.f
Edi=0xeecdcdcd Esi=0xffcdcdcd Ebx=0xbbcdcdcd
Edx=0xddcdcdcd Ecx=0xcccdcdcd
@@
Eax=0x00000000
Ebp=0x........ Eip=0x........ SegCs=0x000000..
EFlags & 0xFFFF=0x00000202 Esp=0x........ SegSs=0x000000..
<floating point area>
SegGs=0x000000.. SegFs=0x000000.. SegEs=0x000000.. SegDs=0x000000..
SegGs=0x000000.. SegFs=0x000000.. SegEs=0x000000.. SegDs=0x000000..
DATA: After exception handler
# endif
STOP
Expand Down
Loading

0 comments on commit 86e40f9

Please sign in to comment.