Skip to content

Commit 427aadd

Browse files
committed
docs(gpu): document CDI-derived GPU sandbox policy
Describe how Docker GPU sandboxes derive filesystem access from the selected CDI devices, which CDI mounts are rejected, the NVIDIA MPS pipe directory exception, missing additionalGids handling on Docker releases before 29.5, and SELinux requirements for the projected spec directories. Add cluster debugging entries for CDI projection, admission, probe and restart failures, and document the CDI context in the core, sandbox and Docker driver READMEs. Signed-off-by: Evan Lezar <elezar@nvidia.com>
1 parent e073be7 commit 427aadd

7 files changed

Lines changed: 192 additions & 9 deletions

File tree

‎crates/openshell-core/README.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,10 @@ Use `openshell_core::proto_struct` when crossing between `serde_json` values and
6464
`prost_types::{Struct, Value}`. Both conversion directions live in this module;
6565
JSON-to-protobuf conversion is fallible so callers cannot silently replace an
6666
unrepresentable number.
67+
68+
## CDI context
69+
70+
The shared `cdi` module defines the versioned context passed from compute drivers
71+
to the workload, including selected devices and projected specification directories.
72+
The CDI resolver and its dependency live in `openshell-sandbox`; see that crate's
73+
README for resource validation and enforcement.

‎crates/openshell-driver-docker/README.md‎

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ traverse every parent and write and enter the workdir; OpenShell does not
9393
change its ownership or mode.
9494

9595
Image `VOLUME` declarations and user mounts must not cover the workdir, one of
96-
its parents, or the reserved `/.openshell` runtime/channel tree. OpenShell asks
96+
its parents, or the reserved `/.openshell/channel` and `/.openshell/cdi-specs`
97+
paths. OpenShell asks
9798
the kernel to validate access under the final identity, so POSIX ACL and host
9899
LSM decisions remain authoritative.
99100

@@ -111,6 +112,7 @@ LSM decisions remain authoritative.
111112
| Private named volumes | One carries the authenticated sandbox/supervisor channel. The other is mounted only into the supervisor and contains its JWT and private gateway credentials. |
112113
| In-memory `/run/openshell-supervisor-ca` tmpfs | Holds only the public supervisor CA certificate and trust bundle without making all of `/run` writable. |
113114
| CDI GPU request | Assigns the exact validated CDI devices requested by driver config or count-based selection. |
115+
| CDI workload projection | For GPU sandboxes only, embeds selected device metadata in the protected boundary config and mounts Docker daemon CDI spec directories read-only into the workload. |
114116

115117
## Stop, Start, and Delete
116118

@@ -124,6 +126,39 @@ Delete force-removes both containers, the driver-owned runtime volumes, and the
124126
host-private runtime descriptor. Missing or altered descriptor and channel resources
125127
fail closed; the driver does not run an older combined-supervisor layout.
126128

129+
## CDI GPU Metadata
130+
131+
Docker remains the source of truth for GPU injection. The driver selects opaque
132+
CDI device IDs from `driver_config.cdi_devices` or the daemon's discovered CDI
133+
inventory, then passes the same IDs to Docker with a CDI `DeviceRequest`.
134+
135+
For a GPU sandbox, the driver writes those IDs and projected CDI spec directory
136+
paths to a versioned context in the protected workload boundary configuration.
137+
It uses structured, read-only, non-recursive binds at `/.openshell/cdi-specs/<index>`.
138+
The driver checks source existence on the daemon host using one temporary,
139+
stopped container per directory and the prepared workload image. Each probe has
140+
one read-only, private bind with source creation disabled. Successful probes are
141+
removed with anonymous image-volume cleanup, including after provisioning
142+
cancellation. Confirmed missing directories are omitted; other errors fail
143+
provisioning. Results are cached for 30 seconds and refreshed on demand; observed
144+
changes to daemon identity, configured directories, or GPU inventory invalidate
145+
the cache. The resulting context records only available projections. The actual
146+
sandbox is created once, and selected devices must still resolve from the
147+
projected specs. No probe starts a process, creates missing directories, or
148+
relabels host spec directories. Administrators must make the existing directories readable
149+
under the host's SELinux policy when SELinux is enabled.
150+
151+
Whole spec directories are available to the trusted runtime, which resolves only
152+
the selected devices. The mandatory Landlock baseline hides these projections
153+
from agent processes, even when the authored policy grants a broad ancestor.
154+
The sandbox runtime resolves the selected devices in that mount namespace before
155+
it launches agent processes, derives Landlock paths from CDI `containerEdits`,
156+
and warns when the runtime did not apply the spec's `additionalGids`. Containers with CDI context fail startup if their
157+
selected devices cannot be resolved. Existing GPU containers without context
158+
retain legacy baseline handling on restart. Admission checks persisted context
159+
for existing containers and the creation context for new ones. Non-GPU sandboxes
160+
receive no CDI context or spec mounts.
161+
127162
## Driver Config Mounts
128163

