security(format): fix unchecked-addition bounds check in symbol_table.rs

SymbolTableNode::parse used raw offset+8 arithmetic that can overflow
on a crafted v1-group B-tree leaf with a near-u64::MAX SNOD child
pointer (group_v1.rs passes such offsets through unchecked). Switch to
checked_add, matching read_offset in the same file. Also harden the
entries_start + num_symbols*entry_size computation with checked_add
for consistency, even though num_symbols being u16 already bounds
that multiply. Add regression tests.

INT-02
This commit is contained in:
ClawHDF5 Coding Agent
2026-08-17 00:26:13 +00:00
parent 73bc067fea
commit 864faf3656
+28 -3
View File
@@ -80,9 +80,9 @@ impl SymbolTableNode {
offset_size: u8,
) -> Result<SymbolTableNode, FormatError> {
// signature(4) + version(1) + reserved(1) + number_of_symbols(2) = 8
if offset + 8 > file_data.len() {
if offset.checked_add(8).is_none_or(|end| end > file_data.len()) {
return Err(FormatError::UnexpectedEof {
expected: offset + 8,
expected: offset.saturating_add(8),
available: file_data.len(),
});
}
@@ -103,7 +103,12 @@ impl SymbolTableNode {
// Each entry: link_name_offset(os) + obj_hdr_addr(os) + cache_type(4) + reserved(4) + scratch(16)
let entry_size = os + os + 4 + 4 + 16;
let entries_start = offset + 8;
let needed = entries_start + num_symbols * entry_size;
let needed = entries_start
.checked_add(num_symbols * entry_size)
.ok_or(FormatError::UnexpectedEof {
expected: usize::MAX,
available: file_data.len(),
})?;
if needed > file_data.len() {
return Err(FormatError::UnexpectedEof {
expected: needed,
@@ -228,4 +233,24 @@ mod tests {
let err = SymbolTableNode::parse(&data, 0, 8).unwrap_err();
assert_eq!(err, FormatError::InvalidSymbolTableNodeVersion(2));
}
/// A near-`usize::MAX` SNOD offset must error cleanly, not overflow/panic.
#[test]
fn parse_snod_rejects_offset_overflow() {
let data = build_snod(&[], 8);
let result = SymbolTableNode::parse(&data, usize::MAX - 4, 8);
assert!(result.is_err());
}
/// A huge symbol count combined with a large entries_start must not
/// overflow the `needed` size computation.
#[test]
fn parse_snod_rejects_entries_size_overflow() {
let mut data = build_snod(&[], 8);
// num_symbols at offset 6..8 — set to max to blow up entries_start + num_symbols*entry_size
data[6] = 0xFF;
data[7] = 0xFF;
let result = SymbolTableNode::parse(&data, usize::MAX / 2, 8);
assert!(result.is_err());
}
}