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 <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-7
This commit is contained in:
Rob Bradford
2026-04-26 22:10:40 +01:00
parent f77f168532
commit 59f9c7c08a

View File

@@ -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<u32> = 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 {