3.3.3 Process Security Descriptors

Every process carries a security descriptor controlling who may operate on it, stored on the PSB alongside the PIP and mitigation fields. It replaces Linux's UID-based process access control — a patchwork of UID comparisons and capabilities — with a single descriptor evaluation.

3.3.3.1 Process access rights #

RightValueMeaning
PROCESS_TERMINATE0x0001Send signals whose default action is termination.
PROCESS_SIGNAL0x0002Send informational signals whose default action is to ignore: SIGCHLD, SIGURG, SIGWINCH.
PROCESS_VM_READ0x0010Read process memory — ptrace peek, /proc/<pid>/mem, process_vm_readv.
PROCESS_VM_WRITE0x0020Write process memory — ptrace poke, /proc/<pid>/mem, process_vm_writev. Includes debugger attach.
PROCESS_DUP_HANDLE0x0040Extract file descriptors from the process through pidfd_getfd.
PROCESS_SET_INFORMATION0x0200Change priority, CPU affinity, I/O priority, resource limits, process group membership where Linux permits it, timer slack, memory-placement policy or pages, and mutable /proc/<pid> task state — sched, autogroup, timens_offsets, timerslack_ns, coredump_filter, oom_adj, oom_score_adj, make-it-fail, fail-nth, latency and clear_refs, plus write intent on the coupled uid_map, gid_map, projid_map and setgroups seq files.
PROCESS_QUERY_INFORMATION0x0400Inspect the process's token; read the detailed /proc/<pid>/* files — cmdline, status, io, limits, sched, autogroup, timens_offsets, personality, syscall, latency, timers, timerslack_ns, mounts, mountinfo, mountstats, coredump_filter, oom_adj, oom_score_adj, loginuid, make-it-fail, fail-nth, seccomp_cache, ksm_merging_pages and ksm_stat — plus read intent on the coupled uid_map, gid_map, projid_map and setgroups seq files; query Linux compatibility capability state through capget(pid); and query detailed scheduler, CPU-affinity and I/O-priority state.
PROCESS_SUSPEND_RESUME0x0800Send signals whose default action is to stop or continue.
PROCESS_QUERY_LIMITED0x1000Read basic process information: PID, process group ID, session ID, image name, state, CPU and memory usage — stat, statm, comm, wchan, schedstat, cpuset, cgroup, cpu_resctrl_groups, oom_score, sessionid, patch_state, stack_depth and arch_status. This is what ps and top show, it covers /proc/<pid>/stat, and it is the right required for pidfd_open() and for kill(pid, 0) existence probes.
READ_CONTROL0x20000Read the process's own descriptor.
WRITE_DAC0x40000Modify the process's DACL.
WRITE_OWNER0x80000Change the descriptor's owner.

Three /proc entries are not where the right names suggest. maps, fd and environ are not gated by PROCESS_QUERY_INFORMATION: they keep their upstream PTRACE_MODE_READ_FSCREDS gating, which maps to PROCESS_VM_READ — reading a process's memory map is treated as reading its memory, which is defensible but is not what the right's name implies. And cgroup sits in the PROCESS_QUERY_LIMITED set rather than the detailed one.

3.3.3.2 Signal classification #

Each Linux signal maps to a process access right according to its default action.

Signal 0 is not delivered at all. A kill(), tkill(), or tgkill() call with signal 0 is an existence and permission probe, requiring PROCESS_QUERY_LIMITED on the target plus PIP dominance.

PROCESS_TERMINATE — default action terminate, or terminate with a core dump:

Signal#DefaultNotes
SIGHUP1TerminateSession hangup
SIGINT2TerminateCtrl-C
SIGQUIT3Terminate + coreQuit request
SIGILL4Terminate + coreIllegal instruction
SIGTRAP5Terminate + coreDebug trap
SIGABRT6Terminate + coreAbort
SIGBUS7Terminate + coreBus error
SIGFPE8Terminate + coreFloating point exception
SIGKILL9TerminateForced kill, cannot be caught
SIGUSR110TerminateUser-defined
SIGSEGV11Terminate + coreSegfault
SIGUSR212TerminateUser-defined
SIGPIPE13TerminateBroken pipe
SIGALRM14TerminateAlarm timer
SIGTERM15TerminateGraceful termination request
SIGSTKFLT16TerminateStack fault
SIGXCPU24Terminate + coreCPU time exceeded
SIGXFSZ25Terminate + coreFile size exceeded
SIGVTALRM26TerminateVirtual timer
SIGPROF27TerminateProfiling timer
SIGIO29TerminateI/O possible
SIGPWR30TerminatePower failure
SIGSYS31Terminate + coreBad syscall

