[5/5] ntdll: add remote process operations (update 2)

Thomas Kho tkho at ucla.edu
Fri Aug 18 03:25:59 CDT 2006


[5/5] ntdll: add remote process operations (update 2)

This patch adds support for CreateRemoteThread, RtlCreateUserThread,
NtAllocateVirtualMemory, NtFreeVirtualMemory and NtQueryVirtualMemory in
another process.

This patch was updated to address an issue where a stop signal was not being
delivered when it was intended to.

Thomas Kho

Copyright 2006 Google (Thomas Kho)
License: LGPL

---

 dlls/kernel/tests/thread.c     |   37 ++++-----
 dlls/kernel/tests/virtual.c    |   66 +++++++---------
 dlls/kernel/thread.c           |   64 ++++++++++++++--
 dlls/ntdll/ntdll_misc.h        |    6 +
 dlls/ntdll/process.c           |  162 ++++++++++++++++++++++++++++++++++++++++
 dlls/ntdll/server.c            |   12 +++
 dlls/ntdll/thread.c            |   48 +++++++++++-
 dlls/ntdll/virtual.c           |   56 ++++++++++++--
 include/wine/server_protocol.h |   29 +++++++
 include/winternl.h             |   58 ++++++++++++++
 server/process.c               |   82 ++++++++++++++++++++
 server/process.h               |    1 
 server/protocol.def            |   19 +++++
 server/request.h               |    2 
 server/thread.c                |   14 +++
 server/trace.c                 |   12 +++
 16 files changed, 591 insertions(+), 77 deletions(-)

