3.3.2 PSB Fields
3.3.2.1 Process identity (fixed at fork) #
| Field | Type | Description |
|---|---|---|
process_guid | UUID | 128-bit identifier for this process instance, generated by the kernel at fork and immutable for the process's lifetime. It is not copied from the parent — every process receives a new one. Used by KMES and kernel-internal consumers for identity stamping and event correlation. |
The process GUID is distinct from the PID. PIDs are recycled; process GUIDs are unique within a boot, and globally unique in practice. It is the stable correlation key that lets KMES attribute events to a process across its whole lifetime.
3.3.2.2 Protection (set at exec, fixed) #
| Field | Type | Description |
|---|---|---|
pip_type | u32 | Process Integrity Protection type. Determined by the binary's cryptographic signature at exec. |
pip_trust | uint | Trust tier within a PIP type; higher values can reach lower ones. Determined by the signer's identity. |
PIP fields are signing-based. At exec the kernel verifies the binary's signature and derives both fields from the signer, using the algorithm and key model of §3.6.
Both are plain unsigned integers rather than enumerations, and are
compared numerically. Three type values are conventional — None (0),
Protected (512) and Isolated (1024) — but only two are producible: the
key table validator accepts a key only at exactly Protected with
PeiosTcb trust (8192) and rejects the whole table otherwise, so
Isolated is unreachable and a signed binary is always
Protected/8192. Neither None nor Isolated is defined as a named
constant in the public ABI at all. The parent process cannot influence the
determination at all — even a compromised peinit running as SYSTEM
cannot forge PIP protection for an unsigned binary. The public
verification key is compiled into the kernel image, and the kernel
only ever verifies; it never signs.
This is a deliberate departure from MS-DTYP, where the parent sets a protection level at process creation and the kernel validates the binary's signature against it. Peios removes the parent-controlled half entirely: one input, one answer.
3.3.2.3 Process mitigations (one-way) #
| Field | Description |
|---|---|
lsv | Library Signature Verification. Only signed shared libraries load. When the process has pip_type != None the library's trust level has to be at or above the process's PIP trust; when pip_type = None any valid signature suffices and trust is not compared. |
wxp | Write-XOR-Execute Protection. No page is simultaneously writable and executable; W+X mappings and transitions between writable and executable are rejected. |
tlp | Trusted Library Paths. Shared libraries load only from approved directory prefixes. Weaker than LSV, since it trusts the path rather than the binary. |
cfif | Forward-Edge Control Flow Integrity. Hardware indirect-branch tracking — Intel IBT, ARM BTI — is locked on and cannot be disabled by the process. Not settable: see below. |
cfib | Backward-Edge Control Flow Integrity. The hardware shadow stack, Intel CET, is locked on and cannot be disabled by the process. |
pie | Position-Independent Executable Requirement. Non-PIE binaries are rejected at exec, so that ASLR is actually effective. |
sml | Speculation Mitigation Lock. Speculation mitigations are locked on and cannot be disabled by the process. |
Mitigations are inherited from the parent at fork and can be set by syscall, typically by peinit between fork and exec. They are one-way: once set they are never cleared, and exec does not reset them — a mitigation set by the launcher persists regardless of which binary is loaded.
Setting a bit is activation-backed. Before a mitigation bit moves from clear to set, KACS either activates the underlying protection for the target process or verifies that the process already satisfies the invariant. If any requested mitigation cannot be activated or verified, the whole operation fails closed without mutating any bit from that request. Once committed, later operations that would disable the protection or make the process violate the invariant are rejected, and re-requesting an already-set mitigation never weakens what is already committed.
For the runtime memory mitigations, activation covers existing state
as well as future transitions. Enabling wxp fails if the process
already has a mapping that is simultaneously writable and executable,
or otherwise already violates the invariant in a way KACS can observe.
Enabling tlp fails if the process already has a file-backed
executable mapping whose kernel-resolved path is missing,
unresolvable, outside the approved prefix cache, or otherwise
TLP-denied. Enabling lsv fails if the process already has a
file-backed executable mapping whose signing material is missing,
invalid, or below the required PIP trust. Anonymous executable
mappings are governed by wxp alone; tlp and lsv apply only to
file-backed ones.
For the architecture-backed mitigations, activation goes through the
architecture's kernel interface to place the process in the protected
state and prevent later process-controlled disablement. Enabling
cfif, cfib, or sml fails closed when the platform cannot make
the protection true for the target.
sml also accepts a second route: a platform that reports speculation
as unconditionally not-affected satisfies activation by that fact
alone, with nothing to enable.
cfif cannot currently be committed at all. Activation against a live
task returns ENODEV unconditionally, because the kernel exposes no
userspace control surface for IBT or BTI, so the bit fails closed on
every request. cfib works, through shadow-stack enable-and-lock,
with one restriction: enabling it on a task other than the caller
fails.
Two mitigations are event-gated rather than retroactive: pie is
enforced at subsequent exec and no_child_process at subsequent
process creation. They still follow the one-way commit rule, and have
to be set before the event they are meant to constrain.
All of this is distinct from PIP. Mitigations are policy set by the launcher; PIP is a property of the binary determined by the kernel.
The mitigations compose deliberately: LSV ensures only signed libraries load, WXP blocks code injection, CFIF blocks forward-edge code reuse through indirect calls and jumps, CFIB blocks return-oriented programming, and PIE makes ASLR effective. Together they make exploitation dramatically harder than any one of them alone.
3.3.2.4 UI access (one-way) #
| Field | Description |
|---|---|
ui_access | Permits interaction with higher-integrity UI elements. Reserved for future desktop functionality. Set by syscall, typically by peinit between fork and exec, and fixed thereafter. |
3.3.2.5 Process restrictions (one-way) #
| Field | Description |
|---|---|
no_child_process | Once set, the process creates no child processes — fork, or clone without CLONE_THREAD. New threads are unaffected, and the flag is never cleared. |
Unlike the exec-time fields, this one can be set at two points. The parent's code, running in the freshly forked child, can set it before exec, so the new binary loads with the restriction already in place. Or a process can restrict itself at any time during its life — after it has finished spawning its own workers, for instance.
3.3.2.6 The TLP cache #
The approved directory prefixes for TLP live in a global kernel cache
rather than on individual PSBs: the tlp flag decides whether a
process is subject to enforcement, while the paths themselves are
machine-wide.
The cache is an array of absolute directory prefix byte strings
evaluated against kernel-resolved Linux path bytes, holding at most 64
entries of at most 4096 bytes each. Every prefix begins with / and
ends with / — so that /usr/lib/ does not match /usr/libevil
— and contains no embedded NUL byte. An empty, relative,
NUL-containing, or non-slash-terminated prefix is invalid and is
rejected without mutating the existing cache, which is staged and
swapped under a mutex so a rejected update cannot leave it partly
written.
The cache has no production writer. The only code that populates
it is a test helper compiled in solely under the KUnit configuration:
there is no syscall, no securityfs node, and no registry path that
fills it. In a shipping build the cache is therefore permanently
empty — and since an empty cache matches no path, enabling tlp on a
process denies every file-backed executable mapping it subsequently
attempts.
At mmap(PROT_EXEC) time, a process with TLP enabled has the mapped
file's current kernel-resolved backing path checked against every
approved prefix. If the path cannot be resolved, if no prefix matches,
or if the cache is empty, the mapping is rejected.
3.3.2.7 Identity virtualization (reserved) #
| Field | Description |
|---|---|
virtualization | Per-process state for setuid compatibility redirection. Not active, and implementations may omit the field until it is. |