Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/arch/aarch64/zone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ impl Zone {
}
match mem_region.mem_type {
MEM_TYPE_RAM | MEM_TYPE_IO => {
// Check for overlap with registered MMIO handler regions.
if inner.is_mmio_handler_overlap(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only protects mappings created by pt_init(). Stage-2 mappings are also added later through gpm_mut(); for example, guest PCI BAR writes call try_insert_quiet() in src/pci/pci_handler.rs without consulting the MMIO handler list. A guest can therefore place a non-MSI-X BAR over an intercepted GPA, create a valid Stage-2 mapping, and make the handler unreachable again. Please route all passthrough mapping insertions through a ZoneInner helper that performs this cross-check, or cover every runtime insertion path. This applies to all four architecture copies of this check.

mem_region.virtual_start as GuestPhysAddr,
mem_region.size as _,
) {
panic!(
"Passthrough region [{:#x}, {:#x}) overlaps with existing MMIO handler",
mem_region.virtual_start,
mem_region.virtual_start as u64 + mem_region.size
);
}
inner
.gpm_mut()
.insert(MemoryRegion::new_with_offset_mapper(
Expand Down
37 changes: 31 additions & 6 deletions src/arch/loongarch64/zone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ impl Zone {
let mem_type = region.mem_type;
match mem_type {
MEM_TYPE_RAM => {
// Check for overlap with registered MMIO handler regions.
if inner.is_mmio_handler_overlap(
region.virtual_start as GuestPhysAddr,
region.size as _,
) {
panic!(
"Passthrough region [{:#x}, {:#x}) overlaps with existing MMIO handler",
region.virtual_start,
region.virtual_start as u64 + region.size
);
}
inner
.gpm_mut()
.insert(MemoryRegion::new_with_offset_mapper(
Expand All @@ -57,6 +68,17 @@ impl Zone {
))?;
}
MEM_TYPE_IO => {
// Check for overlap with registered MMIO handler regions.
if inner.is_mmio_handler_overlap(
region.virtual_start as GuestPhysAddr,
region.size as _,
) {
panic!(
"Passthrough region [{:#x}, {:#x}) overlaps with existing MMIO handler",
region.virtual_start,
region.virtual_start as u64 + region.size
);
}
inner
.gpm_mut()
.insert(MemoryRegion::new_with_offset_mapper(
Expand All @@ -71,6 +93,15 @@ impl Zone {
"loongarch64: pt_init: register virtio mmio region: {:#x?}",
region
);
// Register the mmio handler first, so that the overlap check
// against the stage-2 page table in mmio_region_register does
// not find the VIRTIO trap page we are about to insert.
inner.mmio_region_register(
region.physical_start as _,
region.size as _,
mmio_virtio_handler,
region.physical_start as _,
);
inner
.gpm_mut()
.insert(MemoryRegion::new_with_offset_mapper(
Expand All @@ -79,12 +110,6 @@ impl Zone {
PAGE_SIZE, // since we only need 0x200 size for virtio mmio, but the minimal size is PAGE_SIZE
MemFlags::USER, // we use the USER as a hint flag for invalidating this stage-2 PTE
))?;
inner.mmio_region_register(
region.physical_start as _,
region.size as _,
mmio_virtio_handler,
region.physical_start as _,
);
}
_ => {
error!("loongarch64: pt_init: unknown mem type: {}", mem_type);
Expand Down
11 changes: 11 additions & 0 deletions src/arch/riscv64/zone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ impl Zone {
}
match mem_region.mem_type {
MEM_TYPE_RAM | MEM_TYPE_IO => {
// Check for overlap with registered MMIO handler regions.
if inner.is_mmio_handler_overlap(
mem_region.virtual_start as GuestPhysAddr,
mem_region.size as _,
) {
panic!(
"Passthrough region [{:#x}, {:#x}) overlaps with existing MMIO handler",
mem_region.virtual_start,
mem_region.virtual_start as u64 + mem_region.size
);
}
inner
.gpm_mut()
.insert(MemoryRegion::new_with_offset_mapper(
Expand Down
11 changes: 11 additions & 0 deletions src/arch/x86_64/zone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ impl Zone {
}
match mem_region.mem_type {
MEM_TYPE_RAM | MEM_TYPE_IO | MEM_TYPE_RESERVED => {
// Check for overlap with registered MMIO handler regions.
if inner.is_mmio_handler_overlap(
mem_region.virtual_start as GuestPhysAddr,
mem_region.size as _,
) {
panic!(
"Passthrough region [{:#x}, {:#x}) overlaps with existing MMIO handler",
mem_region.virtual_start,
mem_region.virtual_start as u64 + mem_region.size
);
}
inner.gpm_mut().insert(MemoryRegion::new_with_offset_mapper(
mem_region.virtual_start as GuestPhysAddr,
mem_region.physical_start as HostPhysAddr,
Expand Down
24 changes: 24 additions & 0 deletions src/memory/mm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,30 @@ where
self.pt.root_paddr()
}

/// Check whether `[start, start+size)` overlaps with any existing region in this MemorySet.
pub fn is_range_overlap(&self, start: usize, size: usize) -> bool
where
PT::VA: From<usize>,
{
let end = start + size;
let va_start: PT::VA = start.into();
// Check the region immediately before `start`.
if let Some((_, before)) = self.regions.range(..va_start).last() {
let before_end: usize = before.start.into() + before.size;
if before_end > start {
return true;
}
}
// Check the region at or after `start`.
if let Some((_, after)) = self.regions.range(va_start..).next() {
let after_start: usize = after.start.into();
if after_start < end {
return true;
}
}
false
}

fn test_free_area(&self, other: &MemoryRegion<PT::VA>) -> bool {
if let Some((_, before)) = self.regions.range(..other.start).last() {
if before.is_overlap_with(other) {
Expand Down
7 changes: 7 additions & 0 deletions src/memory/mmio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ impl MMIORegion {
pub fn contains_region(&self, addr: GuestPhysAddr, sz: usize) -> bool {
addr >= self.start && addr + (sz as usize) <= self.start + (self.size as usize)
}

/// Check whether this region overlaps with `other`.
pub fn is_overlap_with(&self, other: &MMIORegion) -> bool {
let self_end = self.start + self.size;
let other_end = other.start + other.size;
!(self_end <= other.start || self.start >= other_end)
}
}

pub fn mmio_perform_access(base: usize, mmio: &mut MMIOAccess) {
Expand Down
32 changes: 31 additions & 1 deletion src/zone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,30 @@ impl ZoneInner {
mmio.handler = handler;
mmio.arg = arg;
} else {
let new_region = MMIORegion { start, size };

// Check for overlap with existing mmio handler regions.
if let Some(existing) = self
.mmio
.iter()
.find(|cfg| cfg.region.is_overlap_with(&new_region))
{
panic!(
"New MMIO handler region {:#x?} overlaps with existing handler {:#x?}",
new_region, existing.region
);
}

// Check for overlap with passthrough regions in the stage-2 page table.
if self.gpm.is_range_overlap(start, size) {
panic!(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This turns a recoverable zone-configuration error into a hypervisor hang. HvZoneStart already propagates the HvResult returned by zone_create(), while the panic handler spins forever. A conflicting non-root-zone config submitted by the root zone will therefore wedge the current root vCPU instead of returning EINVAL. Please make mmio_region_register() return HvResult and propagate it, and return EINVAL from the pt_init() overlap checks rather than panicking.

"New MMIO handler region {:#x?} overlaps with passthrough region in stage-2 page table",
new_region
);
}

self.mmio.push(MMIOConfig {
region: MMIORegion { start, size },
region: new_region,
handler,
arg,
})
Expand Down Expand Up @@ -253,6 +275,14 @@ impl ZoneInner {
.find(|cfg| cfg.region.contains_region(addr, size))
.map(|cfg| (cfg.region, cfg.handler, cfg.arg))
}

/// Check whether `[start, start+size)` overlaps with any registered MMIO handler region.
pub fn is_mmio_handler_overlap(&self, start: GuestPhysAddr, size: usize) -> bool {
let region = MMIORegion { start, size };
self.mmio
.iter()
.any(|cfg| cfg.region.is_overlap_with(&region))
}
/// If irq_id belongs to this zone
pub fn irq_in_zone(&self, irq_id: u32) -> bool {
let idx = (irq_id / 32) as usize;
Expand Down
Loading