fix: close only exec-inheritable fds before sandbox exec

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
viyatb-oai
2026-04-08 15:53:30 -07:00
parent 074b4a4a24
commit 1f8d56818f
2 changed files with 13 additions and 39 deletions

View File

@@ -1,49 +1,20 @@
//! File descriptor hygiene before entering the sandboxed command.
/// Mark helper-inherited descriptors close-on-exec unless they are standard
/// input/output/error.
/// Close helper-inherited descriptors unless they are standard input/output/error
/// or already close-on-exec.
///
/// 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 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 mark inherited file descriptors close-on-exec: {err}"),
}
pub(crate) fn close_inherited_exec_fds() {
let fds = match non_stdio_fds_from_proc() {
Ok(fds) => fds,
Err(err) => panic!("failed to enumerate inherited file descriptors: {err}"),
};
for fd in fds {
set_fd_cloexec_ignoring_badf(fd);
close_fd_if_inheritable(fd);
}
}
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,
libc::CLOSE_RANGE_CLOEXEC,
)
};
if result == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error())
}
}
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 || code == libc::EINVAL
)
}
fn non_stdio_fds_from_proc() -> std::io::Result<Vec<libc::c_int>> {
let mut fds = Vec::new();
for entry in std::fs::read_dir("/proc/self/fd")? {
@@ -62,7 +33,7 @@ fn non_stdio_fds_from_proc() -> std::io::Result<Vec<libc::c_int>> {
Ok(fds)
}
fn set_fd_cloexec_ignoring_badf(fd: libc::c_int) {
fn close_fd_if_inheritable(fd: libc::c_int) {
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
if flags == -1 {
let err = std::io::Error::last_os_error();
@@ -71,12 +42,15 @@ fn set_fd_cloexec_ignoring_badf(fd: libc::c_int) {
}
return;
}
if flags & libc::FD_CLOEXEC != 0 {
return;
}
let result = unsafe { libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) };
if result == -1 {
let result = unsafe { libc::close(fd) };
if result != 0 {
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}");
panic!("failed to close inherited file descriptor {fd}: {err}");
}
}
}

View File

@@ -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::mark_inherited_fds_cloexec;
use crate::fd_cleanup::close_inherited_exec_fds;
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<String>) -> ! {
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());
mark_inherited_fds_cloexec();
close_inherited_exec_fds();
unsafe {
libc::execvp(c_command.as_ptr(), c_args_ptrs.as_ptr());