Why does Cygwin not use MiCloneProcessAddressSpace() for fork()
Lionel Cons
lionelcons1972@gmail.com
Mon Sep 22 12:23:10 GMT 2025
On Mon, 22 Sept 2025 at 13:55, Lionel Cons <lionelcons1972@gmail.com> wrote:
>
> On Mon, 22 Sept 2025 at 13:28, Old, Oliver via Cygwin <cygwin@cygwin.com> 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
-------------- next part --------------
/*
* 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 <nenolod@dereferenced.org>
*
* 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);
}
More information about the Cygwin
mailing list