@@ -59,8 +59,11 @@ pub fn create_oci_spec(
5959 tty : bool ,
6060) -> BoxliteResult < Spec > {
6161 let caps = build_capabilities ( cap_overrides) ?;
62+ let privileged = cap_overrides
63+ . iter ( )
64+ . any ( |c| c. enabled && ( c. name == "ALL" || c. name == "SYS_ADMIN" ) ) ;
6265 let namespaces = build_default_namespaces ( ) ?;
63- let mut mounts = build_standard_mounts ( bundle_path) ?;
66+ let mut mounts = build_standard_mounts ( bundle_path, privileged ) ?;
6467
6568 // Add user-specified bind mounts
6669 for user_mount in user_mounts {
@@ -95,7 +98,7 @@ pub fn create_oci_spec(
9598
9699 let process = build_process_spec ( entrypoint, env, workdir, uid, gid, caps, tty) ?;
97100 let root = build_root_spec ( rootfs) ?;
98- let linux = build_linux_spec ( container_id, namespaces) ?;
101+ let linux = build_linux_spec ( container_id, namespaces, privileged ) ?;
99102
100103 SpecBuilder :: default ( )
101104 . version ( "1.0.2" )
@@ -460,6 +463,7 @@ fn build_root_spec(rootfs: &str) -> BoxliteResult<oci_spec::runtime::Root> {
460463fn build_linux_spec (
461464 container_id : & str ,
462465 namespaces : Vec < oci_spec:: runtime:: LinuxNamespace > ,
466+ privileged : bool ,
463467) -> BoxliteResult < oci_spec:: runtime:: Linux > {
464468 // UID/GID mappings for user namespace
465469 // Map full range of UIDs/GIDs to allow non-root users (nginx=33, etc.)
@@ -507,19 +511,28 @@ fn build_linux_spec(
507511 // let cgroups_path = format!("/boxlite/{}", container_id);
508512 let _ = container_id; // Suppress unused warning
509513
510- LinuxBuilder :: default ( )
514+ let mut builder = LinuxBuilder :: default ( )
511515 . namespaces ( namespaces)
512516 . uid_mappings ( uid_mappings)
513- . gid_mappings ( gid_mappings)
514- // .masked_paths(masked_paths)
515- // .readonly_paths(readonly_paths)
516- // .cgroups_path(cgroups_path)
517+ . gid_mappings ( gid_mappings) ;
518+ // .cgroups_path(cgroups_path)
519+
520+ if privileged {
521+ // DinD writes /proc/sys/net/ipv4/ip_forward when bringing up its
522+ // bridge. Clearing these lists matches the privileged-container shape
523+ // needed for that path while non-privileged boxes keep OCI defaults.
524+ builder = builder
525+ . masked_paths ( Vec :: < String > :: new ( ) )
526+ . readonly_paths ( Vec :: < String > :: new ( ) ) ;
527+ }
528+
529+ builder
517530 . build ( )
518531 . map_err ( |e| BoxliteError :: Internal ( format ! ( "Failed to build linux spec: {}" , e) ) )
519532}
520533
521534/// Build standard mounts for container filesystem
522- fn build_standard_mounts ( bundle_path : & Path ) -> BoxliteResult < Vec < Mount > > {
535+ fn build_standard_mounts ( bundle_path : & Path , cgroup_rw : bool ) -> BoxliteResult < Vec < Mount > > {
523536 let mut mounts = vec ! [
524537 // /proc - Process information
525538 MountBuilder :: default ( )
@@ -610,6 +623,27 @@ fn build_standard_mounts(bundle_path: &Path) -> BoxliteResult<Vec<Mount>> {
610623 // .map_err(|e| {
611624 // BoxliteError::Internal(format!("Failed to build /sys/fs/cgroup mount: {}", e))
612625 // })?,
626+ ] ;
627+
628+ if cgroup_rw {
629+ mounts. push (
630+ MountBuilder :: default ( )
631+ . destination ( "/sys/fs/cgroup" )
632+ . typ ( "cgroup2" )
633+ . source ( "cgroup2" )
634+ . options ( vec ! [
635+ "nosuid" . to_string( ) ,
636+ "noexec" . to_string( ) ,
637+ "nodev" . to_string( ) ,
638+ ] )
639+ . build ( )
640+ . map_err ( |e| {
641+ BoxliteError :: Internal ( format ! ( "Failed to build cgroup2 mount: {}" , e) )
642+ } ) ?,
643+ ) ;
644+ }
645+
646+ mounts. extend ( vec ! [
613647 // /tmp - Temporary filesystem
614648 MountBuilder :: default ( )
615649 . destination( "/tmp" )
@@ -622,7 +656,7 @@ fn build_standard_mounts(bundle_path: &Path) -> BoxliteResult<Vec<Mount>> {
622656 ] )
623657 . build( )
624658 . map_err( |e| BoxliteError :: Internal ( format!( "Failed to build /tmp mount: {}" , e) ) ) ?,
625- ] ;
659+ ] ) ;
626660
627661 // Bind-mount /etc/hostname, /etc/hosts, /etc/resolv.conf from the bundle
628662 // dir into the container. Uses rbind + rprivate (matching Docker defaults).
@@ -1292,7 +1326,7 @@ mod tests {
12921326
12931327 #[ test]
12941328 fn linux_spec_keeps_default_proc_sys_hardening ( ) {
1295- let spec = build_linux_spec ( "c" , build_default_namespaces ( ) . unwrap ( ) ) . unwrap ( ) ;
1329+ let spec = build_linux_spec ( "c" , build_default_namespaces ( ) . unwrap ( ) , false ) . unwrap ( ) ;
12961330 let ro = spec
12971331 . readonly_paths ( )
12981332 . as_ref ( )
@@ -1302,4 +1336,40 @@ mod tests {
13021336 "security options must not implicitly make /proc/sys writable; got {ro:?}"
13031337 ) ;
13041338 }
1339+
1340+ #[ test]
1341+ fn privileged_linux_spec_clears_readonly_and_masked_paths ( ) {
1342+ let spec = build_linux_spec ( "c" , build_default_namespaces ( ) . unwrap ( ) , true ) . unwrap ( ) ;
1343+ assert_eq ! (
1344+ spec. readonly_paths( ) . as_ref( ) . map( Vec :: len) ,
1345+ Some ( 0 ) ,
1346+ "privileged containers need writable /proc/sys for DinD bridge setup"
1347+ ) ;
1348+ assert_eq ! (
1349+ spec. masked_paths( ) . as_ref( ) . map( Vec :: len) ,
1350+ Some ( 0 ) ,
1351+ "privileged containers should clear masked paths"
1352+ ) ;
1353+ }
1354+
1355+ #[ test]
1356+ fn privileged_standard_mounts_add_cgroup2 ( ) {
1357+ let dir = tempfile:: TempDir :: new ( ) . unwrap ( ) ;
1358+ let mounts = build_standard_mounts ( dir. path ( ) , true ) . unwrap ( ) ;
1359+ assert ! (
1360+ mounts
1361+ . iter( )
1362+ . any( |m| m. destination( ) == "/sys/fs/cgroup"
1363+ && m. typ( ) . as_deref( ) == Some ( "cgroup2" ) ) ,
1364+ "privileged DinD path must mount writable cgroup2"
1365+ ) ;
1366+
1367+ let unprivileged_mounts = build_standard_mounts ( dir. path ( ) , false ) . unwrap ( ) ;
1368+ assert ! (
1369+ unprivileged_mounts
1370+ . iter( )
1371+ . all( |m| m. destination( ) != "/sys/fs/cgroup" ) ,
1372+ "default boxes should not pay the cgroup2 mount cost"
1373+ ) ;
1374+ }
13051375}
0 commit comments