Skip to content

Commit c9056bb

Browse files
authored
fix(sandbox): decouple GPU baseline from network policy (#1524)
Signed-off-by: Evan Lezar <elezar@nvidia.com>
1 parent a3ed421 commit c9056bb

1 file changed

Lines changed: 113 additions & 39 deletions

File tree

  • crates/openshell-sandbox/src

crates/openshell-sandbox/src/lib.rs

Lines changed: 113 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1451,22 +1451,39 @@ fn enumerate_gpu_device_nodes() -> Vec<String> {
14511451
paths
14521452
}
14531453

1454-
/// Collect all baseline paths for enrichment: proxy defaults + GPU (if present).
1455-
/// Returns `(read_only, read_write)` as owned `String` vecs.
1456-
fn baseline_enrichment_paths() -> (Vec<String>, Vec<String>) {
1457-
let mut ro: Vec<String> = PROXY_BASELINE_READ_ONLY
1458-
.iter()
1459-
.map(|&s| s.to_string())
1460-
.collect();
1461-
let mut rw: Vec<String> = PROXY_BASELINE_READ_WRITE
1462-
.iter()
1463-
.map(|&s| s.to_string())
1464-
.collect();
1454+
fn push_unique(paths: &mut Vec<String>, path: String) {
1455+
if !paths.iter().any(|p| p == &path) {
1456+
paths.push(path);
1457+
}
1458+
}
14651459

1466-
if has_gpu_devices() {
1467-
ro.extend(GPU_BASELINE_READ_ONLY.iter().map(|&s| s.to_string()));
1468-
rw.extend(GPU_BASELINE_READ_WRITE.iter().map(|&s| s.to_string()));
1469-
rw.extend(enumerate_gpu_device_nodes());
1460+
fn collect_baseline_enrichment_paths(
1461+
include_proxy: bool,
1462+
include_gpu: bool,
1463+
gpu_device_nodes: Vec<String>,
1464+
) -> (Vec<String>, Vec<String>) {
1465+
let mut ro = Vec::new();
1466+
let mut rw = Vec::new();
1467+
1468+
if include_proxy {
1469+
for &path in PROXY_BASELINE_READ_ONLY {
1470+
push_unique(&mut ro, path.to_string());
1471+
}
1472+
for &path in PROXY_BASELINE_READ_WRITE {
1473+
push_unique(&mut rw, path.to_string());
1474+
}
1475+
}
1476+
1477+
if include_gpu {
1478+
for &path in GPU_BASELINE_READ_ONLY {
1479+
push_unique(&mut ro, path.to_string());
1480+
}
1481+
for &path in GPU_BASELINE_READ_WRITE {
1482+
push_unique(&mut rw, path.to_string());
1483+
}
1484+
for path in gpu_device_nodes {
1485+
push_unique(&mut rw, path);
1486+
}
14701487
}
14711488

14721489
// A path promoted to read_write (e.g. /proc for GPU) should not also
@@ -1477,14 +1494,33 @@ fn baseline_enrichment_paths() -> (Vec<String>, Vec<String>) {
14771494
(ro, rw)
14781495
}
14791496

1480-
/// Ensure a proto `SandboxPolicy` includes the baseline filesystem paths
1481-
/// required for proxy-mode sandboxes. Paths are only added if missing;
1482-
/// user-specified paths are never removed.
1483-
///
1484-
/// Returns `true` if the policy was modified (caller may want to sync back).
1485-
fn enrich_proto_baseline_paths(proto: &mut openshell_core::proto::SandboxPolicy) -> bool {
1486-
// Only enrich if network_policies are present (proxy mode indicator).
1487-
if proto.network_policies.is_empty() {
1497+
fn active_baseline_enrichment_paths(include_proxy: bool) -> (Vec<String>, Vec<String>) {
1498+
let include_gpu = has_gpu_devices();
1499+
let gpu_device_nodes = if include_gpu {
1500+
enumerate_gpu_device_nodes()
1501+
} else {
1502+
Vec::new()
1503+
};
1504+
collect_baseline_enrichment_paths(include_proxy, include_gpu, gpu_device_nodes)
1505+
}
1506+
1507+
/// Collect all active baseline paths for tests and diagnostics.
1508+
/// Returns `(read_only, read_write)` as owned `String` vecs.
1509+
#[cfg(test)]
1510+
fn baseline_enrichment_paths() -> (Vec<String>, Vec<String>) {
1511+
active_baseline_enrichment_paths(true)
1512+
}
1513+
1514+
fn enrich_proto_baseline_paths_with<F>(
1515+
proto: &mut openshell_core::proto::SandboxPolicy,
1516+
ro: &[String],
1517+
rw: &[String],
1518+
path_exists: F,
1519+
) -> bool
1520+
where
1521+
F: Fn(&str) -> bool,
1522+
{
1523+
if ro.is_empty() && rw.is_empty() {
14881524
return false;
14891525
}
14901526

@@ -1495,17 +1531,10 @@ fn enrich_proto_baseline_paths(proto: &mut openshell_core::proto::SandboxPolicy)
14951531
..Default::default()
14961532
});
14971533

1498-
let (ro, rw) = baseline_enrichment_paths();
1499-
1500-
// Baseline paths are system-injected, not user-specified. Skip paths
1501-
// that do not exist in this container image to avoid noisy warnings from
1502-
// Landlock and, more critically, to prevent a single missing baseline
1503-
// path from abandoning the entire Landlock ruleset under best-effort
1504-
// mode (see issue #664).
15051534
let mut modified = false;
1506-
for path in &ro {
1535+
for path in ro {
15071536
if !fs.read_only.iter().any(|p| p == path) && !fs.read_write.iter().any(|p| p == path) {
1508-
if !std::path::Path::new(path).exists() {
1537+
if !path_exists(path) {
15091538
debug!(
15101539
path,
15111540
"Baseline read-only path does not exist, skipping enrichment"
@@ -1516,11 +1545,11 @@ fn enrich_proto_baseline_paths(proto: &mut openshell_core::proto::SandboxPolicy)
15161545
modified = true;
15171546
}
15181547
}
1519-
for path in &rw {
1548+
for path in rw {
15201549
if fs.read_only.iter().any(|p| p == path) || fs.read_write.iter().any(|p| p == path) {
15211550
continue;
15221551
}
1523-
if !std::path::Path::new(path).exists() {
1552+
if !path_exists(path) {
15241553
debug!(
15251554
path,
15261555
"Baseline read-write path does not exist, skipping enrichment"
@@ -1531,6 +1560,26 @@ fn enrich_proto_baseline_paths(proto: &mut openshell_core::proto::SandboxPolicy)
15311560
modified = true;
15321561
}
15331562

1563+
modified
1564+
}
1565+
1566+
/// Ensure a proto `SandboxPolicy` includes the baseline filesystem paths
1567+
/// required by proxy-mode sandboxes and GPU runtimes. Paths are only added if
1568+
/// missing; user-specified paths are never removed.
1569+
///
1570+
/// Returns `true` if the policy was modified (caller may want to sync back).
1571+
fn enrich_proto_baseline_paths(proto: &mut openshell_core::proto::SandboxPolicy) -> bool {
1572+
let (ro, rw) = active_baseline_enrichment_paths(!proto.network_policies.is_empty());
1573+
1574+
// Baseline paths are system-injected, not user-specified. Skip paths
1575+
// that do not exist in this container image to avoid noisy warnings from
1576+
// Landlock and, more critically, to prevent a single missing baseline
1577+
// path from abandoning the entire Landlock ruleset under best-effort
1578+
// mode (see issue #664).
1579+
let modified = enrich_proto_baseline_paths_with(proto, &ro, &rw, |path| {
1580+
std::path::Path::new(path).exists()
1581+
});
1582+
15341583
if modified {
15351584
ocsf_emit!(
15361585
ConfigStateChangeBuilder::new(ocsf_ctx())
@@ -1546,15 +1595,15 @@ fn enrich_proto_baseline_paths(proto: &mut openshell_core::proto::SandboxPolicy)
15461595
}
15471596

15481597
/// Ensure a `SandboxPolicy` (Rust type) includes the baseline filesystem
1549-
/// paths required for proxy-mode sandboxes. Used for the local-file code
1550-
/// path where no proto is available.
1598+
/// paths required by proxy-mode sandboxes and GPU runtimes. Used for the
1599+
/// local-file code path where no proto is available.
15511600
fn enrich_sandbox_baseline_paths(policy: &mut SandboxPolicy) {
1552-
if !matches!(policy.network.mode, NetworkMode::Proxy) {
1601+
let (ro, rw) =
1602+
active_baseline_enrichment_paths(matches!(policy.network.mode, NetworkMode::Proxy));
1603+
if ro.is_empty() && rw.is_empty() {
15531604
return;
15541605
}
15551606

1556-
let (ro, rw) = baseline_enrichment_paths();
1557-
15581607
let mut modified = false;
15591608
for path in &ro {
15601609
let p = std::path::PathBuf::from(path);
@@ -1709,6 +1758,31 @@ mod baseline_tests {
17091758
);
17101759
}
17111760

1761+
#[test]
1762+
fn proto_gpu_enrichment_adds_devices_without_network_policy() {
1763+
let mut policy = openshell_policy::restrictive_default_policy();
1764+
assert!(
1765+
policy.network_policies.is_empty(),
1766+
"regression setup must exercise the no-network default path"
1767+
);
1768+
let (ro, rw) =
1769+
collect_baseline_enrichment_paths(false, true, vec!["/dev/nvidia0".to_string()]);
1770+
1771+
let enriched = enrich_proto_baseline_paths_with(&mut policy, &ro, &rw, |path| {
1772+
matches!(path, "/proc" | "/dev/nvidia0")
1773+
});
1774+
1775+
let filesystem = policy.filesystem.expect("filesystem policy");
1776+
assert!(
1777+
enriched,
1778+
"GPU enrichment should not require network policies"
1779+
);
1780+
assert!(
1781+
filesystem.read_write.contains(&"/dev/nvidia0".to_string()),
1782+
"GPU enrichment should add enumerated device nodes without network policies"
1783+
);
1784+
}
1785+
17121786
#[test]
17131787
fn gpu_baseline_read_write_contains_dxg() {
17141788
// /dev/dxg must be present so WSL2 sandboxes get the Landlock

0 commit comments

Comments
 (0)