Performance, security and provenance hardening (ann/io/migrate/agent) + two audit fixes #2
@@ -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 {
|
fn is_undefined_addr(addr: u64, offset_size: u8) -> bool {
|
||||||
match offset_size {
|
match offset_size {
|
||||||
2 => addr == 0xFFFF,
|
2 => addr == 0xFFFF,
|
||||||
@@ -98,12 +111,7 @@ impl ExtensibleArrayHeader {
|
|||||||
// 6 stats fields (each length_size) + index_block_address(offset_size) + checksum(4)
|
// 6 stats fields (each length_size) + index_block_address(offset_size) + checksum(4)
|
||||||
let min_size =
|
let min_size =
|
||||||
4 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 6 * length_size as usize + offset_size as usize + 4;
|
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() {
|
ensure_len(file_data, offset, min_size)?;
|
||||||
return Err(FormatError::UnexpectedEof {
|
|
||||||
expected: offset + min_size,
|
|
||||||
available: file_data.len(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let d = &file_data[offset..];
|
let d = &file_data[offset..];
|
||||||
if &d[0..4] != b"EAHD" {
|
if &d[0..4] != b"EAHD" {
|
||||||
@@ -275,12 +283,7 @@ fn read_data_block_elements(
|
|||||||
) -> Result<Vec<ChunkInfo>, FormatError> {
|
) -> Result<Vec<ChunkInfo>, FormatError> {
|
||||||
// AEDB: signature(4) + version(1) + client_id(1) + header_address(offset_size)
|
// AEDB: signature(4) + version(1) + client_id(1) + header_address(offset_size)
|
||||||
let db_header_size = 4 + 1 + 1 + offset_size as usize;
|
let db_header_size = 4 + 1 + 1 + offset_size as usize;
|
||||||
if db_offset + db_header_size > file_data.len() {
|
ensure_len(file_data, db_offset, db_header_size)?;
|
||||||
return Err(FormatError::UnexpectedEof {
|
|
||||||
expected: db_offset + db_header_size,
|
|
||||||
available: file_data.len(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let d = &file_data[db_offset..];
|
let d = &file_data[db_offset..];
|
||||||
if &d[0..4] != b"EADB" {
|
if &d[0..4] != b"EADB" {
|
||||||
@@ -427,12 +430,7 @@ pub fn read_extensible_array_chunks(
|
|||||||
// Parse index block (AEIB)
|
// Parse index block (AEIB)
|
||||||
let ib_offset = header.index_block_address as usize;
|
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
|
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() {
|
ensure_len(file_data, ib_offset, ib_header_size)?;
|
||||||
return Err(FormatError::UnexpectedEof {
|
|
||||||
expected: ib_offset + ib_header_size,
|
|
||||||
available: file_data.len(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let ib = &file_data[ib_offset..];
|
let ib = &file_data[ib_offset..];
|
||||||
if &ib[0..4] != b"EAIB" {
|
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)
|
// AESB: signature(4) + version(1) + client_id(1) + header_address(offset_size)
|
||||||
let sb_header_size = 4 + 1 + 1 + os;
|
let sb_header_size = 4 + 1 + 1 + os;
|
||||||
if sb_offset + sb_header_size > file_data.len() {
|
ensure_len(file_data, sb_offset, sb_header_size)?;
|
||||||
return Err(FormatError::UnexpectedEof {
|
|
||||||
expected: sb_offset + sb_header_size,
|
|
||||||
available: file_data.len(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if &file_data[sb_offset..sb_offset + 4] != b"EASB" {
|
if &file_data[sb_offset..sb_offset + 4] != b"EASB" {
|
||||||
return Err(FormatError::ChunkedReadError(
|
return Err(FormatError::ChunkedReadError(
|
||||||
@@ -759,6 +752,33 @@ mod tests {
|
|||||||
assert!(result.is_err());
|
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]
|
#[test]
|
||||||
fn parse_header_invalid_version() {
|
fn parse_header_invalid_version() {
|
||||||
let mut buf = vec![0u8; 256];
|
let mut buf = vec![0u8; 256];
|
||||||
|
|||||||
@@ -47,6 +47,19 @@ fn read_length(data: &[u8], pos: usize, size: u8) -> Result<u64, FormatError> {
|
|||||||
read_offset(data, pos, size)
|
read_offset(data, pos, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(data: &[u8], pos: usize, size: u8) -> bool {
|
fn is_undefined(data: &[u8], pos: usize, size: u8) -> bool {
|
||||||
let s = size as usize;
|
let s = size as usize;
|
||||||
if pos + s > data.len() {
|
if pos + s > data.len() {
|
||||||
@@ -66,12 +79,7 @@ impl FixedArrayHeader {
|
|||||||
// FAHD signature(4) + version(1) + client_id(1) + element_size(1) +
|
// FAHD signature(4) + version(1) + client_id(1) + element_size(1) +
|
||||||
// max_nelmts_bits(1) + num_elements(length_size) + data_block_addr(offset_size) + checksum(4)
|
// max_nelmts_bits(1) + num_elements(length_size) + data_block_addr(offset_size) + checksum(4)
|
||||||
let min_size = 4 + 1 + 1 + 1 + 1 + length_size as usize + offset_size as usize + 4;
|
let min_size = 4 + 1 + 1 + 1 + 1 + length_size as usize + offset_size as usize + 4;
|
||||||
if offset + min_size > file_data.len() {
|
ensure_len(file_data, offset, min_size)?;
|
||||||
return Err(FormatError::UnexpectedEof {
|
|
||||||
expected: offset + min_size,
|
|
||||||
available: file_data.len(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let d = &file_data[offset..];
|
let d = &file_data[offset..];
|
||||||
if &d[0..4] != b"FAHD" {
|
if &d[0..4] != b"FAHD" {
|
||||||
@@ -126,12 +134,7 @@ pub fn read_fixed_array_chunks(
|
|||||||
|
|
||||||
// Parse data block header: FADB(4) + version(1) + client_id(1) + header_address(offset_size)
|
// Parse data block header: FADB(4) + version(1) + client_id(1) + header_address(offset_size)
|
||||||
let db_header_size = 4 + 1 + 1 + offset_size as usize;
|
let db_header_size = 4 + 1 + 1 + offset_size as usize;
|
||||||
if db_offset + db_header_size > file_data.len() {
|
ensure_len(file_data, db_offset, db_header_size)?;
|
||||||
return Err(FormatError::UnexpectedEof {
|
|
||||||
expected: db_offset + db_header_size,
|
|
||||||
available: file_data.len(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let d = &file_data[db_offset..];
|
let d = &file_data[db_offset..];
|
||||||
if &d[0..4] != b"FADB" {
|
if &d[0..4] != b"FADB" {
|
||||||
@@ -489,6 +492,29 @@ mod tests {
|
|||||||
assert!(r.is_err());
|
assert!(r.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 = FixedArrayHeader::parse(&buf, usize::MAX - 4, 8, 8);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A near-`usize::MAX` data block address must error cleanly, not overflow/panic.
|
||||||
|
#[test]
|
||||||
|
fn read_rejects_data_block_offset_overflow() {
|
||||||
|
let header = FixedArrayHeader {
|
||||||
|
client_id: 0,
|
||||||
|
element_size: 8,
|
||||||
|
max_nelmts_bits: 10,
|
||||||
|
num_elements: 1,
|
||||||
|
data_block_address: (usize::MAX - 4) as u64,
|
||||||
|
};
|
||||||
|
let buf = vec![0u8; 64];
|
||||||
|
let r = read_fixed_array_chunks(&buf, &header, &[100], &[20], 8, 8, 8);
|
||||||
|
assert!(r.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_fixed_array_header_invalid_version() {
|
fn parse_fixed_array_header_invalid_version() {
|
||||||
let mut buf = vec![0u8; 256];
|
let mut buf = vec![0u8; 256];
|
||||||
|
|||||||
Reference in New Issue
Block a user