Job Objects and Server Silos

Windows Internals
Time it takes to read this article 5 minutes.

Most of this series has treated the process as the fundamental unit of resource ownership and the thread as the unit of execution. But Windows also needs a way to manage a group of processes as one — to apply a memory limit to a whole tree of workers, to guarantee that closing a parent tears down every child, or to give a set of processes an isolated view of the system. That primitive is the job object, and its more powerful descendant, the silo, is the foundation on which Windows containers are built. This article looks at both.

Job Objects

A job is a kernel object — Object Manager type Job, backed by the executive nt!_EJOB structure — that represents a collection of processes to be managed together. A process is placed into a job with AssignProcessToJobObject after the job is created with CreateJobObject (which can optionally be named, so unrelated processes can open the same job). Once assigned, a process cannot leave its job, and any child it creates is (by default) automatically part of the same job. Modern Windows also supports nested jobs, so a process can belong to a hierarchy of jobs whose limits combine.

The kernel records a process’s job membership through its EPROCESS: the executive process block points at the EJOB it belongs to, which is how the kernel knows, on every relevant operation, which job-wide accounting and limits to apply. Because the association lives in the process object itself, it travels with the process for its entire lifetime.

Limits and Controls

The reason to put processes in a job is control. A job can impose a wide range of limits, configured through SetInformationJobObject with structures such as JOBOBJECT_BASIC_LIMIT_INFORMATION and its extended form. The most useful limits include:

  • Memory limits — a per-process commit cap and a job-wide commit cap, so a runaway worker (or the group as a whole) cannot exhaust memory.
  • CPU rate control — cap the group at a percentage of CPU or a hard cycle budget per interval, useful for throttling background or untrusted work.
  • Active process count — limit how many processes the job may contain at once, bounding fork-bomb-style behavior.
  • Priority and affinity — force every process in the job to a scheduling priority class or a subset of processors.
  • UI restrictions — deny the job’s processes access to the clipboard, global atoms, the desktop, or the ability to change display settings — a lightweight sandbox for GUI-less workers.

One flag deserves special mention: JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. When set, closing the last handle to the job terminates every process still inside it. This is the clean solution to the classic “orphaned child process” problem — a launcher creates a job, assigns its children, and holds the handle; if the launcher dies for any reason, the kernel guarantees the whole tree is reaped rather than leaking runaway processes.

Job Notifications

A controller often needs to know what its job is doing. By associating a job with an I/O completion port (via JOBOBJECT_ASSOCIATE_COMPLETION_PORT), the kernel delivers asynchronous messages as job events occur: a new process was added, a process exited, the active-process limit was reached, a memory limit was exceeded, or the last process left the job. The controller drains these with GetQueuedCompletionStatus, giving it an event-driven view of the group without polling. This is exactly how process-management frameworks and container runtimes keep tabs on the workloads they supervise.

Silos: Application and Server Silos

A silo is a job that has been elevated from a mere management container into an isolation boundary. Where an ordinary job constrains resources, a silo changes what its processes can see. Windows defines two kinds:

  • Application silos — a lighter form used to give an application a partially redirected view (the Desktop Bridge / packaged-app machinery builds on this idea), without spinning up a full isolated environment.
  • Server silos — the heavyweight form that provides a nearly complete, isolated instance of the user-mode environment. A server silo gets its own view of the Object Manager namespace, its own registry, its own session and logon state, and its own device and network configuration.

The server silo is the crucial one, because it is what a Windows Server Container actually is under the hood. Inside a server silo, a process that opens \BaseNamedObjects or reads HKLM is transparently redirected to the silo’s private copy — so two containers on the same host can each have their own “global” named objects and registry hives without colliding, and without seeing the host’s. The silo effectively virtualizes the namespaces this series has already examined (the object namespace, the registry, sessions) on a per-container basis.

Containers: Process Isolation vs Hyper-V Isolation

Windows containers come in two isolation modes, and the difference is exactly where the security boundary sits:

  • Process isolation — the container is a server silo running directly on the host kernel. Containers share the host kernel and are separated only by the silo’s namespace virtualization. This is efficient (no VM overhead) but means the host kernel is a shared trust boundary.
  • Hyper-V isolation — each container runs inside its own lightweight utility VM with its own kernel, and the silo lives in that guest. The isolation is enforced by the hypervisor rather than only by the silo, which is the same virtualization-based security foundation discussed in the kernel-protections article. This trades some overhead for a far stronger boundary.

The practical takeaway is that a process-isolated container’s security ultimately rests on the correctness of the host kernel and the completeness of silo namespace separation, whereas a Hyper-V-isolated container adds a hardware-enforced wall around it.

Inspecting Jobs and Silos

Both jobs and silos are directly observable. In a kernel debugger you can read the job structure and enumerate membership; on a live system, Sysinternals tools surface job assignment:

# WinDbg (kernel mode): dump the EJOB structure layout
dt nt!_EJOB

# Inspect a specific job: its processes, limits, and (if a silo) isolation state
!job <job-address>

# Find a process, then follow its job pointer
!process 0 0 dockerd.exe
dt nt!_EPROCESS <eprocess-address> Job

# A server silo is a job with silo fields populated; !job shows the silo context

!job lists the member processes and every configured limit, and for a silo it reports the silo context that anchors the isolated namespaces. On a running system, Process Explorer shades processes that belong to a job and shows the job’s limits in the process properties, which is the quickest way to confirm that a sandbox or container actually placed a process where you expected.

Security Relevance

Jobs and silos are first and foremost defensive primitives. A job is a cheap sandbox: a launcher can drop an untrusted or crash-prone process into a job with tight memory, CPU, process-count, and UI limits, plus KILL_ON_JOB_CLOSE, and be confident the process cannot exhaust the machine or outlive its supervisor. AppContainer and silo isolation build on the same lineage to contain browser renderer processes and packaged apps, each confined to its own namespaces.

Because silos are the container boundary, they are also a research target: a container escape on a process-isolated container means breaking out of the silo’s namespace virtualization to reach the host — which is why security-sensitive workloads prefer Hyper-V isolation. Defenders should also understand nested-job semantics: when sandboxing, be sure a process cannot escape a restrictive job simply by relying on limit-combining rules, and remember that assigning a process to a new job does not loosen limits already imposed by an enclosing one. Used correctly, these are exactly the mechanisms that keep risky code boxed in.

Conclusion

Job objects turn a loose collection of processes into a single manageable, limitable unit, and silos extend that idea into full namespace isolation — the machinery behind Windows Server Containers, in both process-isolated and Hyper-V-isolated forms. Together they show how Windows reuses one primitive, the job, to serve both resource governance and security isolation. In the next article we return to a single process’s own scaffolding for a closer look at the TEB and PEB, the user-mode structures that every thread and process depends on.

You Might Also Like

This article is part of the Windows Internals series. These related deep-dives cover adjacent parts of the system:

Comments

Copied title and URL