security(format): fix unchecked-addition bounds checks in fixed_array/extensible_array
Six sites used raw `offset + N > file_data.len()` arithmetic that can overflow on a crafted file with an address field near u64::MAX, bypassing the bounds check before the next slice op panics. Switch to the checked_add-based ensure_len pattern already used by local_heap.rs and other parsers in this crate. Add regression tests for offsets near usize::MAX in both files. INT-01
This commit is contained in:
@@ -54,6 +54,19 @@ fn read_offset(data: &[u8], pos: usize, size: u8) -> Result<u64, FormatError> {
|
||||
})
|
||||
}
|
||||
|
||||
fn ensure_len(data: &[u8], offset: usize, needed: usize) -> Result<(), FormatError> {
|
||||
if offset
|
||||
.checked_add(needed)
|
||||
.is_none_or(|end| end > data.len())
|
||||
{
|
||||
return Err(FormatError::UnexpectedEof {
|
||||
expected: offset.saturating_add(needed),
|
||||
available: data.len(),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_undefined_addr(addr: u64, offset_size: u8) -> bool {
|
||||
match offset_size {
|
||||
2 => addr == 0xFFFF,
|
||||
@@ -98,12 +111,7 @@ impl ExtensibleArrayHeader {
|
||||
// 6 stats fields (each length_size) + index_block_address(offset_size) + checksum(4)
|
||||
let min_size =
|
||||
4 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 6 * length_size as usize + offset_size as usize + 4;
|
||||
if offset + min_size > file_data.len() {
|
||||
return Err(FormatError::UnexpectedEof {
|
||||
expected: offset + min_size,
|
||||
available: file_data.len(),
|
||||
});
|
||||
}
|
||||
ensure_len(file_data, offset, min_size)?;
|
||||
|
||||
let d = &file_data[offset..];
|
||||
if &d[0..4] != b"EAHD" {
|
||||
@@ -275,12 +283,7 @@ fn read_data_block_elements(
|
||||
) -> Result<Vec<ChunkInfo>, FormatError> {
|
||||
// AEDB: signature(4) + version(1) + client_id(1) + header_address(offset_size)
|
||||
let db_header_size = 4 + 1 + 1 + offset_size as usize;
|
||||
if db_offset + db_header_size > file_data.len() {
|
||||
return Err(FormatError::UnexpectedEof {
|
||||
expected: db_offset + db_header_size,
|
||||
available: file_data.len(),
|
||||
});
|
||||
}
|
||||
ensure_len(file_data, db_offset, db_header_size)?;
|
||||
|
||||
let d = &file_data[db_offset..];
|
||||
if &d[0..4] != b"EADB" {
|
||||
@@ -427,12 +430,7 @@ pub fn read_extensible_array_chunks(
|
||||
// Parse index block (AEIB)
|
||||
let ib_offset = header.index_block_address as usize;
|
||||
let ib_header_size = 4 + 1 + 1 + offset_size as usize; // sig + ver + client + hdr_addr
|
||||
if ib_offset + ib_header_size > file_data.len() {
|
||||
return Err(FormatError::UnexpectedEof {
|
||||
expected: ib_offset + ib_header_size,
|
||||
available: file_data.len(),
|
||||
});
|
||||
}
|
||||
ensure_len(file_data, ib_offset, ib_header_size)?;
|
||||
|
||||
let ib = &file_data[ib_offset..];
|
||||
if &ib[0..4] != b"EAIB" {
|
||||
@@ -628,12 +626,7 @@ fn read_super_block(
|
||||
|
||||
// AESB: signature(4) + version(1) + client_id(1) + header_address(offset_size)
|
||||
let sb_header_size = 4 + 1 + 1 + os;
|
||||
if sb_offset + sb_header_size > file_data.len() {
|
||||
return Err(FormatError::UnexpectedEof {
|
||||
expected: sb_offset + sb_header_size,
|
||||
available: file_data.len(),
|
||||
});
|
||||
}
|
||||
ensure_len(file_data, sb_offset, sb_header_size)?;
|
||||
|
||||
if &file_data[sb_offset..sb_offset + 4] != b"EASB" {
|
||||
return Err(FormatError::ChunkedReadError(
|
||||
@@ -759,6 +752,33 @@ mod tests {
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
/// A near-`usize::MAX` offset must error cleanly, not overflow/panic.
|
||||
#[test]
|
||||
fn parse_rejects_offset_overflow() {
|
||||
let buf = vec![0u8; 64];
|
||||
let result = ExtensibleArrayHeader::parse(&buf, usize::MAX - 4, 8, 8);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
/// A near-`usize::MAX` index block address must error cleanly, not overflow/panic.
|
||||
#[test]
|
||||
fn read_rejects_index_block_offset_overflow() {
|
||||
let header = ExtensibleArrayHeader {
|
||||
client_id: 0,
|
||||
element_size: 8,
|
||||
max_nelmts_bits: 10,
|
||||
idx_blk_elmts: 2,
|
||||
min_dblk_nelmts: 4,
|
||||
super_blk_min_nelmts: 2,
|
||||
max_dblk_nelmts_bits: 8,
|
||||
num_elements: 5,
|
||||
index_block_address: (usize::MAX - 4) as u64,
|
||||
};
|
||||
let buf = vec![0u8; 64];
|
||||
let r = read_extensible_array_chunks(&buf, &header, &[100], &[20], 8, 8, 8);
|
||||
assert!(r.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_header_invalid_version() {
|
||||
let mut buf = vec![0u8; 256];
|
||||
|
||||
Reference in New Issue
Block a user