diff --git a/codex-rs/linux-sandbox/src/fd_cleanup.rs b/codex-rs/linux-sandbox/src/fd_cleanup.rs index 8a82555501..3908f9f8fb 100644 --- a/codex-rs/linux-sandbox/src/fd_cleanup.rs +++ b/codex-rs/linux-sandbox/src/fd_cleanup.rs @@ -1,14 +1,15 @@ //! File descriptor hygiene before entering the sandboxed command. -/// Close helper-inherited descriptors that are not standard input/output/error. +/// Mark helper-inherited descriptors close-on-exec unless they are standard +/// input/output/error. /// /// The sandboxed command can still create allowed local IPC after exec, but it /// must not inherit an already-connected network socket from the launcher. -pub(crate) fn close_inherited_fds() { - match close_fds_with_close_range() { +pub(crate) fn mark_inherited_fds_cloexec() { + match set_cloexec_with_close_range() { Ok(()) => return, Err(err) if can_fallback_from_close_range(&err) => {} - Err(err) => panic!("failed to close inherited file descriptors: {err}"), + Err(err) => panic!("failed to mark inherited file descriptors close-on-exec: {err}"), } let fds = match non_stdio_fds_from_proc() { @@ -16,17 +17,17 @@ pub(crate) fn close_inherited_fds() { Err(err) => panic!("failed to enumerate inherited file descriptors: {err}"), }; for fd in fds { - close_fd_ignoring_badf(fd); + set_fd_cloexec_ignoring_badf(fd); } } -fn close_fds_with_close_range() -> std::io::Result<()> { +fn set_cloexec_with_close_range() -> std::io::Result<()> { let result = unsafe { libc::syscall( libc::SYS_close_range, (libc::STDERR_FILENO + 1) as libc::c_uint, u32::MAX as libc::c_uint, - 0 as libc::c_uint, + libc::CLOSE_RANGE_CLOEXEC, ) }; if result == 0 { @@ -39,7 +40,7 @@ fn close_fds_with_close_range() -> std::io::Result<()> { fn can_fallback_from_close_range(err: &std::io::Error) -> bool { matches!( err.raw_os_error(), - Some(code) if code == libc::ENOSYS || code == libc::EPERM + Some(code) if code == libc::ENOSYS || code == libc::EPERM || code == libc::EINVAL ) } @@ -61,13 +62,21 @@ fn non_stdio_fds_from_proc() -> std::io::Result> { Ok(fds) } -fn close_fd_ignoring_badf(fd: libc::c_int) { - let result = unsafe { libc::close(fd) }; - if result == 0 { +fn set_fd_cloexec_ignoring_badf(fd: libc::c_int) { + let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) }; + if flags == -1 { + let err = std::io::Error::last_os_error(); + if err.raw_os_error() != Some(libc::EBADF) { + panic!("failed to inspect inherited file descriptor {fd}: {err}"); + } return; } - let err = std::io::Error::last_os_error(); - if err.raw_os_error() != Some(libc::EBADF) { - panic!("failed to close inherited file descriptor {fd}: {err}"); + + let result = unsafe { libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) }; + if result == -1 { + let err = std::io::Error::last_os_error(); + if err.raw_os_error() != Some(libc::EBADF) { + panic!("failed to mark inherited file descriptor {fd} close-on-exec: {err}"); + } } } diff --git a/codex-rs/linux-sandbox/src/linux_run_main.rs b/codex-rs/linux-sandbox/src/linux_run_main.rs index 119f3fd9fc..605aa3601b 100644 --- a/codex-rs/linux-sandbox/src/linux_run_main.rs +++ b/codex-rs/linux-sandbox/src/linux_run_main.rs @@ -10,7 +10,7 @@ use std::path::PathBuf; use crate::bwrap::BwrapNetworkMode; use crate::bwrap::BwrapOptions; use crate::bwrap::create_bwrap_command_args; -use crate::fd_cleanup::close_inherited_fds; +use crate::fd_cleanup::mark_inherited_fds_cloexec; use crate::landlock::apply_sandbox_policy_to_current_thread; use crate::launcher::exec_bwrap; use crate::launcher::preferred_bwrap_supports_argv0; @@ -730,7 +730,7 @@ fn exec_or_panic(command: Vec) -> ! { let mut c_args_ptrs: Vec<*const libc::c_char> = c_args.iter().map(|arg| arg.as_ptr()).collect(); c_args_ptrs.push(std::ptr::null()); - close_inherited_fds(); + mark_inherited_fds_cloexec(); unsafe { libc::execvp(c_command.as_ptr(), c_args_ptrs.as_ptr());