129164
The gateway forwards the `docker` block from `--driver-config-json`. Supported

‎crates/openshell-sandbox/README.md‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,54 @@ the kernel then tracks the lifetime through workload-owned descriptors. Native
1717
the last workload reference to a listener removes it without waiting for registry
1818
collection. Broker source descriptors always use close-on-exec independently of
1919
the workload's requested descriptor flags.
20+
21+
## CDI policy provenance
22+
23+
The workload resolves trusted CDI context into filesystem requirements. Library
24+
mounts expand to their parent directory; the expanded path must pass the same
25+
broad-root rejection as other CDI destinations. Specs are trusted host
26+
administrator input. A shared-library parent grant also makes sibling files
27+
readable, so administrators must choose library directories accordingly, including
28+
when a spec places libraries under `/etc`, `/home`, or `/root`. These checks do
29+
not turn administrator-authored specs into an untrusted-input interface.
30+
CDI enrichment adds only spec-derived paths; it does not grant all of `/sys`.
31+
32+
`WorkloadLaunchContext` retains derived filesystem requirements and required GIDs
33+
separately from the authored policy. The initial process launcher and retained
34+
exec executor share this context. Protobuf and boundary policy messages carry
35+
no derived requirements or GIDs; the workload resolves them from trusted CDI context.
36+
The sandbox processes deduplicated CDI targets sequentially: open a target,
37+
validate its type and broad-root object identity, add matching Landlock rules
38+
using that same descriptor, and close it before opening the next target.
39+
Targets covered by authored ancestors are validated without adding a rule.
40+
Only path requirements and root identities are retained; the number of open
41+
CDI descriptors does not grow with the spec. Symlinks to legitimate library
42+
directories remain supported. CDI validation and enforcement failures abort launch, including
43+
when the authored Landlock compatibility is best effort. Authored paths retain
44+
their configured opening behavior; absent authored paths can still be skipped.
45+
Ruleset construction and enforcement remain mandatory when CDI filesystem targets
46+
are present, so failures cannot silently discard CDI confinement.
47+
48+
Projected specs reside under `/.openshell/cdi-specs`, which the mandatory baseline
49+
excludes from agent access independently of authored grants. Fixture tests use
50+
the production resolver with injected workload mount observations; real Linux
51+
mount-state tests verify the OS inspection separately.
52+
53+
Original CDI mount destinations remain separate from expanded library parents.
54+
The workload classifies their effective read-only flags rather than trusting
55+
spec option defaults, and checks original directory mounts for writable
56+
submounts using one mount-table snapshot per pass. Ordinary writable file and
57+
directory mounts are rejected, even when an exact or ancestor `read_write`
58+
grant covers the destination. Dedicated authorization for CDI host writes is
59+
not currently supported. The sequential Landlock pass rechecks mount access
60+
on its opened descriptors and rejects persisted writable requirements too.
61+
CDI does not add write access to `/proc`; existing thread-name mediation applies.
62+
Required groups must already be supplied by the container runtime. The sandbox
63+
checks membership before both the initial process launch and subsequent execs.
64+
65+
CDI Unix socket mounts are recorded as exact socket destinations, separately from
66+
ordinary file mounts. Their opened objects must remain Unix sockets at each
67+
launch or exec. They do not derive filesystem grants, expand to parent
68+
directories, or require writable filesystem grants, regardless of mount flags.
69+
Communication follows the existing Unix IPC and inode permission checks; this
70+
validation does not pin the endpoint for later pathname-based connections.

