mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
virtio-devices: iommu: Cap number of domains per device
The per-domain mapping cap bounds memory inside one domain, but a guest can still grow the domains map indefinitely with ATTACH requests for distinct domain IDs. Reject ATTACH with VIRTIO_IOMMU_S_NOMEM at 64K domains. Signed-off-by: Rob Bradford <rbradford@meta.com> Assisted-by: Claude:claude-opus-4-7
This commit is contained in:
@@ -77,6 +77,10 @@ const VIRTIO_IOMMU_PAGE_GRANULE: u64 = 1u64 << VIRTIO_IOMMU_PAGE_SIZE_MASK.trail
|
||||
// ~64 MiB at ~64 bytes/entry, well above any legitimate workload.
|
||||
const MAX_MAPPINGS_PER_DOMAIN: usize = 1 << 20;
|
||||
|
||||
// Bound the per-device domain count so a guest cannot grow the
|
||||
// domain map indefinitely with ATTACH-only requests.
|
||||
const MAX_DOMAINS: usize = 1 << 16;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(C, packed)]
|
||||
#[allow(dead_code)]
|
||||
@@ -321,6 +325,8 @@ enum Error {
|
||||
InvalidUnmapRequestPartialOverlap,
|
||||
#[error("Per-domain mapping count cap exceeded")]
|
||||
MappingCountExceeded,
|
||||
#[error("Per-device domain count cap exceeded: current:{0}, max:{MAX_DOMAINS}")]
|
||||
DomainCountExceeded(usize),
|
||||
#[error("Guest sent us invalid PROBE request")]
|
||||
InvalidProbeRequest,
|
||||
#[error("Failed to performing external mapping")]
|
||||
@@ -419,6 +425,17 @@ impl Request {
|
||||
return Err(Error::InvalidAttachRequest);
|
||||
}
|
||||
|
||||
// Refuse before mutating any state so a failed ATTACH
|
||||
// does not leave a phantom endpoint pointing at a
|
||||
// domain that was never created.
|
||||
{
|
||||
let domains = mapping.domains.read().unwrap();
|
||||
if !domains.contains_key(&domain_id) && domains.len() >= MAX_DOMAINS {
|
||||
status = VIRTIO_IOMMU_S_NOMEM;
|
||||
return Err(Error::DomainCountExceeded(domains.len()));
|
||||
}
|
||||
}
|
||||
|
||||
let mut old_domain_id = domain_id;
|
||||
if let Some(&id) = mapping.endpoints.read().unwrap().get(&endpoint) {
|
||||
old_domain_id = id;
|
||||
@@ -449,6 +466,18 @@ impl Request {
|
||||
|
||||
// Add new domain with no mapping if the entry didn't exist yet
|
||||
let mut domains = mapping.domains.write().unwrap();
|
||||
if !domains.contains_key(&domain_id) && domains.len() >= MAX_DOMAINS {
|
||||
// Defensive re-check under write lock. Single-threaded
|
||||
// today, but keeps the invariant if processing ever
|
||||
// becomes concurrent. Drop the domains write lock
|
||||
// before taking the endpoints write lock so we never
|
||||
// hold both simultaneously.
|
||||
let count = domains.len();
|
||||
drop(domains);
|
||||
mapping.endpoints.write().unwrap().remove(&endpoint);
|
||||
status = VIRTIO_IOMMU_S_NOMEM;
|
||||
return Err(Error::DomainCountExceeded(count));
|
||||
}
|
||||
let domain = Domain {
|
||||
mappings: BTreeMap::new(),
|
||||
bypass,
|
||||
|
||||
Reference in New Issue
Block a user