forked from 1b-agent/opencadstudio-upstream
fix(gdi): reject DIB headers shorter than their fixed fields
Bug: decode_packed (RLE-packed DIB decoding) guarded the header with a 12-byte check but then read fixed fields at offsets 14-16, 16-20 and 32-36. Any header shorter than 36 bytes that took the non-core path panicked with an out-of-bounds slice - every input in that range. Reproduce: 1. Open a BMP whose DIB header takes the non-core path but is shorter than 36 bytes (a 12-35 byte header claiming packed data). 2. Before: out-of-bounds panic at the fixed-field reads. 3. After: the header is rejected and the image skipped; valid headers (including a 1x1 DIB) still decode. Fix: The non-core branch requires len >= 36 before any of its fixed reads - every input under 36 bytes panicked before the fix, so no non-panicking behaviour changes. Valid packed DIBs (tests include a 1x1 image) decode unchanged.
This commit is contained in:
1 parent
8e4b405980
commit
9bbee20c6c
1 file changed
+49
@@ -45,6 +45,13 @@ pub fn decode_packed(dibdata: &[u8]) -> Option<(Vec<u8>, u32, u32)> {
|
||||
let bpp = u16::from_le_bytes(dibdata[10..12].try_into().ok()?) as usize;
|
||||
(bpp, 0usize, 0usize)
|
||||
} else {
|
||||
// The fixed fields below reach byte 36 (biClrUsed), but `bi_size`
|
||||
// may be any value in 12..=len — a short malformed header (e.g.
|
||||
// len = bi_size = 13) used to panic slicing [14..16]/[16..20]/
|
||||
// [32..36]. Anything under 36 bytes cannot hold them.
|
||||
if dibdata.len() < 36 {
|
||||
return None;
|
||||
}
|
||||
let bpp = u16::from_le_bytes(dibdata[14..16].try_into().ok()?) as usize;
|
||||
let comp = u32::from_le_bytes(dibdata[16..20].try_into().ok()?) as usize;
|
||||
let used = u32::from_le_bytes(dibdata[32..36].try_into().ok()?) as usize;
|
||||
@@ -87,3 +94,45 @@ pub fn ranges<'a>(
|
||||
let bits = record.get(off_bits..off_bits.checked_add(cb_bits)?)?;
|
||||
Some((bmi, bits))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod decode_packed_guard_tests {
|
||||
use super::decode_packed;
|
||||
|
||||
/// `len`-byte buffer whose `biSize` claims the whole buffer (so the
|
||||
/// `12 <= bi_size <= len` guard passes) — but the non-core branch then
|
||||
/// reads fixed offsets up to byte 36.
|
||||
fn malformed(len: usize) -> Vec<u8> {
|
||||
let mut d = vec![0u8; len];
|
||||
d[0..4].copy_from_slice(&(len as u32).to_le_bytes());
|
||||
d
|
||||
}
|
||||
|
||||
/// The report's three panic windows: `[14..16]` (len 13..15),
|
||||
/// `[16..20]` (len 16..19), `[32..36]` (len 20..35).
|
||||
#[test]
|
||||
fn short_noncore_headers_are_rejected_not_oob() {
|
||||
for len in 13..=35 {
|
||||
assert!(
|
||||
decode_packed(&malformed(len)).is_none(),
|
||||
"len {len} must be rejected without panicking"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A well-formed 1×1 32-bpp DIB still decodes through the same path.
|
||||
#[test]
|
||||
fn a_valid_info_header_dib_still_decodes() {
|
||||
let mut d = vec![0u8; 44];
|
||||
d[0..4].copy_from_slice(&40u32.to_le_bytes()); // biSize
|
||||
d[4..8].copy_from_slice(&1i32.to_le_bytes()); // width
|
||||
d[8..12].copy_from_slice(&1i32.to_le_bytes()); // height
|
||||
d[12..14].copy_from_slice(&1u16.to_le_bytes()); // planes
|
||||
d[14..16].copy_from_slice(&32u16.to_le_bytes()); // bitCount
|
||||
d[20..24].copy_from_slice(&4u32.to_le_bytes()); // biSizeImage
|
||||
d[40..44].copy_from_slice(&[0, 0, 255, 255]); // one BGRA pixel
|
||||
let (rgba, w, h) = decode_packed(&d).expect("valid DIB decodes");
|
||||
assert_eq!((w, h), (1, 1));
|
||||
assert_eq!(rgba, vec![255, 0, 0, 255]);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user