mirror of
https://github.com/openai/codex.git
synced 2026-05-24 04:54:52 +00:00
## Why The Linux managed-proxy bridge helpers are long-lived child processes in the sandbox networking path. Before this change they stayed dumpable and the network seccomp profile did not block cross-process memory syscalls, so another same-user process could potentially inspect or modify bridge memory instead of interacting only through the intended proxy interface. ## What changed - reuse the shared `codex-process-hardening` helper to mark bridge helper children non-dumpable before they begin serving - deny `process_vm_readv` and `process_vm_writev` in the existing network seccomp filter ## Security impact Bridge helpers are less exposed to same-user cross-process inspection or memory writes, which reduces the chance that sandboxed code can interfere with proxy support processes outside the intended IPC path. ## Verification - `cargo test -p codex-process-hardening` - `cargo test -p codex-linux-sandbox` - attempted `cargo check -p codex-linux-sandbox --target x86_64-unknown-linux-gnu`; blocked on missing `x86_64-linux-gnu-gcc` on this macOS host --------- Co-authored-by: Codex <noreply@openai.com>
201 lines
6.1 KiB
Rust
201 lines
6.1 KiB
Rust
#[cfg(unix)]
|
|
use std::ffi::OsString;
|
|
|
|
#[cfg(unix)]
|
|
use std::os::unix::ffi::OsStrExt;
|
|
|
|
/// This is designed to be called pre-main() (using `#[ctor::ctor]`) to perform
|
|
/// various process hardening steps, such as
|
|
/// - disabling core dumps
|
|
/// - disabling ptrace attach on Linux and macOS.
|
|
/// - removing dangerous or noisy environment variables such as LD_PRELOAD,
|
|
/// DYLD_*, and macOS malloc stack-logging controls
|
|
pub fn pre_main_hardening() {
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
pre_main_hardening_linux();
|
|
|
|
#[cfg(target_os = "macos")]
|
|
pre_main_hardening_macos();
|
|
|
|
// On FreeBSD and OpenBSD, apply similar hardening to Linux/macOS:
|
|
#[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
|
|
pre_main_hardening_bsd();
|
|
|
|
#[cfg(windows)]
|
|
pre_main_hardening_windows();
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
const PRCTL_FAILED_EXIT_CODE: i32 = 5;
|
|
|
|
#[cfg(target_os = "macos")]
|
|
const PTRACE_DENY_ATTACH_FAILED_EXIT_CODE: i32 = 6;
|
|
|
|
#[cfg(any(
|
|
target_os = "linux",
|
|
target_os = "android",
|
|
target_os = "macos",
|
|
target_os = "freebsd",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd"
|
|
))]
|
|
const SET_RLIMIT_CORE_FAILED_EXIT_CODE: i32 = 7;
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
pub(crate) fn pre_main_hardening_linux() {
|
|
// Disable ptrace attach / mark process non-dumpable.
|
|
let ret_code = unsafe { libc::prctl(libc::PR_SET_DUMPABLE, 0, 0, 0, 0) };
|
|
if ret_code != 0 {
|
|
eprintln!(
|
|
"ERROR: prctl(PR_SET_DUMPABLE, 0) failed: {}",
|
|
std::io::Error::last_os_error()
|
|
);
|
|
std::process::exit(PRCTL_FAILED_EXIT_CODE);
|
|
}
|
|
|
|
// For "defense in depth," set the core file size limit to 0.
|
|
set_core_file_size_limit_to_zero();
|
|
|
|
// Official Codex releases are MUSL-linked, which means that variables such
|
|
// as LD_PRELOAD are ignored anyway, but just to be sure, clear them here.
|
|
remove_env_vars_with_prefix(b"LD_");
|
|
}
|
|
|
|
/// Mark the current Linux process non-dumpable so same-user processes cannot attach with ptrace.
|
|
#[cfg(target_os = "linux")]
|
|
pub fn disable_process_dumping() -> std::io::Result<()> {
|
|
let ret_code = unsafe { libc::prctl(libc::PR_SET_DUMPABLE, 0, 0, 0, 0) };
|
|
if ret_code == 0 {
|
|
Ok(())
|
|
} else {
|
|
Err(std::io::Error::last_os_error())
|
|
}
|
|
}
|
|
|
|
#[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
|
|
pub(crate) fn pre_main_hardening_bsd() {
|
|
// FreeBSD/OpenBSD: set RLIMIT_CORE to 0 and clear LD_* env vars
|
|
set_core_file_size_limit_to_zero();
|
|
|
|
remove_env_vars_with_prefix(b"LD_");
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
pub(crate) fn pre_main_hardening_macos() {
|
|
// Prevent debuggers from attaching to this process.
|
|
let ret_code = unsafe { libc::ptrace(libc::PT_DENY_ATTACH, 0, std::ptr::null_mut(), 0) };
|
|
if ret_code == -1 {
|
|
eprintln!(
|
|
"ERROR: ptrace(PT_DENY_ATTACH) failed: {}",
|
|
std::io::Error::last_os_error()
|
|
);
|
|
std::process::exit(PTRACE_DENY_ATTACH_FAILED_EXIT_CODE);
|
|
}
|
|
|
|
// Set the core file size limit to 0 to prevent core dumps.
|
|
set_core_file_size_limit_to_zero();
|
|
|
|
// Remove all DYLD_ environment variables, which can be used to subvert
|
|
// library loading.
|
|
remove_env_vars_with_prefix(b"DYLD_");
|
|
|
|
// Remove macOS malloc stack-logging controls so allocator diagnostics from
|
|
// Codex or inherited child processes do not get sprayed into the TUI:
|
|
// https://github.com/openai/codex/issues/11555
|
|
remove_env_vars_with_prefix(b"MallocStackLogging");
|
|
remove_env_vars_with_prefix(b"MallocLogFile");
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
fn set_core_file_size_limit_to_zero() {
|
|
let rlim = libc::rlimit {
|
|
rlim_cur: 0,
|
|
rlim_max: 0,
|
|
};
|
|
|
|
let ret_code = unsafe { libc::setrlimit(libc::RLIMIT_CORE, &rlim) };
|
|
if ret_code != 0 {
|
|
eprintln!(
|
|
"ERROR: setrlimit(RLIMIT_CORE) failed: {}",
|
|
std::io::Error::last_os_error()
|
|
);
|
|
std::process::exit(SET_RLIMIT_CORE_FAILED_EXIT_CODE);
|
|
}
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
pub(crate) fn pre_main_hardening_windows() {
|
|
// TODO(mbolin): Perform the appropriate configuration for Windows.
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
fn remove_env_vars_with_prefix(prefix: &[u8]) {
|
|
for key in env_keys_with_prefix(std::env::vars_os(), prefix) {
|
|
unsafe {
|
|
std::env::remove_var(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
fn env_keys_with_prefix<I>(vars: I, prefix: &[u8]) -> Vec<OsString>
|
|
where
|
|
I: IntoIterator<Item = (OsString, OsString)>,
|
|
{
|
|
vars.into_iter()
|
|
.filter_map(|(key, _)| {
|
|
key.as_os_str()
|
|
.as_bytes()
|
|
.starts_with(prefix)
|
|
.then_some(key)
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
#[cfg(all(test, unix))]
|
|
mod tests {
|
|
use super::*;
|
|
use pretty_assertions::assert_eq;
|
|
use std::ffi::OsStr;
|
|
use std::os::unix::ffi::OsStrExt;
|
|
use std::os::unix::ffi::OsStringExt;
|
|
|
|
#[test]
|
|
fn env_keys_with_prefix_handles_non_utf8_entries() {
|
|
// RÖDBURK
|
|
let non_utf8_key1 = OsStr::from_bytes(b"R\xD6DBURK").to_os_string();
|
|
assert!(non_utf8_key1.clone().into_string().is_err());
|
|
let non_utf8_key2 = OsString::from_vec(vec![b'L', b'D', b'_', 0xF0]);
|
|
assert!(non_utf8_key2.clone().into_string().is_err());
|
|
|
|
let non_utf8_value = OsString::from_vec(vec![0xF0, 0x9F, 0x92, 0xA9]);
|
|
|
|
let keys = env_keys_with_prefix(
|
|
vec![
|
|
(non_utf8_key1, non_utf8_value.clone()),
|
|
(non_utf8_key2.clone(), non_utf8_value),
|
|
],
|
|
b"LD_",
|
|
);
|
|
assert_eq!(
|
|
keys,
|
|
vec![non_utf8_key2],
|
|
"non-UTF-8 env entries with LD_ prefix should be retained"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn env_keys_with_prefix_filters_only_matching_keys() {
|
|
let ld_test_var = OsStr::from_bytes(b"LD_TEST");
|
|
let vars = vec![
|
|
(OsString::from("PATH"), OsString::from("/usr/bin")),
|
|
(ld_test_var.to_os_string(), OsString::from("1")),
|
|
(OsString::from("DYLD_FOO"), OsString::from("bar")),
|
|
];
|
|
|
|
let keys = env_keys_with_prefix(vars, b"LD_");
|
|
assert_eq!(keys.len(), 1);
|
|
assert_eq!(keys[0].as_os_str(), ld_test_var);
|
|
}
|
|
}
|