‎docs/how-it-works/policies/default-policy.mdx‎

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,18 +87,58 @@ the sandbox's policy.
8787

8888
### GPU Sandboxes
8989

90-
On the Docker and VM compute drivers, a sandbox that requests a GPU receives
91-
additional paths when the corresponding GPU device is present:
90+
GPU sandboxes without a CDI context receive additional paths when the
91+
corresponding GPU device is present:
9292

9393
| Access | Paths |
9494
|---|---|
9595
| Read-only | `/run/nvidia-persistenced`, `/usr/lib/wsl` |
96-
| Read-write | `/dev/nvidiactl`, `/dev/nvidia-uvm`, `/dev/nvidia-uvm-tools`, `/dev/nvidia-modeset`, `/dev/dxg`, numbered `/dev/nvidia<N>` device nodes, and `/proc` |
97-
98-
CUDA writes thread names under `/proc` during initialization, so GPU enrichment
99-
moves `/proc` from read-only to read-write. OpenShell adds each path only when
100-
it exists in the workload. These paths apply at runtime and are not saved in
101-
the sandbox's policy.
96+
| Read-write | `/dev/nvidiactl`, `/dev/nvidia-uvm`, `/dev/nvidia-uvm-tools`, `/dev/nvidia-modeset`, `/dev/dxg`, numbered `/dev/nvidia<N>` device nodes |
97+
98+
CUDA thread-name writes under `/proc` are handled by the existing process
99+
open mediation. GPU enrichment preserves authored `/proc` access instead of
100+
adding a blanket write grant. OpenShell adds each path
101+
only when it exists in the workload. These paths apply at runtime and are not
102+
saved in the sandbox's policy.
103+
104+
Docker GPU sandboxes receive CDI-derived filesystem
105+
requirements instead of the hard-coded GPU device and library baseline. The
106+
workload-side sandbox runtime resolves the selected CDI device specs and adds
107+
CDI device nodes as read-write paths; listing a device node in
108+
`filesystem_policy.read_only` stops the launch. For read-only mounts, it adds the parent
109+
directories of shared libraries whose filenames end in `.so` or a numeric
110+
SONAME suffix with any number of dot-separated components, such as `.so.1`,
111+
`.so.1.2`, or `.so.1.2.3`. It retains exact destinations for other read-only
112+
mounts. OpenShell does not add a derived path when an existing read-only or
113+
read-write parent already covers it. The container runtime supplies CDI `additionalGids` as supplemental groups.
114+
Docker releases before 29.5 ignore them. OpenShell checks the workload's groups
115+
before initial launch and subsequent execs and logs a warning naming any missing
116+
group. The process still starts, but devices restricted to that group may be
117+
inaccessible.
118+
119+
Unix socket mounts are validated as sockets at their exact destinations at each
120+
launch or exec. They do not add filesystem grants or parent directory access and
121+
do not require `read_write` entries, regardless of mount flags. Communication
122+
uses the existing Unix IPC and socket permission checks.
123+
124+
Ordinary CDI file and directory mounts must be effectively read-only. OpenShell
125+
rejects writable mounts even when `filesystem_policy.read_write` names their
126+
exact destination or an ancestor. Explicit authorization for CDI host writes is
127+
not currently supported. This restriction does not apply to device nodes or
128+
Unix socket communication.
129+
130+
The NVIDIA MPS pipe directory `/tmp/nvidia-mps` is the only writable directory
131+
mount allowed. When CDI mounts it with `nosuid`, `nodev`, and `noexec`, OpenShell
132+
grants read-write access to that exact directory so MPS clients can open its
133+
pipes. OpenShell checks the directory and its mount flags at each launch or
134+
exec. Listing it in `filesystem_policy.read_only` stops the launch.
135+
136+
CDI paths such as `/`, `/dev`, `/proc`, `/sys`, `/run`, or `/usr` remain rejected
137+
during sandbox startup, even when authored policy grants access to those roots.
138+
139+
CDI does not add a blanket `/sys` grant. If an application needs sysfs paths
140+
that the selected CDI spec does not supply, include those paths in your authored
141+
filesystem policy.
102142

