返回 CodeWhale
process_hardening.rs
根目录 / crates / tui / src / sandbox / process_hardening.rs
1 //! Process hardening for Linux sandbox defense-in-depth (#2183).
2 //!
3 //! This module applies kernel-level restrictions to the codewhale-tui process
4 //! itself. These hardening measures protect the *parent* TUI process and its
5 //! descendants from information leaks and privilege-escalation vectors; they
6 //! are not a filesystem or network sandbox for child commands. The seccomp
7 //! source module is not wired into child execution yet.
8 //!
9 //! # Ordering constraints
10 //!
11 //! `apply_process_hardening()` MUST be called **before** the Tokio runtime is
12 //! booted and **before** any worker threads are spawned. The reasons:
13 //!
14 //! 1. `PR_SET_DUMPABLE` — once set to 0, the process cannot be ptraced and
15 //! `/proc/self/` becomes root-owned. This must happen before any threads
16 //! exist, because the kernel applies dumpable state per-thread-group and
17 //! changing it after threads are live can race with `/proc` lookups.
18 //!
19 //! 2. `PR_SET_NO_NEW_PRIVS` — prevents the process and all descendants from
20 //! ever gaining new privileges via setuid/setgid/fscaps. This is
21 //! irreversible and must be applied before executing any helper binaries or
22 //! subprocesses that might (incorrectly) rely on privilege boundaries.
23 //!
24 //! 3. `RLIMIT_CORE` — disables core dumps so that sensitive in-memory data
25 //! (API keys, tokens, prompt content) is never written to disk on a crash.
26 //! Setting this before any data is loaded into memory is the safest posture.
27 //!
28 //! # Platform support
29 //!
30 //! These hardening measures are Linux-only (they use `prctl` and `setrlimit`
31 //! from the `libc` crate). On non-Linux platforms, `apply_process_hardening()`
32 //! is a no-op that logs a debug-level message.
33
34 /// Apply process-level hardening measures.
35 ///
36 /// On Linux, this:
37 /// - Sets `PR_SET_DUMPABLE` to 0 (prevents ptrace, core dumps)
38 /// - Sets `PR_SET_NO_NEW_PRIVS` to 1 (irreversible no-new-privileges)
39 /// - Sets `RLIMIT_CORE` to 0 (disables core dumps)
40 ///
41 /// On non-Linux platforms this is a no-op.
42 ///
43 /// # Panics
44 ///
45 /// Does NOT panic. Failures are logged via `tracing::warn` because the
46 /// hardening is defense-in-depth. A failure does not abort startup or change
47 /// whether a separately configured Seatbelt/bubblewrap command wrapper is
48 /// available.
49 pub fn apply_process_hardening() {
50 #[cfg(all(target_os = "linux", not(target_env = "ohos")))]
51 {
52 apply_linux_hardening();
53 }
54 #[cfg(not(all(target_os = "linux", not(target_env = "ohos"))))]
55 {
56 tracing::debug!("Process hardening skipped: not on Linux");
57 }
58 }
59
60 /// Linux-specific hardening implementation.
61 #[cfg(all(target_os = "linux", not(target_env = "ohos")))]
62 fn apply_linux_hardening() {
63 // ── PR_SET_DUMPABLE = 0 ────────────────────────────────────────────────
64 //
65 // When dumpable is 0:
66 // - The process cannot be ptraced by non-root
67 // - /proc/<pid>/ becomes owned by root:root (mode 0400)
68 // - No core dumps are produced
69 //
70 // Pattern from openai/codex codex-rs/codex-sandbox/src/linux.rs; reimplemented.
71 //
72 // Safety: prctl with PR_SET_DUMPABLE modifies only the calling process.
73 let result = unsafe { libc::prctl(libc::PR_SET_DUMPABLE, 0i64, 0i64, 0i64, 0i64) };
74 if result != 0 {
75 let err = std::io::Error::last_os_error();
76 tracing::warn!(
77 "PR_SET_DUMPABLE failed ({}); continuing without this hardening",
78 err
79 );
80 } else {
81 tracing::debug!("PR_SET_DUMPABLE=0 applied");
82 }
83
84 // ── PR_SET_NO_NEW_PRIVS = 1 ────────────────────────────────────────────
85 //
86 // Once set, neither this process nor any descendant can ever gain new
87 // privileges via setuid, setgid, file capabilities, or LSMs like SELinux
88 // transitions. This is the strongest anti-escalation primitive the kernel
89 // offers.
90 //
91 // Pattern from openai/codex codex-rs/codex-sandbox/src/linux.rs; reimplemented.
92 //
93 // Safety: prctl with PR_SET_NO_NEW_PRIVS modifies only the calling process
94 // and its future descendants.
95 let result = unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1i64, 0i64, 0i64, 0i64) };
96 if result != 0 {
97 let err = std::io::Error::last_os_error();
98 tracing::warn!(
99 "PR_SET_NO_NEW_PRIVS failed ({}); continuing without this hardening",
100 err
101 );
102 } else {
103 tracing::debug!("PR_SET_NO_NEW_PRIVS=1 applied");
104 }
105
106 // ── RLIMIT_CORE = 0 ────────────────────────────────────────────────────
107 //
108 // Disables core dumps at the rlimit level. In combination with
109 // PR_SET_DUMPABLE=0, this provides a belt-and-suspenders guarantee that
110 // no core file will ever be written.
111 //
112 // Safety: setrlimit modifies resource limits for the calling process only.
113 let rlim_core = libc::rlimit {
114 rlim_cur: 0,
115 rlim_max: 0,
116 };
117 let result = unsafe { libc::setrlimit(libc::RLIMIT_CORE, &raw const rlim_core) };
118 if result != 0 {
119 let err = std::io::Error::last_os_error();
120 tracing::warn!(
121 "RLIMIT_CORE failed ({}); continuing without this hardening",
122 err
123 );
124 } else {
125 tracing::debug!("RLIMIT_CORE=0 applied");
126 }
127 }
128
129 #[cfg(test)]
130 mod tests {
131 use super::*;
132
133 #[test]
134 fn test_apply_process_hardening_does_not_panic() {
135 // This test exists to ensure the function can be called without
136 // panicking, even on platforms where hardening is a no-op.
137 apply_process_hardening();
138 }
139 }
140
140 lines RUST