diff --git a/dlls/kernel/tests/thread.c b/dlls/kernel/tests/thread.c
index 0c97bf2..8bd66d9 100644
--- a/dlls/kernel/tests/thread.c
+++ b/dlls/kernel/tests/thread.c
@@ -210,48 +210,43 @@ static VOID test_CreateRemoteThread(void
     /* create suspended remote thread with entry point SetEvent() */
     hThread = CreateRemoteThread(hProcess, NULL, 0, threadFunc_SetEvent,
                                  hRemoteEvent, CREATE_SUSPENDED, &tid);
-    todo_wine ok(hThread != NULL, "CreateRemoteThread failed, err=%lu\n",
-                 GetLastError());
+    ok(hThread != NULL, "CreateRemoteThread failed, err=%lu\n", GetLastError());
     ok(tid != 0, "null tid\n");
     ret = SuspendThread(hThread);
-    todo_wine ok(ret == 1, "ret=%lu, err=%lu\n", ret, GetLastError());
+    ok(ret == 1, "ret=%lu, err=%lu\n", ret, GetLastError());
     ret = ResumeThread(hThread);
-    todo_wine ok(ret == 2, "ret=%lu, err=%lu\n", ret, GetLastError());
+    ok(ret == 2, "ret=%lu, err=%lu\n", ret, GetLastError());
 
     /* thread still suspended, so wait times out */
     ret = WaitForSingleObject(hEvent, 100);
     ok(ret == WAIT_TIMEOUT, "wait did not time out, ret=%lu\n", ret);
 
     ret = ResumeThread(hThread);
-    todo_wine ok(ret == 1, "ret=%lu, err=%lu\n", ret, GetLastError());
+    ok(ret == 1, "ret=%lu, err=%lu\n", ret, GetLastError());
 
     /* wait that doesn't time out */
     ret = WaitForSingleObject(hEvent, 100);
-    todo_wine ok(ret == WAIT_OBJECT_0, "object not signaled, ret=%lu\n", ret);
+    ok(ret == WAIT_OBJECT_0, "object not signaled, ret=%lu\n", ret);
 
     /* wait for thread end */
     ret = WaitForSingleObject(hThread, 100);
-    todo_wine ok(ret == WAIT_OBJECT_0,
-                 "waiting for thread failed, ret=%lu\n", ret);
+    ok(ret == WAIT_OBJECT_0, "waiting for thread failed, ret=%lu\n", ret);
     CloseHandle(hThread);
 
     /* create and wait for remote thread with entry point CloseHandle() */
     hThread = CreateRemoteThread(hProcess, NULL, 0,
                                  threadFunc_CloseHandle,
                                  hRemoteEvent, 0, &tid);
-    todo_wine ok(hThread != NULL,
-                 "CreateRemoteThread failed, err=%lu\n", GetLastError());
+    ok(hThread != NULL, "CreateRemoteThread failed, err=%lu\n", GetLastError());
     ret = WaitForSingleObject(hThread, 100);
-    todo_wine ok(ret == WAIT_OBJECT_0,
-                 "waiting for thread failed, ret=%lu\n", ret);
+    ok(ret == WAIT_OBJECT_0, "waiting for thread failed, ret=%lu\n", ret);
     CloseHandle(hThread);
 
     /* create remote thread with entry point SetEvent() */
     hThread = CreateRemoteThread(hProcess, NULL, 0,
                                  threadFunc_SetEvent,
                                  hRemoteEvent, 0, &tid);
-    todo_wine ok(hThread != NULL,
-                 "CreateRemoteThread failed, err=%lu\n", GetLastError());
+    ok(hThread != NULL, "CreateRemoteThread failed, err=%lu\n", GetLastError());
 
     /* closed handle, so wait times out */
     ret = WaitForSingleObject(hEvent, 100);
@@ -259,10 +254,8 @@ static VOID test_CreateRemoteThread(void
 
     /* check that remote SetEvent() failed */
     ret = GetExitCodeThread(hThread, &exitcode);
-    todo_wine ok(ret != 0,
-                 "GetExitCodeThread failed, err=%lu\n", GetLastError());
-    todo_wine ok(exitcode == 0,
-                 "SetEvent succeeded, expected to fail\n");
+    ok(ret != 0, "GetExitCodeThread failed, err=%lu\n", GetLastError());
+    ok(exitcode == 0, "SetEvent succeeded, expected to fail\n");
     CloseHandle(hThread);
 
     TerminateProcess(hProcess, 0);
@@ -277,10 +270,10 @@ static DWORD WINAPI create_remote_thread
         HANDLE hThread;
         hThread = CreateRemoteThread(hStressTestProcess, NULL, 0,
                                      threadFunc2, NULL, 0, NULL);
-        todo_wine ok(hThread != NULL, "CreateRemoteThread failed, error %lu\n",
-                     GetLastError());
-        todo_wine ok(WaitForSingleObject(hThread, 200) == WAIT_OBJECT_0,
-                     "Remote thread did not exit in time\n");
+        ok(hThread != NULL, "CreateRemoteThread failed, error %lu\n",
+           GetLastError());
+        ok(WaitForSingleObject(hThread, 200) == WAIT_OBJECT_0,
+           "Remote thread did not exit in time\n");
         if (hThread == NULL) break;
         CloseHandle(hThread);
     }
diff --git a/dlls/kernel/tests/virtual.c b/dlls/kernel/tests/virtual.c
index 75dd0d6..ca26511 100644
--- a/dlls/kernel/tests/virtual.c
+++ b/dlls/kernel/tests/virtual.c
@@ -107,7 +107,7 @@ static void test_VirtualAllocEx(void)
 
     addr1 = VirtualAllocEx(hProcess, NULL, alloc_size, MEM_COMMIT,
                            PAGE_EXECUTE_READWRITE);
-    todo_wine ok(addr1 != NULL, "VirtualAllocEx error %lu\n", GetLastError());
+    ok(addr1 != NULL, "VirtualAllocEx error %lu\n", GetLastError());
     b = WriteProcessMemory(hProcess, addr1, src, alloc_size, &bytes_written);
     ok(b && (bytes_written == alloc_size), "%lu bytes written\n",
        bytes_written);
@@ -115,7 +115,7 @@ static void test_VirtualAllocEx(void)
     ok(b && (bytes_read == alloc_size), "%lu bytes read\n", bytes_read);
     ok(!memcmp(src, dst, alloc_size), "Data from remote process differs\n");
     b = VirtualFreeEx(hProcess, addr1, 0, MEM_RELEASE);
-    todo_wine ok(b != 0, "VirtualFreeEx, error %lu\n", GetLastError());
+    ok(b != 0, "VirtualFreeEx, error %lu\n", GetLastError());
 
     HeapFree( GetProcessHeap(), 0, src );
     HeapFree( GetProcessHeap(), 0, dst );
@@ -132,26 +132,23 @@ static void test_VirtualAllocEx(void)
         "got %ld, expected ERROR_INVALID_PARAMETER\n", GetLastError());
 
     addr1 = VirtualAllocEx(hProcess, 0, 0xFFFC, MEM_RESERVE, PAGE_NOACCESS);
