23c9481af1
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>
138 lines
5.6 KiB
Rust
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();
|
|
}
|
|
}
|