Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions architecture/sandbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,11 @@ OpenShell uses overlapping controls rather than a single sandbox primitive:
| Outer network fence | The component that owns network enforcement prevents any missed or unsupported kernel path from escaping. Current examples are Docker `network_mode=none`, a NIC-less VM, and Kubernetes NetworkPolicy. |
| Policy proxy | Evaluates destination, binary identity, TLS/L7 rules, SSRF checks, and inference interception. |

The supervisor may enrich baseline filesystem allowances for runtime-required
paths, such as proxy support files or GPU device paths when a GPU is present.
The supervisor may enrich baseline filesystem allowances for proxy support
files. GPU allowances are added by the workload-side sandbox only when the
immutable driver resource claims request a GPU and GPU devices are visible
inside the workload. Host supervisor device discovery must not influence these
allowances; a CPU-only VM preserves read-only `/proc` even on a GPU host.
These internal allowances must stay sandbox-scoped and avoid exposing host
secrets. For example, MXC governed egress grants the generated public CA bundle
while the ephemeral CA private key remains in the host proxy's memory.
Expand Down
1 change: 1 addition & 0 deletions crates/openshell-driver-vm/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1557,6 +1557,7 @@ impl VmDriver {
agent_uid: sandbox_owner_state.uid,
agent_gid: sandbox_owner_state.gid,
child_env: merged_environment(&sandbox),
gpu_requested: is_gpu,
}
.provision()
.map_err(|error| Status::failed_precondition(error.to_string()))?;
Expand Down
22 changes: 21 additions & 1 deletion crates/openshell-driver-vm/src/isolation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use openshell_isolation_interface::contract::{
BackendError, OuterFenceGuarantee, OuterFenceGuarantees, ResolvedWorkloadIdentity,
};
use openshell_sandbox_backend::GPU_RESOURCE_CLAIM;
use openshell_sandbox_backend::boundary_protocol::{
BoundaryConfig, BoundaryListener, GatewayVerificationKey, SandboxRuntimeDescriptor,
SandboxTlsClientConfig, SandboxTlsServerConfig, SandboxTransport,
Expand Down Expand Up @@ -70,6 +71,7 @@ pub struct VmBoundarySpec {
pub agent_uid: u32,
pub agent_gid: u32,
pub child_env: HashMap<String, String>,
pub gpu_requested: bool,
}

/// The protected guest config and matching host descriptor for one VM.
Expand All @@ -89,10 +91,13 @@ impl VmBoundarySpec {
"vm-config".to_string(),
self.image_identity.clone(),
)?;
let resource_claims = BTreeMap::from([
let mut resource_claims = BTreeMap::from([
("vm.generation".to_string(), self.generation.clone()),
("vm.image_identity".to_string(), self.image_identity),
]);
if self.gpu_requested {
resource_claims.insert(GPU_RESOURCE_CLAIM.to_string(), "true".to_string());
}
let outer_fence = VmOuterFenceEvidence {
generation: &self.generation,
network_device_count: 0,
Expand Down Expand Up @@ -165,6 +170,12 @@ mod tests {

#[test]
fn provisioning_binds_identical_resource_claims() {
for gpu_requested in [false, true] {
assert_provisioning_claims(gpu_requested);
}
}

fn assert_provisioning_claims(gpu_requested: bool) {
let session_id = openshell_core::SandboxSessionId::new();
let material = generate_sandbox_tls_material(session_id).unwrap();
let provisioned = VmBoundarySpec {
Expand Down Expand Up @@ -195,6 +206,7 @@ mod tests {
agent_uid: 1000,
agent_gid: 1000,
child_env: HashMap::new(),
gpu_requested,
}
.provision()
.unwrap();
Expand All @@ -203,6 +215,14 @@ mod tests {
provisioned.boundary_config.resource_claims,
provisioned.runtime_descriptor.resource_claims
);
assert_eq!(
provisioned
.runtime_descriptor
.resource_claims
.get(GPU_RESOURCE_CLAIM)
.map(String::as_str),
gpu_requested.then_some("true")
);
assert_eq!(
provisioned.runtime_descriptor.resource_claims["vm.generation"],
"generation-1"
Expand Down
9 changes: 7 additions & 2 deletions crates/openshell-sandbox/src/boundary_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,12 @@ mod linux {
const MAX_REPLAY_LEDGER_ENTRIES: usize = 4096;
const MAX_RETAINED_EXEC_PROCESSES: usize = 64;

// NVML may traverse the persistenced socket directory during initialization;
// WSL2 supplies GPU libraries under /usr/lib/wsl and the /dev/dxg device.
const GPU_BASELINE_READ_ONLY: &[&str] = &["/run/nvidia-persistenced", "/usr/lib/wsl"];
// CUDA opens device nodes read-write and writes thread names through
// /proc/<pid>/task/<tid>/comm during cuInit(). A /proc/self rule would bind
// to the launcher's inodes, not those of its workload children.
const GPU_BASELINE_READ_WRITE: &[&str] = &[
"/dev/nvidiactl",
"/dev/nvidia-uvm",
Expand All @@ -97,8 +102,8 @@ mod linux {
}

/// Add the filesystem paths required by GPU devices visible inside the
/// workload container. The companion supervisor intentionally has no GPU
/// devices, so it cannot discover these paths on the sandbox's behalf.
/// workload. The supervisor's device namespace can differ from the
/// workload's, so discovery must happen here, gated by the resource claim.
fn enrich_gpu_filesystem_paths(
policy: &mut openshell_core::policy::SandboxPolicy,
gpu_requested: bool,
Expand Down
Loading
Loading