@@ -340,12 +340,16 @@ fn rootfs_path(spec: &Spec, bundle: &Path) -> Result<Option<PathBuf>> {
340340/// - **tmpfs** writable scratch (`/tmp`, `/run`, `/dev/shm`, …): backed by a
341341/// host directory under the sandbox state dir and bind-mounted read-write,
342342/// so it works on a read-only root and stays isolated from the rootfs.
343- /// - **proc**: `fs_mount(dest, /proc)` — the host procfs, so the sandbox gets a
344- /// full `/proc` like a container; the seccomp `/proc` handler synthesizes the
345- /// limit-aware files on top and blocks foreign PIDs / sensitive paths.
346- /// - **tmpfs at `/dev`, sysfs**: passed through read-only so sandlock's `/dev`
347- /// interception services them; an empty backing dir would shadow `/dev/null`,
348- /// `/sys/*`, etc.
343+ /// - **proc**: `fs_mount(dest, /proc)` read-only — the host procfs, so the
344+ /// sandbox gets a full `/proc` like a container; the seccomp `/proc` handler
345+ /// synthesizes the limit-aware files on top and blocks foreign PIDs /
346+ /// sensitive paths. Read-only blocks host-global writes (`/proc/sys/*`).
347+ /// - **sysfs**: `fs_mount(dest, /sys)` read-only — the host sysfs, so the
348+ /// sandbox sees a populated `/sys`. Read-only blocks host-global writes
349+ /// (`/sys/power/state`, …); there is no per-path filter, so reads are full
350+ /// host sysfs (as in a container's ro `/sys`).
351+ /// - **tmpfs at `/dev`**: passed through read-only so sandlock's `/dev`
352+ /// interception services it; an empty backing dir would shadow `/dev/null`.
349353/// - **devpts/mqueue/cgroup**: skipped (no safe namespace-less equivalent).
350354fn map_mounts (
351355 mounts : & [ oci_spec:: runtime:: Mount ] ,
@@ -377,9 +381,17 @@ fn map_mounts(
377381 fs_mount. push ( ( dest. to_path_buf ( ) , PathBuf :: from ( "/proc" ) ) ) ;
378382 fs_mount_ro. push ( dest. to_path_buf ( ) ) ;
379383 }
380- // sysfs: pass the host's through read-only; sandlock's sensitive-path
381- // filter still gates it.
382- "sysfs" => fs_read. push ( PathBuf :: from ( "/sys" ) ) ,
384+ // sysfs: mount the host /sys READ-ONLY at the requested destination
385+ // so the sandbox sees a populated /sys like a container (an empty
386+ // rootfs /sys is otherwise bare). Read-only is mandatory: writable
387+ // sysfs exposes host-global controls (/sys/power/state,
388+ // /sys/kernel/*, device controls). Unlike /proc there is no
389+ // per-path sandlock filter for sysfs, so this is full read-only host
390+ // sysfs exposure (consistent with a container's ro /sys mount).
391+ "sysfs" => {
392+ fs_mount. push ( ( dest. to_path_buf ( ) , PathBuf :: from ( "/sys" ) ) ) ;
393+ fs_mount_ro. push ( dest. to_path_buf ( ) ) ;
394+ }
383395
384396 "tmpfs" => {
385397 if dest. to_str ( ) == Some ( "/dev" ) {
@@ -656,6 +668,7 @@ mod tests {
656668 "mounts" : [
657669 { "destination" : "/tmp" , "type" : "tmpfs" , "source" : "tmpfs" } ,
658670 { "destination" : "/proc" , "type" : "proc" , "source" : "proc" } ,
671+ { "destination" : "/sys" , "type" : "sysfs" , "source" : "sysfs" } ,
659672 { "destination" : "/dev" , "type" : "tmpfs" , "source" : "tmpfs" }
660673 ]
661674 } ) ;
@@ -681,6 +694,12 @@ mod tests {
681694 // ...and it is read-only, so the sandbox can't write host-global
682695 // controls like /proc/sys/* through the mount.
683696 assert ! ( policy. fs_mount_ro. contains( & PathBuf :: from( "/proc" ) ) ) ;
697+ // /sys mounts the host sysfs read-only, same as /proc.
698+ assert ! ( policy
699+ . fs_mount
700+ . iter( )
701+ . any( |( d, h) | d == Path :: new( "/sys" ) && h == Path :: new( "/sys" ) ) ) ;
702+ assert ! ( policy. fs_mount_ro. contains( & PathBuf :: from( "/sys" ) ) ) ;
684703 // /dev is passed through read-only, not emulated.
685704 assert ! ( policy. fs_read. contains( & PathBuf :: from( "/dev" ) ) ) ;
686705 assert ! ( !policy. fs_mount. iter( ) . any( |( d, _) | d == Path :: new( "/dev" ) ) ) ;
0 commit comments