format: verify B-tree v2 internal node checksums
Only leaves and the header were checked. Harmless while every lookup read the whole tree, but the indexed lookup prunes children by the keys stored in internal nodes, so one corrupted byte there could route a name to the wrong child and report it missing with no error. A BTIN whose lookup3 checksum does not match is now ChecksumMismatch on every read (lookups and full traversals), as in libhdf5. Test: one byte of the root BTIN of the 35 001-link h5py group's name index changed -> lookups, paths and listings through File, MmapFile and LazyFile all fail with ChecksumMismatch, and h5py refuses both. Before, lookups returned Ok. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -355,6 +355,23 @@ fn read_internal_node(
|
||||
pos += total_nrec_width; // skip total records in subtree
|
||||
children.push((addr, child_nrec));
|
||||
}
|
||||
|
||||
// The checksum follows the child pointers and covers the node up to it.
|
||||
// Lookups prune children by the keys in this node, so an unverified
|
||||
// internal node could hide a record without any error: libhdf5 refuses
|
||||
// a mismatch here, and so does this.
|
||||
#[cfg(feature = "checksum")]
|
||||
{
|
||||
ensure_len(file_data, pos, 4)?;
|
||||
let stored = LittleEndian::read_u32(&file_data[pos..pos + 4]);
|
||||
let computed = crate::checksum::jenkins_lookup3(&file_data[offset..pos]);
|
||||
if computed != stored {
|
||||
return Err(FormatError::ChecksumMismatch {
|
||||
expected: stored,
|
||||
computed,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok((records_start, children))
|
||||
}
|
||||
|
||||
@@ -733,6 +750,8 @@ mod tests {
|
||||
buf.extend_from_slice(&child_nrec.to_le_bytes()[..nrec_width]);
|
||||
buf.resize(buf.len() + total_width, 0);
|
||||
}
|
||||
let sum = crate::checksum::jenkins_lookup3(&buf);
|
||||
buf.extend_from_slice(&sum.to_le_bytes());
|
||||
buf
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user