{"record":{"id":"ceb5d909290f6e78","repo":"firecracker-microvm/firecracker","slug":"discard-length-overflow","errorCode":null,"errorMessage":"discard length overflow","messagePattern":"discard length overflow","errorType":"validation","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"src/vmm/src/devices/virtio/block/virtio/io/sync_io.rs","lineNumber":62,"sourceCode":"        return Ok(0);\n    }\n    let discarded = len;\n    let len = u64::from(len);\n\n    if file_type.is_block_device() {\n        let mut range = [offset, len];\n        // SAFETY: file is a valid fd, BLKDISCARD expects a pointer to two u64 values\n        // representing byte offset and byte length.\n        let ret = unsafe { libc::ioctl(file.as_raw_fd(), BLKDISCARD, range.as_mut_ptr()) };\n        if ret < 0 {\n            return Err(std::io::Error::last_os_error());\n        }\n    } else {\n        let off = libc::off_t::try_from(offset).map_err(|_| {\n            std::io::Error::new(std::io::ErrorKind::InvalidInput, \"discard offset overflow\")\n        })?;\n        let len = libc::off_t::try_from(len).map_err(|_| {\n            std::io::Error::new(std::io::ErrorKind::InvalidInput, \"discard length overflow\")\n        })?;\n        // SAFETY: file is a valid fd and fallocate does not retain the passed values.\n        let ret = unsafe {\n            libc::fallocate(\n                file.as_raw_fd(),\n                FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE,\n                off,\n                len,\n            )\n        };\n        if ret < 0 {\n            return Err(std::io::Error::last_os_error());\n        }\n    }\n\n    Ok(discarded)\n}\n","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/firecracker-microvm/firecracker/blob/a5a45f68b862689efa31ea0847695c108d872121/src/vmm/src/devices/virtio/block/virtio/io/sync_io.rs#L44-L80","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Bound the discard length to the maximum the syscall supports (min(len, disk_size - offset)) before calling file_discard_range.","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).","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.","On 32-bit platforms, ensure off_t is 64-bit (LFS) if lengths larger than 2 GiB are expected."],"exampleFix":"// before: unbounded length handed to file_discard_range\nfile_discard_range(&file, (offset, len_u64))?;\n\n// after: clamp so it always fits off_t and the file\nlet len_u64 = len_u64.min(i64::MAX as u64).min(disk_size - offset);\nfile_discard_range(&file, (offset, u32::try_from(len_u64).unwrap()))?;","handlingStrategy":"validation","validationCode":"const OFF_T_MAX: u64 = i64::MAX as u64;\nfn discard_len_ok(offset: u64, len: u64, disk_size: u64) -> bool {\n    len > 0 && len <= OFF_T_MAX && offset.saturating_add(len) <= disk_size\n}\n// call before file_discard_range:\nassert!(discard_len_ok(offset, len as u64, disk_size), \"discard length out of range\");","typeGuard":"fn fits_off_t(v: u64) -> bool { v <= i64::MAX as u64 }","tryCatchPattern":"match file_discard_range(&file, (offset, len)) {\n    Ok(n) => n,\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput && e.to_string().contains(\"length overflow\") => {\n        // clamp len or reject the request; do not retry\n    }\n    Err(e) => return Err(SyncIoError::Discard(e)),\n}","preventionTips":["Keep the discard length at u32 at the API boundary so overflow is structurally impossible; widen only with an explicit check.","Clamp len to disk_size - offset before calling, which also bounds it below off_t limits.","Watch for u32-to-u64 widening in refactors; any widening reintroduces the overflow path.","On 32-bit platforms verify off_t width (LFS) since even u32 lengths can overflow a 32-bit off_t."],"tags":["linux","io","fallocate","integer-overflow","virtio-block"],"backgroundTag":"value-out-of-range","analyzedSha":"a5a45f68b862689efa31ea0847695c108d872121","analyzedAt":"2026-09-10T14:33:13.708Z","contentChangedAt":"2026-09-10T14:33:13.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}