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;
|
let bpp = u16::from_le_bytes(dibdata[10..12].try_into().ok()?) as usize;
|
||||||
(bpp, 0usize, 0usize)
|
(bpp, 0usize, 0usize)
|
||||||
} else {
|
} 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 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 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;
|
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)?)?;
|
let bits = record.get(off_bits..off_bits.checked_add(cb_bits)?)?;
|
||||||
Some((bmi, 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