From e87d25f8dcd8b346320282d45a153c0f604da1d8 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Thu, 1 Oct 2026 17:19:33 -0700 Subject: [PATCH 1/2] fix(sandbox): restrict provider file mode (fixes #4091) Signed-off-by: Drew Newberry --- crates/openshell-sandbox/src/provider_files.rs | 7 +++++++ e2e/rust/tests/provider_files.rs | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/crates/openshell-sandbox/src/provider_files.rs b/crates/openshell-sandbox/src/provider_files.rs index 5efd4270f2..2ac5748886 100644 --- a/crates/openshell-sandbox/src/provider_files.rs +++ b/crates/openshell-sandbox/src/provider_files.rs @@ -211,6 +211,11 @@ fn sealed_memfd(content: &[u8]) -> io::Result { return Err(io::Error::last_os_error()); } let mut file = unsafe { File::from_raw_fd(fd) }; + // memfd_create defaults to 0777. Keep the metadata private as well as the + // returned descriptor read-only, since workloads may inspect it with fstat. + if unsafe { libc::fchmod(file.as_raw_fd(), 0o600) } < 0 { + return Err(io::Error::last_os_error()); + } file.write_all(content)?; file.seek(SeekFrom::Start(0))?; let seals = libc::F_SEAL_SEAL | libc::F_SEAL_WRITE | libc::F_SEAL_GROW | libc::F_SEAL_SHRINK; @@ -228,6 +233,7 @@ mod tests { use std::collections::HashMap; use std::io::Read as _; use std::os::fd::AsRawFd as _; + use std::os::unix::fs::PermissionsExt as _; #[test] fn paths_cannot_escape_the_managed_tree() { @@ -249,6 +255,7 @@ mod tests { #[test] fn memfd_is_read_only_and_positioned_at_start() { let mut file = sealed_memfd(b"version = 1\n").unwrap(); + assert_eq!(file.metadata().unwrap().permissions().mode() & 0o777, 0o600); let flags = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_GETFL) }; assert_eq!(flags & libc::O_ACCMODE, libc::O_RDONLY); let mut read = String::new(); diff --git a/e2e/rust/tests/provider_files.rs b/e2e/rust/tests/provider_files.rs index c2779fe097..18db5db9b7 100644 --- a/e2e/rust/tests/provider_files.rs +++ b/e2e/rust/tests/provider_files.rs @@ -107,6 +107,23 @@ async fn provider_file_open_update_and_detach() -> Result<(), String> { if !before.contains("project = \"production\"") { return Err(format!("initial provider file content missing:\n{before}")); } + let permissions = sandbox + .exec(&[ + "sh", + "-c", + &format!( + "set -eu; exec 3<{path}; test \"$(stat -Lc %a /proc/self/fd/3)\" = 600; \ + test \"$(stat -Lc %F /proc/self/fd/3)\" = 'regular file'; \ + if (printf x >&3) 2>/dev/null; then exit 1; fi; \ + echo provider-file-permissions-ok" + ), + ]) + .await?; + if !permissions.contains("provider-file-permissions-ok") { + return Err(format!( + "provider file permission assertion did not complete:\n{permissions}" + )); + } cli_ok(&[ "provider", From f68d7e7cc9b6b1d35943726a5ff3ab7f99589614 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Thu, 1 Oct 2026 21:26:09 -0700 Subject: [PATCH 2/2] refactor(sandbox): set provider file mode with safe API Signed-off-by: Drew Newberry --- crates/openshell-sandbox/src/provider_files.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/openshell-sandbox/src/provider_files.rs b/crates/openshell-sandbox/src/provider_files.rs index 2ac5748886..ab719cd355 100644 --- a/crates/openshell-sandbox/src/provider_files.rs +++ b/crates/openshell-sandbox/src/provider_files.rs @@ -7,9 +7,10 @@ use std::collections::HashMap; use std::ffi::CString; -use std::fs::File; +use std::fs::{File, Permissions}; use std::io::{self, Seek as _, SeekFrom, Write as _}; use std::os::fd::{AsRawFd as _, FromRawFd as _}; +use std::os::unix::fs::PermissionsExt as _; use std::sync::{Arc, RwLock}; use openshell_isolation_interface::linux::seccomp_notify::{Notification, NotificationListener}; @@ -213,9 +214,7 @@ fn sealed_memfd(content: &[u8]) -> io::Result { let mut file = unsafe { File::from_raw_fd(fd) }; // memfd_create defaults to 0777. Keep the metadata private as well as the // returned descriptor read-only, since workloads may inspect it with fstat. - if unsafe { libc::fchmod(file.as_raw_fd(), 0o600) } < 0 { - return Err(io::Error::last_os_error()); - } + file.set_permissions(Permissions::from_mode(0o600))?; file.write_all(content)?; file.seek(SeekFrom::Start(0))?; let seals = libc::F_SEAL_SEAL | libc::F_SEAL_WRITE | libc::F_SEAL_GROW | libc::F_SEAL_SHRINK;