@@ -1450,22 +1450,39 @@ fn enumerate_gpu_device_nodes() -> Vec<String> {
14501450 paths
14511451}
14521452
1453- /// Collect all baseline paths for enrichment: proxy defaults + GPU (if present).
1454- /// Returns `(read_only, read_write)` as owned `String` vecs.
1455- fn baseline_enrichment_paths ( ) -> ( Vec < String > , Vec < String > ) {
1456- let mut ro: Vec < String > = PROXY_BASELINE_READ_ONLY
1457- . iter ( )
1458- . map ( |& s| s. to_string ( ) )
1459- . collect ( ) ;
1460- let mut rw: Vec < String > = PROXY_BASELINE_READ_WRITE
1461- . iter ( )
1462- . map ( |& s| s. to_string ( ) )
1463- . collect ( ) ;
1453+ fn push_unique ( paths : & mut Vec < String > , path : String ) {
1454+ if !paths. iter ( ) . any ( |p| p == & path) {
1455+ paths. push ( path) ;
1456+ }
1457+ }
14641458
1465- if has_gpu_devices ( ) {
1466- ro. extend ( GPU_BASELINE_READ_ONLY . iter ( ) . map ( |& s| s. to_string ( ) ) ) ;
1467- rw. extend ( GPU_BASELINE_READ_WRITE . iter ( ) . map ( |& s| s. to_string ( ) ) ) ;
1468- rw. extend ( enumerate_gpu_device_nodes ( ) ) ;
1459+ fn collect_baseline_enrichment_paths (
1460+ include_proxy : bool ,
1461+ include_gpu : bool ,
1462+ gpu_device_nodes : Vec < String > ,
1463+ ) -> ( Vec < String > , Vec < String > ) {
1464+ let mut ro = Vec :: new ( ) ;
1465+ let mut rw = Vec :: new ( ) ;
1466+
1467+ if include_proxy {
1468+ for & path in PROXY_BASELINE_READ_ONLY {
1469+ push_unique ( & mut ro, path. to_string ( ) ) ;
1470+ }
1471+ for & path in PROXY_BASELINE_READ_WRITE {
1472+ push_unique ( & mut rw, path. to_string ( ) ) ;
1473+ }
1474+ }
1475+
1476+ if include_gpu {
1477+ for & path in GPU_BASELINE_READ_ONLY {
1478+ push_unique ( & mut ro, path. to_string ( ) ) ;
1479+ }
1480+ for & path in GPU_BASELINE_READ_WRITE {
1481+ push_unique ( & mut rw, path. to_string ( ) ) ;
1482+ }
1483+ for path in gpu_device_nodes {
1484+ push_unique ( & mut rw, path) ;
1485+ }
14691486 }
14701487
14711488 // A path promoted to read_write (e.g. /proc for GPU) should not also
@@ -1476,14 +1493,33 @@ fn baseline_enrichment_paths() -> (Vec<String>, Vec<String>) {
14761493 ( ro, rw)
14771494}
14781495
1479- /// Ensure a proto `SandboxPolicy` includes the baseline filesystem paths
1480- /// required for proxy-mode sandboxes. Paths are only added if missing;
1481- /// user-specified paths are never removed.
1482- ///
1483- /// Returns `true` if the policy was modified (caller may want to sync back).
1484- fn enrich_proto_baseline_paths ( proto : & mut openshell_core:: proto:: SandboxPolicy ) -> bool {
1485- // Only enrich if network_policies are present (proxy mode indicator).
1486- if proto. network_policies . is_empty ( ) {
1496+ fn active_baseline_enrichment_paths ( include_proxy : bool ) -> ( Vec < String > , Vec < String > ) {
1497+ let include_gpu = has_gpu_devices ( ) ;
1498+ let gpu_device_nodes = if include_gpu {
1499+ enumerate_gpu_device_nodes ( )
1500+ } else {
1501+ Vec :: new ( )
1502+ } ;
1503+ collect_baseline_enrichment_paths ( include_proxy, include_gpu, gpu_device_nodes)
1504+ }
1505+
1506+ /// Collect all active baseline paths for tests and diagnostics.
1507+ /// Returns `(read_only, read_write)` as owned `String` vecs.
1508+ #[ cfg( test) ]
1509+ fn baseline_enrichment_paths ( ) -> ( Vec < String > , Vec < String > ) {
1510+ active_baseline_enrichment_paths ( true )
1511+ }
1512+
1513+ fn enrich_proto_baseline_paths_with < F > (
1514+ proto : & mut openshell_core:: proto:: SandboxPolicy ,
1515+ ro : & [ String ] ,
1516+ rw : & [ String ] ,
1517+ path_exists : F ,
1518+ ) -> bool
1519+ where
1520+ F : Fn ( & str ) -> bool ,
1521+ {
1522+ if ro. is_empty ( ) && rw. is_empty ( ) {
14871523 return false ;
14881524 }
14891525
@@ -1494,17 +1530,10 @@ fn enrich_proto_baseline_paths(proto: &mut openshell_core::proto::SandboxPolicy)
14941530 ..Default :: default ( )
14951531 } ) ;
14961532
1497- let ( ro, rw) = baseline_enrichment_paths ( ) ;
1498-
1499- // Baseline paths are system-injected, not user-specified. Skip paths
1500- // that do not exist in this container image to avoid noisy warnings from
1501- // Landlock and, more critically, to prevent a single missing baseline
1502- // path from abandoning the entire Landlock ruleset under best-effort
1503- // mode (see issue #664).
15041533 let mut modified = false ;
1505- for path in & ro {
1534+ for path in ro {
15061535 if !fs. read_only . iter ( ) . any ( |p| p == path) && !fs. read_write . iter ( ) . any ( |p| p == path) {
1507- if !std :: path :: Path :: new ( path) . exists ( ) {
1536+ if !path_exists ( path) {
15081537 debug ! (
15091538 path,
15101539 "Baseline read-only path does not exist, skipping enrichment"
@@ -1515,11 +1544,11 @@ fn enrich_proto_baseline_paths(proto: &mut openshell_core::proto::SandboxPolicy)
15151544 modified = true ;
15161545 }
15171546 }
1518- for path in & rw {
1547+ for path in rw {
15191548 if fs. read_only . iter ( ) . any ( |p| p == path) || fs. read_write . iter ( ) . any ( |p| p == path) {
15201549 continue ;
15211550 }
1522- if !std :: path :: Path :: new ( path) . exists ( ) {
1551+ if !path_exists ( path) {
15231552 debug ! (
15241553 path,
15251554 "Baseline read-write path does not exist, skipping enrichment"
@@ -1530,6 +1559,26 @@ fn enrich_proto_baseline_paths(proto: &mut openshell_core::proto::SandboxPolicy)
15301559 modified = true ;
15311560 }
15321561
1562+ modified
1563+ }
1564+
1565+ /// Ensure a proto `SandboxPolicy` includes the baseline filesystem paths
1566+ /// required by proxy-mode sandboxes and GPU runtimes. Paths are only added if
1567+ /// missing; user-specified paths are never removed.
1568+ ///
1569+ /// Returns `true` if the policy was modified (caller may want to sync back).
1570+ fn enrich_proto_baseline_paths ( proto : & mut openshell_core:: proto:: SandboxPolicy ) -> bool {
1571+ let ( ro, rw) = active_baseline_enrichment_paths ( !proto. network_policies . is_empty ( ) ) ;
1572+
1573+ // Baseline paths are system-injected, not user-specified. Skip paths
1574+ // that do not exist in this container image to avoid noisy warnings from
1575+ // Landlock and, more critically, to prevent a single missing baseline
1576+ // path from abandoning the entire Landlock ruleset under best-effort
1577+ // mode (see issue #664).
1578+ let modified = enrich_proto_baseline_paths_with ( proto, & ro, & rw, |path| {
1579+ std:: path:: Path :: new ( path) . exists ( )
1580+ } ) ;
1581+
15331582 if modified {
15341583 ocsf_emit ! (
15351584 ConfigStateChangeBuilder :: new( ocsf_ctx( ) )
@@ -1545,15 +1594,15 @@ fn enrich_proto_baseline_paths(proto: &mut openshell_core::proto::SandboxPolicy)
15451594}
15461595
15471596/// Ensure a `SandboxPolicy` (Rust type) includes the baseline filesystem
1548- /// paths required for proxy-mode sandboxes. Used for the local-file code
1549- /// path where no proto is available.
1597+ /// paths required by proxy-mode sandboxes and GPU runtimes. Used for the
1598+ /// local-file code path where no proto is available.
15501599fn enrich_sandbox_baseline_paths ( policy : & mut SandboxPolicy ) {
1551- if !matches ! ( policy. network. mode, NetworkMode :: Proxy ) {
1600+ let ( ro, rw) =
1601+ active_baseline_enrichment_paths ( matches ! ( policy. network. mode, NetworkMode :: Proxy ) ) ;
1602+ if ro. is_empty ( ) && rw. is_empty ( ) {
15521603 return ;
15531604 }
15541605
1555- let ( ro, rw) = baseline_enrichment_paths ( ) ;
1556-
15571606 let mut modified = false ;
15581607 for path in & ro {
15591608 let p = std:: path:: PathBuf :: from ( path) ;
@@ -1708,6 +1757,31 @@ mod baseline_tests {
17081757 ) ;
17091758 }
17101759
1760+ #[ test]
1761+ fn proto_gpu_enrichment_adds_devices_without_network_policy ( ) {
1762+ let mut policy = openshell_policy:: restrictive_default_policy ( ) ;
1763+ assert ! (
1764+ policy. network_policies. is_empty( ) ,
1765+ "regression setup must exercise the no-network default path"
1766+ ) ;
1767+ let ( ro, rw) =
1768+ collect_baseline_enrichment_paths ( false , true , vec ! [ "/dev/nvidia0" . to_string( ) ] ) ;
1769+
1770+ let enriched = enrich_proto_baseline_paths_with ( & mut policy, & ro, & rw, |path| {
1771+ matches ! ( path, "/proc" | "/dev/nvidia0" )
1772+ } ) ;
1773+
1774+ let filesystem = policy. filesystem . expect ( "filesystem policy" ) ;
1775+ assert ! (
1776+ enriched,
1777+ "GPU enrichment should not require network policies"
1778+ ) ;
1779+ assert ! (
1780+ filesystem. read_write. contains( & "/dev/nvidia0" . to_string( ) ) ,
1781+ "GPU enrichment should add enumerated device nodes without network policies"
1782+ ) ;
1783+ }
1784+
17111785 #[ test]
17121786 fn gpu_baseline_read_write_contains_dxg ( ) {
17131787 // /dev/dxg must be present so WSL2 sandboxes get the Landlock
0 commit comments