diff --git a/architecture/security-policy.md b/architecture/security-policy.md index ad8d7d0ec..b9963e773 100644 --- a/architecture/security-policy.md +++ b/architecture/security-policy.md @@ -21,6 +21,13 @@ For the field-by-field YAML reference, use Filesystem and process policy are startup-time controls. Network policy is dynamic and can be hot-reloaded when the new policy validates successfully. +Before applying Landlock, the supervisor enriches baseline filesystem paths that +the runtime needs. Missing baseline paths are skipped so one absent runtime path +does not weaken the whole ruleset. When GPU devices are present, GPU baseline +enrichment adds existing GPU device nodes as read-write paths and promotes +`/proc` to read-write because CUDA workloads write thread metadata under +`/proc//task//comm`. + ## Network Decisions Ordinary network traffic follows this order: diff --git a/crates/openshell-sandbox/src/lib.rs b/crates/openshell-sandbox/src/lib.rs index 4a0e61e57..39d71c7c7 100644 --- a/crates/openshell-sandbox/src/lib.rs +++ b/crates/openshell-sandbox/src/lib.rs @@ -1556,7 +1556,7 @@ where } } for path in rw { - if fs.read_only.iter().any(|p| p == path) || fs.read_write.iter().any(|p| p == path) { + if fs.read_write.iter().any(|p| p == path) { continue; } if !path_exists(path) { @@ -1566,6 +1566,18 @@ where ); continue; } + if fs.read_only.iter().any(|p| p == path) { + if path == "/proc" { + info!( + path, + "Promoting /proc from read-only to read-write for GPU runtime compatibility" + ); + fs.read_only.retain(|p| p != path); + fs.read_write.push(path.clone()); + modified = true; + } + continue; + } fs.read_write.push(path.clone()); modified = true; } @@ -1769,7 +1781,7 @@ mod baseline_tests { } #[test] - fn proto_gpu_enrichment_adds_devices_without_network_policy() { + fn proto_gpu_enrichment_promotes_proc_without_network_policy() { let mut policy = openshell_policy::restrictive_default_policy(); assert!( policy.network_policies.is_empty(), @@ -1791,6 +1803,14 @@ mod baseline_tests { filesystem.read_write.contains(&"/dev/nvidia0".to_string()), "GPU enrichment should add enumerated device nodes without network policies" ); + assert!( + !filesystem.read_only.contains(&"/proc".to_string()), + "GPU enrichment should remove /proc from read_only" + ); + assert!( + filesystem.read_write.contains(&"/proc".to_string()), + "GPU enrichment should promote /proc to read_write" + ); } #[test] diff --git a/docs/sandboxes/policies.mdx b/docs/sandboxes/policies.mdx index d7e762445..a9c0d3231 100644 --- a/docs/sandboxes/policies.mdx +++ b/docs/sandboxes/policies.mdx @@ -62,6 +62,8 @@ Raw streams are connection-scoped and outside L7 live-reload guarantees. This in When a sandbox runs in proxy mode (the default), OpenShell automatically adds baseline filesystem paths required for the sandbox child process to function: `/usr`, `/lib`, `/etc`, `/var/log` (read-only) and `/sandbox`, `/tmp` (read-write). Paths like `/app` are included in the baseline set but are only added if they exist in the container image. +For GPU sandboxes, OpenShell also adds existing GPU device nodes as read-write paths. CUDA workloads require write access to procfs for thread metadata, so GPU baseline enrichment moves `/proc` from read-only to read-write when GPU devices are present. + This filtering prevents a missing baseline path from degrading Landlock enforcement. Without it, a single missing path could cause the entire Landlock ruleset to fail, leaving the sandbox with no filesystem restrictions at all. User-specified paths in your policy YAML are not pre-filtered. If you list a path that does not exist: