firecracker-microvm/firecracker · error · std::io::Error

discard offset overflow

Error message

discard offset overflow

What it means

This std::io::Error (InvalidInput) is raised in file_discard_range when the u64 discard offset cannot be converted to libc::off_t. The non-block-device path punches a hole with fallocate(2), which takes a signed off_t; on 64-bit Linux off_t is i64, so any offset above i64::MAX fails TryFrom and the function refuses to issue the ioctl rather than silently truncating. It signals that the requested discard range lies beyond what the syscall ABI can express.

Source

Thrown at src/vmm/src/devices/virtio/block/virtio/io/sync_io.rs:59

    let (offset, len) = range;

    if len == 0 {
        return Ok(0);
    }
    let discarded = len;
    let len = u64::from(len);

    if file_type.is_block_device() {
        let mut range = [offset, len];
        // SAFETY: file is a valid fd, BLKDISCARD expects a pointer to two u64 values
        // representing byte offset and byte length.
        let ret = unsafe { libc::ioctl(file.as_raw_fd(), BLKDISCARD, range.as_mut_ptr()) };
        if ret < 0 {
            return Err(std::io::Error::last_os_error());
        }
    } else {
        let off = libc::off_t::try_from(offset).map_err(|_| {
            std::io::Error::new(std::io::ErrorKind::InvalidInput, "discard offset overflow")
        })?;
        let len = libc::off_t::try_from(len).map_err(|_| {
            std::io::Error::new(std::io::ErrorKind::InvalidInput, "discard length overflow")
        })?;
        // SAFETY: file is a valid fd and fallocate does not retain the passed values.
        let ret = unsafe {
            libc::fallocate(
                file.as_raw_fd(),
                FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE,
                off,
                len,
            )
        };
        if ret < 0 {
            return Err(std::io::Error::last_os_error());
        }
    }

View on GitHub (pinned to a5a45f68b8)

Solutions

  1. Validate the discard offset against the backing file's actual size before calling file_discard_range; reject requests beyond the disk end in the virtio-block layer.
  2. Clamp or bound the guest-provided sector so offset = sector * sector_size <= i64::MAX, returning an error to the guest instead of attempting the discard.
  3. Run on a platform where off_t is 64-bit (64-bit Linux); on 32-bit builds compile with LFS (large file support) so off_t is 64-bit.
  4. If the offset is legitimately huge, split the discard into multiple in-range chunks or return EINVAL to the guest since fallocate cannot address it.

Example fix

// before: driver passes an unvalidated u64 offset
file_discard_range(&file, (offset, len))?;

// after: caller validates the offset fits in off_t and the disk before discarding
const MAX_OFF: u64 = i64::MAX as u64;
if offset > MAX_OFF || offset as u64 + len as u64 > disk_size {
    return Err(io::Error::new(ErrorKind::InvalidInput, "discard out of range"));
}
file_discard_range(&file, (offset, len))?;
Defensive patterns

Strategy: validation

Validate before calling

const OFF_T_MAX: u64 = i64::MAX as u64;
fn discard_offset_ok(offset: u64, len: u32, disk_size: u64) -> bool {
    offset <= OFF_T_MAX && offset.saturating_add(len as u64) <= disk_size
}
// call before file_discard_range:
assert!(discard_offset_ok(offset, len, disk_size), "discard offset out of range");

Type guard

fn fits_off_t(v: u64) -> bool { v <= i64::MAX as u64 }

Try / catch

match file_discard_range(&file, (offset, len)) {
    Ok(n) => n,
    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput && e.to_string().contains("offset overflow") => {
        // reject the guest request; do not retry
    }
    Err(e) => return Err(SyncIoError::Discard(e)),
}

Prevention

When it happens

Trigger: Calling file_discard_range (directly or via SyncFileEngine::discard / the virtio-block DISCARD request path) with range.0 (offset) > i64::MAX on a regular file backend, so libc::off_t::try_from(offset) returns Err.

Common situations: A guest issues DISCARD/TRIM with a sector-derived byte offset computed from a corrupt or malicious descriptor; a mis-sized backing file paired with a disk geometry whose total size exceeds i64 bytes (8 EiB); arithmetic overflow in the offset calculation (sector * 512 wrapping to a huge u64); tests constructing synthetic ranges near u64::MAX.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of firecracker-microvm/firecracker@a5a45f68b8 (2026-09-10). Data as JSON: /api/errors/63269ca94e2094fa. Report an issue: GitHub.