Skip to content

Commit 2de916e

Browse files
committed
feat(core): make --gpu grant only the selected /dev/nvidiaN nodes
Signed-off-by: Cong Wang <cwang@multikernel.io>
1 parent e7f2993 commit 2de916e

1 file changed

Lines changed: 50 additions & 10 deletions

File tree

crates/sandlock-core/src/landlock.rs

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,24 @@ fn write_access(abi: u32) -> u64 {
9090
mask
9191
}
9292

93+
/// Indices N for which `/dev/nvidiaN` exists. Used for the "all GPUs"
94+
/// (`gpu_devices == []`) case. Matches `nvidia<digits>` exactly, so the
95+
/// control/capability nodes (`nvidiactl`, `nvidia-uvm`, `nvidia-caps`,
96+
/// `nvidia-modeset`) are excluded.
97+
fn present_gpu_indices() -> Vec<u32> {
98+
let Ok(entries) = std::fs::read_dir("/dev") else {
99+
return Vec::new();
100+
};
101+
// `parse::<u32>` accepts only "nvidia<digits>", rejecting nvidiactl,
102+
// nvidia-uvm, nvidia-caps, nvidia-modeset, etc.
103+
let mut out: Vec<u32> = entries
104+
.flatten()
105+
.filter_map(|ent| ent.file_name().to_str()?.strip_prefix("nvidia")?.parse().ok())
106+
.collect();
107+
out.sort_unstable();
108+
out
109+
}
110+
93111
// ============================================================
94112
// ABI version detection
95113
// ============================================================
@@ -472,23 +490,45 @@ fn confine_inner(policy: &Sandbox, handle_net: bool) -> Result<(), SandlockError
472490
}
473491

474492
// GPU device paths (when gpu_devices is set)
475-
if policy.gpu_devices.is_some() {
476-
// Read-write access to GPU device nodes
477-
for path in &[
478-
"/dev/nvidia0", "/dev/nvidia1", "/dev/nvidia2", "/dev/nvidia3",
479-
"/dev/nvidiactl", "/dev/nvidia-uvm", "/dev/nvidia-uvm-tools",
480-
"/dev/dri",
481-
] {
482-
let _ = add_path_rule(&ruleset_fd, std::path::Path::new(path), fs_write_mask);
483-
// Ignore errors — devices may not exist
493+
if let Some(ref devices) = policy.gpu_devices {
494+
// Shared control nodes: required for ANY GPU use, not per-GPU.
495+
// (nvidiactl = RM control channel; uvm = unified memory.)
496+
for path in &["/dev/nvidiactl", "/dev/nvidia-uvm", "/dev/nvidia-uvm-tools"] {
497+
let _ = add_path_rule(&ruleset_fd, Path::new(path), fs_write_mask);
498+
// Ignore errors — nodes may not exist (e.g. uvm before first use)
499+
}
500+
501+
// Per-GPU render nodes. Each physical GPU N is a distinct node
502+
// /dev/nvidiaN; opening it O_RDWR is REQUIRED to map that GPU. Granting
503+
// only the requested indices makes selection a hard kernel boundary
504+
// instead of the soft CUDA_VISIBLE_DEVICES hint set in context.rs.
505+
// Empty list = all present GPUs.
506+
let indices = if devices.is_empty() {
507+
present_gpu_indices()
508+
} else {
509+
devices.clone()
510+
};
511+
for idx in indices {
512+
let node = format!("/dev/nvidia{idx}");
513+
let _ = add_path_rule(&ruleset_fd, Path::new(&node), fs_write_mask);
484514
}
515+
516+
// DRM render nodes. /dev/dri is a directory of per-GPU card*/renderD*
517+
// nodes; a single rule exposes ALL of them, which would reopen the
518+
// sharing hole this selection closes. CUDA compute does not need
519+
// /dev/dri, so only grant it for the all-GPUs case; per-index DRM
520+
// mapping would need a PCI-BDF -> renderD* lookup (future work).
521+
if devices.is_empty() {
522+
let _ = add_path_rule(&ruleset_fd, Path::new("/dev/dri"), fs_write_mask);
523+
}
524+
485525
// Read-only access to GPU sysfs/procfs
486526
for path in &[
487527
"/proc/driver/nvidia",
488528
"/sys/bus/pci/devices",
489529
"/sys/module/nvidia",
490530
] {
491-
let _ = add_path_rule(&ruleset_fd, std::path::Path::new(path), READ_ACCESS);
531+
let _ = add_path_rule(&ruleset_fd, Path::new(path), READ_ACCESS);
492532
}
493533
}
494534

0 commit comments

Comments
 (0)