guest: gate boot on GPU TEE attestation via requirements.verify_gpu#765
guest: gate boot on GPU TEE attestation via requirements.verify_gpu#765kvinwang wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds a guest-side security gate that prevents GPU-enabled CVMs from continuing boot (and reaching key provisioning) unless an attached NVIDIA GPU passes local TEE attestation, controlled by a new app-compose requirement (requirements.verify_gpu, defaulting to true).
Changes:
- Add a GPU detection + attestation + “ready state” gate to
dstack-util setupthat runs before filesystem setup/key provisioning. - Extend
dstack-typesmanifestRequirementswithverify_gpuplus anAppCompose::verify_gpu()accessor (default true) and tests.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| dstack-util/src/system_setup.rs | Adds GPU presence detection, runs nvattest (local verifier) when required, and sets NVIDIA CC “ready state” during system setup. |
| dstack-types/src/lib.rs | Introduces requirements.verify_gpu (optional bool) with defaulting behavior and unit tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| pub(super) fn nvidia_gpu_present() -> bool { | ||
| let Ok(entries) = fs::read_dir("/sys/bus/pci/devices") else { | ||
| return false; | ||
| }; |
There was a problem hiding this comment.
Addressed in f3a5c19 — nvidia_gpu_present() now returns Result<bool> and setup fails when the PCI bus cannot be enumerated, instead of treating it as "no GPU".
| pub(super) fn set_gpu_ready_state() -> Result<()> { | ||
| let output = Command::new("nvidia-smi") | ||
| .args(["conf-compute", "-srs", "1"]) | ||
| .output() | ||
| .context("failed to run nvidia-smi conf-compute")?; |
There was a problem hiding this comment.
Addressed in f3a5c19 — both nvattest and nvidia-smi now run through a shared run_command() helper (tokio process + bounded timeout: 300s attest, 60s nvidia-smi), so a wedged driver cannot hang the boot.
| let output = Command::new("nvidia-smi") | ||
| .args(["conf-compute", "-srs", "1"]) | ||
| .output() | ||
| .context("failed to run nvidia-smi conf-compute")?; |
There was a problem hiding this comment.
Addressed in f3a5c19 — nvidia-smi is now invoked as /usr/bin/nvidia-smi. (PATH hijack is not really in the threat model here — the rootfs is measured and the boot PATH is systemd-controlled — but consistency with the nvattest invocation costs nothing.)
| pub fn verify_gpu(&self) -> bool { | ||
| self.requirements | ||
| .as_ref() | ||
| .and_then(|r| r.verify_gpu) | ||
| .unwrap_or(true) | ||
| } |
There was a problem hiding this comment.
Addressed in f3a5c19 — verify_gpu() now ignores the opt-out unless manifest_version >= 3, with a test (legacy_optout). Within dstack-util this was already unreachable (verify_app_compose_policy rejects legacy manifests carrying requirements before setup_gpu runs), but the helper is now safe to call standalone.
|
Verified end-to-end on GCP Default path (generated app-compose, manifest v2, no Workload container afterwards: Opt-out path (verbatim app-compose, Container likewise sees Not exercised on real hardware: the rejection path (non-CC GPU / CC off → nvattest non-zero exit → setup bails before key provisioning). GCP a3 instances always come up with CC enabled, so this can't be provoked there; the failure propagation itself (nvattest non-zero → bail → boot.error → dstack-prepare failure/reboot) follows the same code path as a missing-nvattest bail. |
f8b4691 to
f3a5c19
Compare
Problem
On NVIDIA images the GPU conf-compute ready state is set unconditionally by
nvidia-persistenced.service(ExecStartPost=nvidia-smi conf-compute -srs 1), so CUDA work can be submitted to a GPU that never proved it is a genuine, CC-enabled NVIDIA TEE. A malicious host can attach a non-CC GPU (or leave CC mode off) and the CVM boots, gets its app keys, and runs the workload anyway.An earlier iteration of meta-dstack#83 gated this with a dedicated
nvidia-gpu-attestation.serviceplusRequires=drop-ins on five units — more moving parts, and the policy was baked into the image with no per-app control.Fix
Move the whole gate into
dstack-util setup(system_setup.rs) and make it an app-compose requirement:requirements.verify_gpu(new, defaults to true when omitted): whether an attached GPU must pass local TEE attestation before boot continues.do_sys_setup, after time sync and before key provisioning:0x10de, class0x0300xx/0x0302xx) → skip; GPU-less CVMs are unaffected.verify_gpu=true→ runnvattest attest --device gpu --verifier local --nonce <32 random bytes>(5 min timeout, verifier output kept in/run/nvidia-gpu-attestation/attestation.out, 0600), then set the ready state. Any failure — a non-CC GPU, CC mode left off by the host, or a missingnvattestbinary — aborts setup, so the CVM fails closed before app keys are provisioned (boot.errorreported to the VMM, dstack-prepare reboots).verify_gpu=false→ skip attestation and set the ready state directly (best-effort: a GPU without CC mode has no ready state to set).The companion meta-dstack change (meta-dstack#83) drops the systemd service/wrapper/drop-ins and ships only the nvattest CLI plus an ordering drop-in (
dstack-prepare.serviceafternvidia-persistenced/nvidia-fabricmanager).Like the other
requirementsfields,verify_gpuneedsmanifest_version: "3", so older guests fail closed instead of silently ignoring the opt-out.Verification
cargo test -p dstack-types -p dstack-util(new tests:verify_gpu_defaults_to_true; 23 + 34 pass),cargo fmt --check, clippy clean.