On Mon, 22 Sept 2025 at 13:55, Lionel Cons <[email protected]> wrote: > > On Mon, 22 Sept 2025 at 13:28, Old, Oliver via Cygwin <[email protected]> > wrote: > > > > Mark Liam Brown wrote: > > > This is exposed to the userspace as NtCreateProcessEx() as documented in > > > https://github.com/huntandhackett/process-cloning > > > > From the linked page: > > > Unfortunately, it doesn't matter which method we prefer because the > > > results > > > will be identically disappointing: STATUS_PROCESS_IS_TERMINATING, or, in > > > other > > > words, "An attempt was made to access an exiting process." The system > > > considers the cloned threadless process as waiting for deletion and, thus, > > > refuses to create threads in it – something we inevitably need to execute > > > code. Sorry, but NtCreateProcessEx-based cloning is incompatible with code > > > execution. > > > > > > > Note that it wasn't always the case. The kernel allowed creating > > > > threads in > > > > such processes until Windows 8.1. > > That was a bug, long fixed. UWIN added pthread support at some point, > so yes, threads and RtlCloneUserProcess() do work together.
Attached is a proof that threads (in child and parent process) and RtlCloneUserProcess() work together: winforktest1.c.txt Lionel
/* * winforktest.c - demo fork() via |RtlCloneUserProcess()| * Original from https://gist.github.com/petrsmid/d96446beac825c8c0cf5a35240f444a8 * * rm -f winforktest1.exe ; clang -target x86_64-pc-windows-gnu -Wall winforktest1.c -o winforktest1.exe */ /* * fork.c * Experimental fork() on Windows. Requires NT 6 subsystem or * newer. * * Improved version with fixed Console * * Copyright (c) 2023 Petr Smid * Copyright (c) 2012 William Pitcock <[email protected]> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * This software is provided 'as is' and without any warranty, express or * implied. In no event shall the authors be liable for any damages arising * from the use of this software. */ #define _WIN32_WINNT 0x0600 #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <winnt.h> #include <winternl.h> #include <stdio.h> #include <errno.h> #include <assert.h> #include <process.h> #include <processthreadsapi.h> #include <Windows.h> /* typedef struct _CLIENT_ID { PVOID UniqueProcess; PVOID UniqueThread; } CLIENT_ID, * PCLIENT_ID; */ typedef struct _SECTION_IMAGE_INFORMATION { PVOID EntryPoint; ULONG StackZeroBits; ULONG StackReserved; ULONG StackCommit; ULONG ImageSubsystem; WORD SubSystemVersionLow; WORD SubSystemVersionHigh; ULONG Unknown1; ULONG ImageCharacteristics; ULONG ImageMachineType; ULONG Unknown2[3]; } SECTION_IMAGE_INFORMATION, * PSECTION_IMAGE_INFORMATION; typedef struct _RTL_USER_PROCESS_INFORMATION { ULONG Size; HANDLE Process; HANDLE Thread; CLIENT_ID ClientId; SECTION_IMAGE_INFORMATION ImageInformation; } RTL_USER_PROCESS_INFORMATION, * PRTL_USER_PROCESS_INFORMATION; #define RTL_CLONE_PROCESS_FLAGS_CREATE_SUSPENDED 0x00000001 #define RTL_CLONE_PROCESS_FLAGS_INHERIT_HANDLES 0x00000002 #define RTL_CLONE_PROCESS_FLAGS_NO_SYNCHRONIZE 0x00000004 #define RTL_CLONE_PARENT 0 #define RTL_CLONE_CHILD 297 typedef DWORD winpid_t; typedef NTSTATUS(*RtlCloneUserProcess_f)(ULONG ProcessFlags, PSECURITY_DESCRIPTOR ProcessSecurityDescriptor /* optional */, PSECURITY_DESCRIPTOR ThreadSecurityDescriptor /* optional */, HANDLE DebugPort /* optional */, PRTL_USER_PROCESS_INFORMATION ProcessInformation); int winfork() { DWORD parent_pid = GetCurrentProcessId(); HMODULE mod; RtlCloneUserProcess_f clone_p; RTL_USER_PROCESS_INFORMATION process_info; NTSTATUS result; mod = GetModuleHandle("ntdll.dll"); if (!mod) return -ENOSYS; clone_p = (RtlCloneUserProcess_f)GetProcAddress(mod, "RtlCloneUserProcess"); if (clone_p == NULL) return -ENOSYS; /* lets do this */ result = clone_p(RTL_CLONE_PROCESS_FLAGS_CREATE_SUSPENDED | RTL_CLONE_PROCESS_FLAGS_INHERIT_HANDLES, NULL, NULL, NULL, &process_info); if (result == RTL_CLONE_PARENT) { // HANDLE me = 0; HANDLE hp = 0, ht = 0; DWORD pi, ti; // me = GetCurrentProcess(); pi = (DWORD)process_info.ClientId.UniqueProcess; ti = (DWORD)process_info.ClientId.UniqueThread; assert(hp = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pi)); assert(ht = OpenThread(THREAD_ALL_ACCESS, FALSE, ti)); ResumeThread(ht); CloseHandle(ht); CloseHandle(hp); return (int)pi; } else if (result == RTL_CLONE_CHILD) { /* fix stdio */ FreeConsole(); AttachConsole(parent_pid); return 0; } else return -1; /* NOTREACHED */ return -1; } void child_thread_one(void *dummy) { (void)dummy; Sleep(200*5); (void)printf("I thread one in the child.\n"); fflush(stdout); } void child_thread_two(void *dummy) { (void)dummy; Sleep(200*4); (void)printf("I thread two in the child.\n"); fflush(stdout); } void parent_thread_one(void *dummy) { (void)dummy; Sleep(200*2); (void)printf("I thread one in the parent.\n"); fflush(stdout); } void parent_thread_two(void *dummy) { (void)dummy; Sleep(200*1); (void)printf("I thread two in the parent.\n"); fflush(stdout); } int main(int argc, const char* argv[]) { winpid_t pid; pid = winfork(); switch (pid) { case 0: //child { (void)printf("I am child.\n"); fflush(stdout); _beginthread(child_thread_one, 0, NULL); _beginthread(child_thread_two, 0, NULL); Sleep(300); break; } default: //parent Sleep(100); (void)printf("I am parent. Child process PID: %ld\n", (long)pid); fflush(stdout); _beginthread(parent_thread_one, 0, NULL); _beginthread(parent_thread_two, 0, NULL); break; } Sleep(1000); exit(0); }
-- Problem reports: https://cygwin.com/problems.html FAQ: https://cygwin.com/faq/ Documentation: https://cygwin.com/docs.html Unsubscribe info: https://cygwin.com/ml/#unsubscribe-simple

