In kernel mode, GS base is not set to a TEB, but fortunately the offset for the current thread object, which EasyAntiCheat.sys relies on, doesn't conflict with anything in the TEB.
Signed-off-by: Derek Lesho dlesho@codeweavers.com --- dlls/ntoskrnl.exe/ntoskrnl.c | 37 +++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-)
diff --git a/dlls/ntoskrnl.exe/ntoskrnl.c b/dlls/ntoskrnl.exe/ntoskrnl.c index e94feabc826..a0f45b4135c 100644 --- a/dlls/ntoskrnl.exe/ntoskrnl.c +++ b/dlls/ntoskrnl.exe/ntoskrnl.c @@ -870,7 +870,7 @@ NTSTATUS CDECL wine_ntoskrnl_main_loop( HANDLE stop_event )
for (;;) { - NtCurrentTeb()->Reserved5[1] = NULL; + NtCurrentTeb()->SystemReserved1[15] = NULL; if (!context.in_buff && !(context.in_buff = HeapAlloc( GetProcessHeap(), 0, context.in_size ))) { ERR( "failed to allocate buffer\n" ); @@ -891,7 +891,7 @@ NTSTATUS CDECL wine_ntoskrnl_main_loop( HANDLE stop_event ) context.params = reply->params; context.in_size = reply->in_size; client_tid = reply->client_tid; - NtCurrentTeb()->Reserved5[1] = wine_server_get_ptr( reply->client_thread ); + NtCurrentTeb()->SystemReserved1[15] = wine_server_get_ptr( reply->client_thread ); } else { @@ -2338,7 +2338,7 @@ POBJECT_TYPE PsThreadType = &thread_type; */ PRKTHREAD WINAPI KeGetCurrentThread(void) { - struct _KTHREAD *thread = NtCurrentTeb()->Reserved5[1]; + struct _KTHREAD *thread = NtCurrentTeb()->SystemReserved1[15];
if (!thread) { @@ -2351,7 +2351,11 @@ PRKTHREAD WINAPI KeGetCurrentThread(void) kernel_object_from_handle( handle, PsThreadType, (void**)&thread ); if (handle != GetCurrentThread()) NtClose( handle );
- NtCurrentTeb()->Reserved5[1] = thread; + /* In kernel mode, GS base is not set to a TEB, but fortunately the offset + for the current thread object, which EasyAntiCheat.sys relies on, + doesn't conflict with anything in the TEB. */ + + NtCurrentTeb()->SystemReserved1[15] = thread; }
return thread; @@ -2912,6 +2916,22 @@ DEVICE_OBJECT* WINAPI IoGetAttachedDeviceReference( DEVICE_OBJECT *device ) }
+struct system_thread_ctx +{ + PKSTART_ROUTINE start; + PVOID context; +}; + +static void WINAPI init_system_thread(PVOID context) +{ + struct system_thread_ctx info = *(struct system_thread_ctx *)context; + HeapFree( GetProcessHeap(), 0, context ); + + NtCurrentTeb()->SystemReserved1[15] = KeGetCurrentThread(); + + info.start(info.context); +} + /*********************************************************************** * PsCreateSystemThread (NTOSKRNL.EXE.@) */ @@ -2920,9 +2940,16 @@ NTSTATUS WINAPI PsCreateSystemThread(PHANDLE ThreadHandle, ULONG DesiredAccess, HANDLE ProcessHandle, PCLIENT_ID ClientId, PKSTART_ROUTINE StartRoutine, PVOID StartContext) { + struct system_thread_ctx *info; + if (!ProcessHandle) ProcessHandle = GetCurrentProcess(); + + info = HeapAlloc( GetProcessHeap(), 0, sizeof(*info) ); + info->start = StartRoutine; + info->context = StartContext; + return RtlCreateUserThread(ProcessHandle, 0, FALSE, 0, 0, - 0, StartRoutine, StartContext, + 0, init_system_thread, info, ThreadHandle, ClientId); }
Signed-off-by: Derek Lesho dlesho@codeweavers.com --- v2: Minor naming adjustments and applies to latest head. --- dlls/ntoskrnl.exe/ntoskrnl.exe.spec | 2 +- dlls/ntoskrnl.exe/sync.c | 10 ++ dlls/ntoskrnl.exe/tests/driver.c | 145 +++++++++++++++++++++++++++- dlls/ntoskrnl.exe/tests/driver.h | 2 + dlls/ntoskrnl.exe/tests/ntoskrnl.c | 40 ++++++++ 5 files changed, 197 insertions(+), 2 deletions(-)
diff --git a/dlls/ntoskrnl.exe/ntoskrnl.exe.spec b/dlls/ntoskrnl.exe/ntoskrnl.exe.spec index 8892fde5054..8127fd417e1 100644 --- a/dlls/ntoskrnl.exe/ntoskrnl.exe.spec +++ b/dlls/ntoskrnl.exe/ntoskrnl.exe.spec @@ -581,7 +581,7 @@ @ stub KeInsertDeviceQueue @ stub KeInsertHeadQueue @ stdcall KeInsertQueue(ptr ptr) -@ stub KeInsertQueueApc +@ stdcall KeInsertQueueApc(ptr ptr ptr long) @ stub KeInsertQueueDpc @ stub KeIsAttachedProcess @ stub KeIsExecutingDpc diff --git a/dlls/ntoskrnl.exe/sync.c b/dlls/ntoskrnl.exe/sync.c index 0ddb1b353c5..c96a490ff04 100644 --- a/dlls/ntoskrnl.exe/sync.c +++ b/dlls/ntoskrnl.exe/sync.c @@ -696,6 +696,16 @@ void WINAPI KeInitializeApc(PRKAPC apc, PRKTHREAD thread, KAPC_ENVIRONMENT env, } }
+/*********************************************************************** + * KeInsertQueueApc (NTOSKRNL.EXE.@) + */ +BOOLEAN WINAPI KeInsertQueueApc(PRKAPC apc, PVOID sysarg1, PVOID sysarg2, KPRIORITY increment) +{ + FIXME("apc %p arg1 %p arg2 %p inc %u\n", apc, sysarg1, sysarg2, increment); + + return FALSE; +} + /*********************************************************************** * KeTestAlertThread (NTOSKRNL.EXE.@) */ diff --git a/dlls/ntoskrnl.exe/tests/driver.c b/dlls/ntoskrnl.exe/tests/driver.c index dfc742eec3b..32095476abf 100644 --- a/dlls/ntoskrnl.exe/tests/driver.c +++ b/dlls/ntoskrnl.exe/tests/driver.c @@ -60,7 +60,7 @@ static const WCHAR driver_link[] = {'\','D','o','s','D','e','v','i','c','e','s' static DRIVER_OBJECT *driver_obj; static DEVICE_OBJECT *lower_device, *upper_device;
-static POBJECT_TYPE *pExEventObjectType, *pIoFileObjectType, *pPsThreadType, *pIoDriverObjectType; +static POBJECT_TYPE *pExEventObjectType, *pIoFileObjectType, *pPsThreadType, *pPsProcessType, *pIoDriverObjectType; static PEPROCESS *pPsInitialSystemProcess; static void *create_caller_thread;
@@ -1990,6 +1990,145 @@ static void test_dpc(void) KeRevertToUserAffinityThread(); }
+void WINAPI apc_host_thread_func(PVOID context) +{ + /* kernel mode APCs don't require an alertable wait */ + wait_single((PKEVENT)context, 50 * -10000000); + + PsTerminateSystemThread(STATUS_SUCCESS); +} + +enum +{ + SPECIAL_KERNEL, + NORMAL_KERNEL, + NORMAL_USER, +} current_apc_type; + +PKTHREAD current_apc_thread; + +static void WINAPI kernel_routine(PKAPC apc, PKNORMAL_ROUTINE *nrml_routine, PVOID *normal_ctx, PVOID *sysarg1, PVOID *sysarg2) +{ + ok(KeGetCurrentThread() == current_apc_thread, "Got unexpected current thread: %p!=%p\n", KeGetCurrentThread(), current_apc_thread); + ok(apc && nrml_routine && normal_ctx && sysarg1 && sysarg2, "Missing parameter pointer in kernel_routine\n"); + if (current_apc_type == SPECIAL_KERNEL) + { + ok(*nrml_routine == NULL, "Got non-null normal routine value %p in special APC.\n", *nrml_routine); + ok(*normal_ctx == NULL, "Got unexpected normal context %p in APC.\n", *normal_ctx); + KeSetEvent((PKEVENT)*sysarg2, 0, FALSE); + } + else + { + ok(*nrml_routine == (PVOID)(ULONG_PTR)0xdeadbeef, "Got unexpected initial normal routine value: %p\n", *normal_ctx); + ok(*normal_ctx == (PVOID)(ULONG_PTR)0xdeadbeef, "Got unexpected normal context: %p\n", *normal_ctx); + } + /* in a special kernel APC, the normal routine we set should be ignored */ + /* in a normal routine, the special section has the final say on the normal routine and parameters */ + *nrml_routine = *sysarg1; +} + +static void WINAPI rundown_routine(PKAPC apc) +{ + ok(FALSE, "Unexpected execution of rundown routine\n"); +} + +static void WINAPI normal_routine(PVOID normal_ctx, PVOID sysarg1, PVOID sysarg2) +{ + ok(KeGetCurrentThread() == current_apc_thread, "Got unexpected current thread: %p!=%p\n", KeGetCurrentThread(), current_apc_thread); + ok (current_apc_type == NORMAL_KERNEL, "Got unexpected current APC type: %u\n", current_apc_type); + KeSetEvent((PKEVENT)sysarg2, 0, FALSE); +} + +static void test_apc(const struct test_input *test_input) +{ + void (WINAPI *pKeInitializeApc)(PRKAPC,PRKTHREAD,KAPC_ENVIRONMENT,PKKERNEL_ROUTINE,PKRUNDOWN_ROUTINE,PKNORMAL_ROUTINE,KPROCESSOR_MODE,PVOID); + BOOLEAN (WINAPI *pKeInsertQueueApc)(PKAPC,PVOID,PVOID,KPRIORITY); + OBJECT_ATTRIBUTES attr; + PEPROCESS user_process; + PKTHREAD user_apc_thread; + HANDLE user_process_handle; + HANDLE done_event_system_handle, done_event_user_handle; + KEVENT *done_event, terminate_event; + HANDLE apc_host_thread; + KAPC special_apc, kernel_apc, user_apc; + BOOLEAN res; + NTSTATUS stat; + + pKeInitializeApc = get_proc_address("KeInitializeApc"); + pKeInsertQueueApc = get_proc_address("KeInsertQueueApc"); + + ok(pKeInitializeApc && pKeInsertQueueApc, "Unable to find KeInitializeApc and KeInsertQueueApc\n"); + if (!pKeInitializeApc || !pKeInsertQueueApc) + return; + + InitializeObjectAttributes(&attr, NULL, OBJ_KERNEL_HANDLE, NULL, NULL); + ZwCreateEvent(&done_event_system_handle, SYNCHRONIZE|EVENT_MODIFY_STATE, &attr, SynchronizationEvent, FALSE); + ObReferenceObjectByHandle(done_event_system_handle, 0, *pExEventObjectType, KernelMode, (void**)&done_event, NULL); + + KeInitializeEvent(&terminate_event, NotificationEvent, FALSE); + + apc_host_thread = create_thread(apc_host_thread_func, &terminate_event); + ObReferenceObjectByHandle(apc_host_thread, 0, *pPsThreadType, KernelMode, (void**)¤t_apc_thread, NULL); + ZwClose(apc_host_thread); + + current_apc_type = SPECIAL_KERNEL; + pKeInitializeApc(&special_apc, current_apc_thread, OriginalApcEnvironment, kernel_routine, rundown_routine, + NULL, KernelMode, (PVOID)(ULONG_PTR)0xdeadbeef); + res = pKeInsertQueueApc(&special_apc, normal_routine, done_event, 0); +todo_wine + ok(res, "KeInsertQueueApc failed.\n"); + if (!res) + { + KeSetEvent(&terminate_event, 0, FALSE); + ObDereferenceObject(current_apc_thread); + ObDereferenceObject(done_event); + ZwClose(done_event_system_handle); + return; + } + stat = wait_single(done_event, 5 * -10000000); + ok(stat == STATUS_WAIT_0, "Waiting on special kernel APC to complete failed: %#x\n", stat); + + current_apc_type = NORMAL_KERNEL; + pKeInitializeApc(&kernel_apc, current_apc_thread, OriginalApcEnvironment, kernel_routine, rundown_routine, + (PVOID)(ULONG_PTR)0xdeadbeef, KernelMode, (PVOID)(ULONG_PTR)0xdeadbeef); + res = pKeInsertQueueApc(&kernel_apc, normal_routine, done_event, 0); + ok(res, "KeInsertQueueApc failed.\n"); + stat = wait_single(done_event, 5 * -10000000); + ok(stat == STATUS_WAIT_0, "Waiting on normal kernel APC to complete failed: %#x\n", stat); + + KeSetEvent(&terminate_event, 0, FALSE); + ObDereferenceObject(current_apc_thread); + + /* Get usermode thread accepting APCs */ + stat = PsLookupThreadByThreadId((HANDLE)(ULONG_PTR)test_input->apc_thread_id, (PETHREAD *)&user_apc_thread); + ok(stat == STATUS_SUCCESS, "Got unexpected status %#x.\n", stat); + + /* create handle for done event (once user mode handles are supported, this won't be necessary) */ + stat = PsLookupProcessByProcessId((HANDLE)(ULONG_PTR)test_input->process_id, &user_process); + ok(stat == STATUS_SUCCESS, "Got unexpected status %#x.\n", stat); + + stat = ObOpenObjectByPointer(user_process, OBJ_KERNEL_HANDLE, NULL, PROCESS_DUP_HANDLE, *pPsProcessType, KernelMode, &user_process_handle); + ok(stat == STATUS_SUCCESS, "Got unexpected status %#x.\n", stat); + ObDereferenceObject(user_process); + + stat = ZwDuplicateObject(NtCurrentProcess(), done_event_system_handle, user_process_handle, &done_event_user_handle, SYNCHRONIZE|EVENT_MODIFY_STATE, 0, DUPLICATE_SAME_ACCESS|DUPLICATE_CLOSE_SOURCE); + ok(stat == STATUS_SUCCESS, "Got unexpected status %#x.\n", stat); + + current_apc_type = NORMAL_USER; + current_apc_thread = user_apc_thread; + pKeInitializeApc(&user_apc, user_apc_thread, OriginalApcEnvironment, kernel_routine, rundown_routine, + (PVOID)(ULONG_PTR)0xdeadbeef, UserMode, (PVOID)(ULONG_PTR)0xdeadbeef); + pKeInsertQueueApc(&user_apc, test_input->apc_func, done_event_user_handle, 0); + ok(res, "KeInsertQueueApc failed.\n"); + + stat = wait_single(done_event, 5 * -10000000); + ok(stat == STATUS_WAIT_0, "Waiting on user APC to complete failed: %#x\n", stat); + + ZwClose(done_event_user_handle); + ObDereferenceObject(done_event); + ObDereferenceObject(current_apc_thread); +} + static void test_process_memory(const struct test_input *test_input) { NTSTATUS (WINAPI *pMmCopyVirtualMemory)(PEPROCESS fromprocess, void *fromaddress, PEPROCESS toprocess, @@ -2135,6 +2274,9 @@ static NTSTATUS main_test(DEVICE_OBJECT *device, IRP *irp, IO_STACK_LOCATION *st pPsThreadType = get_proc_address("PsThreadType"); ok(!!pPsThreadType, "IofileObjectType not found\n");
+ pPsProcessType = get_proc_address("PsProcessType"); + ok(!!pPsProcessType, "PsProcessType not found\n"); + pPsInitialSystemProcess = get_proc_address("PsInitialSystemProcess"); ok(!!pPsInitialSystemProcess, "PsInitialSystemProcess not found\n");
@@ -2158,6 +2300,7 @@ static NTSTATUS main_test(DEVICE_OBJECT *device, IRP *irp, IO_STACK_LOCATION *st #endif test_affinity(); test_dpc(); + test_apc(test_input); test_process_memory(test_input); test_permanence();
diff --git a/dlls/ntoskrnl.exe/tests/driver.h b/dlls/ntoskrnl.exe/tests/driver.h index 58a92d4838e..d0a609329e0 100644 --- a/dlls/ntoskrnl.exe/tests/driver.h +++ b/dlls/ntoskrnl.exe/tests/driver.h @@ -44,6 +44,8 @@ struct test_input int winetest_report_success; int winetest_debug; DWORD process_id; + DWORD apc_thread_id; + PKNORMAL_ROUTINE apc_func; SIZE_T teststr_offset; ULONG64 *modified_value; WCHAR path[1]; diff --git a/dlls/ntoskrnl.exe/tests/ntoskrnl.c b/dlls/ntoskrnl.exe/tests/ntoskrnl.c index 61b1e401c56..83766a7783a 100644 --- a/dlls/ntoskrnl.exe/tests/ntoskrnl.c +++ b/dlls/ntoskrnl.exe/tests/ntoskrnl.c @@ -30,6 +30,7 @@ #include "winsock2.h" #include "wine/test.h" #include "wine/heap.h" +#include "ddk/wdm.h"
#include "driver.h"
@@ -153,6 +154,27 @@ static BOOL start_driver(HANDLE service, BOOL vista_plus) }
static ULONG64 modified_value; +static BOOL apc_ran = FALSE; + +DWORD WINAPI apc_host_thread_func(PVOID param) +{ + HANDLE done_event = param; + + WaitForSingleObjectEx(done_event, INFINITE, TRUE); +todo_wine + ok (apc_ran == TRUE, "Driver failed to queue user mode APC\n"); + CloseHandle(done_event); + return 0; +} + +static void WINAPI apc_func(PVOID normal_ctx, PVOID sysarg1, PVOID sysarg2) +{ + HANDLE done_event = sysarg2; + + ok(normal_ctx == (PVOID)(ULONG_PTR)0xdeadbeef, "Unexpected normal context %p\n", normal_ctx); + apc_ran = TRUE; + ok(SetEvent(done_event), "Setting done event failed: %u\n", GetLastError()); +}
static void main_test(void) { @@ -160,8 +182,11 @@ static void main_test(void) WCHAR temppathW[MAX_PATH], pathW[MAX_PATH]; struct test_input *test_input; DWORD len, written, read; + DWORD apc_thread_id = 0; UNICODE_STRING pathU; LONG new_failures; + HANDLE apc_thread; + HANDLE done_event; char buffer[512]; HANDLE okfile; BOOL res; @@ -171,12 +196,24 @@ static void main_test(void) GetTempFileNameW(temppathW, dokW, 0, pathW); pRtlDosPathNameToNtPathName_U( pathW, &pathU, NULL, NULL );
+ done_event = CreateEventW(NULL, TRUE, FALSE, NULL); + if (done_event) + { + if ((apc_thread = CreateThread(NULL, 0, apc_host_thread_func, done_event, 0, &apc_thread_id))) + CloseHandle(apc_thread); + else + CloseHandle(done_event); + } + ok (apc_thread_id, "APC host thread creation failed: %u\n", GetLastError()); + len = pathU.Length + sizeof(WCHAR); test_input = heap_alloc( offsetof( struct test_input, path[len / sizeof(WCHAR)]) ); test_input->running_under_wine = !strcmp(winetest_platform, "wine"); test_input->winetest_report_success = winetest_report_success; test_input->winetest_debug = winetest_debug; test_input->process_id = GetCurrentProcessId(); + test_input->apc_thread_id = apc_thread_id; + test_input->apc_func = apc_func; test_input->teststr_offset = (SIZE_T)((BYTE *)&teststr - (BYTE *)NtCurrentTeb()->Peb->ImageBaseAddress); test_input->modified_value = &modified_value; modified_value = 0; @@ -188,6 +225,9 @@ static void main_test(void) ok(res, "DeviceIoControl failed: %u\n", GetLastError()); ok(written == sizeof(new_failures), "got size %x\n", written);
+ if (apc_thread_id) + SetEvent(done_event); + okfile = CreateFileW(pathW, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); ok(okfile != INVALID_HANDLE_VALUE, "failed to create %s: %u\n", wine_dbgstr_w(pathW), GetLastError());
Signed-off-by: Derek Lesho dlesho@codeweavers.com --- dlls/ntoskrnl.exe/ntoskrnl.c | 2 + dlls/ntoskrnl.exe/ntoskrnl_private.h | 4 + dlls/ntoskrnl.exe/sync.c | 160 ++++++++++++++++++++++++++- dlls/ntoskrnl.exe/tests/driver.c | 9 -- dlls/ntoskrnl.exe/tests/ntoskrnl.c | 1 - server/protocol.def | 14 ++- server/thread.c | 136 +++++++++++++++++++---- server/thread.h | 1 + 8 files changed, 290 insertions(+), 37 deletions(-)
diff --git a/dlls/ntoskrnl.exe/ntoskrnl.c b/dlls/ntoskrnl.exe/ntoskrnl.c index a0f45b4135c..43f203faa10 100644 --- a/dlls/ntoskrnl.exe/ntoskrnl.c +++ b/dlls/ntoskrnl.exe/ntoskrnl.c @@ -2318,6 +2318,8 @@ static void *create_thread_object( HANDLE handle ) } }
+ InitializeListHead(&thread->ApcListHead[KernelMode]); + InitializeListHead(&thread->ApcListHead[UserMode]);
return thread; } diff --git a/dlls/ntoskrnl.exe/ntoskrnl_private.h b/dlls/ntoskrnl.exe/ntoskrnl_private.h index a1e1b892e8c..12e67f75e0d 100644 --- a/dlls/ntoskrnl.exe/ntoskrnl_private.h +++ b/dlls/ntoskrnl.exe/ntoskrnl_private.h @@ -50,6 +50,10 @@ struct _KTHREAD CLIENT_ID id; unsigned int critical_region; KAFFINITY user_affinity; + LIST_ENTRY ApcListHead[2]; + CRITICAL_SECTION apc_cs; + HANDLE apc_event; + HANDLE imposter_thread; };
struct _ETHREAD diff --git a/dlls/ntoskrnl.exe/sync.c b/dlls/ntoskrnl.exe/sync.c index c96a490ff04..b9ca7d71957 100644 --- a/dlls/ntoskrnl.exe/sync.c +++ b/dlls/ntoskrnl.exe/sync.c @@ -696,14 +696,170 @@ void WINAPI KeInitializeApc(PRKAPC apc, PRKTHREAD thread, KAPC_ENVIRONMENT env, } }
+static DWORD WINAPI thread_impersonate_loop(PVOID context) +{ + PKTHREAD thread = (PKTHREAD) context; + PKAPC apc; + HANDLE apc_handle; + PKKERNEL_ROUTINE krnl_routine; + PKNORMAL_ROUTINE nrml_routine; + PVOID nrml_ctx; + PVOID sysarg1; + PVOID sysarg2; + KPROCESSOR_MODE apc_mode; + BOOL special_apc; + PKAPC wait_apcs[2]; + HANDLE wait_handles[3]; + DWORD wait_count; + NTSTATUS stat; + + NtCurrentTeb()->SystemReserved1[15] = thread; + + for(;;) + { + wait_handles[0] = thread->apc_event; + wait_count = 1; + EnterCriticalSection(&thread->apc_cs); + if (!IsListEmpty(&thread->ApcListHead[KernelMode])) + { + wait_apcs[0] = CONTAINING_RECORD(thread->ApcListHead[KernelMode].Flink, KAPC, ApcListEntry); + wait_handles[1] = wine_server_ptr_handle(wait_apcs[0]->Spare0); + wait_count++; + } + if (!IsListEmpty(&thread->ApcListHead[UserMode])) + { + wait_apcs[wait_count - 1] = CONTAINING_RECORD(thread->ApcListHead[UserMode].Flink, KAPC, ApcListEntry); + wait_handles[wait_count] = wine_server_ptr_handle(wait_apcs[wait_count - 1]->Spare0); + wait_count++; + } + LeaveCriticalSection(&thread->apc_cs); + + TRACE("%u %p %p %p\n", wait_count, wait_handles[0], wait_handles[1], wait_handles[2]); + stat = NtWaitForMultipleObjects(wait_count, wait_handles, WaitAny, FALSE, NULL); + if (stat < 0) + { + ERR("Failed to wait for APC err=%x\n", stat); + return 1; + } + if (stat == WAIT_OBJECT_0) + continue; + + apc = wait_apcs[stat - 1]; + + EnterCriticalSection(&thread->apc_cs); + RemoveEntryList(&apc->ApcListEntry); + LeaveCriticalSection(&thread->apc_cs); + + apc->Inserted = FALSE; + + /* copy the APC, as KernelRoutine can free it */ + apc_handle = wine_server_ptr_handle(apc->Spare0); + krnl_routine = apc->KernelRoutine; + nrml_routine = apc->NormalRoutine; + nrml_ctx = apc->NormalContext; + sysarg1 = apc->SystemArgument1; + sysarg2 = apc->SystemArgument2; + apc_mode = apc->ApcMode; + + special_apc = apc_mode == KernelMode && !nrml_routine; + + TRACE("\1%04x:%04x:Call %s APC %p\n", (DWORD)thread->process->info.UniqueProcessId, (DWORD)thread->id.UniqueThread, special_apc ? "Special" : "Normal", krnl_routine); + krnl_routine(apc, &nrml_routine, &nrml_ctx, &sysarg1, &sysarg2); + TRACE("\1%04x:%04x:Ret %s APC %p\n", (DWORD)thread->process->info.UniqueProcessId, (DWORD)thread->id.UniqueThread, special_apc ? "Special" : "Normal", krnl_routine); + + if (nrml_routine && apc_mode == KernelMode && !special_apc) + { + TRACE("\1%04x:%04x:Call kernel APC NormalRoutine %p\n", (DWORD)thread->process->info.UniqueProcessId, (DWORD)thread->id.UniqueThread, nrml_routine); + nrml_routine(nrml_ctx, sysarg1, sysarg2); + TRACE("\1%04x:%04x:Ret kernel APC NormalRoutine %p\n", (DWORD)thread->process->info.UniqueProcessId, (DWORD)thread->id.UniqueThread, nrml_routine); + } + + SERVER_START_REQ(finalize_apc) + { + req->handle = wine_server_obj_handle(apc_handle); + if (apc_mode == UserMode && nrml_routine) + { + TRACE("finalizing APC as %p\n", nrml_routine); + req->call.type = APC_USER; + req->call.user.user.func = wine_server_client_ptr(nrml_routine); + req->call.user.user.args[0] = (ULONG_PTR) nrml_ctx; + req->call.user.user.args[1] = (ULONG_PTR) sysarg1; + req->call.user.user.args[2] = (ULONG_PTR) sysarg2; + } + else + req->call.type = APC_NONE; + if ((stat = wine_server_call( req ))) + { + ERR("Failed to finalize apc! err=%x\n", stat); + } + } + SERVER_END_REQ; + + CloseHandle(apc_handle); + } + + return 0; +} + /*********************************************************************** * KeInsertQueueApc (NTOSKRNL.EXE.@) */ BOOLEAN WINAPI KeInsertQueueApc(PRKAPC apc, PVOID sysarg1, PVOID sysarg2, KPRIORITY increment) { - FIXME("apc %p arg1 %p arg2 %p inc %u\n", apc, sysarg1, sysarg2, increment); + NTSTATUS stat; + HANDLE thread_handle; + obj_handle_t apc_handle; + + TRACE("apc %p arg1 %p arg2 %p inc %u\n", apc, sysarg1, sysarg2, increment);
- return FALSE; + if(!apc->Thread->imposter_thread) + { + if (!(apc->Thread->apc_event = CreateEventA(NULL, FALSE, FALSE, NULL))) + return FALSE; + InitializeCriticalSection(&apc->Thread->apc_cs); + if (!(apc->Thread->imposter_thread = CreateThread(NULL, 0, thread_impersonate_loop, apc->Thread, 0, NULL))) + { + DeleteCriticalSection(&apc->Thread->apc_cs); + if (apc->Thread->apc_event) CloseHandle(apc->Thread->apc_event); + return FALSE; + } + } + + if ((stat = ObOpenObjectByPointer(apc->Thread, OBJ_KERNEL_HANDLE, NULL, THREAD_SET_CONTEXT, PsThreadType, KernelMode, &thread_handle))) + { + ERR("Failed to open APC thread; err=%x\n", stat); + return FALSE; + } + + SERVER_START_REQ( queue_apc ) + { + req->handle = wine_server_obj_handle(thread_handle); + req->call.type = apc->ApcMode ? APC_REAL_USER : APC_REAL_KERNEL; + req->call.real_apc.special_apc = apc->ApcMode == APC_REAL_KERNEL && !apc->NormalRoutine; + stat = wine_server_call( req ); + apc_handle = reply->handle; + } + SERVER_END_REQ; + + CloseHandle(thread_handle); + + if (stat) + { + ERR("Failed to queue real APC, err=%x\n", stat); + return FALSE; + } + + apc->SystemArgument1 = sysarg1; + apc->SystemArgument2 = sysarg2; + apc->Inserted = TRUE; + apc->Spare0 = apc_handle; + + EnterCriticalSection(&apc->Thread->apc_cs); + InsertTailList(&apc->Thread->ApcListHead[(DWORD)apc->ApcMode], &apc->ApcListEntry); + if (!(SetEvent(apc->Thread->apc_event))) + ERR("Failed to set apc event!\n"); + LeaveCriticalSection(&apc->Thread->apc_cs); + return TRUE; }
/*********************************************************************** diff --git a/dlls/ntoskrnl.exe/tests/driver.c b/dlls/ntoskrnl.exe/tests/driver.c index 32095476abf..a5a9bd67658 100644 --- a/dlls/ntoskrnl.exe/tests/driver.c +++ b/dlls/ntoskrnl.exe/tests/driver.c @@ -2075,16 +2075,7 @@ static void test_apc(const struct test_input *test_input) pKeInitializeApc(&special_apc, current_apc_thread, OriginalApcEnvironment, kernel_routine, rundown_routine, NULL, KernelMode, (PVOID)(ULONG_PTR)0xdeadbeef); res = pKeInsertQueueApc(&special_apc, normal_routine, done_event, 0); -todo_wine ok(res, "KeInsertQueueApc failed.\n"); - if (!res) - { - KeSetEvent(&terminate_event, 0, FALSE); - ObDereferenceObject(current_apc_thread); - ObDereferenceObject(done_event); - ZwClose(done_event_system_handle); - return; - } stat = wait_single(done_event, 5 * -10000000); ok(stat == STATUS_WAIT_0, "Waiting on special kernel APC to complete failed: %#x\n", stat);
diff --git a/dlls/ntoskrnl.exe/tests/ntoskrnl.c b/dlls/ntoskrnl.exe/tests/ntoskrnl.c index 83766a7783a..d8d9204d782 100644 --- a/dlls/ntoskrnl.exe/tests/ntoskrnl.c +++ b/dlls/ntoskrnl.exe/tests/ntoskrnl.c @@ -161,7 +161,6 @@ DWORD WINAPI apc_host_thread_func(PVOID param) HANDLE done_event = param;
WaitForSingleObjectEx(done_event, INFINITE, TRUE); -todo_wine ok (apc_ran == TRUE, "Driver failed to queue user mode APC\n"); CloseHandle(done_event); return 0; diff --git a/server/protocol.def b/server/protocol.def index 92290af701c..8c5cb253ac5 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -462,7 +462,9 @@ enum apc_type APC_MAP_VIEW, APC_UNMAP_VIEW, APC_CREATE_THREAD, - APC_BREAK_PROCESS + APC_BREAK_PROCESS, + APC_REAL_USER, + APC_REAL_KERNEL, };
typedef union @@ -572,6 +574,11 @@ typedef union mem_size_t reserve; /* reserve size for thread stack */ mem_size_t commit; /* commit size for thread stack */ } create_thread; + struct + { + enum apc_type type; /* APC_REAL_KERNEL && APC_REAL_USER */ + int special_apc; + } real_apc; } apc_call_t;
typedef union @@ -1037,6 +1044,11 @@ struct rawinput_device int self; /* run APC in caller itself? */ @END
+@REQ(finalize_apc) + obj_handle_t handle; /* handle to the apc */ + apc_call_t call; /* real call */ +@END +
/* Get the result of an APC call */ @REQ(get_apc_result) diff --git a/server/thread.c b/server/thread.c index 9b14174578e..69b2886462b 100644 --- a/server/thread.c +++ b/server/thread.c @@ -75,6 +75,7 @@ struct thread_wait struct thread *thread; /* owner thread */ int count; /* count of objects */ int flags; + int in_kernel; /* we can't timeout when winedevice is working on the thread's behalf */ int abandoned; enum select_op select; client_ptr_t key; /* wait key for keyed events */ @@ -90,6 +91,7 @@ struct thread_apc { struct object obj; /* object header */ struct list entry; /* queue linked list */ + struct thread *callee; /* thread this apc is queued on */ struct thread *caller; /* thread that queued this apc */ struct object *owner; /* object that queued this apc */ int executed; /* has it been executed by the client? */ @@ -245,6 +247,7 @@ static inline void init_thread_structure( struct thread *thread )
list_init( &thread->mutex_list ); list_init( &thread->system_apc ); + list_init( &thread->kernel_apc); list_init( &thread->user_apc ); list_init( &thread->kernel_object );
@@ -391,6 +394,7 @@ static void cleanup_thread( struct thread *thread ) thread->context = NULL; } clear_apc_queue( &thread->system_apc ); + clear_apc_queue( &thread->kernel_apc ); clear_apc_queue( &thread->user_apc ); free( thread->req_data ); free( thread->reply_data ); @@ -488,6 +492,7 @@ static int thread_apc_signaled( struct object *obj, struct wait_queue_entry *ent static void thread_apc_destroy( struct object *obj ) { struct thread_apc *apc = (struct thread_apc *)obj; + if (apc->callee) release_object( apc->callee); if (apc->caller) release_object( apc->caller ); if (apc->owner) release_object( apc->owner ); } @@ -501,6 +506,7 @@ static struct thread_apc *create_apc( struct object *owner, const apc_call_t *ca { apc->call = *call_data; apc->caller = NULL; + apc->callee = NULL; apc->owner = owner; apc->executed = 0; apc->result.type = APC_NONE; @@ -769,6 +775,7 @@ static int wait_on( const select_op_t *select_op, unsigned int count, struct obj wait->user = NULL; wait->when = when; wait->abandoned = 0; + wait->in_kernel = 0; current->wait = wait;
for (i = 0, entry = wait->queues; i < count; i++, entry++) @@ -816,6 +823,16 @@ static int check_wait( struct thread *thread ) if ((wait->flags & SELECT_INTERRUPTIBLE) && !list_empty( &thread->system_apc )) return STATUS_KERNEL_APC;
+ if ((wait->flags & SELECT_INTERRUPTIBLE && !list_empty( &thread->kernel_apc ))) + { + struct thread_apc *apc = LIST_ENTRY( list_head(&thread->kernel_apc), struct thread_apc, entry ); + assert(apc->call.type == APC_REAL_KERNEL); + thread->wait->in_kernel = 1; + apc->executed = 1; + wake_up( &apc->obj, 0 ); + return -1; + } + /* Suspended threads may not acquire locks, but they can run system APCs */ if (thread->process->suspend + thread->suspend > 0) return -1;
@@ -834,7 +851,20 @@ static int check_wait( struct thread *thread ) if (entry->obj->ops->signaled( entry->obj, entry )) return i; }
- if ((wait->flags & SELECT_ALERTABLE) && !list_empty(&thread->user_apc)) return STATUS_USER_APC; + if ((wait->flags & SELECT_ALERTABLE) && !list_empty(&thread->user_apc)) + { + struct thread_apc *apc = LIST_ENTRY( list_head(&thread->user_apc), struct thread_apc, entry ); + if (apc->call.type != APC_REAL_USER) + return STATUS_USER_APC; + else + { + thread->wait->in_kernel = 1; + apc->executed = 1; + wake_up( &apc->obj, 0 ); + return -1; + } + } + if (wait->when >= 0 && wait->when <= current_time) return STATUS_TIMEOUT; if (wait->when < 0 && -wait->when <= monotonic_time) return STATUS_TIMEOUT; return -1; @@ -1018,7 +1048,7 @@ static void select_on( const select_op_t *select_op, data_size_t op_size, client }
/* now we need to wait */ - if (current->wait->when != TIMEOUT_INFINITE) + if (current->wait->when != TIMEOUT_INFINITE && !current->wait->in_kernel) { if (!(current->wait->user = add_timeout_user( abstime_to_timeout(current->wait->when), thread_timeout, current->wait ))) @@ -1056,7 +1086,10 @@ static inline struct list *get_apc_queue( struct thread *thread, enum apc_type t case APC_NONE: case APC_USER: case APC_TIMER: + case APC_REAL_USER: return &thread->user_apc; + case APC_REAL_KERNEL: + return &thread->kernel_apc; default: return &thread->system_apc; } @@ -1111,13 +1144,14 @@ static int queue_apc( struct process *process, struct thread *thread, struct thr if (thread->state == TERMINATED) return 0; queue = get_apc_queue( thread, apc->call.type ); /* send signal for system APCs if needed */ - if (queue == &thread->system_apc && list_empty( queue ) && !is_in_apc_wait( thread )) + if ((queue == &thread->system_apc || queue == &thread->kernel_apc) && list_empty( queue ) && !is_in_apc_wait( thread )) { if (!send_thread_signal( thread, SIGUSR1 )) return 0; } /* cancel a possible previous APC with the same owner */ if (apc->owner) thread_cancel_apc( thread, apc->owner, apc->call.type ); } + apc->callee = (struct thread *) grab_object( thread );
grab_object( apc ); list_add_tail( queue, &apc->entry ); @@ -1699,6 +1733,7 @@ DECL_HANDLER(queue_apc) struct thread *thread = NULL; struct process *process = NULL; struct thread_apc *apc; + obj_handle_t apc_handle = 0;
if (!(apc = create_apc( NULL, &req->call ))) return;
@@ -1706,6 +1741,8 @@ DECL_HANDLER(queue_apc) { case APC_NONE: case APC_USER: + case APC_REAL_USER: + case APC_REAL_KERNEL: thread = get_thread_from_handle( req->handle, THREAD_SET_CONTEXT ); break; case APC_VIRTUAL_ALLOC: @@ -1744,34 +1781,85 @@ DECL_HANDLER(queue_apc) break; }
- if (thread) + if (!thread && !process) { - if (!queue_apc( NULL, thread, apc )) set_error( STATUS_THREAD_IS_TERMINATING ); - release_object( thread ); + release_object(apc); + return; } - else if (process) + + apc_handle = alloc_handle( current->process, apc, SYNCHRONIZE, 0 ); + + if (apc_handle) { - reply->self = (process == current->process); - if (!reply->self) + if (process) + reply->self = (process == current->process); + if (queue_apc( process, thread, apc )) { - obj_handle_t handle = alloc_handle( current->process, apc, SYNCHRONIZE, 0 ); - if (handle) - { - if (queue_apc( process, NULL, apc )) - { - apc->caller = (struct thread *)grab_object( current ); - reply->handle = handle; - } - else - { - close_handle( current->process, handle ); - set_error( STATUS_PROCESS_IS_TERMINATING ); - } - } + apc->caller = (struct thread *)grab_object( current ); + reply->handle = apc_handle; + } + else + { + close_handle( current->process, apc_handle ); + set_error( thread ? STATUS_THREAD_IS_TERMINATING : STATUS_PROCESS_IS_TERMINATING ); } - release_object( process ); + release_object( thread ? (void*) thread : (void*) process ); }
+ /* Optimization: APC_USER and APC_NONE don't use the handle */ + if (apc->call.type == APC_USER || apc->call.type == APC_NONE) + close_handle(current->process, apc_handle); + + release_object( apc ); +} + +/* transform the APC object into the type for usermode */ +DECL_HANDLER(finalize_apc) +{ + struct thread_apc *apc; + struct list *queue; + + if (!(apc = (struct thread_apc *)get_handle_obj(current->process, req->handle, + 0, &thread_apc_ops ))) return; + + if (apc->executed != 1) + { + set_error(STATUS_INVALID_PARAMETER); + goto done; + } + + if (apc->call.type != APC_REAL_USER && apc->call.type != APC_REAL_KERNEL) + { + set_error(STATUS_INVALID_PARAMETER); + goto done; + } + + queue = get_apc_queue(apc->callee, apc->call.type); + if (list_head(queue) != &apc->entry) + { + set_error(STATUS_INVALID_PARAMETER); + goto done; + } + + apc->executed = 0; + + if (apc->call.type == APC_REAL_KERNEL) + { + /* dequeue */ + list_remove(&apc->entry); + release_object(apc); + + if (req->call.type != APC_NONE) + set_error(STATUS_INVALID_PARAMETER); + } + else + apc->call = req->call; + + if (apc->callee->wait) + apc->callee->wait->in_kernel = 0; + wake_thread( apc->callee ); + + done: release_object( apc ); }
diff --git a/server/thread.h b/server/thread.h index 650bc44628d..feb1916a81a 100644 --- a/server/thread.h +++ b/server/thread.h @@ -59,6 +59,7 @@ struct thread struct msg_queue *queue; /* message queue */ struct thread_wait *wait; /* current wait condition if sleeping */ struct list system_apc; /* queue of system async procedure calls */ + struct list kernel_apc; /* queue of kernel async procedure calls */ struct list user_apc; /* queue of user async procedure calls */ struct inflight_fd inflight[MAX_INFLIGHT_FDS]; /* fds currently in flight */ unsigned int error; /* current error code */