From 3377afd84a420193839a06e237118e7a267e9f94 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Tue, 28 Apr 2026 11:52:50 -0700 Subject: [PATCH] fix(network-proxy): harden linux proxy bridge helpers (#20001) ## 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 --- codex-rs/Cargo.lock | 1 + codex-rs/linux-sandbox/Cargo.toml | 1 + codex-rs/linux-sandbox/src/landlock.rs | 2 ++ codex-rs/linux-sandbox/src/proxy_routing.rs | 9 +++++++-- codex-rs/process-hardening/src/lib.rs | 11 +++++++++++ 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 968adba7a..5d17c0f60 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -2816,6 +2816,7 @@ dependencies = [ "cc", "clap", "codex-core", + "codex-process-hardening", "codex-protocol", "codex-sandboxing", "codex-utils-absolute-path", diff --git a/codex-rs/linux-sandbox/Cargo.toml b/codex-rs/linux-sandbox/Cargo.toml index 519ae5138..05967661e 100644 --- a/codex-rs/linux-sandbox/Cargo.toml +++ b/codex-rs/linux-sandbox/Cargo.toml @@ -17,6 +17,7 @@ workspace = true [target.'cfg(target_os = "linux")'.dependencies] clap = { workspace = true, features = ["derive"] } +codex-process-hardening = { workspace = true } codex-protocol = { workspace = true } codex-sandboxing = { workspace = true } codex-utils-absolute-path = { workspace = true } diff --git a/codex-rs/linux-sandbox/src/landlock.rs b/codex-rs/linux-sandbox/src/landlock.rs index 579498253..50ea87bd1 100644 --- a/codex-rs/linux-sandbox/src/landlock.rs +++ b/codex-rs/linux-sandbox/src/landlock.rs @@ -176,6 +176,8 @@ fn install_network_seccomp_filter_on_current_thread( let mut rules: BTreeMap> = BTreeMap::new(); deny_syscall(&mut rules, libc::SYS_ptrace); + deny_syscall(&mut rules, libc::SYS_process_vm_readv); + deny_syscall(&mut rules, libc::SYS_process_vm_writev); deny_syscall(&mut rules, libc::SYS_io_uring_setup); deny_syscall(&mut rules, libc::SYS_io_uring_enter); deny_syscall(&mut rules, libc::SYS_io_uring_register); diff --git a/codex-rs/linux-sandbox/src/proxy_routing.rs b/codex-rs/linux-sandbox/src/proxy_routing.rs index 07e1893ee..d28b86466 100644 --- a/codex-rs/linux-sandbox/src/proxy_routing.rs +++ b/codex-rs/linux-sandbox/src/proxy_routing.rs @@ -450,7 +450,7 @@ fn spawn_host_bridge(endpoint: SocketAddr, uds_path: &Path) -> io::Result io::Result<()> { - set_parent_death_signal()?; + harden_bridge_process()?; if uds_path.exists() { std::fs::remove_file(uds_path)?; } @@ -501,7 +501,7 @@ fn spawn_local_bridge(uds_path: &Path) -> io::Result { } fn run_local_bridge(uds_path: &Path, ready_fd: libc::c_int) -> io::Result<()> { - set_parent_death_signal()?; + harden_bridge_process()?; let listener = bind_local_loopback_listener()?; let port = listener.local_addr()?.port(); @@ -614,6 +614,11 @@ fn set_parent_death_signal() -> io::Result<()> { } } +fn harden_bridge_process() -> io::Result<()> { + set_parent_death_signal()?; + codex_process_hardening::disable_process_dumping() +} + fn proxy_bidirectional(mut tcp_stream: TcpStream, mut unix_stream: UnixStream) -> io::Result<()> { let mut tcp_reader = tcp_stream.try_clone()?; let mut unix_writer = unix_stream.try_clone()?; diff --git a/codex-rs/process-hardening/src/lib.rs b/codex-rs/process-hardening/src/lib.rs index 1c206aae8..f9695fcbd 100644 --- a/codex-rs/process-hardening/src/lib.rs +++ b/codex-rs/process-hardening/src/lib.rs @@ -61,6 +61,17 @@ pub(crate) fn pre_main_hardening_linux() { 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