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

Reply via email to