From: Derek Lesho Subject: [PATCH 3/3] ntoskrnl.exe: Implement KeInsertQueueApc. Message-Id: <20200909183229.695475-3-dlesho@codeweavers.com> Date: Wed, 9 Sep 2020 13:32:29 -0500 In-Reply-To: <20200909183229.695475-1-dlesho@codeweavers.com> References: <20200909183229.695475-1-dlesho@codeweavers.com> Signed-off-by: Derek Lesho --- 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 */ -- 2.28.0