Skip to content

Commit 280002f

Browse files
authored
Merge pull request #7 from kagenti/fix/sandbox-tmp-permissions
fix(sandbox): ensure read_write directories are writable by sandbox user
2 parents 603ee40 + 7c4d1bc commit 280002f

1 file changed

Lines changed: 49 additions & 9 deletions

File tree

  • crates/openshell-sandbox/src

crates/openshell-sandbox/src/lib.rs

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1856,7 +1856,9 @@ fn validate_sandbox_user(policy: &SandboxPolicy) -> Result<()> {
18561856
/// still needs to be chowned to the sandbox user/group. Existing paths keep
18571857
/// their image-defined ownership.
18581858
#[cfg(unix)]
1859-
fn prepare_read_write_path(path: &std::path::Path) -> Result<bool> {
1859+
fn prepare_read_write_path(path: &std::path::Path, uid: Option<nix::unistd::Uid>) -> Result<bool> {
1860+
use std::os::unix::fs::{MetadataExt, PermissionsExt};
1861+
18601862
// SECURITY: use symlink_metadata (lstat) to inspect each path *before*
18611863
// calling chown. chown follows symlinks, so a malicious container image
18621864
// could place a symlink (e.g. /sandbox -> /etc/shadow) to trick the
@@ -1871,10 +1873,28 @@ fn prepare_read_write_path(path: &std::path::Path) -> Result<bool> {
18711873
));
18721874
}
18731875

1874-
debug!(
1875-
path = %path.display(),
1876-
"Preserving ownership for existing read_write path"
1877-
);
1876+
// Ensure existing read_write directories are accessible by the sandbox
1877+
// user. Container images may ship directories like /tmp as root:root
1878+
// 0700, which prevents the unprivileged sandbox user from writing.
1879+
if meta.is_dir() {
1880+
let mode = meta.permissions().mode();
1881+
let owned_by_target = uid.is_some_and(|u| u.as_raw() == meta.uid());
1882+
let world_writable = mode & 0o002 != 0;
1883+
let user_writable = owned_by_target && (mode & 0o200 != 0);
1884+
1885+
if !world_writable && !user_writable {
1886+
let new_mode = 0o1777;
1887+
debug!(
1888+
path = %path.display(),
1889+
old_mode = format!("{:04o}", mode & 0o7777),
1890+
new_mode = format!("{:04o}", new_mode),
1891+
"Fixing permissions on existing read_write directory"
1892+
);
1893+
std::fs::set_permissions(path, std::fs::Permissions::from_mode(new_mode))
1894+
.into_diagnostic()?;
1895+
}
1896+
}
1897+
18781898
Ok(false)
18791899
} else {
18801900
debug!(path = %path.display(), "Creating read_write directory");
@@ -1930,8 +1950,9 @@ fn prepare_filesystem(policy: &SandboxPolicy) -> Result<()> {
19301950
};
19311951

19321952
// Create missing read_write paths and only chown the ones we created.
1953+
// For existing paths, ensure they are writable by the sandbox user.
19331954
for path in &policy.filesystem.read_write {
1934-
if prepare_read_write_path(path)? {
1955+
if prepare_read_write_path(path, uid)? {
19351956
debug!(
19361957
path = %path.display(),
19371958
?uid,
@@ -2725,7 +2746,7 @@ filesystem_policy:
27252746
let dir = tempfile::tempdir().unwrap();
27262747
let missing = dir.path().join("missing").join("nested");
27272748

2728-
assert!(prepare_read_write_path(&missing).unwrap());
2749+
assert!(prepare_read_write_path(&missing, None).unwrap());
27292750
assert!(missing.is_dir());
27302751
}
27312752

@@ -2736,7 +2757,7 @@ filesystem_policy:
27362757
let existing = dir.path().join("existing");
27372758
std::fs::create_dir(&existing).unwrap();
27382759

2739-
assert!(!prepare_read_write_path(&existing).unwrap());
2760+
assert!(!prepare_read_write_path(&existing, None).unwrap());
27402761
assert!(existing.is_dir());
27412762
}
27422763

@@ -2749,7 +2770,7 @@ filesystem_policy:
27492770
std::fs::create_dir(&target).unwrap();
27502771
symlink(&target, &link).unwrap();
27512772

2752-
let error = prepare_read_write_path(&link).unwrap_err();
2773+
let error = prepare_read_write_path(&link, None).unwrap_err();
27532774
assert!(
27542775
error
27552776
.to_string()
@@ -2792,4 +2813,23 @@ filesystem_policy:
27922813
assert_eq!(after.uid(), before.uid());
27932814
assert_eq!(after.gid(), before.gid());
27942815
}
2816+
2817+
#[cfg(unix)]
2818+
#[test]
2819+
fn prepare_read_write_path_fixes_inaccessible_tmp() {
2820+
use std::os::unix::fs::PermissionsExt;
2821+
2822+
let dir = tempfile::tempdir().unwrap();
2823+
let tmp = dir.path().join("tmp");
2824+
std::fs::create_dir(&tmp).unwrap();
2825+
// Simulate container image shipping /tmp as root:root 0700
2826+
std::fs::set_permissions(&tmp, std::fs::Permissions::from_mode(0o700)).unwrap();
2827+
2828+
// Use a UID different from the owner to trigger the fix
2829+
let fake_uid = nix::unistd::Uid::from_raw(998);
2830+
assert!(!prepare_read_write_path(&tmp, Some(fake_uid)).unwrap());
2831+
2832+
let meta = std::fs::metadata(&tmp).unwrap();
2833+
assert_eq!(meta.permissions().mode() & 0o7777, 0o1777);
2834+
}
27952835
}

0 commit comments

Comments
 (0)