-    todo_wine ok(addr1 != NULL, "VirtualAllocEx failed\n");
+    ok(addr1 != NULL, "VirtualAllocEx failed\n");
 
     /* test a not committed memory */
     memset(&info, 'q', sizeof(info));
-    todo_wine ok(VirtualQueryEx(hProcess, addr1, &info, sizeof(info))
+    ok(VirtualQueryEx(hProcess, addr1, &info, sizeof(info))
                  == sizeof(info), "VirtualQueryEx failed\n");
-    todo_wine ok(info.BaseAddress == addr1, "%p != %p\n", info.BaseAddress,
-                 addr1);
-    todo_wine ok(info.AllocationBase == addr1, "%p != %p\n",
-                 info.AllocationBase, addr1);
-    todo_wine ok(info.AllocationProtect == PAGE_NOACCESS,
+    ok(info.BaseAddress == addr1, "%p != %p\n", info.BaseAddress, addr1);
+    ok(info.AllocationBase == addr1, "%p != %p\n", info.AllocationBase, addr1);
+    ok(info.AllocationProtect == PAGE_NOACCESS,
                  "%lx != PAGE_NOACCESS\n", info.AllocationProtect);
-    todo_wine ok(info.RegionSize == 0x10000, "%lx != 0x10000\n",
-                 info.RegionSize);
-    todo_wine ok(info.State == MEM_RESERVE, "%lx != MEM_RESERVE\n", info.State);
+    ok(info.RegionSize == 0x10000, "%lx != 0x10000\n", info.RegionSize);
+    ok(info.State == MEM_RESERVE, "%lx != MEM_RESERVE\n", info.State);
     /* NT reports Protect == 0 for a not committed memory block */
-    todo_wine ok(info.Protect == 0 /* NT */ ||
+    ok(info.Protect == 0 /* NT */ ||
        info.Protect == PAGE_NOACCESS, /* Win9x */
         "%lx != PAGE_NOACCESS\n", info.Protect);
-    todo_wine ok(info.Type == MEM_PRIVATE, "%lx != MEM_PRIVATE\n", info.Type);
+    ok(info.Type == MEM_PRIVATE, "%lx != MEM_PRIVATE\n", info.Type);
 
     SetLastError(0xdeadbeef);
     ok(!VirtualProtectEx(hProcess, addr1, 0xFFFC, PAGE_READONLY, &old_prot),
@@ -164,21 +161,17 @@ static void test_VirtualAllocEx(void)
     ok(addr1 == addr2, "VirtualAllocEx failed\n");
 
     /* test a committed memory */
-    todo_wine ok(VirtualQueryEx(hProcess, addr1, &info, sizeof(info))
-                 == sizeof(info),
-        "VirtualQueryEx failed\n");
-    todo_wine ok(info.BaseAddress == addr1, "%p != %p\n", info.BaseAddress,
-                 addr1);
-    todo_wine ok(info.AllocationBase == addr1, "%p != %p\n",
-                 info.AllocationBase, addr1);
-    todo_wine ok(info.AllocationProtect == PAGE_NOACCESS,
-                 "%lx != PAGE_NOACCESS\n", info.AllocationProtect);
-    todo_wine ok(info.RegionSize == 0x1000, "%lx != 0x1000\n", info.RegionSize);
-    todo_wine ok(info.State == MEM_COMMIT, "%lx != MEM_COMMIT\n", info.State);
+    ok(VirtualQueryEx(hProcess, addr1, &info, sizeof(info)) == sizeof(info),
+       "VirtualQueryEx failed\n");
+    ok(info.BaseAddress == addr1, "%p != %p\n", info.BaseAddress, addr1);
+    ok(info.AllocationBase == addr1, "%p != %p\n", info.AllocationBase, addr1);
+    ok(info.AllocationProtect == PAGE_NOACCESS,
+       "%lx != PAGE_NOACCESS\n", info.AllocationProtect);
+    ok(info.RegionSize == 0x1000, "%lx != 0x1000\n", info.RegionSize);
+    ok(info.State == MEM_COMMIT, "%lx != MEM_COMMIT\n", info.State);
     /* this time NT reports PAGE_NOACCESS as well */
-    todo_wine ok(info.Protect == PAGE_NOACCESS, "%lx != PAGE_NOACCESS\n",
-                 info.Protect);
-    todo_wine ok(info.Type == MEM_PRIVATE, "%lx != MEM_PRIVATE\n", info.Type);
+    ok(info.Protect == PAGE_NOACCESS, "%lx != PAGE_NOACCESS\n", info.Protect);
+    ok(info.Type == MEM_PRIVATE, "%lx != MEM_PRIVATE\n", info.Type);
 
     /* this should fail, since not the whole range is committed yet */
     SetLastError(0xdeadbeef);
@@ -200,20 +193,19 @@ static void test_VirtualAllocEx(void)
 
     ok(!VirtualFreeEx(hProcess, addr1, 0x10000, 0),
        "VirtualFreeEx should fail with type 0\n");
-    todo_wine ok(GetLastError() == ERROR_INVALID_PARAMETER,
-        "got %ld, expected ERROR_INVALID_PARAMETER\n", GetLastError());
+    ok(GetLastError() == ERROR_INVALID_PARAMETER,
+       "got %ld, expected ERROR_INVALID_PARAMETER\n", GetLastError());
 
-    todo_wine ok(VirtualFreeEx(hProcess, addr1, 0x10000, MEM_DECOMMIT),
-                 "VirtualFreeEx failed\n");
+    ok(VirtualFreeEx(hProcess, addr1, 0x10000, MEM_DECOMMIT),
+       "VirtualFreeEx failed\n");
 
     /* if the type is MEM_RELEASE, size must be 0 */
     ok(!VirtualFreeEx(hProcess, addr1, 1, MEM_RELEASE),
        "VirtualFreeEx should fail\n");
-    todo_wine ok(GetLastError() == ERROR_INVALID_PARAMETER,
+    ok(GetLastError() == ERROR_INVALID_PARAMETER,
         "got %ld, expected ERROR_INVALID_PARAMETER\n", GetLastError());
 
-    todo_wine ok(VirtualFreeEx(hProcess, addr1, 0, MEM_RELEASE),
-                 "VirtualFreeEx failed\n");
+    ok(VirtualFreeEx(hProcess, addr1, 0, MEM_RELEASE), "VirtualFreeEx failed\n");
 
     TerminateProcess(hProcess, 0);
     CloseHandle(hProcess);
@@ -226,8 +218,8 @@ static DWORD WINAPI create_virtualalloce
         VOID *mem;
         mem = VirtualAllocEx(hStressTestProcess, NULL, 1<<20,
                              MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
-        todo_wine ok(mem != NULL, "VirtualAllocEx error %lu\n", GetLastError());
-        todo_wine ok(VirtualFreeEx(hStressTestProcess, mem, 0, MEM_RELEASE) != 0,
+        ok(mem != NULL, "VirtualAllocEx error %lu\n", GetLastError());
+        ok(VirtualFreeEx(hStressTestProcess, mem, 0, MEM_RELEASE) != 0,
                      "VirtualFreeEx error %lu\n", GetLastError());
         if (!mem) break;
     }
diff --git a/dlls/kernel/thread.c b/dlls/kernel/thread.c
index bf29aac..7531622 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,65 @@ 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 (GetProcessId( hProcess ) != GetCurrentProcessId())
+    {
+        /* We need a dedicated remote call for CreateRemoteThread because it
+         * calls RtlCreateUserThread with a pointer to THREAD_start, which
+         * isn't valid across processes. Once Wine always locates kernel32 and
+         * ntdll in the same place, we can remove this remote call and move
+         * struct RemoteOp into ntdll_misc.h */
+        struct RemoteOp ro;
+        HANDLE done_event;
+
+        ro.op = REMOTE_OP_CREATEREMOTETHREAD;
+        ro.kernel32thread.stack = stack;
+        ro.kernel32thread.start = start;
+        ro.kernel32thread.param = param;
+        ro.kernel32thread.flags = flags;
+
+        if ((status = NtCreateEvent(&done_event, EVENT_ALL_ACCESS, NULL, FALSE,
+                                    FALSE)))
+            return 0;
+
+        SERVER_START_REQ( remote_op )
+        {
+            req->handle = hProcess;
+            req->op = ro.op;
+            req->event = done_event;
+            req->args = &ro;
+
+            status = wine_server_call( req );
+        }
+        SERVER_END_REQ;
+        if (status)
+            return 0;
+
+        NtWaitForSingleObject( done_event, FALSE, NULL );
+        NtClose( done_event );
+
+        if (id) *id = ro.kernel32thread.id;
+
+        return ro.kernel32thread.handle;
+    }
+
+    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;
+
+    if (GetProcessId( hProcess ) != GetCurrentProcessId())
+    {
+        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 +199,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 +207,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 ab01bde..5c8bd44 100644
--- a/dlls/ntdll/ntdll_misc.h
+++ b/dlls/ntdll/ntdll_misc.h
@@ -62,6 +62,12 @@ extern void DECLSPEC_NORETURN server_pro
 extern void DECLSPEC_NORETURN server_exit_thread( int status );
 extern void DECLSPEC_NORETURN server_abort_thread( int status );
 
+/* remote operations support */
+extern NTSTATUS NTDLL_remote_call( HANDLE process, struct RemoteOp *ro );
+extern void CALLBACK __wine_RemoteProcessOperation( ULONG_PTR arg1,
+                                                    ULONG_PTR arg2,
+                                                    ULONG_PTR arg3 );
+
 /* module handling */
 extern NTSTATUS MODULE_DllThreadAttach( LPVOID lpReserved );
 extern FARPROC RELAY_GetProcAddress( HMODULE module, const IMAGE_EXPORT_DIRECTORY *exports,
diff --git a/dlls/ntdll/process.c b/dlls/ntdll/process.c
index 02e2b15..6b262a1 100644
--- a/dlls/ntdll/process.c
+++ b/dlls/ntdll/process.c
@@ -395,3 +395,165 @@ NTSTATUS  WINAPI NtOpenProcess(PHANDLE h
     SERVER_END_REQ;
     return status;
 }
+
+/***********************************************************************
+ *      __wine_RemoteProcessOperation
+ *
+ * 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 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 REMOTE_OP_CREATEREMOTETHREAD:
+      {
+          typedef HANDLE (WINAPI *func_type) ( HANDLE, SECURITY_ATTRIBUTES *,
+                                               SIZE_T, LPTHREAD_START_ROUTINE,
+                                               LPVOID, DWORD, LPDWORD );
+          static func_type func = NULL;
+          HANDLE hThread;
+          NTSTATUS ret;
+
+          ro.status = 0;
+          ro.kernel32thread.handle = NULL;
+
+          if (!func)
+          {
+              HMODULE hkernel32;
+              UNICODE_STRING module;
+              ANSI_STRING function;
+              WCHAR kernel32W[] = {'k','e','r','n','e','l','3','2',
+                  '.','d','l','l',0};
+
+              RtlInitUnicodeString(&module, kernel32W);
+              RtlInitAnsiString(&function, "CreateRemoteThread");
+              if ((ro.status = LdrGetDllHandle(0, 0, &module, &hkernel32)))
+                  break;
+              if ((ro.status = LdrGetProcedureAddress(hkernel32, &function, 0,
+                                                      (PVOID *) &func)))
+                  break;
+          }
+
+          hThread = func( NtCurrentProcess(), NULL,
+                          ro.kernel32thread.stack,
+                          ro.kernel32thread.start,
+                          ro.kernel32thread.param,
+                          ro.kernel32thread.flags,
+                          &ro.kernel32thread.id );
+          if ((ret = NtDuplicateObject( NtCurrentProcess(), hThread,
+                                        hProcess, &ro.kernel32thread.handle,
+                                        0, 0, DUPLICATE_SAME_ACCESS|
+                                        DUPLICATE_CLOSE_SOURCE )))
+              ERR("Cannot duplicate handle in remote process, error %x\n",
+                  (unsigned) ret);
+          break;
+      }
+      case REMOTE_OP_RTLCREATEUSERTHREAD:
+      {
+        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 %x\n",
+                    (unsigned) ret);
+            else if ((ret = NtWriteVirtualMemory( hProcess,
+                                                  ro.thread.handle_ptr,
+                                                  &hRemoteNewThread,
+                                                  sizeof(HANDLE),
+                                                  NULL )))
+                ERR("Cannot write back handle, error %x\n", (unsigned) 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 %x\n", (unsigned) ret);
+        }
+        break;
+      }
+      case REMOTE_OP_NTALLOCATEVIRTUALMEMORY:
+      {
+        ro.status = NtAllocateVirtualMemory( NtCurrentProcess(),
+                                             &ro.alloc.ret,
+                                             ro.alloc.zero_bits,
+                                             &ro.alloc.size,
+                                             ro.alloc.type,
+                                             ro.alloc.protect );
+        break;
+      }
+      case REMOTE_OP_NTFREEVIRTUALMEMORY:
+      {
+        ro.status = NtFreeVirtualMemory( NtCurrentProcess(),
+                                         &ro.free.addr,
+                                         &ro.free.size,
+                                         ro.free.type );
+        break;
+      }
+      case REMOTE_OP_NTQUERYVIRTUALMEMORY:
+      {
+        void *res;
+        NTSTATUS ret;
+        SIZE_T res_len;
+
+        if (!(res = RtlAllocateHeap( 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.status) break;
+        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 %x\n", (unsigned) ret);
+        }
+        if (ro.query.buffer)
+        {
+            if ((ret = NtWriteVirtualMemory( hProcess, ro.query.buffer,
+                                             res, ro.query.len, NULL )))
+                ERR("Cannot write back thread id, error %x\n", (unsigned) ret);
+        }
+        RtlFreeHeap( NtCurrentTeb()->Peb->ProcessHeap, 0, res );
+        break;
+      }
+    }
+
+    /* FIXME optimize write back, perhaps req/ret args? */
+    NtWriteVirtualMemory( hProcess, args, &ro, sizeof(ro), NULL );
+
+    NtSetEvent( hEvent, NULL );
+    NtClose( hEvent );
+    NtClose( hProcess );
+}
diff --git a/dlls/ntdll/server.c b/dlls/ntdll/server.c
index 702980f..09c8d31 100644
--- a/dlls/ntdll/server.c
+++ b/dlls/ntdll/server.c
@@ -900,6 +900,7 @@ size_t server_init_thread( int unix_pid,
     int reply_pipe[2];
     struct sigaction sig_act;
     size_t info_size;
+    sigset_t sigset;
 
     sig_act.sa_handler = SIG_IGN;
     sig_act.sa_flags   = 0;
@@ -932,6 +933,9 @@ #endif
         req->unix_tid    = unix_tid;
         req->teb         = NtCurrentTeb();
         req->peb         = NtCurrentTeb()->Peb;
+        /* necessary as ntdll and kernel32 are not always in the same place in
+         * memory for all processes */
+        req->remote_op_func = __wine_RemoteProcessOperation;
         req->entry       = entry_point;
         req->ldt_copy    = &wine_ldt_copy;
         req->reply_fd    = reply_pipe[1];
@@ -946,6 +950,14 @@ #endif
     }
     SERVER_END_REQ;
 
+    /* Unconditionally unblock SIGINT, SIGUSR1 and SIGUSR2. If this thread was
+     * created from within a signal handler, then they would be masked. */
+    sigemptyset( &sigset );
+    sigaddset( &sigset, SIGINT );
+    sigaddset( &sigset, SIGUSR1 );
+    sigaddset( &sigset, SIGUSR2 );
+    pthread_functions.sigprocmask( SIG_UNBLOCK, &sigset, NULL );
+
     if (ret) server_protocol_error( "init_thread failed with status %x\n", ret );
     if (version != SERVER_PROTOCOL_VERSION)
         server_protocol_error( "version mismatch %d/%d.\n"
diff --git a/dlls/ntdll/thread.c b/dlls/ntdll/thread.c
index f732573..27986bd 100644
--- a/dlls/ntdll/thread.c
+++ b/dlls/ntdll/thread.c
@@ -395,6 +395,36 @@ static void start_thread( struct wine_pt
     func( arg );
 }
 
+/***********************************************************************
+ *              NTDLL_remote_call
+ */
+NTSTATUS NTDLL_remote_call( HANDLE process, struct RemoteOp *ro )
+{
+    NTSTATUS ret;
+    HANDLE done_event;
+
+    if ((ret = NtCreateEvent(&done_event, EVENT_ALL_ACCESS, NULL, FALSE,
+                             FALSE)))
+        return ret;
+
+    SERVER_START_REQ( remote_op )
+    {
+        req->handle = process;
+        req->op = ro->op;
+        req->event = done_event;
+        req->args = ro;
+
+        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.@)
@@ -419,8 +449,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 = REMOTE_OP_RTLCREATEUSERTHREAD;
+        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 8c6de4a..4a07247 100644
--- a/dlls/ntdll/virtual.c
+++ b/dlls/ntdll/virtual.c
@@ -1322,8 +1322,25 @@ NTSTATUS WINAPI NtAllocateVirtualMemory(
 
     if (!is_current_process( process ))
     {
-        ERR("Unsupported on other process\n");
-        return STATUS_ACCESS_DENIED;
+        struct RemoteOp ro;
+
+        ro.op = REMOTE_OP_NTALLOCATEVIRTUALMEMORY;
+        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 */
@@ -1422,8 +1439,23 @@ NTSTATUS WINAPI NtFreeVirtualMemory( HAN
 
     if (!is_current_process( process ))
     {
-        ERR("Unsupported on other process\n");
-        return STATUS_ACCESS_DENIED;
+        struct RemoteOp ro;
+
+        ro.op = REMOTE_OP_NTFREEVIRTUALMEMORY;
+        ro.free.addr = *addr_ptr;
+        ro.free.size = *size_ptr;
+        ro.free.type = type;
+
+        if ((status = NTDLL_remote_call( process, &ro )))
+            return status;
+
+        TRACE("NtFreeVirtualMemory addr=%x, status=%x\n", ro.free.addr,
+              (unsigned) status);
+
+        *addr_ptr = ro.free.addr;
+        *size_ptr = ro.free.size;
+
+        return ro.status;
     }
 
     /* Fix the parameters */
@@ -1586,8 +1618,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 = REMOTE_OP_NTQUERYVIRTUALMEMORY;
+        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 193e02a..08e746a 100644
--- a/include/winternl.h
+++ b/include/winternl.h
@@ -2396,6 +2396,64 @@ static inline PLIST_ENTRY RemoveTailList
     return e;
 }
 
+/***********************************************************************
+ * Remote operations support
+ */
+struct RemoteOp {
+    int op; /* in */
+    NTSTATUS status; /* out */
+
+    union {
+        struct {
+            /* in */
+            ULONG zero_bits;
+            ULONG type;
+            ULONG protect;
+            /* in/out */
+            PVOID ret;
+            SIZE_T size;
+        } alloc; /* REMOTE_OP_NTALLOCATEVIRUALMEMORY */
+        struct {
+            /* in */
+            ULONG type;
+            /* in/out */
+            PVOID addr;
+            SIZE_T size;
+        } free; /* REMOTE_OP_NTFREEVIRTUALMEMORY */
+        struct {
+            /* in */
+            LPVOID addr;
+            MEMORY_INFORMATION_CLASS info_class;
+            PVOID buffer;
+            SIZE_T len;
+            /* out */
+            SIZE_T *res_len;
+        } query; /* REMOTE_OP_NTQUERYVIRTUALMEMORY */
+        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; /* REMOTE_OP_RTLCREATEUSERTHREAD */
+        struct {
+            /* in */
+            SIZE_T stack;
+            DWORD (CALLBACK *start) (LPVOID);
+            LPVOID param;
+            DWORD flags;
+            /* out */
+            DWORD id;
+            HANDLE handle;
+        } kernel32thread; /* REMOTE_OP_CREATEUSERTHREAD */
+    };
+};
+
 #ifdef __cplusplus
 } /* extern "C" */
 #endif /* defined(__cplusplus) */
diff --git a/server/process.c b/server/process.c
index 75ed6f9..9f3e88a 100644
--- a/server/process.c
+++ b/server/process.c
@@ -1112,3 +1112,85 @@ DECL_HANDLER(get_process_idle_event)
         release_object( process );
     }
 }
+
+/* Signal a remote operation */
+DECL_HANDLER(remote_op)
+{
+    int access, waiting_thread;
+    struct process *src = NULL, *dst = NULL;
+    struct thread *dst_thread, *thread;
+    obj_handle_t dupevent = NULL, dupprocess = NULL;
+
+    switch ( req->op )
+    {
+        case REMOTE_OP_CREATEREMOTETHREAD:
+        case REMOTE_OP_RTLCREATEUSERTHREAD:
+            access = PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION
+                     | PROCESS_VM_OPERATION | PROCESS_VM_READ
+                     | PROCESS_VM_WRITE;
+            break;
+        case REMOTE_OP_NTALLOCATEVIRTUALMEMORY:
+        case REMOTE_OP_NTFREEVIRTUALMEMORY:
+            access = PROCESS_VM_OPERATION;
+            break;
+        case REMOTE_OP_NTQUERYVIRTUALMEMORY:
+            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;
+    dst_thread = get_process_first_thread( dst );
+    if (!is_process_init_done( dst ) || !dst_thread)
+    {
+        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( dst_thread, NULL, dst->remote_op_func, APC_KERNEL,
+                           APC_PROCESS_QUEUE_SYSTEM,
+                           dupprocess, req->args, dupevent ))
+        goto cleanup;
+
+    /* force an unlucky thread to do a wait if no other thread is waiting,
+     * otherwise, we have no guarantee a wait will ever happen. */
+    waiting_thread = 0;
+    dst_thread = NULL;
+    LIST_FOR_EACH_ENTRY( thread, &dst->thread_list, struct thread, proc_entry )
+    {
+        if (thread->suspend)
+        {
+            waiting_thread = 1;
+            break;
+        }
+        else if (!dst_thread && thread->unix_pid != -1)
+            dst_thread = thread;
+    }
+    if (!waiting_thread)
+        send_thread_signal( dst_thread, SIGUSR1 );
+
+cleanup:
+    if (src)
+        release_object( src );
+    if (dst)
+        release_object( dst );
+}
diff --git a/server/process.h b/server/process.h
index 02fb95e..b04cb48 100644
--- a/server/process.h
+++ b/server/process.h
@@ -81,6 +81,7 @@ struct process
     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 */
+    void                *remote_op_func;  /* remote op handler address in client */
 };
 
 struct process_snapshot
diff --git a/server/protocol.def b/server/protocol.def
index 3166696..8825824 100644
--- a/server/protocol.def
+++ b/server/protocol.def
@@ -277,6 +277,7 @@ struct token_groups
     void*        peb;          /* address of PEB (in thread address space) */
     void*        entry;        /* thread entry point (in thread address space) */
     void*        ldt_copy;     /* address of LDT copy (in thread address space) */
+    void*        remote_op_func; /* remote op handler address in client */
     int          reply_fd;     /* fd for reply pipe */
     int          wait_fd;      /* fd for blocking calls pipe */
     int          debug_level;  /* new debug level */
@@ -2630,3 +2631,21 @@ #define MAILSLOT_SET_READ_TIMEOUT  1
 @REPLY
     VARARG(target_name,unicode_str); /* target name */
 @END
+
+
+/* Signal a remote operation */
+ at 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 */
+ at REPLY
+ at END
+enum remote_op
+{
+    REMOTE_OP_RTLCREATEUSERTHREAD,
+    REMOTE_OP_NTALLOCATEVIRTUALMEMORY,
+    REMOTE_OP_NTFREEVIRTUALMEMORY,
+    REMOTE_OP_NTQUERYVIRTUALMEMORY,
+    REMOTE_OP_CREATEREMOTETHREAD /* in kernel32 */
+};
diff --git a/server/thread.c b/server/thread.c
index 6d41c65..76151a3 100644
--- a/server/thread.c
+++ b/server/thread.c
@@ -648,9 +648,16 @@ int thread_queue_apc( struct thread *thr
 
     /* cancel a possible previous APC with the same owner */
     if (owner) thread_cancel_apc( thread, owner, apc_queue );
-    if (thread->state == TERMINATED) return 0;
-
-    if (!(apc = mem_alloc( sizeof(*apc) ))) return 0;
+    if (thread->state == TERMINATED)
+    {
+        set_error( STATUS_ACCESS_DENIED );
+        return 0;
+    }
+    if (!(apc = mem_alloc( sizeof(*apc) )))
+    {
+        set_error( STATUS_NO_MEMORY );
+        return 0;
+    }
     apc->owner  = owner;
     apc->func   = func;
     apc->type   = type;
@@ -901,6 +908,7 @@ DECL_HANDLER(init_thread)
         process->unix_pid = current->unix_pid;
         process->peb      = req->peb;
         process->ldt_copy = req->ldt_copy;
+        process->remote_op_func = req->remote_op_func;
         reply->info_size  = init_process( current );
     }
     else



More information about the wine-patches mailing list