Files
codewhale/crates/tui/src/sandbox/process_hardening.rs
T
Hunter B 23c9481af1 feat: add HarmonyOS OpenHarmony support
Harvest the HarmonyOS/OpenHarmony port from PR #2634 and make it publish-safe by target-gating unsupported host dependencies out of the OHOS TUI graph. Self-update is disabled on OHOS, PTY shell mode reports unsupported, and Starlark execpolicy parsing returns an explicit unsupported-platform error until upstream starlark/rustyline/nix support catches up.

Add OHOS SDK setup docs and launcher scripts, install the rustls ring provider for rustls-no-provider entrypoints, and keep the packaged codewhale-tui OHOS graph free of starlark, rustyline, nix@0.28, portable-pty, and arboard.

Validation: cargo fmt --all -- --check; git diff --check; git diff --cached --check; cargo check -p codewhale-cli --locked; cargo check -p codewhale-app-server --locked; cargo check -p codewhale-tui --locked; cargo test -p codewhale-cli --locked update::tests::; cargo test -p codewhale-release --locked; cargo test -p codewhale-tui --locked background_tty_command_has_controlling_terminal; cargo test -p codewhale-tui --locked clipboard; cargo package -p codewhale-tui --allow-dirty --no-verify --locked; packaged OHOS cargo tree checks. OHOS target check still requires a loaded OpenHarmony SDK/sysroot and currently stops in ring with missing assert.h when CC/CFLAGS/linker are unset.

Harvested from PR #2634 by @shenjackyuanjie.

Co-authored-by: shenjackyuanjie <54507071+shenjackyuanjie@users.noreply.github.com>
2026-06-03 21:02:46 -07:00

138 lines
5.6 KiB
Rust

//! Process hardening for Linux sandbox defense-in-depth (#2183).
//!
//! This module applies kernel-level restrictions to the codewhale-tui process
//! itself. Unlike Landlock/seccomp which restrict child processes spawned for
//! shell commands, these hardening measures protect the *parent* TUI process
//! from information leaks and privilege-escalation vectors.
//!
//! # Ordering constraints
//!
//! `apply_process_hardening()` MUST be called **before** the Tokio runtime is
//! booted and **before** any worker threads are spawned. The reasons:
//!
//! 1. `PR_SET_DUMPABLE` — once set to 0, the process cannot be ptraced and
//! `/proc/self/` becomes root-owned. This must happen before any threads
//! exist, because the kernel applies dumpable state per-thread-group and
//! changing it after threads are live can race with `/proc` lookups.
//!
//! 2. `PR_SET_NO_NEW_PRIVS` — prevents the process and all descendants from
//! ever gaining new privileges via setuid/setgid/fscaps. This is
//! irreversible and must be applied before executing any helper binaries or
//! subprocesses that might (incorrectly) rely on privilege boundaries.
//!
//! 3. `RLIMIT_CORE` — disables core dumps so that sensitive in-memory data
//! (API keys, tokens, prompt content) is never written to disk on a crash.
//! Setting this before any data is loaded into memory is the safest posture.
//!
//! # Platform support
//!
//! These hardening measures are Linux-only (they use `prctl` and `setrlimit`
//! from the `libc` crate). On non-Linux platforms, `apply_process_hardening()`
//! is a no-op that logs a debug-level message.
/// Apply process-level hardening measures.
///
/// On Linux, this:
/// - Sets `PR_SET_DUMPABLE` to 0 (prevents ptrace, core dumps)
/// - Sets `PR_SET_NO_NEW_PRIVS` to 1 (irreversible no-new-privileges)
/// - Sets `RLIMIT_CORE` to 0 (disables core dumps)
///
/// On non-Linux platforms this is a no-op.
///
/// # Panics
///
/// Does NOT panic. Failures are logged via `tracing::warn` because the
/// hardening is defense-in-depth — the sandbox still protects child processes
/// even if these prctls fail (e.g., in a container where some are restricted).
pub fn apply_process_hardening() {
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
{
apply_linux_hardening();
}
#[cfg(not(all(target_os = "linux", not(target_env = "ohos"))))]
{
tracing::debug!("Process hardening skipped: not on Linux");
}
}
/// Linux-specific hardening implementation.
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
fn apply_linux_hardening() {
// ── PR_SET_DUMPABLE = 0 ────────────────────────────────────────────────
//
// When dumpable is 0:
// - The process cannot be ptraced by non-root
// - /proc/<pid>/ becomes owned by root:root (mode 0400)
// - No core dumps are produced
//
// Pattern from openai/codex codex-rs/codex-sandbox/src/linux.rs; reimplemented.
//
// Safety: prctl with PR_SET_DUMPABLE modifies only the calling process.
let result = unsafe { libc::prctl(libc::PR_SET_DUMPABLE, 0i64, 0i64, 0i64, 0i64) };
if result != 0 {
let err = std::io::Error::last_os_error();
tracing::warn!(
"PR_SET_DUMPABLE failed ({}); continuing without this hardening",
err
);
} else {
tracing::debug!("PR_SET_DUMPABLE=0 applied");
}
// ── PR_SET_NO_NEW_PRIVS = 1 ────────────────────────────────────────────
//
// Once set, neither this process nor any descendant can ever gain new
// privileges via setuid, setgid, file capabilities, or LSMs like SELinux
// transitions. This is the strongest anti-escalation primitive the kernel
// offers.
//
// Pattern from openai/codex codex-rs/codex-sandbox/src/linux.rs; reimplemented.
//
// Safety: prctl with PR_SET_NO_NEW_PRIVS modifies only the calling process
// and its future descendants.
let result = unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1i64, 0i64, 0i64, 0i64) };
if result != 0 {
let err = std::io::Error::last_os_error();
tracing::warn!(
"PR_SET_NO_NEW_PRIVS failed ({}); continuing without this hardening",
err
);
} else {
tracing::debug!("PR_SET_NO_NEW_PRIVS=1 applied");
}
// ── RLIMIT_CORE = 0 ────────────────────────────────────────────────────
//
// Disables core dumps at the rlimit level. In combination with
// PR_SET_DUMPABLE=0, this provides a belt-and-suspenders guarantee that
// no core file will ever be written.
//
// Safety: setrlimit modifies resource limits for the calling process only.
let rlim_core = libc::rlimit {
rlim_cur: 0,
rlim_max: 0,
};
let result = unsafe { libc::setrlimit(libc::RLIMIT_CORE, &raw const rlim_core) };
if result != 0 {
let err = std::io::Error::last_os_error();
tracing::warn!(
"RLIMIT_CORE failed ({}); continuing without this hardening",
err
);
} else {
tracing::debug!("RLIMIT_CORE=0 applied");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_apply_process_hardening_does_not_panic() {
// This test exists to ensure the function can be called without
// panicking, even on platforms where hardening is a no-op.
apply_process_hardening();
}
}