From 59f9c7c08acaf514a98888dbf4eddd7bbfcd8bba Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Sun, 26 Apr 2026 22:10:40 +0100 Subject: [PATCH] virtio-devices: iommu: Cap mappings per domain Domain::mappings only shrinks on UNMAP. Without a bound a guest can issue MAP for arbitrarily many distinct virt_start values and drive the VMM heap until the host runs out. Reject MAP with VIRTIO_IOMMU_S_NOMEM at 1M entries per domain. Signed-off-by: Rob Bradford Assisted-by: Claude:claude-opus-4-7 --- virtio-devices/src/iommu.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/virtio-devices/src/iommu.rs b/virtio-devices/src/iommu.rs index 7724e7126..fcf837383 100644 --- a/virtio-devices/src/iommu.rs +++ b/virtio-devices/src/iommu.rs @@ -73,6 +73,9 @@ const VIRTIO_IOMMU_F_BYPASS_CONFIG: u32 = 6; // Support 2MiB and 4KiB page sizes. const VIRTIO_IOMMU_PAGE_SIZE_MASK: u64 = (2 << 20) | (4 << 10); +// ~64 MiB at ~64 bytes/entry, well above any legitimate workload. +const MAX_MAPPINGS_PER_DOMAIN: usize = 1 << 20; + #[derive(Copy, Clone, Debug, Default)] #[repr(C, packed)] #[allow(dead_code)] @@ -130,7 +133,6 @@ const VIRTIO_IOMMU_S_RANGE: u8 = 5; const VIRTIO_IOMMU_S_NOENT: u8 = 6; #[allow(unused)] const VIRTIO_IOMMU_S_FAULT: u8 = 7; -#[allow(unused)] const VIRTIO_IOMMU_S_NOMEM: u8 = 8; #[derive(Copy, Clone, Debug, Default)] @@ -323,6 +325,8 @@ enum Error { InvalidUnmapRequestMissingDomain, #[error("UNMAP range partially overlaps an existing mapping")] InvalidUnmapRequestPartialOverlap, + #[error("Per-domain mapping count cap exceeded")] + MappingCountExceeded, #[error("Guest sent us invalid PROBE request")] InvalidProbeRequest, #[error("Failed to performing external mapping")] @@ -509,6 +513,16 @@ impl Request { return Err(Error::InvalidMapRequest); } + { + let domains = mapping.domains.read().unwrap(); + if let Some(d) = domains.get(&domain_id) + && d.mappings.len() >= MAX_MAPPINGS_PER_DOMAIN + { + status = VIRTIO_IOMMU_S_NOMEM; + return Err(Error::MappingCountExceeded); + } + } + let mut mapped: Vec = Vec::new(); let rollback = |mapped: &[u32]| { for ep in mapped { @@ -537,6 +551,11 @@ impl Request { status = VIRTIO_IOMMU_S_NOENT; return Err(Error::InvalidMapRequestMissingDomain); }; + if domain.mappings.len() >= MAX_MAPPINGS_PER_DOMAIN { + rollback(&mapped); + status = VIRTIO_IOMMU_S_NOMEM; + return Err(Error::MappingCountExceeded); + } domain.mappings.insert( req.virt_start, Mapping {