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

discard length overflow

Error message

discard length overflow

What it means

This std::io::Error (InvalidInput) is raised in file_discard_range when the u32/u64 discard length cannot be converted to libc::off_t for the fallocate(2) call used to punch a hole in a regular file. fallocate's length parameter is a signed off_t, so a length above i64::MAX fails TryFrom and the engine returns this error instead of passing a truncated value to the kernel. Note len itself is a u32 at the call site (u64::from(len) only for the block path), so this is effectively unreachable via normal callers and mainly guards direct/internal use and future widening.

Source

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

        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());
        }
    }

    Ok(discarded)
}

View on GitHub (pinned to a5a45f68b8)

Solutions

  1. Bound the discard length to the maximum the syscall supports (min(len, disk_size - offset)) before calling file_discard_range.
  2. If length is derived from guest sectors, cap the per-request discard segment size in the virtio-block layer (e.g. reject len > i64::MAX as InvalidInput before the call).
  3. Keep len as u32 at the public boundary (as the current API does) so the conversion can never fail; only widen it with an explicit range check.
  4. On 32-bit platforms, ensure off_t is 64-bit (LFS) if lengths larger than 2 GiB are expected.

Example fix

// before: unbounded length handed to file_discard_range
file_discard_range(&file, (offset, len_u64))?;

// after: clamp so it always fits off_t and the file
let len_u64 = len_u64.min(i64::MAX as u64).min(disk_size - offset);
file_discard_range(&file, (offset, u32::try_from(len_u64).unwrap()))?;
Defensive patterns

Strategy: validation

Validate before calling

const OFF_T_MAX: u64 = i64::MAX as u64;
fn discard_len_ok(offset: u64, len: u64, disk_size: u64) -> bool {
    len > 0 && len <= OFF_T_MAX && offset.saturating_add(len) <= disk_size
}
// call before file_discard_range:
assert!(discard_len_ok(offset, len as u64, disk_size), "discard length 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("length overflow") => {
        // clamp len or reject the request; do not retry
    }
    Err(e) => return Err(SyncIoError::Discard(e)),
}

Prevention

When it happens

Trigger: Calling file_discard_range with range.1 (len) whose u64 value exceeds i64::MAX on a non-block-device file, making libc::off_t::try_from(len) fail; only reachable through internal calls or test code since SyncFileEngine::discard passes a u32 length.

Common situations: Internal refactors widening the length parameter from u32 to u64 without bounding it; test code (e.g. test_discard_regular_file variants) passing synthetic huge lengths; future support for larger discard segments on a 32-bit target where off_t is 32-bit and a u32 length near u32::MAX (2 GiB) would overflow a 32-bit off_t.

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/ceb5d909290f6e78. Report an issue: GitHub.