mirror of
https://github.com/Momoko-Ayase/Senbei.git
synced 2026-09-19 03:57:59 -04:00
fix(unpacker): classify checksum range failures
This commit is contained in:
@@ -382,11 +382,28 @@ pub fn calculate_checksum(d: &[u8], pos: u32) -> u32 {
|
|||||||
|
|
||||||
/// CRC32 chained checksum. The (offset, length) descriptor at `pos` is read
|
/// CRC32 chained checksum. The (offset, length) descriptor at `pos` is read
|
||||||
/// from `d`; the bytes themselves are read from the separate `clean` buffer
|
/// from `d`; the bytes themselves are read from the separate `clean` buffer
|
||||||
/// (the original file image). `start` is the initial CRC accumulator.
|
/// (the original file image). `start` is the initial CRC accumulator. Returns
|
||||||
pub fn calculate_checksum2(d: &[u8], clean: &[u8], pos: u32, start: u32) -> u32 {
|
/// a range error instead of panicking when a descriptor points past `clean`.
|
||||||
|
pub fn calculate_checksum2(
|
||||||
|
d: &[u8],
|
||||||
|
clean: &[u8],
|
||||||
|
pos: u32,
|
||||||
|
start: u32,
|
||||||
|
) -> Result<u32, crate::Error> {
|
||||||
let offset = get_u32(d, pos);
|
let offset = get_u32(d, pos);
|
||||||
let length = get_u32(d, pos.wrapping_add(4));
|
let length = get_u32(d, pos.wrapping_add(4));
|
||||||
crc32::append(start, &clean[offset as usize..(offset + length) as usize])
|
let data_start = offset as usize;
|
||||||
|
let size = length as usize;
|
||||||
|
let end = data_start.checked_add(size);
|
||||||
|
let Some(end) = end.filter(|&end| end <= clean.len()) else {
|
||||||
|
return Err(crate::Error::BufferRangeOutOfBounds {
|
||||||
|
operation: crate::BufferOperation::Read,
|
||||||
|
offset: data_start,
|
||||||
|
size,
|
||||||
|
buffer_len: clean.len(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Ok(crc32::append(start, &clean[data_start..end]))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -1095,4 +1112,22 @@ mod tests {
|
|||||||
assert!(decompress(&mut d, 0x40, 0x80, 0, 4, 3));
|
assert!(decompress(&mut d, 0x40, 0x80, 0, 4, 3));
|
||||||
assert_eq!(&d[0x80..0x83], &[0x5A, 0x5A, 0x5A]);
|
assert_eq!(&d[0x80..0x83], &[0x5A, 0x5A, 0x5A]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn checksum2_rejects_source_range_outside_clean_image() {
|
||||||
|
let mut descriptor = [0u8; 8];
|
||||||
|
descriptor[0..4].copy_from_slice(&448u32.to_le_bytes());
|
||||||
|
descriptor[4..8].copy_from_slice(&634_432u32.to_le_bytes());
|
||||||
|
let clean = vec![0u8; 590_896];
|
||||||
|
let error = calculate_checksum2(&descriptor, &clean, 0, 0).expect_err("range must fail");
|
||||||
|
assert!(matches!(
|
||||||
|
error,
|
||||||
|
crate::Error::BufferRangeOutOfBounds {
|
||||||
|
operation: crate::BufferOperation::Read,
|
||||||
|
offset: 448,
|
||||||
|
size: 634_432,
|
||||||
|
buffer_len: 590_896,
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,6 +149,16 @@ pub enum UnpackError {
|
|||||||
buffer_len: usize,
|
buffer_len: usize,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[error(
|
||||||
|
"EXE checksum descriptor at 0x{descriptor:08X} points outside input (offset {offset}, size {size}, input length {image_len})"
|
||||||
|
)]
|
||||||
|
ExeChecksumRangeOutOfBounds {
|
||||||
|
descriptor: u32,
|
||||||
|
offset: usize,
|
||||||
|
size: usize,
|
||||||
|
image_len: usize,
|
||||||
|
},
|
||||||
|
|
||||||
#[error(
|
#[error(
|
||||||
"{table} descriptor out of bounds (offset {offset}, size 16, image length {image_len})"
|
"{table} descriptor out of bounds (offset {offset}, size 16, image length {image_len})"
|
||||||
)]
|
)]
|
||||||
|
|||||||
@@ -73,8 +73,22 @@ impl<'a> Unpacker<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Strategy (a): delegate to primitives::calculate_checksum2
|
// Strategy (a): delegate to primitives::calculate_checksum2
|
||||||
fn calculate_checksum2(&self, pos: u32, start: u32) -> u32 {
|
fn calculate_checksum2(&self, pos: u32, start: u32) -> Result<u32, UnpackError> {
|
||||||
primitives::calculate_checksum2(&self.decompressed, self.file_data, pos, start)
|
primitives::calculate_checksum2(&self.decompressed, self.file_data, pos, start).map_err(
|
||||||
|
|error| match error {
|
||||||
|
senbei_crypto::Error::BufferRangeOutOfBounds {
|
||||||
|
offset,
|
||||||
|
size,
|
||||||
|
buffer_len,
|
||||||
|
..
|
||||||
|
} => UnpackError::ExeChecksumRangeOutOfBounds {
|
||||||
|
descriptor: pos,
|
||||||
|
offset,
|
||||||
|
size,
|
||||||
|
image_len: buffer_len,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy (a): delegate to primitives::decrypt_data1
|
// Strategy (a): delegate to primitives::decrypt_data1
|
||||||
@@ -1018,7 +1032,19 @@ impl<'a> Unpacker<'a> {
|
|||||||
let n = get_u32(&u.decompressed, p.wrapping_add(4));
|
let n = get_u32(&u.decompressed, p.wrapping_add(4));
|
||||||
walk3 = walk3.wrapping_add(16);
|
walk3 = walk3.wrapping_add(16);
|
||||||
if n != 0 {
|
if n != 0 {
|
||||||
chain_crc = u.calculate_checksum2(walk3.wrapping_sub(16), chain_crc);
|
match u.calculate_checksum2(walk3.wrapping_sub(16), chain_crc) {
|
||||||
|
Ok(next) => chain_crc = next,
|
||||||
|
Err(UnpackError::ExeChecksumRangeOutOfBounds { .. }) => {
|
||||||
|
if verbose {
|
||||||
|
println!(
|
||||||
|
" checksum chain terminates at 0x{:08X}: descriptor payload is outside protected input",
|
||||||
|
walk3.wrapping_sub(16)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if get_u32(&u.decompressed, walk3.wrapping_sub(16).wrapping_add(4)) == 0 {
|
if get_u32(&u.decompressed, walk3.wrapping_sub(16).wrapping_add(4)) == 0 {
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user