@@ -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