ntdll: CreateRemoteThread and friends for remote processes, take 3, via APCs
Hello,
Much thanks for the comments on yesterday's patch of a service thread implementation for remote process operations. This patch uses APCs to implement CreateRemoteThread/RtlCreateUserThread, VirtualAllocEx/NtAllocateVirtualMemory and VirtualQueryEx/NtQueryVirtualMemory.
APCs are queued per-thread, so I made minimal changes to add a per-process APC queue. Of course, this implementation suffers from the limitation that the remote process must go into an interuptible wait for the operation to succeed; an operation targeting a suspended process would block.
Stefan Siebert mentioned that VirtualQueryEx is necessary for Lotus Notes Software Diagnostics (nsd.exe). I successfully tested this function with a simple program that prints a process' memory map: http://www.codecomments.com/archive371-2005-3-421246.html
Lastly, I should mention that I have two pending patches to add conformance tests for remote processes operations: http://www.winehq.org/pipermail/wine-patches/2006-July/029259.html http://www.winehq.org/pipermail/wine-patches/2006-July/029260.html
Thanks,
Thomas Kho
---
include/wine/server_protocol.h | 2 +- server/process.c | 1 + server/process.h | 1 + server/thread.c | 50 ++++++++++++++++++++++++---------------- server/thread.h | 23 +++++++++++++++++- server/timer.c | 2 +- 6 files changed, 55 insertions(+), 24 deletions(-)
diff --git a/server/process.c b/server/process.c index 3e5ef97..75ca490 100644 --- a/server/process.c +++ b/server/process.c @@ -254,6 +254,7 @@ struct thread *create_process( int fd, s list_init( &process->locks ); list_init( &process->classes ); list_init( &process->dlls ); + list_init( &process->system_apc );
gettimeofday( &process->start_time, NULL ); process->end_time.tv_sec = process->end_time.tv_usec = 0; diff --git a/server/process.h b/server/process.h index 6edb1e6..2e9c9b8 100644 --- a/server/process.h +++ b/server/process.h @@ -78,6 +78,7 @@ struct process struct list dlls; /* list of loaded dlls */ void *peb; /* PEB address in client address space */ void *ldt_copy; /* pointer to LDT copy in client addr space */ + struct list system_apc; /* queue of system aync procedure calls */ };
struct process_snapshot diff --git a/server/thread.c b/server/thread.c index 60b74c9..d21df7f 100644 --- a/server/thread.c +++ b/server/thread.c @@ -64,19 +64,6 @@ struct thread_wait struct wait_queue_entry queues[1]; };
-/* asynchronous procedure calls */ - -struct thread_apc -{ - struct list entry; /* queue linked list */ - struct object *owner; /* object that queued this apc */ - void *func; /* function to call in client */ - enum apc_type type; /* type of apc function */ - void *arg1; /* function arguments */ - void *arg2; - void *arg3; -}; -
/* thread operations */
@@ -464,6 +451,8 @@ static int check_wait( struct thread *th }
other_checks: + /* FIXME remote operations will block on suspended threads */ + if ((wait->flags & SELECT_INTERRUPTIBLE) && !list_empty(&thread->process->system_apc)) return STATUS_USER_APC; if ((wait->flags & SELECT_INTERRUPTIBLE) && !list_empty(&thread->system_apc)) return STATUS_USER_APC; if ((wait->flags & SELECT_ALERTABLE) && !list_empty(&thread->user_apc)) return STATUS_USER_APC; if (wait->flags & SELECT_TIMEOUT) @@ -622,15 +611,32 @@ void wake_up( struct object *obj, int ma } }
+/* return a pointer to the correct queue */ +static struct list *get_apc_queue( struct thread *thread, + enum apc_queue apc_queue ) +{ + switch (apc_queue) + { + case APC_PROCESS_QUEUE_SYSTEM: + return &thread->process->system_apc; + case APC_THREAD_QUEUE_SYSTEM: + return &thread->system_apc; + case APC_THREAD_QUEUE_USER: + default: + return &thread->user_apc; + } +} + /* queue an async procedure call */ int thread_queue_apc( struct thread *thread, struct object *owner, void *func, - enum apc_type type, int system, void *arg1, void *arg2, void *arg3 ) + enum apc_type type, enum apc_queue apc_queue, void *arg1, + void *arg2, void *arg3 ) { struct thread_apc *apc; - struct list *queue = system ? &thread->system_apc : &thread->user_apc; + struct list *queue = get_apc_queue( thread, apc_queue );
/* cancel a possible previous APC with the same owner */ - if (owner) thread_cancel_apc( thread, owner, system ); + if (owner) thread_cancel_apc( thread, owner, apc_queue ); if (thread->state == TERMINATED) return 0;
if (!(apc = mem_alloc( sizeof(*apc) ))) return 0; @@ -648,10 +654,11 @@ int thread_queue_apc( struct thread *thr }
/* cancel the async procedure call owned by a specific object */ -void thread_cancel_apc( struct thread *thread, struct object *owner, int system ) +void thread_cancel_apc( struct thread *thread, struct object *owner, + enum apc_queue apc_queue ) { struct thread_apc *apc; - struct list *queue = system ? &thread->system_apc : &thread->user_apc; + struct list *queue = get_apc_queue( thread, apc_queue ); LIST_FOR_EACH_ENTRY( apc, queue, struct thread_apc, entry ) { if (apc->owner != owner) continue; @@ -665,8 +672,9 @@ void thread_cancel_apc( struct thread *t static struct thread_apc *thread_dequeue_apc( struct thread *thread, int system_only ) { struct thread_apc *apc = NULL; - struct list *ptr = list_head( &thread->system_apc ); + struct list *ptr = list_head( &thread->process->system_apc );
+ if (!ptr) ptr = list_head( &thread->system_apc ); if (!ptr && !system_only) ptr = list_head( &thread->user_apc ); if (ptr) { @@ -1007,7 +1015,9 @@ DECL_HANDLER(queue_apc) struct thread *thread; if ((thread = get_thread_from_handle( req->handle, THREAD_SET_CONTEXT ))) { - thread_queue_apc( thread, NULL, req->func, APC_USER, !req->user, + enum apc_queue queue; + queue = req->user ? APC_THREAD_QUEUE_USER : APC_THREAD_QUEUE_SYSTEM; + thread_queue_apc( thread, NULL, req->func, APC_USER, queue, req->arg1, req->arg2, req->arg3 ); release_object( thread ); } diff --git a/server/thread.h b/server/thread.h index 61911cd..b8b6cf0 100644 --- a/server/thread.h +++ b/server/thread.h @@ -96,6 +96,25 @@ struct thread_snapshot int priority; /* priority class */ };
+/* asynchronous procedure calls */ +struct thread_apc +{ + struct list entry; /* queue linked list */ + struct object *owner; /* object that queued this apc */ + void *func; /* function to call in client */ + enum apc_type type; /* type of apc function */ + void *arg1; /* function arguments */ + void *arg2; + void *arg3; +}; + +enum apc_queue +{ + APC_THREAD_QUEUE_USER, + APC_THREAD_QUEUE_SYSTEM, + APC_PROCESS_QUEUE_SYSTEM +}; + extern struct thread *current;
/* thread functions */ @@ -112,8 +131,8 @@ extern void kill_thread( struct thread * extern void break_thread( struct thread *thread ); extern void wake_up( struct object *obj, int max ); extern int thread_queue_apc( struct thread *thread, struct object *owner, void *func, - enum apc_type type, int system, void *arg1, void *arg2, void *arg3 ); -extern void thread_cancel_apc( struct thread *thread, struct object *owner, int system ); + enum apc_type type, enum apc_queue apc_queue, void *arg1, void *arg2, void *arg3 ); +extern void thread_cancel_apc( struct thread *thread, struct object *owner, enum apc_queue apc_queue ); extern int thread_add_inflight_fd( struct thread *thread, int client, int server ); extern int thread_get_inflight_fd( struct thread *thread, int client ); extern struct thread_snapshot *thread_snap( int *count ); diff --git a/server/timer.c b/server/timer.c index d1c035b..f6e23c8 100644 --- a/server/timer.c +++ b/server/timer.c @@ -136,7 +136,7 @@ static int cancel_timer( struct timer *t } if (timer->thread) { - thread_cancel_apc( timer->thread, &timer->obj, 0 ); + thread_cancel_apc( timer->thread, &timer->obj, APC_THREAD_QUEUE_USER ); release_object( timer->thread ); timer->thread = NULL; }
---
dlls/kernel/kernel32.spec | 3 + dlls/kernel/process.c | 108 ++++++++++++++++++++++++++++++++++++++++ dlls/kernel/thread.c | 24 +++++++-- dlls/ntdll/ntdll_misc.h | 2 + dlls/ntdll/thread.c | 85 +++++++++++++++++++++++++++++++ dlls/ntdll/virtual.c | 37 ++++++++++++-- include/wine/server_protocol.h | 31 +++++++++++ include/winternl.h | 58 +++++++++++++++++++++ server/process.c | 67 +++++++++++++++++++++++++ server/protocol.def | 21 ++++++++ server/request.h | 2 + server/trace.c | 12 ++++ 12 files changed, 436 insertions(+), 14 deletions(-)
diff --git a/dlls/kernel/kernel32.spec b/dlls/kernel/kernel32.spec index 7369b59..80fcba4 100644 --- a/dlls/kernel/kernel32.spec +++ b/dlls/kernel/kernel32.spec @@ -1239,3 +1239,6 @@ # Unix files
# Init code @ cdecl __wine_kernel_init() + +# Remote process operations +@ cdecl __wine_RemoteProcessOperation(ptr ptr ptr) diff --git a/dlls/kernel/process.c b/dlls/kernel/process.c index 7092d2c..07fa4ef 100644 --- a/dlls/kernel/process.c +++ b/dlls/kernel/process.c @@ -2827,3 +2827,111 @@ BOOL WINAPI CmdBatNotification( BOOL bBa FIXME("%d\n", bBatchRunning); return FALSE; } + +/*********************************************************************** + * __wine_RemoteProcessOperation (KERNEL32.@) Not a Windows API + * + * Execute a remote operation. + * arg1 is an all-access handle to the requesting process. + * arg2 is a pointer to the RemoteOp structure in the requesting process. + * arg3 is an event to notify the requesting process of completion. + */ +void WINAPI CALLBACK __wine_RemoteProcessOperation( ULONG_PTR arg1, + ULONG_PTR arg2, + ULONG_PTR arg3 ) +{ + HANDLE hProcess = (HANDLE) arg1; + void *args = (void *) arg2; + HANDLE hEvent = (HANDLE) arg3; + struct RemoteOp ro; + + NtReadVirtualMemory( hProcess, args, &ro, sizeof(ro), NULL ); + + switch( ro.op ) + { + case RO_NEW_THREAD: + { + CLIENT_ID cidLocal; + HANDLE hThread; + NTSTATUS ret; + + ro.status = RtlCreateUserThread( NtCurrentProcess(), NULL, + ro.thread.suspended, + ro.thread.stack_addr, + ro.thread.stack_reserve, + ro.thread.stack_commit, + ro.thread.start, + ro.thread.param, + ro.thread.handle_ptr ? &hThread : 0, + ro.thread.id_ptr ? &cidLocal : 0); + if (ro.status) break; + + if (ro.thread.handle_ptr) + { + HANDLE hRemoteNewThread; + if ((ret = NtDuplicateObject( NtCurrentProcess(), hThread, + hProcess, &hRemoteNewThread, 0, 0, + DUPLICATE_SAME_ACCESS| + DUPLICATE_CLOSE_SOURCE ))) + ERR("Cannot duplicate handle in remote process, error %lu\n", + ret); + else + { + if ((ret = NtWriteVirtualMemory( hProcess, + ro.thread.handle_ptr, + &hRemoteNewThread, + sizeof(HANDLE), NULL ))) + ERR("Cannot write back handle, error %lu\n", ret); + } + } + if (ro.thread.id_ptr) + if ((ret = NtWriteVirtualMemory( hProcess, ro.thread.id_ptr, + &cidLocal, sizeof(CLIENT_ID), + NULL ))) + ERR("Cannot write back thread id, error %lu\n", ret); + + break; + } + case RO_ALLOCATE: + { + ro.status = NtAllocateVirtualMemory( NtCurrentProcess(), + &ro.alloc.ret, + ro.alloc.zero_bits, + &ro.alloc.size, + ro.alloc.type, + ro.alloc.protect ); + break; + } + case RO_QUERY: + { + void *res; + NTSTATUS ret; + SIZE_T res_len; + + if (!(res = HeapAlloc( GetProcessHeap(), 0, ro.query.len ))) + { + ro.status = STATUS_NO_MEMORY; + break; + } + ro.status = NtQueryVirtualMemory( NtCurrentProcess(), ro.query.addr, + ro.query.info_class, res, + ro.query.len, &res_len ); + if (ro.query.res_len) + if ((ret = NtWriteVirtualMemory( hProcess, ro.query.res_len, + &res_len, sizeof(SIZE_T), NULL ))) + ERR("Cannot write back thread id, error %lu\n", ret); + if (ro.query.buffer) + if ((ret = NtWriteVirtualMemory( hProcess, ro.query.buffer, + res, ro.query.len, NULL ))) + ERR("Cannot write back thread id, error %lu\n", ret); + HeapFree( GetProcessHeap(), 0, res ); + } + } + + /* FIXME optimize write back */ + NtWriteVirtualMemory( hProcess, args, &ro, sizeof(ro), NULL ); + + NtSetEvent( hEvent, NULL ); + NtClose( hEvent ); + NtClose( hProcess ); +} diff --git a/dlls/kernel/thread.c b/dlls/kernel/thread.c index bf29aac..bfa8ef4 100644 --- a/dlls/kernel/thread.c +++ b/dlls/kernel/thread.c @@ -66,7 +66,7 @@ static void CALLBACK THREAD_Start( void LPTHREAD_START_ROUTINE func = info->func; void *arg = info->arg;
- RtlFreeHeap( GetProcessHeap(), 0, info ); + VirtualFreeEx( GetCurrentProcess(), info, 0, MEM_RELEASE );
if (TRACE_ON(relay)) DPRINTF("%04lx:Starting thread (entryproc=%p)\n", GetCurrentThreadId(), func ); @@ -122,13 +122,25 @@ HANDLE WINAPI CreateRemoteThread( HANDLE SIZE_T stack_reserve = 0, stack_commit = 0; struct new_thread_info *info;
- if (!(info = RtlAllocateHeap( GetProcessHeap(), 0, sizeof(*info) ))) + if (!(info = VirtualAllocEx( hProcess, NULL, sizeof(*info), + MEM_COMMIT|MEM_RESERVE, + PAGE_EXECUTE_READWRITE ))) { SetLastError( ERROR_NOT_ENOUGH_MEMORY ); return 0; } - info->func = start; - info->arg = param; + + /* FIXME sub with is_current_process() in ntdll */ + if (hProcess != GetCurrentProcess()) + { + struct new_thread_info nti = { start, param }; + WriteProcessMemory( hProcess, info, &nti, sizeof(nti), NULL ); + } + else + { + info->func = start; + info->arg = param; + }
if (flags & STACK_SIZE_PARAM_IS_A_RESERVATION) stack_reserve = stack; else stack_commit = stack; @@ -147,7 +159,7 @@ HANDLE WINAPI CreateRemoteThread( HANDLE if (NtResumeThread( handle, &ret )) { NtClose( handle ); - RtlFreeHeap( GetProcessHeap(), 0, info ); + VirtualFreeEx( hProcess, info, 0, MEM_RELEASE ); SetLastError( ERROR_NOT_ENOUGH_MEMORY ); handle = 0; } @@ -155,7 +167,7 @@ HANDLE WINAPI CreateRemoteThread( HANDLE } else { - RtlFreeHeap( GetProcessHeap(), 0, info ); + VirtualFreeEx( hProcess, info, 0, MEM_RELEASE ); SetLastError( RtlNtStatusToDosError(status) ); handle = 0; } diff --git a/dlls/ntdll/ntdll_misc.h b/dlls/ntdll/ntdll_misc.h index 03110a4..cdf6c63 100644 --- a/dlls/ntdll/ntdll_misc.h +++ b/dlls/ntdll/ntdll_misc.h @@ -61,6 +61,8 @@ extern void DECLSPEC_NORETURN server_pro extern void DECLSPEC_NORETURN server_protocol_perror( const char *err ); extern void DECLSPEC_NORETURN server_exit_thread( int status ); extern void DECLSPEC_NORETURN server_abort_thread( int status ); +extern NTSTATUS NTDLL_remote_call( HANDLE process, struct RemoteOp *ro ); +
/* module handling */ extern NTSTATUS MODULE_DllThreadAttach( LPVOID lpReserved ); diff --git a/dlls/ntdll/thread.c b/dlls/ntdll/thread.c index dbcc5a6..94c146b 100644 --- a/dlls/ntdll/thread.c +++ b/dlls/ntdll/thread.c @@ -385,6 +385,73 @@ static void start_thread( struct wine_pt func( arg ); }
+/*********************************************************************** + * get_remote_call_handler + */ +static NTSTATUS get_remote_call_handler( void **func ) +{ + static void *handler = NULL; + HMODULE hkernel32; + UNICODE_STRING module; + ANSI_STRING function; + WCHAR kernel32W[] = {'k','e','r','n','e','l','3','2','.','d','l','l',0}; + NTSTATUS ret; + + if (handler != NULL) + { + *func = handler; + return STATUS_SUCCESS; + } + + RtlInitUnicodeString(&module, kernel32W); + RtlInitAnsiString(&function, "__wine_RemoteProcessOperation"); + if ((ret = LdrGetDllHandle(0, 0, &module, &hkernel32))) + return ret; + if ((ret = LdrGetProcedureAddress(hkernel32, &function, 0, &handler))) + return ret; + *func = handler; + return STATUS_SUCCESS; +} + +/*********************************************************************** + * NTDLL_remote_call + */ +NTSTATUS NTDLL_remote_call( HANDLE process, struct RemoteOp *ro ) +{ + NTSTATUS ret; + HANDLE done_event; + void *func; + + /* FIXME check this */ + if ((ret = NtCreateEvent(&done_event, EVENT_ALL_ACCESS, NULL, FALSE, + FALSE))) + return ret; + + if ((ret = get_remote_call_handler(&func))) + { + ERR("Cannot locate remote call handler\n"); + return ret; + } + + SERVER_START_REQ( remote_op ) + { + req->handle = process; + req->op = ro->op; + req->event = done_event; + req->args = ro; + req->func = func; + + ret = wine_server_call( req ); + } + SERVER_END_REQ; + + if (ret) return ret; + + NtWaitForSingleObject( done_event, FALSE, NULL ); + NtClose( done_event ); + + return STATUS_SUCCESS; +}
/*********************************************************************** * RtlCreateUserThread (NTDLL.@) @@ -408,8 +475,22 @@ NTSTATUS WINAPI RtlCreateUserThread( HAN
if( ! is_current_process( process ) ) { - ERR("Unsupported on other process\n"); - return STATUS_ACCESS_DENIED; + struct RemoteOp ro; + + ro.op = RO_NEW_THREAD; + ro.thread.suspended = suspended; + ro.thread.stack_addr = stack_addr; + ro.thread.stack_reserve = stack_reserve; + ro.thread.stack_commit = stack_commit; + ro.thread.start = start; + ro.thread.param = param; + ro.thread.handle_ptr = handle_ptr; + ro.thread.id_ptr = id; + + if ((status = NTDLL_remote_call( process, &ro ))) + return status; + + return ro.status; }
if (pipe( request_pipe ) == -1) return STATUS_TOO_MANY_OPENED_FILES; diff --git a/dlls/ntdll/virtual.c b/dlls/ntdll/virtual.c index f7b829f..ee6a196 100644 --- a/dlls/ntdll/virtual.c +++ b/dlls/ntdll/virtual.c @@ -1317,8 +1317,25 @@ NTSTATUS WINAPI NtAllocateVirtualMemory(
if (!is_current_process( process )) { - ERR("Unsupported on other process\n"); - return STATUS_ACCESS_DENIED; + struct RemoteOp ro; + + ro.op = RO_ALLOCATE; + ro.alloc.ret = *ret; + ro.alloc.zero_bits = zero_bits; + ro.alloc.size = *size_ptr; + ro.alloc.type = type; + ro.alloc.protect = protect; + + if ((status = NTDLL_remote_call( process, &ro ))) + return status; + + TRACE("NtAllocateVirtualMemory addr=%x, status=%x\n", ro.alloc.ret, + (unsigned) status); + + *ret = ro.alloc.ret; + *size_ptr = ro.alloc.size; + + return ro.status; }
/* Round parameters to a page boundary */ @@ -1578,8 +1595,20 @@ NTSTATUS WINAPI NtQueryVirtualMemory( HA
if (!is_current_process( process )) { - ERR("Unsupported on other process\n"); - return STATUS_ACCESS_DENIED; + struct RemoteOp ro; + NTSTATUS status; + + ro.op = RO_QUERY; + ro.query.addr = (void *) addr; + ro.query.info_class = info_class; + ro.query.buffer = buffer; + ro.query.len = len; + ro.query.res_len = res_len; + + if ((status = NTDLL_remote_call( process, &ro ))) + return status; + + return ro.status; }
base = ROUND_ADDR( addr, page_mask ); diff --git a/include/winternl.h b/include/winternl.h index 5177348..4d92e42 100644 --- a/include/winternl.h +++ b/include/winternl.h @@ -2243,7 +2243,6 @@ extern NTSTATUS wine_nt_to_unix_file_nam UINT disposition, BOOLEAN check_case ); extern NTSTATUS wine_unix_to_nt_file_name( const ANSI_STRING *name, UNICODE_STRING *nt );
- /*********************************************************************** * Inline functions */ @@ -2396,6 +2395,63 @@ static inline PLIST_ENTRY RemoveTailList return e; }
+struct RemoteOp { + int op; /* in, FIXME enum remote_op */ + NTSTATUS status; /* out */ + + union { + struct { + /* in */ + ULONG zero_bits; + ULONG type; + ULONG protect; + /* in/out */ + PVOID ret; + SIZE_T size; + } alloc; + struct { + PVOID addr; + SIZE_T size; + ULONG type; + } free; + struct { + PVOID addr; + SIZE_T size; + ULONG new_prot; + ULONG old_prot; + } protect; + struct { + LPVOID addr; + MEMORY_INFORMATION_CLASS info_class; + PVOID buffer; + SIZE_T len; + SIZE_T *res_len; + } query; + struct { + PVOID addr; + SIZE_T size; + ULONG unknown; + } lock; + struct { + PVOID addr; + SIZE_T size; + ULONG unknown; + } unlock; + struct { + /* in */ + BOOLEAN suspended; + PVOID stack_addr; + SIZE_T stack_reserve; + SIZE_T stack_commit; + PRTL_THREAD_START_ROUTINE start; + void *param; + /* out */ + HANDLE *handle_ptr; + CLIENT_ID *id_ptr; + } thread; + }; +}; + #ifdef __cplusplus } /* extern "C" */ #endif /* defined(__cplusplus) */ diff --git a/server/process.c b/server/process.c index 75ca490..6ecc7b7 100644 --- a/server/process.c +++ b/server/process.c @@ -1056,3 +1056,70 @@ DECL_HANDLER(get_process_idle_event) release_object( process ); } } + +/* Signal a remote operation it */ +DECL_HANDLER(remote_op) +{ + int access; + struct process *src = NULL, *dst = NULL; + struct thread *thread; + obj_handle_t dupevent = NULL, dupprocess = NULL; + + switch ( req->op ) + { + case RO_NEW_THREAD: + access = PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION + | PROCESS_VM_OPERATION | PROCESS_VM_READ + | PROCESS_VM_WRITE; + break; + case RO_ALLOCATE: + case RO_FREE: + case RO_PROTECT: + case RO_LOCK: /* FIXME check */ + case RO_UNLOCK: /* FIXME check */ + access = PROCESS_VM_OPERATION; + break; + case RO_QUERY: + access = PROCESS_QUERY_INFORMATION; + break; + default: + set_error( STATUS_INVALID_PARAMETER ); + return; + } + + if (!(src = get_process_from_handle( (obj_handle_t) 0xffffffff, 0 ))) + goto cleanup; + if (!(dst = get_process_from_handle( req->handle, access ))) + goto cleanup; + if (!(thread = get_process_first_thread( dst ))) + { + /* FIXME check if this can even happen */ + set_error( STATUS_ACCESS_DENIED ); + goto cleanup; + } + + /* duplicate done event handle */ + dupevent = duplicate_handle(src, req->event, dst, 0, 0, + DUPLICATE_SAME_ACCESS); + if (!dupevent) goto cleanup; + /* duplicate an all-access handle to requesting process */ + dupprocess = duplicate_handle(src, (obj_handle_t) 0xffffffff, dst, 0, 0, + DUPLICATE_SAME_ACCESS); + if (!dupprocess) + { + close_handle( dst, dupevent, 0 ); + goto cleanup; + } + + /* queue remote_op on the remote process' apc queue */ + if (!thread_queue_apc( thread, NULL, req->func, APC_USER, + APC_PROCESS_QUEUE_SYSTEM, + dupprocess, req->args, dupevent )) + set_error( STATUS_INVALID_HANDLE ); + +cleanup: + if (src) + release_object( src ); + if (dst) + release_object( dst ); +} diff --git a/server/protocol.def b/server/protocol.def index e64cf43..98671ae 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -2618,3 +2618,24 @@ #define MAILSLOT_SET_READ_TIMEOUT 1 @REPLY VARARG(target_name,unicode_str); /* target name */ @END + + +/* Signal a remote operation */ +@REQ(remote_op) + obj_handle_t handle; /* handle to target process */ + int op; /* remote operation */ + obj_handle_t event; /* completion event */ + void* args; /* pointer to arguments in requesting process */ + void* func; /* pointer to remote op handler in kernel32 */ +@REPLY +@END +enum remote_op +{ + RO_NEW_THREAD, + RO_ALLOCATE, + RO_FREE, + RO_LOCK, + RO_PROTECT, + RO_QUERY, + RO_UNLOCK +};
On Thu, 03 Aug 2006 02:08:26 -0500, Thomas Kho wrote:
APCs are queued per-thread, so I made minimal changes to add a per-process APC queue. Of course, this implementation suffers from the limitation that the remote process must go into an interuptible wait for the operation to succeed; an operation targeting a suspended process would block.
I'll let Alexandre do the review this time. Just one thing - can you try it with something copy protected? I think some programs rely on being able to CreateRemoteThread into a program that runs all the time and never sleeps (eg games that try and get the highest FPS possible).
thanks -mike
On 8/3/06, Mike Hearn mike@plan99.net wrote:
Just one thing - can you try it with something copy protected? I think some programs rely on being able to CreateRemoteThread into a program that runs all the time and never sleeps (eg games that try and get the highest FPS possible).
Do you know of a game that does this? I can't readily find one...
Tommy
On 8/3/06, Thomas Kho tkho@ucla.edu wrote:
On 8/3/06, Mike Hearn mike@plan99.net wrote:
Just one thing - can you try it with something copy protected? I think some programs rely on being able to CreateRemoteThread into a program that runs all the time and never sleeps (eg games that try and get the highest FPS possible).
Do you know of a game that does this? I can't readily find one...
Unable to test a copy protected app, I was able to get the WinSpy app at http://www.codeguru.com/cpp/w-p/system/processesmodules/article.php/c5767/ working with minor changes (there was a bug in WinSpy, see http://silenceisdefeat.org/~tkho/patches/WinSpy-fix.patch). This little app excercises a common pattern of VirtualAllocEx/CreateRemoteThread/VirtualFreeEx.
Tommy
On Thu, 2006-08-03 at 02:08 -0500, Thomas Kho wrote:
APCs are queued per-thread, so I made minimal changes to add a per-process APC queue. Of course, this implementation suffers from the limitation that the remote process must go into an interuptible wait for the operation to succeed;
And I suppose in Windows they don't have this limitation? This seems like a decent trade-off for the moment. It would be interesting to consider the case when we have some sort of kernel support...
On 8/3/06, Dimi Paun dimi@lattica.com wrote:
On Thu, 2006-08-03 at 02:08 -0500, Thomas Kho wrote:
APCs are queued per-thread, so I made minimal changes to add a per-process APC queue. Of course, this implementation suffers from the limitation that the remote process must go into an interuptible wait for the operation to succeed;
And I suppose in Windows they don't have this limitation?
That's how APC's work even under Windows. See http://blogs.msdn.com/oldnewthing/archive/2006/05/03/589110.aspx http://windowssdk.msdn.microsoft.com/en-us/library/ms684840.aspx
I think the win32 api designer's thinking was "APCs should only run when the thread is expecting to wait for a long time anyway, so let's make it easy for the app to say 'oh, and run any apcs' when the app makes an explicit long wait api call."
Wine's implementation of APCs can also choose to run the apc even when the thread is only in an interruptable state rather than an alertable state. (This appears to be used only for timers at the moment, e.g. for CreateWaitableTimer(), but I may have missed its main use in my quick look.)
Potentially if one of our special process-wide APCs has been sitting for "too long", say two seconds, we could send it to a thread that's only in an interruptable wait state. That's a bit risky, though.
Kernel support would let us do this without kludges, but it'll be a couple years before we get to mess with the real OS kernel. - Dan