PROCESS_SUSPEND_RESUME — default action stop or continue:

Signal#DefaultNotes
SIGSTOP19StopForced stop, cannot be caught
SIGTSTP20StopTerminal stop, Ctrl-Z
SIGTTIN21StopBackground read from terminal
SIGTTOU22StopBackground write to terminal
SIGCONT18ContinueResume a stopped process

PROCESS_SIGNAL — default action ignore:

Signal#DefaultNotes
SIGCHLD17IgnoreChild status change
SIGURG23IgnoreUrgent socket data
SIGWINCH28IgnoreWindow resize

The real-time signals, SIGRTMIN through SIGRTMAX (32–64), default to terminate and therefore require PROCESS_TERMINATE.

3.3.3.2.1 What bypasses the check #

This classification applies only to signals sent by userspace through kill(), tkill(), and tgkill(). Kernel-generated signals — hardware faults such as SIGSEGV, SIGBUS and SIGFPE, SIGCHLD from a child exiting, SIGPIPE from a broken pipe — are delivered by the kernel and bypass the process descriptor check entirely, because the task_kill LSM hook does not fire for kernel-originated delivery.

Terminal-generated job control signals are kernel-originated under that rule and bypass the check the same way: SIGINT, SIGQUIT and SIGTSTP from the tty driver's isig handling, and SIGHUP on hangup. This is intentional. Authorization for keyboard-driven signals is possession of the controlling terminal, which was gated by the terminal's file descriptor at open time — so Ctrl-C reaches the whole foreground process group even when a member of it is more privileged or more PIP-trusted than whoever holds the terminal. A process that cannot accept that exposure must not attach to an untrusted controlling terminal.

The si_uid in a delivered signal's siginfo_t is the sender's projected UID (§3.10), captured at send time. Like every projected credential surface it is informational only and is not an authorization input; si_pid carries the same caveat and is subject to PID reuse besides.

3.3.3.3 Generic mapping #

Generic rightMaps to
GENERIC_READPROCESS_QUERY_INFORMATION | PROCESS_VM_READ | READ_CONTROL
GENERIC_WRITEPROCESS_SET_INFORMATION | PROCESS_VM_WRITE | WRITE_DAC
GENERIC_EXECUTEPROCESS_TERMINATE | PROCESS_SUSPEND_RESUME | PROCESS_QUERY_LIMITED
GENERIC_ALLevery process right above, together with READ_CONTROL, WRITE_DAC and WRITE_OWNER

3.3.3.4 The default process descriptor #

Every process receives a default descriptor at creation:

Owner: <creator's primary token user SID>
Group: <creator's primary token primary group SID>
DACL:
  ALLOW  <process's own user SID>   GENERIC_ALL
  ALLOW  BUILTIN\Administrators     GENERIC_ALL
  ALLOW  SYSTEM                     GENERIC_ALL
  ALLOW  Everyone                   PROCESS_QUERY_LIMITED

A process can therefore do anything to itself; Administrators and SYSTEM have full control over every process; everyone can see basic process information, which is what makes ps and top work for all users; and detailed inspection — token, memory, environment — is restricted to the process itself, administrators, and SYSTEM.

A service can modify its own descriptor at runtime with kacs_set_sd, which requires WRITE_DAC — granted to the process itself by the default DACL. Requesting a custom descriptor at launch through a service definition is not implemented: the only descriptor creation path always builds the default template, so every process starts from it and any deviation is a subsequent write.

3.3.3.5 How PIP relates to it #

PIP and the process descriptor are complementary, and both checks have to pass. The descriptor controls who may operate on the process; PIP controls what trust level is required for invasive access to a protected one. AccessCheck evaluates the caller's token against the target's descriptor for the requested right, and PIP evaluates the caller's trust against the target's pip_type and pip_trust for operations crossing the process boundary.

The two are genuinely independent. A process may have a permissive descriptor granting Administrators GENERIC_ALL and still be PIP-protected, so administrators pass the descriptor check and are stopped only by insufficient PIP trust.

The converse — a process with no PIP protection carrying a restrictive descriptor that denies even administrators — holds with one qualification. When a descriptor check denies access and PIP was not the deciding factor, an enabled SeDebugPrivilege on the caller grants the access anyway and is marked used. The privilege therefore rescues a descriptor denial while remaining unable to cross a PIP boundary, which is exactly the split §3.4.2 describes for it.

Edit this page