From d5b8756491af7ce5691f66603ec3b7bd824e5a5c Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Wed, 23 Sep 2026 21:53:02 -0700 Subject: [PATCH] fix(vmm): restrict listed GPUs to the node's offered devices --- dstack/vmm/src/main_service.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/dstack/vmm/src/main_service.rs b/dstack/vmm/src/main_service.rs index 82501b11c..f81ff674d 100644 --- a/dstack/vmm/src/main_service.rs +++ b/dstack/vmm/src/main_service.rs @@ -110,9 +110,30 @@ pub fn resolve_gpus_with_config( if !cvm_config.gpu.allow_attach_all && gpus.attach_mode.is_all() { bail!("Attaching all GPUs is not allowed"); } + if !gpus.gpus.is_empty() && !gpus.attach_mode.is_all() { + let offered: Vec<_> = cvm_config + .gpu + .list_devices()? + .into_iter() + .map(|dev| dev.slot) + .collect(); + ensure_gpus_offered(&gpus.gpus, &offered)?; + } Ok(gpus) } +/// Listed slots must be among the GPUs `ListGpus` offers, i.e. pass +/// `cvm.gpu.listing`, `include` and `exclude`; otherwise any host PCI device +/// (or a string carrying QEMU option separators) reaches `-device vfio-pci`. +fn ensure_gpus_offered(requested: &[GpuSpec], offered: &[String]) -> Result<()> { + for gpu in requested { + if !offered.contains(&gpu.slot) { + bail!("GPU {} is not offered by this node", gpu.slot); + } + } + Ok(()) +} + pub fn resolve_gpus(gpu_cfg: &rpc::GpuConfig) -> Result { // Check the attach mode to determine how to handle GPUs match gpu_cfg.attach_mode.as_str() { @@ -2826,6 +2847,16 @@ mod tests { Ok(()) } + #[test] + fn listed_gpus_must_be_offered_by_node() { + let offered = ["0000:0f:00.0".to_string()]; + let gpu = |slot: &str| GpuSpec { slot: slot.into() }; + assert!(ensure_gpus_offered(&[gpu("0000:0f:00.0")], &offered).is_ok()); + for slot in ["0000:10:00.0", "0000:0f:00.0,romfile=/tmp/rom"] { + assert!(ensure_gpus_offered(&[gpu(slot)], &offered).is_err()); + } + } + #[test] fn resolve_volumes_attaches_duplicate_root_once() -> Result<()> { let tmp = tempfile::tempdir()?;