3

I'm reading Windows Internals (7th Edition), and they write about processes in Chapter 1:

Processes

[...] a Windows process comprises the following:

  • [...]
  • At least one thread of execution Although an "empty" process is possible, it is (mostly) not useful.

What does "mostly" mean in this context? What could a process with no threads do, and how would that be useful?

EDIT: Also, in a 2015 talk, Mark Russinovich says that a process has "at least one thread" (19:12). Was that a generalization?

Disclaimer: I work for Microsoft.

citelao
  • 4,898
  • 2
  • 22
  • 36
  • 2
    A process with no threads still has a virtual address space and a handle table, so you might create one as a place to hide memory and handles. Very much a [niche scenario](https://devblogs.microsoft.com/oldnewthing/20200306-00/?p=103538). – Raymond Chen May 04 '20 at 20:39
  • 3
    for example `PssCaptureSnapshot` with `PSS_CAPTURE_VA_CLONE` create empty process (by using `ZwCreateProcessEx`) – RbMm May 04 '20 at 21:04
  • Re, "What could a process with no threads do...?" It probably could not _do_ anything. But it could _be_ what @RaymondChen said. – Ohm's Lawman May 04 '20 at 21:10
  • @RbMm and this process snapshot would be used by another application for, say, inspecting the snapshot's memory & handles? Like a "snapshot in time Process Explorer"? – citelao May 05 '20 at 19:56
  • 1
    @citelao, a process snapshot uses a clone (threadless fork) of the process if the VM is captured. IIRC, a clone isn't created for a snapshot of just the handle table. The executive already provides a query to capture the handle table. – Eryk Sun May 05 '20 at 22:32

1 Answers1

1

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 Create­Process with bInheritHandles = true, it will inadvertently inherit your handles.

You can use a threadless process to mitigate this. In general:

  1. Create a threadless process.
  2. DuplicateHandle all of the handles you want to capture into this new threadless process.
  3. 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.

citelao
  • 4,898
  • 2
  • 22
  • 36