ntdll: CreateRemoteThread and RtlCreateUserThread for remote processes, take 2
Hello,
This is an implementation of CreateRemoteThread and RtlCreateUserThread for remote processes that re-introduces a service thread for each process and uses a signal to flag pending operations. Operations are guarded by a global mutex for each process.
A recent thread for the implementation of the functions via thread hijacking with ptrace is at http://winehq.org/pipermail/wine-devel/2006-July/049593.html
I have created a wiki page at http://wiki.winehq.org/RemoteProcessOperations to document the discussion of this class of functions.
Note: With the service thread, this mutex-and-signals approach could be exchanged for queued APCs on the service thread. In addition, there are unaddressed issues with this implementation (thread attach notifications, etc).
Is this a step in the right direction?
Thomas Kho
---
dlls/kernel/thread.c | 42 +++++- dlls/ntdll/Makefile.in | 1 dlls/ntdll/loader.c | 1 dlls/ntdll/ntdll.spec | 4 + dlls/ntdll/ntdll_misc.h | 73 +++++++++++ dlls/ntdll/server.c | 3 dlls/ntdll/service.c | 267 ++++++++++++++++++++++++++++++++++++++++ dlls/ntdll/signal_i386.c | 19 +++ dlls/ntdll/thread.c | 102 +++++++++++++++ dlls/ntdll/virtual.c | 55 ++++++++ include/thread.h | 3 include/wine/server_protocol.h | 54 ++++++++ include/winternl.h | 1 server/handle.c | 2 server/process.c | 97 +++++++++++++++ server/process.h | 1 server/protocol.def | 28 ++++ server/request.h | 6 + server/thread.c | 9 + server/trace.c | 36 +++++ 20 files changed, 788 insertions(+), 16 deletions(-)
diff --git a/dlls/kernel/thread.c b/dlls/kernel/thread.c index bf29aac..162b898 100644 --- a/dlls/kernel/thread.c +++ b/dlls/kernel/thread.c @@ -47,12 +47,12 @@ #include "kernel_private.h" WINE_DEFAULT_DEBUG_CHANNEL(thread); WINE_DECLARE_DEBUG_CHANNEL(relay);
- struct new_thread_info { LPTHREAD_START_ROUTINE func; void *arg; }; +static struct new_thread_info threadInfo;
/*********************************************************************** @@ -62,11 +62,12 @@ struct new_thread_info */ static void CALLBACK THREAD_Start( void *ptr ) { - struct new_thread_info *info = ptr; + struct new_thread_info *info = ptr ? ptr : &threadInfo; LPTHREAD_START_ROUTINE func = info->func; void *arg = info->arg;
- RtlFreeHeap( GetProcessHeap(), 0, info ); + if (ptr) + RtlFreeHeap( GetProcessHeap(), 0, info );
if (TRACE_ON(relay)) DPRINTF("%04lx:Starting thread (entryproc=%p)\n", GetCurrentThreadId(), func ); @@ -116,11 +117,11 @@ HANDLE WINAPI CreateRemoteThread( HANDLE LPTHREAD_START_ROUTINE start, LPVOID param, DWORD flags, LPDWORD id ) { - HANDLE handle; + HANDLE handle, hServiceThreadMutex; CLIENT_ID client_id; NTSTATUS status; SIZE_T stack_reserve = 0, stack_commit = 0; - struct new_thread_info *info; + struct new_thread_info *info = NULL;
if (!(info = RtlAllocateHeap( GetProcessHeap(), 0, sizeof(*info) ))) { @@ -130,12 +131,29 @@ HANDLE WINAPI CreateRemoteThread( HANDLE info->func = start; info->arg = param;
+ if (hProcess != GetCurrentProcess()) + { + status = __wine_get_remote_op_mutex(hProcess, &hServiceThreadMutex); + if (status) return status; + + WriteProcessMemory(hProcess, &threadInfo, info, + sizeof(struct new_thread_info), NULL); + } + if (flags & STACK_SIZE_PARAM_IS_A_RESERVATION) stack_reserve = stack; else stack_commit = stack;
- status = RtlCreateUserThread( hProcess, NULL, TRUE, - NULL, stack_reserve, stack_commit, - THREAD_Start, info, &handle, &client_id ); + if (hProcess != GetCurrentProcess()) + status = RtlCreateUserThread( hProcess, NULL, TRUE, + NULL, stack_reserve, stack_commit, + THREAD_Start, NULL, &handle, + &client_id ); + else + status = RtlCreateUserThread( hProcess, NULL, TRUE, + NULL, stack_reserve, stack_commit, + THREAD_Start, info, &handle, + &client_id ); + if (status == STATUS_SUCCESS) { if (id) *id = (DWORD)client_id.UniqueThread; @@ -149,6 +167,7 @@ HANDLE WINAPI CreateRemoteThread( HANDLE NtClose( handle ); RtlFreeHeap( GetProcessHeap(), 0, info ); SetLastError( ERROR_NOT_ENOUGH_MEMORY ); + if (hProcess != GetCurrentProcess()) handle = 0; } } @@ -157,8 +176,15 @@ HANDLE WINAPI CreateRemoteThread( HANDLE { RtlFreeHeap( GetProcessHeap(), 0, info ); SetLastError( RtlNtStatusToDosError(status) ); + if (hProcess != GetCurrentProcess()) handle = 0; } + + if (hProcess != GetCurrentProcess()) + { + RtlFreeHeap( GetProcessHeap(), 0, info ); + __wine_release_remote_op_mutex(hServiceThreadMutex); + } return handle; }
diff --git a/dlls/ntdll/Makefile.in b/dlls/ntdll/Makefile.in index 646808f..ea843d2 100644 --- a/dlls/ntdll/Makefile.in +++ b/dlls/ntdll/Makefile.in @@ -38,6 +38,7 @@ C_SRCS = \ sec.c \ serial.c \ server.c \ + service.c \ signal_i386.c \ signal_powerpc.c \ signal_sparc.c \ diff --git a/dlls/ntdll/loader.c b/dlls/ntdll/loader.c index 184f64f..d093e59 100644 --- a/dlls/ntdll/loader.c +++ b/dlls/ntdll/loader.c @@ -2151,6 +2151,7 @@ void WINAPI LdrInitializeThunk( ULONG un RtlLeaveCriticalSection( &loader_section );
if (nt->FileHeader.Characteristics & IMAGE_FILE_LARGE_ADDRESS_AWARE) VIRTUAL_UseLargeAddressSpace(); + SERVICE_init(); return;
error: diff --git a/dlls/ntdll/ntdll.spec b/dlls/ntdll/ntdll.spec index 4b5549f..2e26a6e 100644 --- a/dlls/ntdll/ntdll.spec +++ b/dlls/ntdll/ntdll.spec @@ -1390,3 +1390,7 @@ # Filesystem @ cdecl wine_nt_to_unix_file_name(ptr ptr long long) @ cdecl wine_unix_to_nt_file_name(ptr ptr) @ cdecl __wine_init_windows_dir(wstr wstr) + +# Remote process locking +@ cdecl __wine_get_remote_op_mutex(long ptr) +@ cdecl __wine_release_remote_op_mutex(long) diff --git a/dlls/ntdll/ntdll_misc.h b/dlls/ntdll/ntdll_misc.h index 03110a4..cfd37e7 100644 --- a/dlls/ntdll/ntdll_misc.h +++ b/dlls/ntdll/ntdll_misc.h @@ -177,4 +177,77 @@ static inline struct ntdll_thread_regs * return (struct ntdll_thread_regs *)NtCurrentTeb()->SpareBytes1; }
+extern void SERVICE_init(void); + +struct RemoteOp { + enum enum_op { + RO_NEW_THREAD, + RO_ALLOCATE, + RO_FREE, + RO_LOCK, + RO_PROTECT, + RO_QUERY, + RO_UNLOCK + } op; + + /* in */ + HANDLE hDoneEvent; + /* out */ + NTSTATUS status; + + union { + struct { + PVOID ret; + ULONG zero_bits; + SIZE_T size; + ULONG type; + ULONG protect; + } 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 */ + HANDLE src_process; + 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; + }; +}; + +extern struct RemoteOp ro; +extern volatile int pending_service_req; + #endif diff --git a/dlls/ntdll/server.c b/dlls/ntdll/server.c index 841604a..9124751 100644 --- a/dlls/ntdll/server.c +++ b/dlls/ntdll/server.c @@ -880,6 +880,7 @@ void server_init_process(void) sigaddset( &block_set, SIGUSR1 ); sigaddset( &block_set, SIGUSR2 ); sigaddset( &block_set, SIGCHLD ); + sigaddset( &block_set, SIGRTMIN );
/* receive the first thread request fd on the main socket */ ntdll_get_thread_data()->request_fd = receive_fd( &dummy_handle ); @@ -934,6 +935,8 @@ #endif req->reply_fd = reply_pipe[1]; req->wait_fd = ntdll_get_thread_data()->wait_fd[1]; req->debug_level = (TRACE_ON(server) != 0); + req->event = ro.hDoneEvent; + ro.hDoneEvent = 0; /* creation mutex released after server call */ ret = wine_server_call( req ); NtCurrentTeb()->ClientId.UniqueProcess = (HANDLE)reply->pid; NtCurrentTeb()->ClientId.UniqueThread = (HANDLE)reply->tid; diff --git a/dlls/ntdll/service.c b/dlls/ntdll/service.c new file mode 100644 index 0000000..ba6c895 --- /dev/null +++ b/dlls/ntdll/service.c @@ -0,0 +1,267 @@ +/* + * Service thread + * + * Copyright 2006 Google (Thomas Kho) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/* FIXME remove extraneous includes */ +#include "config.h" +#include "wine/port.h" + +#include <sys/types.h> +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif +#ifdef HAVE_SYS_TIMES_H +#include <sys/times.h> +#endif + +#define NONAMELESSUNION +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "thread.h" +#include "winternl.h" +#include "wine/library.h" +#include "wine/server.h" +#include "wine/pthread.h" +#include "wine/debug.h" +#include "ntdll_misc.h" +#include "wine/exception.h" + +WINE_DEFAULT_DEBUG_CHANNEL(thread); + + +#define IS_OPTION_TRUE(ch) ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1') + +volatile int pending_service_req = 0; + +/*********************************************************************** + * use_service_thread + */ +static BOOL use_service_thread(void) +{ + static const WCHAR WineW[] = {'S','o','f','t','w','a','r','e','\', + 'W','i','n','e',0}; + static const WCHAR UseServiceThreadW[] = {'U','s','e', + 'S','e','r','v','i','c','e', 'T','h','r','e','a','d',0}; + + char tmp[80]; + HANDLE root, hkey; + DWORD dummy; + OBJECT_ATTRIBUTES attr; + UNICODE_STRING nameW; + BOOL ret = FALSE; + + attr.Length = sizeof (OBJECT_ATTRIBUTES); + attr.RootDirectory = 0; + attr.ObjectName = &nameW; + attr.Attributes = 0; + attr.SecurityDescriptor = NULL; + attr.SecurityQualityOfService = NULL; + RtlOpenCurrentUser( KEY_ALL_ACCESS, &root ); + attr.RootDirectory = root; + RtlInitUnicodeString( &nameW, WineW ); + + /* @@ Wine registry key: HKCU\Software\Wine\Network */ + if (!NtOpenKey( &hkey, KEY_ALL_ACCESS, &attr )) + { + RtlInitUnicodeString( &nameW, UseServiceThreadW ); + if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, + sizeof(tmp), &dummy )) + { + WCHAR *str = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data; + ret = IS_OPTION_TRUE( str[0] ); + } + NtClose( hkey ); + } + NtClose( root ); + return ret; +} + + +/*********************************************************************** + * create_global_mutex + */ +static HANDLE create_global_mutex(void) +{ + HANDLE hServiceThreadMutex = NULL; + HANDLE hGlobalServiceThreadMutex = NULL; + NTSTATUS status; + OBJECT_ATTRIBUTES attr; + + attr.Length = sizeof(attr); + attr.RootDirectory = 0; + attr.ObjectName = NULL; + attr.Attributes = OBJ_CASE_INSENSITIVE | OBJ_OPENIF; + attr.SecurityDescriptor = NULL; + attr.SecurityQualityOfService = NULL; + status = NtCreateMutant (&hServiceThreadMutex, MUTEX_ALL_ACCESS, &attr, + FALSE); + //printf("created mutant %x, status=%x\n", hServiceThreadMutex, status); + NtDuplicateObject(NtCurrentProcess(), hServiceThreadMutex, + NULL, &hGlobalServiceThreadMutex, + 0, FALSE, DUPLICATE_CLOSE_SOURCE|DUPLICATE_SAME_ACCESS| + DUP_HANDLE_MAKE_GLOBAL); + //printf("new mutant %x, status=%x\n", hGlobalServiceThreadMutex, status); + return hGlobalServiceThreadMutex; +} + + +/*********************************************************************** + * SERVICE_thread + * + * Service thread loop + */ +static DWORD WINAPI SERVICE_thread(LPVOID arg) +{ + static unsigned int i = 0; + sigset_t sigset; + + /* unblock SIGRTMIN only for service thread */ + sigemptyset( &sigset ); + sigaddset( &sigset, SIGRTMIN ); + sigprocmask( SIG_UNBLOCK, &sigset, NULL ); + + //printf("setting event inside SERVICE_thread\n"); + /* signal that the service thread has been created */ + NtSetEvent(*(HANDLE *) arg, NULL); + + /* all work for this thread is done in the rt0 signal handler in + * signal_i386.c */ + while (TRUE) + { + struct timeval timeout = {1, 0}; + + select(0, NULL, NULL, NULL, &timeout); + + if (!pending_service_req) + continue; + pending_service_req = 0; + + switch( ro.op ) + { + case RO_NEW_THREAD: + { + HANDLE hNewThread; + CLIENT_ID cidLocal; + NTSTATUS ret; + HANDLE hServiceThreadMutex = NULL; + /* + printf("inside wine_cloned_thread_creator, " + "params (%x, %x, %x, %x, %x, %x, %x, %x, %x, %x)\n", + ro.thread.suspended, ro.thread.stack_addr, + ro.thread.stack_reserve, ro.thread.stack_commit, + ro.thread.start, ro.thread.param, + ro.hDoneEvent, ro.thread.src_process, + ro.thread.handle_ptr, ro.thread.id_ptr); + */ + + ret = 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 ? &hNewThread : 0, + ro.thread.id_ptr ? &cidLocal : 0); + //printf("RtlCreateUserThread status %x in service thread\n", ret); + + if (ro.thread.handle_ptr) + { + HANDLE hRemoteNewThread; + ret = NtDuplicateObject(NtCurrentProcess(), hNewThread, + ro.thread.src_process, + &hRemoteNewThread, 0, 0, + DUPLICATE_SAME_ACCESS| + DUPLICATE_CLOSE_SOURCE); + if (ret) printf("error 1 %x\n", ret); + /* + printf("writing handle %x (dup of %x)\n", hRemoteNewThread, + hNewThread); + */ + ret = NtWriteVirtualMemory(ro.thread.src_process, + ro.thread.handle_ptr, + &hRemoteNewThread, sizeof(HANDLE), + NULL); + if (ret) printf("error 2 %x\n", ret); + } + if (ro.thread.id_ptr) + { + ret = NtWriteVirtualMemory(ro.thread.src_process, ro.thread.id_ptr, + &cidLocal, + sizeof(CLIENT_ID), NULL); + if (ret) printf("error 5 %x\n", ret); + } + + NtClose(ro.thread.src_process); + break; + } + case RO_ALLOCATE: + { + ro.status = NtAllocateVirtualMemory( NtCurrentProcess(), + &ro.alloc.ret, + ro.alloc.zero_bits, + &ro.alloc.size, + ro.alloc.type, + ro.alloc.protect ); + NtSetEvent( ro.hDoneEvent, NULL ); + NtClose( ro.hDoneEvent ); + break; + } + } + } +} + + +/*********************************************************************** + * SERVICE_init + * + * Initialize the service thread + * + * FIXME check return codes + */ +void SERVICE_init() +{ + HANDLE hNewThread, hGlobalServiceThreadMutex, hThreadCreatedEvent; + + if (!use_service_thread()) + { + ERR("service thread disabled\n"); + return; + } + + hGlobalServiceThreadMutex = create_global_mutex(); + + NtCreateEvent(&hThreadCreatedEvent, EVENT_ALL_ACCESS, + NULL, FALSE, FALSE); + + RtlCreateUserThread( NtCurrentProcess(), NULL, FALSE, NULL, + 0, 0, SERVICE_thread, &hThreadCreatedEvent, + &hNewThread, NULL ); + NtClose(hNewThread); + + NtWaitForSingleObject(hThreadCreatedEvent, FALSE, NULL); + NtClose(hThreadCreatedEvent); + + //ERR("hNewThread=%x, mutex=%x\n", hNewThread, hGlobalServiceThreadMutex); + SERVER_START_REQ( set_remote_op_mutex ) + { + req->mutex = hGlobalServiceThreadMutex; + wine_server_call( req ); + } + SERVER_END_REQ; +} diff --git a/dlls/ntdll/signal_i386.c b/dlls/ntdll/signal_i386.c index 99685f3..508b4dd 100644 --- a/dlls/ntdll/signal_i386.c +++ b/dlls/ntdll/signal_i386.c @@ -1209,6 +1209,17 @@ #endif /* __HAVE_VM86 */
/********************************************************************** + * rt0_handler + * + * Handler for SIGRTMIN. + */ +static HANDLER_DEF(rt0_handler) +{ + pending_service_req = 1; +} + + +/********************************************************************** * segv_handler * * Handler for SIGSEGV and related errors. @@ -1439,6 +1450,7 @@ #endif /* linux */ sigaddset( &sig_act.sa_mask, SIGINT ); sigaddset( &sig_act.sa_mask, SIGUSR1 ); sigaddset( &sig_act.sa_mask, SIGUSR2 ); + sigaddset( &sig_act.sa_mask, SIGRTMIN );
#if defined(linux) || defined(__NetBSD__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) sig_act.sa_flags = SA_RESTART; @@ -1473,6 +1485,7 @@ int __wine_set_signal_handler(unsigned i BOOL SIGNAL_Init(void) { int have_sigaltstack = 0; + sigset_t sigset;
#ifdef HAVE_SIGALTSTACK struct sigaltstack ss; @@ -1505,6 +1518,12 @@ #endif #ifdef __HAVE_VM86 if (set_handler( SIGUSR2, have_sigaltstack, (void (*)())usr2_handler ) == -1) goto error; #endif + if (set_handler( SIGRTMIN, 0, (void (*)())rt0_handler ) == -1) goto error; + + /* block SIGRTMIN by default */ + sigemptyset( &sigset ); + sigaddset( &sigset, SIGRTMIN ); + sigprocmask( SIG_BLOCK, &sigset, NULL );
return TRUE;
diff --git a/dlls/ntdll/thread.c b/dlls/ntdll/thread.c index dbcc5a6..9cf8407 100644 --- a/dlls/ntdll/thread.c +++ b/dlls/ntdll/thread.c @@ -62,6 +62,7 @@ static size_t sigstack_total_size; static ULONG sigstack_zero_bits;
struct wine_pthread_functions pthread_functions = { NULL }; +struct RemoteOp ro = { 0 };
/*********************************************************************** * init_teb @@ -76,6 +77,7 @@ static inline NTSTATUS init_teb( TEB *te teb->Tib.Self = &teb->Tib; teb->StaticUnicodeString.Buffer = teb->StaticUnicodeBuffer; teb->StaticUnicodeString.MaximumLength = sizeof(teb->StaticUnicodeBuffer); + teb->GotThreadMutex = 0;
if (!(thread_regs->fs = wine_ldt_alloc_fs())) return STATUS_TOO_MANY_THREADS; thread_data->request_fd = -1; @@ -385,6 +387,52 @@ static void start_thread( struct wine_pt func( arg ); }
+/*********************************************************************** + * __wine_get_remote_op_mutex (NTDLL.@) Not a Windows API + * + * return the service mutex, or a null handle with success if the thread + * already has the mutex + */ +NTSTATUS WINAPI __wine_get_remote_op_mutex(HANDLE hProcess, HANDLE *hMutex) +{ + NTSTATUS status; + + if (NtCurrentTeb()->GotThreadMutex) + { + *hMutex = NULL; + return STATUS_SUCCESS; + } + + SERVER_START_REQ( get_remote_op_mutex ) + { + req->process = hProcess; + if (!(status = wine_server_call( req ))) + *hMutex = reply->mutex; + } + SERVER_END_REQ; + + if (status || *hMutex == NULL) + return status ? status : STATUS_NOT_IMPLEMENTED; + + status = NtWaitForSingleObject(*hMutex, FALSE, 0); + NtCurrentTeb()->GotThreadMutex = 1; + + return STATUS_SUCCESS; +} + +/*********************************************************************** + * __wine_release_remote_op_mutex (NTDLL.@) Not a Windows API + */ +NTSTATUS WINAPI __wine_release_remote_op_mutex(HANDLE hMutex) +{ + NTSTATUS status; + + if (hMutex == NULL) return STATUS_SUCCESS; + + NtCurrentTeb()->GotThreadMutex = 0; + status = NtReleaseMutant(hMutex, NULL); + return status; +}
/*********************************************************************** * RtlCreateUserThread (NTDLL.@) @@ -408,8 +456,58 @@ NTSTATUS WINAPI RtlCreateUserThread( HAN
if( ! is_current_process( process ) ) { - ERR("Unsupported on other process\n"); - return STATUS_ACCESS_DENIED; + HANDLE hThreadCreatedEvent; + HANDLE hServiceThreadMutex; + struct RemoteOp n; + + status = __wine_get_remote_op_mutex(process, &hServiceThreadMutex); + if (status) return status; + + status = NtCreateEvent(&hThreadCreatedEvent, EVENT_ALL_ACCESS, NULL, + FALSE, FALSE); /* FIXME check this */ + + n.op = RO_NEW_THREAD; + n.thread.suspended = suspended; + n.thread.stack_addr = stack_addr; + n.thread.stack_reserve = stack_reserve; + n.thread.stack_commit = stack_commit; + n.thread.start = start; + n.thread.param = param; + n.thread.handle_ptr = handle_ptr; + n.thread.id_ptr = id; + n.thread.src_process = NULL; + n.hDoneEvent = NULL; + + status = NtWriteVirtualMemory(process, &ro, &n, + sizeof(struct RemoteOp), NULL); + if (status) + { + NtClose( hThreadCreatedEvent ); + return status; + } + + SERVER_START_REQ( remote_op ) + { + req->handle = process; + /* This pair of handles is duplicated and forwarded to the remote + * process. The done event is actually set in the remote process + * during the init_thread server call because it is right after + * this that the thread is suspended. */ + req->event = hThreadCreatedEvent; + req->event_ptr = &ro.hDoneEvent; + req->src_process = NtCurrentProcess(); + req->src_process_ptr = &ro.thread.src_process; + + status = wine_server_call( req ); + } + SERVER_END_REQ; + if (status) return status; + + NtWaitForSingleObject(hThreadCreatedEvent, FALSE, NULL); + __wine_release_remote_op_mutex(hServiceThreadMutex); + + NtClose(hThreadCreatedEvent); + return 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..0600170 100644 --- a/dlls/ntdll/virtual.c +++ b/dlls/ntdll/virtual.c @@ -1317,8 +1317,59 @@ NTSTATUS WINAPI NtAllocateVirtualMemory(
if (!is_current_process( process )) { - ERR("Unsupported on other process\n"); - return STATUS_ACCESS_DENIED; + HANDLE hThreadCreatedEvent; + HANDLE hServiceThreadMutex; + struct RemoteOp n; + + status = __wine_get_remote_op_mutex(process, &hServiceThreadMutex); + if (status) return status; + + status = NtCreateEvent(&hThreadCreatedEvent, EVENT_ALL_ACCESS, NULL, + FALSE, FALSE); /* FIXME check this */ + + n.op = RO_ALLOCATE; + n.alloc.ret = *ret; + n.alloc.zero_bits = zero_bits; + n.alloc.size = *size_ptr; + n.alloc.type = type; + n.alloc.protect = protect; + n.hDoneEvent = NULL; + + status = NtWriteVirtualMemory(process, &ro, &n, + sizeof(struct RemoteOp), NULL); + if (status) + { + NtClose( hThreadCreatedEvent ); + return status; + } + + SERVER_START_REQ( remote_op ) + { + req->handle = process; + + req->event = hThreadCreatedEvent; + req->event_ptr = &ro.hDoneEvent; + req->src_process = NULL; + req->src_process_ptr = NULL; + + status = wine_server_call( req ); + } + SERVER_END_REQ; + + if (status) return status; + + NtWaitForSingleObject(hThreadCreatedEvent, FALSE, NULL); + + NtReadVirtualMemory(process, &ro, &n, sizeof(struct RemoteOp), + NULL); + + *ret = n.alloc.ret; + *size_ptr = n.alloc.size; + + __wine_release_remote_op_mutex(hServiceThreadMutex); + + NtClose(hThreadCreatedEvent); + return n.status; }
/* Round parameters to a page boundary */ diff --git a/include/thread.h b/include/thread.h index 68d7ae2..d6a36e5 100644 --- a/include/thread.h +++ b/include/thread.h @@ -57,7 +57,8 @@ typedef struct _TEB ULONG_PTR dpmi_vif; /* 200 protected mode virtual interrupt flag */ DWORD vm86_pending; /* 204 data for vm86 mode */ /* here is plenty space for wine specific fields (don't forget to change pad6!!) */ - DWORD pad6[309]; /* 208 */ + DWORD GotThreadMutex; /* 208 */ + DWORD pad6[308]; /* 20c */
ULONG gdiRgn; /* 6dc */ ULONG gdiPen; /* 6e0 */ diff --git a/server/process.c b/server/process.c index 3e5ef97..651ded6 100644 --- a/server/process.c +++ b/server/process.c @@ -22,12 +22,15 @@ #include "config.h" #include "wine/port.h"
#include <assert.h> +#include <errno.h> #include <limits.h> +#include <linux/user.h> #include <signal.h> #include <string.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> +#include <sys/ptrace.h> #include <sys/time.h> #ifdef HAVE_SYS_SOCKET_H # include <sys/socket.h> @@ -36,6 +39,10 @@ #include <unistd.h> #ifdef HAVE_POLL_H #include <poll.h> #endif +#include <sys/wait.h> +#include <sys/types.h> +#include <linux/unistd.h> +#include <errno.h>
#include "ntstatus.h" #define WIN32_NO_STATUS @@ -250,6 +257,7 @@ struct thread *create_process( int fd, s process->winstation = 0; process->desktop = 0; process->token = token_create_admin(); + process->service_mutex = NULL; list_init( &process->thread_list ); list_init( &process->locks ); list_init( &process->classes ); @@ -1055,3 +1063,92 @@ 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 = NULL; + int pid; + obj_handle_t dupevent = NULL, dupprocess = NULL; + + /* get process object */ + access = PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION + | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE; + if (!(dst = get_process_from_handle( req->handle, access ))) + { + set_error( STATUS_ACCESS_DENIED ); + goto cleanup; + } + + if (!(thread = get_process_first_thread( dst ))) + { + set_error( STATUS_ACCESS_DENIED ); + goto cleanup; + } + + pid = thread->unix_pid; + + /* clone event and process handles and write to target process */ + if (!(src = get_process_from_handle((obj_handle_t) 0xffffffff, 0))) + { + set_error( STATUS_ACCESS_DENIED ); + goto cleanup; + } + if (req->event) + { + dupevent = duplicate_handle(src, req->event, dst, 0, 0, + DUPLICATE_SAME_ACCESS); + write_process_memory(dst, req->event_ptr, sizeof(obj_handle_t), + (char *) &dupevent); + } + if (req->src_process) + { + dupprocess = duplicate_handle(src, req->src_process, dst, 0, 0, + DUPLICATE_SAME_ACCESS); + write_process_memory(dst, req->src_process_ptr, + sizeof(obj_handle_t), (char *) &dupprocess); + } + + kill(pid, SIGRTMIN); + +cleanup: + if (src) + release_object( src ); + if (dst) + release_object( dst ); +} + +/* FIXME We can actually put the mutex data in a specific place and read + * from it. */ + +/* Set service thread mutex */ +DECL_HANDLER(set_remote_op_mutex) +{ + struct process *p; + if (!(p = get_process_from_handle((obj_handle_t) 0xffffffff, 0))) + { + set_error( STATUS_INVALID_HANDLE ); + return; + } + p->service_mutex = req->mutex; + release_object( p ); +} + +/* Get service thread mutex */ +DECL_HANDLER(get_remote_op_mutex) +{ + struct process *p; + if (!(p = get_process_from_handle(req->process, 0))) + { + set_error( STATUS_INVALID_HANDLE ); + return; + } + if (!(reply->mutex = p->service_mutex)) + { + set_error( STATUS_NOT_IMPLEMENTED ); + return; + } + release_object( p ); +} diff --git a/server/process.h b/server/process.h index 6edb1e6..eb13043 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 */ + obj_handle_t service_mutex; /* mutex of service thread */ };
struct process_snapshot diff --git a/server/protocol.def b/server/protocol.def index e64cf43..3dcf500 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -280,6 +280,7 @@ struct token_groups int reply_fd; /* fd for reply pipe */ int wait_fd; /* fd for blocking calls pipe */ int debug_level; /* new debug level */ + obj_handle_t event; /* set to notify remote process */ @REPLY process_id_t pid; /* process id of the new thread's process */ thread_id_t tid; /* thread id of the new thread */ @@ -2618,3 +2619,30 @@ #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; /* process handle */ + + obj_handle_t event; + void* event_ptr; + obj_handle_t src_process; + void* src_process_ptr; +@REPLY +@END + + +/* Set current process service thread */ +@REQ(set_remote_op_mutex) + obj_handle_t mutex; /* global handle for mutex */ +@REPLY +@END + + +/* Get service thread mutex */ +@REQ(get_remote_op_mutex) + obj_handle_t process; +@REPLY + obj_handle_t mutex; +@END diff --git a/server/thread.c b/server/thread.c index feb3259..f610d28 100644 --- a/server/thread.c +++ b/server/thread.c @@ -886,6 +886,15 @@ DECL_HANDLER(init_thread) } debug_level = max( debug_level, req->debug_level );
+ if (req->event) + { + struct event *event; + if (event = get_event_obj( current->process, req->event, + EVENT_MODIFY_STATE )) + set_event( event ); + close_handle( current->process, req->event, NULL ); + } + reply->pid = get_process_id( process ); reply->tid = get_thread_id( current ); reply->version = SERVER_PROTOCOL_VERSION;
On Tue, 01 Aug 2006 20:41:54 -0500, Thomas Kho wrote:
This is an implementation of CreateRemoteThread and RtlCreateUserThread for remote processes that re-introduces a service thread for each process and uses a signal to flag pending operations. Operations are guarded by a global mutex for each process.
Hey, great, this is a big improvement on last time.
Still I have a few questions about the approach:
- The service thread seems like a good idea to me. But why use a signal in this case, when there are simpler ways to achieve the same thing, for instance with a named pipe?
- I'm not sure using the realtime signals will work. I don't remember the details, Alexandre is the signals guru, but I remember them being "awkward"
- There is a lot of stuff to handle the global mutex. Why is this necessary? I'm afraid I really don't see where the lock is needed here; if you were using a pipe then the service thread would serialize the requests in its main loop
- Writing to a remote process by taking the address of a variable in the current one?! What if the DLLs are located at different base addresses? I know this is unlikely to happen in Windows but on Linux we have ASLR and they introduced this for Vista as well. Assuming a pointer that is valid in one process is also valid in another is not a good idea, in general.
And there are a few small style nits, for instance there's usually no reason to change "void *foo" to "void *foo = NULL", you want it to remain uninitialized because that way the compiler will warn if it's ever used before being set to something: superior to blindly using a NULL ptr with no warning I'm sure you'll agree.
Also watch the code style - internally Wine tends to use unix_style not hungarian notation, which is usually confined to the API definition itself.
Anyway, assuming Alexandre concurs with what I've said, here's where you want to go next:
* Drop the signal, and use a named pipe instead * This should let you drop the global mutex and WriteProcessMemory calls, I think?
thanks -mike
On 8/2/06, Mike Hearn mike@plan99.net wrote:
The service thread seems like a good idea to me. But why use a signal in this case, when there are simpler ways to achieve the same thing, for instance with a named pipe?
I'm not sure using the realtime signals will work. I don't remember the details, Alexandre is the signals guru, but I remember them being "awkward"
The goal is to get rid of the service thread; the service thread was just there as a placeholder. Once it's gone, signals are appropriate. The realtime signal is just there as a placeholder until we figure out how to squeze this down into SIGUSR1 and SIGUSR2.
Thomas is now experimenting with using APCs instead of a service thread. - Dan
On 8/3/06, Dan Kegel dank@kegel.com wrote:
The goal is to get rid of the service thread; the service thread was just there as a placeholder. Once it's gone, signals are appropriate. The realtime signal is just there as a placeholder until we figure out how to squeze this down into SIGUSR1 and SIGUSR2.
Hm so you'll have to co-ordinate switching the signal mask between threads as they start up and shut down? I'm not sure that can be done atomically.
thanks -mike
Mike Hearn wrote:
And there are a few small style nits, for instance there's usually no reason to change "void *foo" to "void *foo = NULL", you want it to remain uninitialized because that way the compiler will warn if it's ever used before being set to something: superior to blindly using a NULL ptr with no warning I'm sure you'll agree.
Let's see:
gcc version 4.1.2 20060729 (prerelease) (Debian 4.1.1-10)
mike@black:~/wine$ cat foobar.c void foo(int *); void bar(void) { int x; if(x) foo(&x); } mike@black:~/wine$ gcc -Wall -c foobar.c mike@black:~/wine$
Superior, if the compiler did happen to warn you...
Mike
On 8/2/06, Mike McCormack mike@codeweavers.com wrote:
["void *foo" is better than "void *foo = NULL", you want it to remain uninitialized because that way the compiler will warn]
gcc version 4.1.2 20060729 (prerelease) (Debian 4.1.1-10)
mike@black:~/wine$ cat foobar.c void foo(int *); void bar(void) { int x; if(x) foo(&x); } mike@black:~/wine$ gcc -Wall -c foobar.c mike@black:~/wine$
Superior, if the compiler did happen to warn you...
No fair, Mike, you should have given the link to the PR: http://gcc.gnu.org/PR19430
Many similar errors will get caught, as long as you turn on the optimizer with -O (otherwise the compiler can't detect them).
It does warn on the use of x as long as you avoid the specific case of PR19430. For instance:
$ cat foobar.c void foo(int *x); void bar(void) { int x; int y; if (x) foo(&y); } $ gcc -O -Wall -c foobar.c foobar.c: In function `bar': foobar.c:2: warning: `x' might be used uninitialized in this function
Thanks for pointing out that bug. I'll nag some compiler folk about it. - Dan
Dan Kegel wrote:
No fair, Mike, you should have given the link to the PR: http://gcc.gnu.org/PR19430
:) If you know a gcc person that has time, nagging them would be great.
Many similar errors will get caught, as long as you turn on the optimizer with -O (otherwise the compiler can't detect them).
It does warn on the use of x as long as you avoid the specific case of PR19430.
In the case where you use &var, ignoring compiler bugs, it always seems like a good idea to initialize var to a known value to avoid indeterminate behaviour.
If the compiler is clever enough to give an uninitialized variable warning, then it will likely be clever enough to optimize out the extra assignment too.
Mike