unit md_systm_fork; {$mode ObjFPC}{$H+} {$CALLING SysV_ABI_CDecl} interface uses sysutils, windows, ntapi; type t_fork_cb=procedure(data:Pointer;size:QWORD); SysV_ABI_CDecl; p_fork_proc=^t_fork_proc; t_fork_proc=record hInput :THandle; //in hOutput:THandle; //in hError :THandle; //in proc:Pointer; //in data:Pointer; //in size:QWORD; //in hProcess:THandle; //out fork_pid:Integer; //out end; function md_getppid:DWORD; procedure md_run_forked; procedure md_fork_unshare; function md_fork_process(var info:t_fork_proc):Integer; implementation uses vmparam, kern_thr, md_systm, md_systm_reserve, md_map; var ppid:DWORD=0; function md_getppid:DWORD; begin Result:=ppid; end; const JobObjectExtendedLimitInformation=9; JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE=$00002000; type JOBOBJECT_BASIC_LIMIT_INFORMATION = record PerProcessUserTimeLimit: LARGE_INTEGER; PerJobUserTimeLimit: LARGE_INTEGER; LimitFlags: DWORD; MinimumWorkingSetSize: SIZE_T; MaximumWorkingSetSize: SIZE_T; ActiveProcessLimit: DWORD; Affinity: ULONG_PTR; PriorityClass: DWORD; SchedulingClass: DWORD; end; PJOBOBJECT_EXTENDED_LIMIT_INFORMATION = ^JOBOBJECT_EXTENDED_LIMIT_INFORMATION; JOBOBJECT_EXTENDED_LIMIT_INFORMATION = record BasicLimitInformation: JOBOBJECT_BASIC_LIMIT_INFORMATION; IoInfo: IO_COUNTERS; ProcessMemoryLimit: SIZE_T; JobMemoryLimit: SIZE_T; PeakProcessMemoryUsed: SIZE_T; PeakJobMemoryUsed: SIZE_T; end; function CreateJobObjectA(lpJobAttributes:LPSECURITY_ATTRIBUTES; lpName:LPCTSTR):THandle; stdcall; external kernel32; function SetInformationJobObject(hJob:HANDLE; JobObjectInformationClass:DWORD; lpJobObjectInformation:LPVOID; cbJobObjectInformationLength:DWORD):BOOL; stdcall; external kernel32; function AssignProcessToJobObject(hJob,hProcess:THandle):BOOL; stdcall; external kernel32; function NtQueryTeb(td_handle:THandle;var teb:p_teb):Integer; var data:array[0..SizeOf(THREAD_BASIC_INFORMATION)-1+7] of Byte; P_TBI:PTHREAD_BASIC_INFORMATION; begin Result:=0; teb:=nil; P_TBI:=Align(@data,8); P_TBI^:=Default(THREAD_BASIC_INFORMATION); Result:=NtQueryInformationThread( td_handle, ThreadBasicInformation, P_TBI, SizeOf(THREAD_BASIC_INFORMATION), nil); if (Result<>0) then Exit; teb:=P_TBI^.TebBaseAddress; end; procedure NtGetVirtualInfo(hProcess:THandle;var base:Pointer;var size:QWORD); var addr:Pointer; prev:Pointer; info:TMemoryBasicInformation; len:ULONG_PTR; begin size:=0; len:=0; NtQueryVirtualMemory( hProcess, base, 0, @info, sizeof(info), @len); if (len=0) then Exit; //allocated? if (info.State=MEM_FREE) then Exit; addr:=info.AllocationBase; base:=addr; repeat len:=0; NtQueryVirtualMemory( hProcess, addr, 0, @info, sizeof(info), @len); if (len=0) then Exit; if (base<>info.AllocationBase) then Break; size:=size+Info.RegionSize; prev:=addr; addr:=addr+Info.RegionSize; until (prev>=addr); end; function NtMoveStack(hProcess,hThread:THandle;var rip:QWORD):Integer; var _Context:array[0..SizeOf(TCONTEXT)+15] of Byte; Context :PCONTEXT; teb :p_teb; kstack:t_td_stack; addr :Pointer; delta :QWORD; size :QWORD; err :DWORD; begin Result:=0; Context:=Align(@_Context,16); Context^:=Default(TCONTEXT); Context^.ContextFlags:=CONTEXT_ALL; err:=NtGetContextThread(hThread,Context); if (err<>0) then Exit(err); rip:=Context^.Rip; //RIP -> RtlUserThreadStart //RCX -> entry (_WinMainCRTStartup) //RDX -> lpThreadParameter err:=NtQueryTeb(hThread,teb); if (err<>0) then Exit(err); kstack:=Default(t_td_stack); err:=md_copyin(@teb^.stack,@kstack,SizeOf(t_td_stack),nil,hProcess); if (err<>0) then Exit(err); delta:=QWORD(kstack.stack)-Context^.Rsp; addr:=kstack.sttop; size:=0; NtGetVirtualInfo(hProcess,addr,size); err:=md_unmap(addr,size,hProcess); if (err<>0) then Exit(err); addr:=Pointer(WIN_MAX_MOVED_STACK-size); err:=md_mmap(addr,size,VM_RW or MD_MAP_FIXED,0,0,hProcess); if (err<>0) then Exit(err); kstack.sttop:=addr; kstack.stack:=addr+size; err:=md_copyout(@kstack,@teb^.stack,SizeOf(t_td_stack),nil,hProcess); if (err<>0) then Exit(err); Context^.Rsp:=QWORD(kstack.stack)-delta; err:=NtSetContextThread(hThread,Context); Exit(err); end; function NtReserve(hProcess:THandle;rip:QWORD):Integer; var addr,prev:Pointer; info:TMemoryBasicInformation; len:ULONG_PTR; m:t_md_map_reserve_result; begin Result:=0; m:=md_map_reserve(hProcess,rip); if (m.error<>0) then begin Writeln(stderr,'md_placeholder_mmap(0x',HexStr(m.base),',0x',HexStr(m.size,16),'):0x',HexStr(m.error,8)); Exit(m.error); end; //fill corners addr:=Pointer($10000 {guest_pmap_mem[0].start}); repeat len:=0; NtQueryVirtualMemory( hProcess, addr, 0, @info, sizeof(info), @len); if (len=0) then Break; if (info.State=MEM_FREE) then begin md_placeholder_mmap(info.BaseAddress,info.RegionSize,MD_MAP_FIXED,hProcess); end; prev:=addr; addr:=addr+Info.RegionSize; if (addr>=Pointer(VM_MAXUSER_ADDRESS)) then Break; until (prev>=addr); end; type p_shared_info=^t_shared_info; t_shared_info=record ppid :QWORD; hStdInput :THandle; hStdOutput:THandle; hStdError :THandle; proc :Pointer; size :QWORD; data :record end; end; procedure md_run_forked; var base:p_shared_info; info:TMemoryBasicInformation; len:ULONG_PTR; proc:Pointer; begin base:=Pointer(WIN_SHARED_ADDR); len:=0; NtQueryVirtualMemory( NtCurrentProcess, base, 0, @info, sizeof(info), @len); if (len=0) then Exit; if (info.State=MEM_FREE) then Exit; ppid:=base^.ppid; SetStdHandle(STD_INPUT_HANDLE ,base^.hStdInput ); SetStdHandle(STD_ERROR_HANDLE ,base^.hStdOutput); SetStdHandle(STD_OUTPUT_HANDLE,base^.hStdError ); proc:=base^.proc; if (proc=nil) then Exit; t_fork_cb(proc)(@base^.data,base^.size); NtTerminateProcess(NtCurrentProcess, 0); end; procedure md_fork_unshare; var base:Pointer; begin base:=Pointer(WIN_SHARED_ADDR); md_unmap(base,0); end; var hProcJob:Thandle=0; function NtFetchJob:THandle; var info:JOBOBJECT_EXTENDED_LIMIT_INFORMATION; begin if (hProcJob<>0) then Exit(hProcJob); hProcJob:=CreateJobObjectA(nil,nil); info:=Default(JOBOBJECT_EXTENDED_LIMIT_INFORMATION); info.BasicLimitInformation.LimitFlags:=JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; SetInformationJobObject(hProcJob, JobObjectExtendedLimitInformation, @info, SizeOf(info)); Exit(hProcJob); end; function NtCreateShared(hProcess:THandle;var info:t_fork_proc):Integer; var base:p_shared_info; full:QWORD; shared_info:t_shared_info; begin base:=Pointer(WIN_SHARED_ADDR); full:=SizeOf(shared_info)+info.size; full:=(info.size+(MD_PAGE_SIZE-1)) and (not (MD_PAGE_SIZE-1)); Result:=md_mmap(base,full,VM_RW or MD_MAP_FIXED,0,0,hProcess); if (Result<>0) then Exit; shared_info:=Default(t_shared_info); shared_info.ppid :=GetCurrentProcessId; shared_info.hStdInput :=md_dup_to_pidfd(hProcess,info.hInput ); shared_info.hStdOutput:=md_dup_to_pidfd(hProcess,info.hOutput); shared_info.hStdError :=md_dup_to_pidfd(hProcess,info.hError ); shared_info.proc:=info.proc; shared_info.size:=info.size; Result:=md_copyout(@shared_info,base,SizeOf(shared_info),nil,hProcess); if (Result<>0) then Exit; if (info.data<>nil) and (info.size<>0) then begin Result:=md_copyout(info.data,@base^.data,info.size,nil,hProcess); end; end; function md_fork_process(var info:t_fork_proc):Integer; type PBUF_PROC_INFO=^TBUF_PROC_INFO; TBUF_PROC_INFO=packed record UNAME:UNICODE_STRING; DATA :array[0..MAX_PATH*2] of WideChar; end; var si:TSTARTUPINFO; pi:PROCESS_INFORMATION; data:array[0..SizeOf(TBUF_PROC_INFO)-1+7] of Byte; P_BUF:PBUF_PROC_INFO; LEN:ULONG; rip:QWORD; b:BOOL; begin Result:=0; P_BUF:=Align(@data,8); P_BUF^:=Default(TBUF_PROC_INFO); LEN:=SizeOf(TBUF_PROC_INFO); Result:=NtQueryInformationProcess(NtCurrentProcess, ProcessImageFileNameWin32, P_BUF, LEN, @LEN); if (Result<>0) then Exit; si:=Default(TSTARTUPINFO); pi:=Default(PROCESS_INFORMATION); si.cb:=SizeOf(si); b:=CreateProcessW(PWideChar(@P_BUF^.DATA),nil,nil,nil,False,CREATE_SUSPENDED,nil,nil,@si,@pi); if not b then Exit(-1); b:=AssignProcessToJobObject(NtFetchJob, pi.hProcess); if not b then Exit(-1); rip:=0; Result:=NtMoveStack(pi.hProcess,pi.hThread,rip); if (Result<>0) then begin Writeln(stderr,'NtMoveStack:0x',HexStr(Result,8)); Exit; end; Result:=NtReserve(pi.hProcess,rip); if (Result<>0) then begin Writeln(stderr,'NtReserve:0x',HexStr(Result,8)); Exit; end; Result:=NtCreateShared(pi.hProcess,info); if (Result<>0) then begin Writeln(stderr,'NtCreateShared:0x',HexStr(Result,8)); Exit; end; Result:=NtResumeProcess(pi.hProcess); if (Result<>0) then begin Writeln(stderr,'NtResumeProcess:0x',HexStr(Result,8)); Exit; end; NtClose(pi.hThread); info.hProcess:=pi.hProcess; info.fork_pid:=pi.dwProcessId; end; end.