Skip to content

openvmm: device I/O offset lookup overflows for a range ending at the top #1384

Open

Description

@tangruize

offset_of on a chipset device's port-I/O or MMIO region whose last byte is the address maximum (for example 2 ports at 0xfffe, or MMIO ending at u64::MAX) overflows computing the region's exclusive end. A debug build panics with "attempt to add with overflow" on the first access to the region. A release build wraps and returns None for the region's own addresses, so the device ignores accesses it owns. No device at the pin maps a region there, so this is latent. The VMM's configuration places these regions; the only one a guest can move (the PIIX4 PM block, 0x37 ports at a 64-byte-aligned base) cannot reach 0xffff, and PCI BARs are matched by BarMappings::find, which does not overflow. None of the devices that call offset_of is in NVX's microVM.

Reproduction

Map a 2-byte port region at 0xfffe and call offset_of(0xfffe). It should return Some(0); 0xfffe + 2 overflows u16 instead. The tests port_offset_of_at_top_boundary and mmio_offset_of_at_top_boundary (vmotherboard) that fix.patch adds cover both widths at the boundary.

Cause

impl_device_range!'s offset_of forms the region's exclusive end:

// vmm_core/vmotherboard/src/chipset/backing/arc_mutex/services.rs
fn offset_of(&self, addr: $addr) -> Option<$addr> {
    let base = self.addr?;
    (base..(base + self.len))
        .contains(&addr)
        .then(|| addr - base)
}

A region ending at the address maximum is valid, but its exclusive end does not fit the address type.

Fix

fix.patch computes addr.checked_sub(base) and accepts the address when the offset is below len, so no end address is formed. Results for every other region are unchanged. The tests check the first and last byte, the byte before the base, and a remap that moves the region down by one.

Activity

  1. tangruize commented on Nov 8, 2026

    @tangruize
    ContributorAuthor

    Proposed patch

    Patch against OpenVMM 3cf58a5234ed824435825c9f87187f5129219724. Save the contents below as fix.patch and run git apply fix.patch.

    fix.patch
    diff --git a/vmm_core/vmotherboard/src/chipset/backing/arc_mutex/services.rs b/vmm_core/vmotherboard/src/chipset/backing/arc_mutex/services.rs
    index 7baae76..25cb247 100644
    --- a/vmm_core/vmotherboard/src/chipset/backing/arc_mutex/services.rs
    +++ b/vmm_core/vmotherboard/src/chipset/backing/arc_mutex/services.rs
    @@ -394,9 +394,8 @@ fn len(&self) -> $addr {
     
                     fn offset_of(&self, addr: $addr) -> Option<$addr> {
                         let base = self.addr?;
    -                    (base..(base + self.len))
    -                        .contains(&addr)
    -                        .then(|| addr - base)
    +                    let offset = addr.checked_sub(base)?;
    +                    (offset < self.len).then_some(offset)
                     }
                 }
             };
    @@ -454,5 +453,34 @@ fn pio_region_drop_releases_its_mapping() {
                 drop(region);
                 assert!(!ranges.is_occupied(0x100));
             }
    +
    +        #[test]
    +        fn port_offset_of_at_top_boundary() {
    +            let mut mapper = mapper(IoRanges::<u16>::new(false, None));
    +            let mut region = mapper.new_io_region("top", 2);
    +            assert_eq!(region.offset_of(0xffff), None);
    +            region.map(0xfffe);
    +            assert_eq!(region.offset_of(0xfffd), None);
    +            assert_eq!(region.offset_of(0xfffe), Some(0));
    +            assert_eq!(region.offset_of(0xffff), Some(1));
    +            region.map(0xfffd);
    +            assert_eq!(region.offset_of(0xfffe), Some(1));
    +            assert_eq!(region.offset_of(0xffff), None);
    +        }
    +
    +        #[test]
    +        fn mmio_offset_of_at_top_boundary() {
    +            let mut mapper = mapper(IoRanges::<u64>::new(false, None));
    +            let mut region = mapper.new_io_region("top", 0x1000);
    +            let base = u64::MAX - 0xfff;
    +            assert_eq!(region.offset_of(base), None);
    +            region.map(base);
    +            assert_eq!(region.offset_of(base - 1), None);
    +            assert_eq!(region.offset_of(base), Some(0));
    +            assert_eq!(region.offset_of(u64::MAX), Some(0xfff));
    +            region.map(base - 1);
    +            assert_eq!(region.offset_of(u64::MAX - 1), Some(0xfff));
    +            assert_eq!(region.offset_of(u64::MAX), None);
    +        }
         }
     }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions