Files
aios/docs/security/trusted-builder-captrace.md
T

5.5 KiB

Trusted Candidate Builder — CAP_SYS_PTRACE security contract

Status: formal (V2R2C2) · Applies to: deploy/systemd/aios-trusted-candidate-builder.service (the live baseline unit)

0. Live baseline (rea truth, not nominal)

The authoritative deployed unit is the live systemd unit on the hw control plane host:

  • User=root / Group=root (the builder runs as root)
  • socket: aios-trusted-candidate-builder.socket ListenStream=/run/aios-trusted-candidate-builder.sock, SocketUser=root, SocketGroup=gitops, SocketMode=0660
  • credentials: LoadCredentialEncrypted for signing_private_key / builder_hmac (under /etc/credstore.encrypted/aios-trusted-candidate-builder/) and source_admission_hmac (under /etc/credstore.encrypted/aios-release-source-admission/)
  • hardening: NoNewPrivileges, PrivateTmp, PrivateNetwork, ProtectSystem=strict, ProtectHome, ProtectControlGroups, ProtectKernelTunables/Modules/Logs, RestrictAddressFamilies=AF_UNIX, LockPersonality, MemoryDenyWriteExecute, RestrictSUIDSGID, SystemCallArchitectures=native
  • capability: AmbientCapabilities=CAP_SYS_PTRACE + CapabilityBoundingSet=CAP_SYS_PTRACE (live current state)

The tracked deploy/systemd/aios-trusted-candidate-builder.service is kept byte-identical (LF-normalized) to the live unit so sha256 drift is detectable: live sha256 = 9d194c23ec981528d29d55f601d1be858635ac6229c7e25c10736df4e44042d3.

This contract deliberately does NOT introduce a separate non-root -ptrace unit. §2 of the V2R2C2 instruction forbids inventing a distinct service uid/group, ProtectProc/ProcSubset, credential paths or socket ownership that diverge from the live baseline. The live baseline is root + the single minimal capability, and that is the only variant tracked.

1. Why a capability is declared at all

TrustedCandidateBuilder._verify_peer validates the connecting peer before signing any candidate. It reads three /proc/<pid> views of the peer:

view function failure → error
/proc/<pid>/cgroup _resolve_cgroups PEER_CGROUP_UNAVAILABLE
/proc/<pid>/exe _resolve_process_identity PEER_PROCESS_UNAVAILABLE
/proc/<pid>/cmdline _resolve_process_identity PEER_PROCESS_UNAVAILABLE

A cross-uid readlink("/proc/<pid>/exe") for a peer of a different uid is subject to the Yama ptrace_scope sysctl and returns EACCES for a process without the minimal CAP_SYS_PTRACE. Result: the builder fails closed (PEER_PROCESS_UNAVAILABLE), which is safe but loses availability for legitimate cross-uid peers.

The minimal CAP_SYS_PTRACE restores the ability to inspect the peer's /proc view across uids.

2. Capability sensitivity — MANDATORY reading

CAP_SYS_PTRACE is a high-sensitivity capability. It is not a read-only helper: it broadens the process's observation and debugging authority across uids, including ptrace, reading /proc/<pid>/mem, and inspecting another user's process state. It is therefore:

  • limited to the single capability CAP_SYS_PTRACE (both AmbientCapabilities and CapabilityBoundingSet), and
  • scoped to the dedicated builder service unit only.

It must never be used to imply "read-only" or "no control authority." It carries real inspection/debug power and is declared for one concrete purpose: restoring cross-uid /proc/<pid>/exe identity resolution for the builder's peer validation. Nothing more.

Three-state verification contract

  • A. No CAP_SYS_PTRACE (fail-closed): cross-uid /proc/<pid>/exe raises PermissionError(EACCES) → resolvers fail closed → PEER_PROCESS_UNAVAILABLE (and PEER_CGROUP_UNAVAILABLE for cgroup). Never degrade to allow.
  • B. Minimal CAP_SYS_PTRACE + legitimate peer: peer uid in allowed_peer_uid, cgroup in allowed_peer_cgroups, executable matches allowed_peer_executable_prefix, argv matches the runtime argument contract → readiness READY.
  • C. CAP present but peer illegitimate: any identity check fails → PEER_IDENTITY_DENIED / PEER_CGROUP_DENIED / PEER_PROCESS_DENIED. The capability must never bypass the identity policy — it restores the ability to perform identity validation, not the authorization itself.

3. Operational rules

  • Keep CapabilityBoundingSet and AmbientCapabilities pinned to exactly CAP_SYS_PTRACE. Never use capability wildcards (e.g. CAP_* applying to every capability).
  • Keep NoNewPrivileges=true; ambient capabilities are the only inheritance path into the exec'd Python process.
  • Do not re-introduce a separate non-root unit with a different uid/group/ProtectProc/ProcSubset/credential paths/socket ownership (see §0). The root + single-capability unit is the live baseline.
  • Runtime verification (three-state) MUST be executed on the real Linux host that runs systemd; the adversarial states require real uids/cgroups/argv.

4. Testing

  • Local (no /proc peer needed): the resolvers are exercised with monkeypatched PermissionError(EACCES) — mirroring the exact bytes a missing CAP_SYS_PTRACE / cross-uid read produces — covering cgroup → PEER_CGROUP_UNAVAILABLE, exe → PEER_PROCESS_UNAVAILABLE, cmdline → PEER_PROCESS_UNAVAILABLE (see tests/test_trusted_candidate_builder_captrace.py).
  • Host (Linux, required for final acceptance): run systemd-analyze verify on the tracked unit; confirm the unit matches the live sha256; launch with/without the capability; confirm A → PEER_PROCESS_UNAVAILABLE, B → READY, C → *_DENIED.