103143
### Protected Paths
104144

@@ -108,6 +148,12 @@ filesystem policy is applied on top of that baseline and can narrow access, but
108148
it cannot expose `/.openshell`. The `best_effort` compatibility setting does not
109149
disable this protection or allow a kernel without ABI v3.
110150

151+
CDI targets are validated strictly before each launch or exec. Authored paths
152+
retain their configured opening behavior, so an absent authored path can still
153+
be skipped in best-effort mode. When CDI filesystem targets are present,
154+
ruleset construction and enforcement failures abort launch instead of falling
155+
back to an unrestricted workload.
156+
111157
## Inspect the Selected Policy
112158

113159
To see which policy a sandbox uses, print its base and effective policies.

‎docs/how-it-works/sandboxes/overview.mdx‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,12 @@ For Docker-backed sandboxes, GPU injection uses Docker CDI. If you enable Docker
155155
CDI after the gateway starts, restart the gateway so OpenShell can detect the
156156
updated Docker daemon capability.
157157
158+
For CDI-enabled Docker sandboxes, OpenShell derives filesystem access from the
159+
selected CDI devices and validates those targets before each launch and exec.
160+
Invalid targets stop the launch. Refer to
161+
[GPU Sandboxes](/how-it-works/policies/default-policy#gpu-sandboxes) for the
162+
paths OpenShell adds and the mounts it rejects.
163+
158164
Docker and Podman refresh the CDI inventory before validating or creating a
159165
default GPU request, so CDI devices added or removed after the driver starts can
160166
be reflected in later sandbox creates. On WSL2 all-only runtimes, the default

‎docs/how-it-works/sandboxes/runtimes.mdx‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,31 @@ Common options in `[openshell.drivers.docker]` are `socket_path`, `grpc_endpoint
121121

122122
Docker Desktop must have host networking enabled, and it cannot use Enhanced Container Isolation. Set `grpc_endpoint` when sandboxes cannot reach the gateway on host loopback. For GPU sandboxes, configure Docker CDI before starting the gateway.
123123

124+
For Docker GPU/CDI sandboxes, OpenShell uses Docker's selected CDI device IDs
125+
and daemon-reported CDI spec directories to build a workload CDI context. The
126+
driver embeds the context in the protected boundary configuration and
127+
projects the spec directories read-only under the protected `/.openshell/cdi-specs`
128+
hierarchy. User mounts and image volumes cannot target this hierarchy. These
129+
structured binds neither create missing host directories nor relabel existing
130+
directories. OpenShell skips spec directories that do not exist on the Docker
131+
host. At least one directory must remain and every selected device must resolve from
132+
its specs. On SELinux hosts, the host policy must allow containers to read the
133+
directories; a denial fails the workload launch. Agent processes cannot read the
134+
projections, even with a broad authored filesystem grant. The sandbox runtime derives filesystem requirements from CDI specs and
135+
checks supplemental groups before launching agent processes. Non-GPU Docker
136+
sandboxes do not receive the CDI context, spec mounts, or CDI-derived policy
137+
changes.
138+
139+
Existing Docker GPU containers without CDI context retain the legacy GPU
140+
filesystem baseline on restart. Admission requires spec projections only when
141+
the driver-owned boundary configuration contains CDI context, and validates
142+
those projections against the directories recorded at creation. Missing or
143+
unreadable boundary configuration blocks restart. To use CDI-derived access on
144+
an older sandbox, recreate it. Ordinary CDI file and directory mounts must be
145+
effectively read-only. Filesystem `read_write` grants do not authorize writable
146+
CDI mounts; explicit authorization for CDI host writes is not currently
147+
supported. Device nodes and Unix socket communication remain supported.
148+
124149
### Docker Corporate Proxy Egress
125150

126151
For proxy-required networks, the Docker driver accepts `https_proxy`,

0 commit comments

Comments
 (0)