I think the answer has come out in the comments. There seem to be at least two scenarios where a threadless process would be useful.
Scenario 1: capturing process snapshots
This is probably the most straightforward one. As RbMm commented, PssCaptureSnapshot
can be called with the PSS_CAPTURE_VA_CLONE
option to create a threadless (or "empty") process (using ZwCreateProcessEx
, presumably to duplicate the target process's memory in kernel mode).
The primary use here would be for debugging, if a developer wanted to inspect a process's memory at a certain point in time.
Notably, Eryk Sun points out that an empty process is not necessary for inspecting handles (even though an empty process holds both its own memory space and handles), since there is already a way to inspect a process's handles without creating a new process or duplicating memory.
Scenario 2: forking processes with specific inherited handles---safely
Raymond Chen explains another use for a threadless process: creating new "real" processes with inherited handles safely.
When a thread wants to create a new process (CreateProcess
), there are several ways for it to pass handles to the new process:
- Make a handle inheritable and
CreateProcess
with bInheritHandles = true
.
- Make a handle inheritable, add it to a
PROC_THREAD_ATTRIBUTE_LIST
, and pass that list to the CreateProcess
call.
However, they offer conflicting guarantees that can cause problems when callers want to create two threads with different handles concurrently. As Raymond puts it in Why do people take a lock around CreateProcess calls?:
In order for a handle to be inherited, you not only have to put it in the PROC_THREAD_ATTRIBUTE_LIST
, but you also must make the handle inheritable. This means that if another thread is not on board with the PROC_THREAD_ATTRIBUTE_LIST
trick and does a straight CreateProcess
with bInheritHandles = true
, it will inadvertently inherit your handles.
You can use a threadless process to mitigate this. In general:
- Create a threadless process.
DuplicateHandle
all of the handles you want to capture into this new threadless process.
CreateProcess
your new, real forked process, using the PROC_THREAD_ATTRIBUTE_LIST
, but set the nominal parent process of this process to be the threadless process (with PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
).
You can now CreateProcess
concurrently without worrying about other callers, and you can now close the duplicate handles and the empty process.