virtio-devices: iommu: Roll back partial MAP and fix DETACH panic

A concurrent DETACH between the read-lock check and the write-lock
get_mut().unwrap() in MAP/UNMAP would panic the worker. Replace the
unwrap with a let-else.

A failure on a later endpoint in the per-endpoint MAP loop, or a
DETACH that races the missing-domain branch, must roll back the
external mappings already installed; otherwise domain.mappings
diverges from VFIO state.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-7
This commit is contained in:
Rob Bradford
2026-04-26 22:09:49 +01:00
parent 4d6c7c95c0
commit f77f168532
+33 -26
View File
@@ -121,7 +121,6 @@ const VIRTIO_IOMMU_S_OK: u8 = 0;
const VIRTIO_IOMMU_S_IOERR: u8 = 1; const VIRTIO_IOMMU_S_IOERR: u8 = 1;
#[allow(unused)] #[allow(unused)]
const VIRTIO_IOMMU_S_UNSUPP: u8 = 2; const VIRTIO_IOMMU_S_UNSUPP: u8 = 2;
#[allow(unused)]
const VIRTIO_IOMMU_S_DEVERR: u8 = 3; const VIRTIO_IOMMU_S_DEVERR: u8 = 3;
#[allow(unused)] #[allow(unused)]
const VIRTIO_IOMMU_S_INVAL: u8 = 4; const VIRTIO_IOMMU_S_INVAL: u8 = 4;
@@ -510,32 +509,41 @@ impl Request {
return Err(Error::InvalidMapRequest); return Err(Error::InvalidMapRequest);
} }
let mut mapped: Vec<u32> = Vec::new();
let rollback = |mapped: &[u32]| {
for ep in mapped {
if let Some(pmap) = ext_mapping.get(ep) {
let _ = pmap.unmap(req.virt_start, size);
}
}
};
// For viommu all endpoints receive their own VFIO container, as a result // For viommu all endpoints receive their own VFIO container, as a result
// Each endpoint within the domain needs to be separately mapped, as the // Each endpoint within the domain needs to be separately mapped, as the
// mapping is done on a per-container level, not a per-domain level // mapping is done on a per-container level, not a per-domain level
for endpoint in endpoints { for endpoint in endpoints {
if let Some(ext_map) = ext_mapping.get(&endpoint) { if let Some(ext_map) = ext_mapping.get(&endpoint) {
ext_map if let Err(e) = ext_map.map(req.virt_start, req.phys_start, size) {
.map(req.virt_start, req.phys_start, size) rollback(&mapped);
.map_err(Error::ExternalMapping)?; status = VIRTIO_IOMMU_S_DEVERR;
return Err(Error::ExternalMapping(e));
}
mapped.push(endpoint);
} }
} }
// Add new mapping associated with the domain let mut domains = mapping.domains.write().unwrap();
mapping let Some(domain) = domains.get_mut(&domain_id) else {
.domains rollback(&mapped);
.write() status = VIRTIO_IOMMU_S_NOENT;
.unwrap() return Err(Error::InvalidMapRequestMissingDomain);
.get_mut(&domain_id) };
.unwrap() domain.mappings.insert(
.mappings req.virt_start,
.insert( Mapping {
req.virt_start, gpa: req.phys_start,
Mapping { size,
gpa: req.phys_start, },
size, );
},
);
} }
VIRTIO_IOMMU_T_UNMAP => { VIRTIO_IOMMU_T_UNMAP => {
if desc_size_left != size_of::<VirtioIommuReqUnmap>() { if desc_size_left != size_of::<VirtioIommuReqUnmap>() {
@@ -614,13 +622,12 @@ impl Request {
} }
} }
// Remove all mappings associated with the domain within the requested range let mut domains = mapping.domains.write().unwrap();
mapping let Some(domain) = domains.get_mut(&domain_id) else {
.domains status = VIRTIO_IOMMU_S_INVAL;
.write() return Err(Error::InvalidUnmapRequestMissingDomain);
.unwrap() };
.get_mut(&domain_id) domain
.unwrap()
.mappings .mappings
.retain(|&x, _| x < req.virt_start || x > req.virt_end); .retain(|&x, _| x < req.virt_start || x > req.virt_end);
} }