Skip to content

VOL 08 / CH 05 / LESSON 01

5.1 Linux Process Permissions: UID, Capabilities, and Privilege Separation ​

Operating system security starts with an uncomfortable assumption: application vulnerabilities have already allowed attackers to execute code within a process. The real issue then becomes what files that process can read, which kernel capabilities it can invoke, which services it can access, and whether it can escalate its privileges further.

Process Carries Security Context ​

Common Linux access control inputs include:

text
real/effective/saved UID and GID
supplementary groups
filesystem mode bits and ACLs
capability sets
namespace view
LSM label/profile
seccomp syscall policy
resource limits and cgroups

UID 0 interacts with capability sets, namespaces, and kernel policy. An unconstrained host-root network service can turn a parser exploit into host control; UID 0 inside a constrained user namespace or MAC domain must be assessed in its actual context.

DAC Based on Owners and Groups ​

Traditional mode bits:

text
rwx for owner | rwx for group | rwx for others

The "read" permission in a directory means listing entries, "write" means modifying directory entries, and "execute" means traversing the directory. Looking only at file permissions may lead to misjudgment: an attacker with both write and execute permission on a parent directory can usually replace or delete a non-writable file. Sticky bits, ACLs, read-only mounts, and MAC policy may impose additional limits.

Service accounts should have one identity per service, avoiding shared app users. The owners of configurations, executables, and secrets must be separated from the running user: typically, a process only needs to read configuration and should not be able to modify its own binaries, systemd units, or startup scripts.

umask determines which permissions are removed by default for new objects, but cannot fix already-existing broad permissions. Temporary files use an atomic safe-create API, and creation should not first check that the name doesn't exist before creating; symbolic links in /tmp can bypass such checks due to race conditions.

Keep Setuid Helpers Small ​

Executing a setuid binary normally sets the effective UID to its owner. Conditions such as a nosuid mount or no_new_privs prevent that elevation, and Linux does not treat interpreter scripts as ordinary setuid binaries. For an effective privileged helper, any input parsing, environment variables, PATH, file descriptors, or dynamic linking issues could serve as a privilege escalation vector. Prefer narrower mechanisms: Unix socket proxies, polkit rules, dedicated daemons, or individual capabilities.

When elevation of privilege for helper processes is necessary, the functionality should be single-purpose, with a fixed absolute path, environment cleanup, unnecessary file descriptors closed, untrusted configurations rejected, and permanent de-escalation of privileges performed after any privileged operation is completed.

Capabilities Split Root Privileges ​

Linux capabilities split certain superuser privileges. For example, binding to a low-port might require CAP_NET_BIND_SERVICE, and modifying network configuration might require CAP_NET_ADMIN.

Don't treat CAP_SYS_ADMIN as a universal fix for "add what's missing", it has a broad scope and is referred to as the new root. The same applies inside containers: capability boundaries within a namespace depend on specific operations and host configurations, not indicating the kernel attack surface has vanished.

Process capabilities include permitted, effective, inheritable, bounding, and ambient sets. A secure process-launch sequence must narrow these sets together. Dropping a bounding-set bit constrains capability acquisition through later exec calls, but does not itself clear existing permitted/effective capabilities; running setcap once in a shell without recording deployment state results in unaudited drift.

This is a hardening fragment for a systemd system service, with ExecStart and other complete-unit configuration omitted. It assumes the dedicated user and /var/lib/atlas-api already exist with the required DAC permissions:

ini
[Service]
User=atlas-api
Group=atlas-api
NoNewPrivileges=yes
CapabilityBoundingSet=
AmbientCapabilities=
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/atlas-api

NoNewPrivileges prevents later exec calls from gaining privileges through setuid or file capabilities; it does not revoke existing privileges. ReadWritePaths creates a writable exception in the mount view, but neither creates the directory nor replaces filesystem permissions. PrivateTmp isolates temporary-directory views, and ProtectSystem does not isolate networking. Verify each setting on a compatible systemd/kernel and the actual workload: hidden write dependencies may surface only after an upgrade.

Privilege Separation Splits High-Risk Steps ​

Don't have a single process responsible for: parsing any uploaded file, holding the master key, writing to system directories, and accessing the management database. Split it into:

text
Internet-facing parser (no secrets, isolated queue for write operations)
        ↓ validated job
worker (read-only input, resource-limited)
        ↓ narrow result
privileged signer (fixed protocol, HSM key, unreadable private key)

Inter-process protocols also need authentication, message length limits, and versioning; otherwise, attackers simply shift from "calling a dangerous function" to "sending dangerous messages to a high-privilege daemon."

Control How Secrets Reach a Process ​

Environment variables are convenient but may be visible in process dumps, diagnostic pages, child processes, and error reports. Command-line arguments might also be seen in process listings. Prefer permission-protected memory injection, short-lived credentials, file descriptors, or dedicated secret agents, and avoid passing sensitive information to unnecessary child processes.

Core dumps, ptrace, visibility of /proc, and debugging interfaces must be restricted according to production threat modeling. Disabling all debugging will reduce incident diagnosis capabilities, so controlled temporary privilege escalation should be used instead of permanent exposure.

Verify the Running Process ​

  • Start as the actual service user and confirm that other services' secrets cannot be read;
  • Try modifying your own binary, unit, configuration, or parent directory;
  • List runtime capabilities instead of relying on the deployed YAML;
  • Check inherited file descriptors, environment variables, and supplementary groups;
  • Let the parsing process handle large malicious inputs and verify that the resource limit is enforced;
  • Verify that dropped privileges cannot be regained through a saved UID or helper;
  • Log the subject, target, and outcome of privileged operations.

The next lesson adds mandatory access control, syscall filtering, and kernel hardening to constrain a compromised application account.

References ​

Built with VitePress | Software Systems Atlas