fix(underlay): bound how far DWF packages and DGN streams inflate

The DWF package reader, the DGN stream inflater and the new DGN
ExtendedColors scan each read a zlib/zip payload to its end, so a
crafted or corrupt file could inflate until memory ran out. Cap them:
1 GiB for a whole DWF package, 512 MiB per DGN stream, 4 MiB per
ExtendedColors record candidate - far above real files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-09-28 17:17:38 +03:00
1 parent d9899be35e
commit 0c1676e612
2 files changed
+21 -4

No files matched your search

+15 -2
View File
@@ -157,10 +157,15 @@ impl<'a> Cfb<'a> {
/// A DGN stream: raw, or zlib data after a 16-byte header.
fn inflate(data: &[u8]) -> Vec<u8> {
// Far above any real element stream; stops a crafted one from
// inflating until memory runs out.
const STREAM_LIMIT: u64 = 512 << 20;
for skip in [16usize, 0] {
if let Some(z) = data.get(skip..) {
let mut out = Vec::new();
if flate2::read::ZlibDecoder::new(z).read_to_end(&mut out).is_ok() && !out.is_empty() {
if flate2::read::ZlibDecoder::new(z).take(STREAM_LIMIT).read_to_end(&mut out).is_ok()
&& !out.is_empty()
{
return out;
}
}
@@ -237,8 +242,16 @@ fn extended_colors(cfb: &Cfb) -> Vec<[u8; 3]> {
if a & 0x0f != 8 || ((a as u16) << 8 | b as u16) % 31 != 0 {
continue;
}
// The record is a short XML list; the cap keeps a stream that
// inflates without end (or a crafted one) from exhausting memory.
const RECORD_LIMIT: u64 = 4 << 20;
let mut out = Vec::new();
if flate2::read::ZlibDecoder::new(&attrs[at..]).read_to_end(&mut out).is_err() || out.len() < 2 {
if flate2::read::ZlibDecoder::new(&attrs[at..])
.take(RECORD_LIMIT)
.read_to_end(&mut out)
.is_err()
|| out.len() < 2
{
continue;
}
let units: Vec<u16> = out.chunks_exact(2).map(|c| u16::from_le_bytes([c[0], c[1]])).collect();
+6 -2
View File
@@ -14,11 +14,15 @@ fn unzip(bytes: &[u8]) -> Option<HashMap<String, Vec<u8>>> {
// the offset itself.
let mut archive = zip::ZipArchive::new(Cursor::new(bytes)).ok()?;
let mut files = HashMap::new();
// Total inflated size a package may reach: far above any real sheet
// set, and a crafted archive cannot exhaust memory.
let mut budget: u64 = 1 << 30;
for i in 0..archive.len() {
let mut file = archive.by_index(i).ok()?;
let file = archive.by_index(i).ok()?;
let name = file.name().replace('\\', "/").trim_start_matches('/').to_string();
let mut data = Vec::new();
file.read_to_end(&mut data).ok()?;
file.take(budget + 1).read_to_end(&mut data).ok()?;
budget = budget.checked_sub(data.len() as u64)?;
files.insert(name, data);
}
Some(files)