3.3.2 PSB Fields

3.3.2.1 Process identity (fixed at fork) #

FieldTypeDescription
process_guidUUID128-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) #

FieldTypeDescription
pip_typeu32Process Integrity Protection type. Determined by the binary's cryptographic signature at exec.
pip_trustuintTrust 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) #

FieldDescription
lsvLibrary 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.
wxpWrite-XOR-Execute Protection. No page is simultaneously writable and executable; W+X mappings and transitions between writable and executable are rejected.
tlpTrusted Library Paths. Shared libraries load only from approved directory prefixes. Weaker than LSV, since it trusts the path rather than the binary.
cfifForward-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.
cfibBackward-Edge Control Flow Integrity. The hardware shadow stack, Intel CET, is locked on and cannot be disabled by the process.
piePosition-Independent Executable Requirement. Non-PIE binaries are rejected at exec, so that ASLR is actually effective.
smlSpeculation 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) #

FieldDescription
ui_accessPermits 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) #

FieldDescription
no_child_processOnce 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) #

FieldDescription
virtualizationPer-process state for setuid compatibility redirection. Not active, and implementations may omit the field until it is.

Edit this page