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 +};