# 3.3.2 PSB Fields

_Peios / Advanced Peios / PKM / KACS / The Process Security Block_

> Every PSB field — the process GUID fixed at fork, the protection fields set at exec, the one-way mitigations and the UI access flag.

## 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. |
