{"record":{"id":"41a0118c497dd7cc","repo":"tursodatabase/turso","slug":"buffer-too-short-for-u32-at-offset-offset","errorCode":null,"errorMessage":"buffer too short for u32 at offset {offset}","messagePattern":"buffer too short for u32 at offset (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"cli/sync_server.rs","lineNumber":1106,"sourceCode":"    let stored_crc = read_u32_le(log, trailer_start)?;\n    if stored_crc != expected_crc {\n        return Err(anyhow!(\n            \"MVCC logical log frame checksum mismatch at offset {offset}\"\n        ));\n    }\n    let end_magic = read_u32_le(log, trailer_start + 4)?;\n    if end_magic != MVCC_TX_END_MAGIC {\n        return Err(anyhow!(\n            \"invalid MVCC logical log frame end magic at offset {offset}\"\n        ));\n    }\n    Ok(Some((frame_end, stored_crc)))\n}\n\nfn read_u32_le(buf: &[u8], offset: usize) -> Result<u32> {\n    let bytes = buf\n        .get(offset..offset + 4)\n        .ok_or_else(|| anyhow!(\"buffer too short for u32 at offset {offset}\"))?;\n    Ok(u32::from_le_bytes(bytes.try_into().unwrap()))\n}\n\nfn read_u64_le(buf: &[u8], offset: usize) -> Result<u64> {\n    let bytes = buf\n        .get(offset..offset + 8)\n        .ok_or_else(|| anyhow!(\"buffer too short for u64 at offset {offset}\"))?;\n    Ok(u64::from_le_bytes(bytes.try_into().unwrap()))\n}\n\nfn current_db_size_pages(conn: &Connection, max_frame: u64) -> Result<u64> {\n    if max_frame > 0 {\n        let frame_size = WAL_FRAME_HEADER_SIZE + PAGE_SIZE;\n        let mut last_frame = vec![0u8; frame_size];\n        let last_info = conn.wal_get_frame(max_frame, &mut last_frame)?;\n        Ok(last_info.db_size as u64)\n    } else {\n        Ok(0)","sourceCodeStart":1088,"sourceCodeEnd":1124,"githubUrl":"https://github.com/tursodatabase/turso/blob/bad083fafbefdeae9a42ec19bdaaad8918dcf411/cli/sync_server.rs#L1088-L1124","documentation":"read_u32_le reads 4 little-endian bytes at an offset using slice::get and returns this error when offset..offset+4 leaves the buffer. Inside the log scanner it is defensive: every caller pre-verifies lengths (log.len() >= 56 before header validation, header+trailer size checks before frame reads), so hitting it there indicates a regression. New call sites must bound-check before calling.","triggerScenarios":"read_u32_le(buf, offset) with fewer than 4 bytes remaining at offset (or an offset large enough that offset+4 itself overflows) — a caller that skipped its length pre-check, or refactored code that invalidated a previously proven invariant.","commonSituations":"New code paths using the helpers on short buffers; refactors that moved or removed size pre-checks; fuzzing the reader with truncated inputs; off-by-one errors in offset math computed by the caller.","solutions":["Check buf.len() >= offset + 4 before calling read_u32_le.","Compute the bound with offset.checked_add(4) so a huge offset cannot silently wrap.","If this fires inside scan_mvcc_log on a length-checked buffer, file a bug — the invariant broke.","Keep read helpers private and always pair them with explicit size assertions at the call site."],"exampleFix":"// before\nlet magic = read_u32_le(buf, offset)?; // may error: buffer too short for u32\n\n// after\nanyhow::ensure!(\n    offset.checked_add(4).is_some_and(|end| end <= buf.len()),\n    \"no room for u32 at offset {offset}\"\n);\nlet magic = read_u32_le(buf, offset)?;","handlingStrategy":"validation","validationCode":"fn has_u32_at(buf: &[u8], offset: usize) -> bool {\n    offset.checked_add(4).is_some_and(|end| end <= buf.len())\n}\n// before reading:\nanyhow::ensure!(has_u32_at(buf, offset), \"no room for u32 at offset {offset}\");\nlet value = read_u32_le(buf, offset)?;","typeGuard":"fn has_u32_at(buf: &[u8], offset: usize) -> bool {\n    offset.checked_add(4).is_some_and(|end| end <= buf.len())\n}","tryCatchPattern":"match read_u32_le(buf, offset) {\n    Ok(value) => { /* use value */ }\n    Err(err) if err.to_string().contains(\"buffer too short for u32\") => {\n        // caller bug: fix the missing length pre-check, do not retry\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Always verify the buffer covers offset+N before any fixed-width read.","Compute bounds with checked_add so huge offsets cannot wrap.","Keep read helpers private; expose APIs that take pre-validated slices.","Add debug_assert! length invariants at call sites in debug builds."],"tags":["bounds-check","parsing","rust","offset"],"backgroundTag":"index-out-of-bounds","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}