Skip to content

Commit 0693dc0

Browse files
committed
fix(sandbox): decouple GPU baseline from network policy
Signed-off-by: Evan Lezar <elezar@nvidia.com>
1 parent bdaa08f commit 0693dc0

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
@@ -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.
15501599
fn 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

Comments
 (0)