|
| 1 | +// SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | + |
| 3 | +//! Process anti-leak hardening, run once at agent startup. |
| 4 | +//! |
| 5 | +//! The agent holds the unwrapped user key (and refresh token) in memory. This |
| 6 | +//! locks down two ways that material could escape the process: |
| 7 | +//! |
| 8 | +//! - **Core dumps** — a crash must not write key bytes to a core file on disk. |
| 9 | +//! - **ptrace** — another process running as the same user must not be able to |
| 10 | +//! attach a debugger and read the agent's memory. |
| 11 | +//! |
| 12 | +//! Both are handled by [`secmem_proc::harden_process`] (lowers `RLIMIT_CORE` |
| 13 | +//! and sets the process non-dumpable via the audited `rustix` syscall layer), |
| 14 | +//! which keeps this crate's `#![forbid(unsafe_code)]` intact. |
| 15 | +//! |
| 16 | +//! This does **not** yet cover swap exposure (`mlock`); that's a tracked |
| 17 | +//! follow-up. mlock is out of scope here. |
| 18 | +
|
| 19 | +/// Harden the current process against core dumps and ptrace. Best-effort: if a |
| 20 | +/// step fails (e.g. a sandbox that restricts `setrlimit`), log a warning and |
| 21 | +/// keep running — a partial-hardening failure must never take the agent down. |
| 22 | +pub fn harden_process() { |
| 23 | + if let Err(e) = secmem_proc::harden_process() { |
| 24 | + eprintln!("vault-agent: process hardening incomplete (continuing): {e}"); |
| 25 | + } |
| 26 | +} |
| 27 | + |
| 28 | +#[cfg(target_os = "linux")] |
| 29 | +#[cfg(test)] |
| 30 | +mod tests { |
| 31 | + /// After hardening, the process must be marked non-dumpable — that's what |
| 32 | + /// blocks core dumps and same-user ptrace. Where `/proc/self/status` |
| 33 | + /// exposes the `Dumpable` field (a normal kernel) we assert it is `0`; some |
| 34 | + /// sandboxed `/proc` mounts omit the field, in which case there is nothing |
| 35 | + /// to check (the call still must not panic). `PR_SET_DUMPABLE(0)` is |
| 36 | + /// unprivileged, so the assertion is deterministic where it applies. |
| 37 | + #[test] |
| 38 | + fn harden_sets_process_non_dumpable() { |
| 39 | + super::harden_process(); |
| 40 | + let status = std::fs::read_to_string("/proc/self/status").expect("read /proc/self/status"); |
| 41 | + if let Some(dumpable) = status |
| 42 | + .lines() |
| 43 | + .find_map(|l| l.strip_prefix("Dumpable:")) |
| 44 | + .map(str::trim) |
| 45 | + { |
| 46 | + assert_eq!( |
| 47 | + dumpable, "0", |
| 48 | + "process should be non-dumpable after hardening" |
| 49 | + ); |
| 50 | + } |
| 51 | + } |
| 52 | +} |
0 commit comments