feat: add support for CODEX_SECURE_MODE=1 to restrict process observability (#4220)
Because the `codex` process could contain sensitive information in memory, such as API keys, we add logic so that when `CODEX_SECURE_MODE=1` is specified, we avail ourselves of whatever the operating system provides to restrict observability/tampering, which includes: - disabling `ptrace(2)`, so it is not possible to attach to the process with a debugger, such as `gdb` - disabling core dumps Admittedly, a user with root privileges can defeat these safeguards. For now, we only add support for this in the `codex` multitool, but we may ultimately want to support this in some of the smaller CLIs that are buildable out of our Cargo workspace.
This commit is contained in:
35
codex-rs/Cargo.lock
generated
35
codex-rs/Cargo.lock
generated
@@ -56,7 +56,7 @@ checksum = "8fac2ce611db8b8cee9b2aa886ca03c924e9da5e5295d0dbd0526e5d0b0710f7"
|
||||
dependencies = [
|
||||
"allocative_derive",
|
||||
"bumpalo",
|
||||
"ctor",
|
||||
"ctor 0.1.26",
|
||||
"hashbrown 0.14.5",
|
||||
"num-bigint",
|
||||
]
|
||||
@@ -641,6 +641,8 @@ dependencies = [
|
||||
"codex-protocol",
|
||||
"codex-protocol-ts",
|
||||
"codex-tui",
|
||||
"ctor 0.5.0",
|
||||
"libc",
|
||||
"owo-colors",
|
||||
"predicates",
|
||||
"pretty_assertions",
|
||||
@@ -1213,6 +1215,22 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67773048316103656a637612c4a62477603b777d91d9c62ff2290f9cde178fdb"
|
||||
dependencies = [
|
||||
"ctor-proc-macro",
|
||||
"dtor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor-proc-macro"
|
||||
version = "0.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2"
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.11"
|
||||
@@ -1463,6 +1481,21 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||
|
||||
[[package]]
|
||||
name = "dtor"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e58a0764cddb55ab28955347b45be00ade43d4d6f3ba4bf3dc354e4ec9432934"
|
||||
dependencies = [
|
||||
"dtor-proc-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtor-proc-macro"
|
||||
version = "0.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5"
|
||||
|
||||
[[package]]
|
||||
name = "dupe"
|
||||
version = "0.9.1"
|
||||
|
||||
@@ -72,6 +72,7 @@ clap = "4"
|
||||
clap_complete = "4"
|
||||
color-eyre = "0.6.3"
|
||||
crossterm = "0.28.1"
|
||||
ctor = "0.5.0"
|
||||
derive_more = "2"
|
||||
diffy = "0.4.2"
|
||||
dirs = "6"
|
||||
|
||||
@@ -28,6 +28,7 @@ codex-mcp-server = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-protocol-ts = { workspace = true }
|
||||
codex-tui = { workspace = true }
|
||||
ctor = { workspace = true }
|
||||
owo-colors = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
supports-color = { workspace = true }
|
||||
@@ -41,6 +42,15 @@ tokio = { workspace = true, features = [
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
libc = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
libc = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
libc = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = { workspace = true }
|
||||
predicates = { workspace = true }
|
||||
|
||||
@@ -21,6 +21,7 @@ use std::path::PathBuf;
|
||||
use supports_color::Stream;
|
||||
|
||||
mod mcp_cmd;
|
||||
mod pre_main_hardening;
|
||||
|
||||
use crate::mcp_cmd::McpCli;
|
||||
use crate::proto::ProtoCli;
|
||||
@@ -194,6 +195,34 @@ fn print_exit_messages(exit_info: AppExitInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const CODEX_SECURE_MODE_ENV_VAR: &str = "CODEX_SECURE_MODE";
|
||||
|
||||
/// As early as possible in the process lifecycle, apply hardening measures
|
||||
/// if the CODEX_SECURE_MODE environment variable is set to "1".
|
||||
#[ctor::ctor]
|
||||
fn pre_main_hardening() {
|
||||
let secure_mode = match std::env::var(CODEX_SECURE_MODE_ENV_VAR) {
|
||||
Ok(value) => value,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
if secure_mode == "1" {
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
crate::pre_main_hardening::pre_main_hardening_linux();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
crate::pre_main_hardening::pre_main_hardening_macos();
|
||||
|
||||
#[cfg(windows)]
|
||||
crate::pre_main_hardening::pre_main_hardening_windows();
|
||||
}
|
||||
|
||||
// Always clear this env var so child processes don't inherit it.
|
||||
unsafe {
|
||||
std::env::remove_var(CODEX_SECURE_MODE_ENV_VAR);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
arg0_dispatch_or_else(|codex_linux_sandbox_exe| async move {
|
||||
cli_main(codex_linux_sandbox_exe).await?;
|
||||
|
||||
98
codex-rs/cli/src/pre_main_hardening.rs
Normal file
98
codex-rs/cli/src/pre_main_hardening.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
#[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"))]
|
||||
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.
|
||||
let ld_keys: Vec<String> = std::env::vars()
|
||||
.filter_map(|(key, _)| {
|
||||
if key.starts_with("LD_") {
|
||||
Some(key)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
for key in ld_keys {
|
||||
unsafe {
|
||||
std::env::remove_var(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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.
|
||||
let dyld_keys: Vec<String> = std::env::vars()
|
||||
.filter_map(|(key, _)| {
|
||||
if key.starts_with("DYLD_") {
|
||||
Some(key)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
for key in dyld_keys {
|
||||
unsafe {
|
||||
std::env::remove_var(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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.
|
||||
}
|
||||
Reference in New Issue
Block a user