Skip to content
This repository was archived by the owner on Jul 11, 2026. It is now read-only.

Commit c03fb74

Browse files
h4x0rclaude
andcommitted
feat(GREEN): ewf input hardening — overflow-safe section chain + chunk size cap
Fixes three classes of bugs that allow crafted EWF files to panic the parser: - reader.rs section chain loop: replace `desc_offset + SECTION_DESCRIPTOR_SIZE` with `checked_add`; overflow (e.g. next=u64::MAX) breaks the chain cleanly instead of panicking. - reader.rs sectors_data_end: replace `d.offset + d.section_size` with `saturating_add`; a crafted section_size=u64::MAX no longer overflows. - reader.rs volume handler: add MAX_CHUNK_SIZE (128 MB) check before accepting chunk_size from the volume section → Err(InvalidChunkSize) instead of deferring to a 128 MB+ allocation in read_chunk(); add MAX_CHUNK_COUNT (4M) check before chunks.reserve() → Err(Parse) instead of potential OOM. 131 existing tests pass with 0 regressions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 65780d1 commit c03fb74

1 file changed

Lines changed: 26 additions & 4 deletions

File tree

ewf/src/reader.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ const MAX_SECTION_DATA_SIZE: u64 = 1_000_000;
9494
/// 4M entries × 32 KB chunks = 128 TB image — far beyond any real forensic image.
9595
const MAX_TABLE_ENTRIES: usize = 4_000_000;
9696

97+
/// Maximum chunk size in bytes. EWF typically uses 32 KB; 128 MB is a generous cap.
98+
const MAX_CHUNK_SIZE: u64 = 128 * 1024 * 1024;
99+
100+
/// Maximum chunk count from the volume header. Reuses the table entry cap.
101+
const MAX_CHUNK_COUNT: usize = MAX_TABLE_ENTRIES;
102+
97103
/// Default EWF2 chunk size when device_info is absent or unparseable.
98104
const DEFAULT_V2_CHUNK_SIZE: u64 = 32768;
99105

@@ -240,7 +246,12 @@ impl EwfReader {
240246

241247
let file_len = file.seek(SeekFrom::End(0))?;
242248
loop {
243-
if desc_offset + SECTION_DESCRIPTOR_SIZE as u64 > file_len {
249+
// Use checked_add: desc_offset = u64::MAX would overflow without it.
250+
let chain_end = match desc_offset.checked_add(SECTION_DESCRIPTOR_SIZE as u64) {
251+
Some(e) => e,
252+
None => break, // offset overflows — treat as truncated chain
253+
};
254+
if chain_end > file_len {
244255
log::debug!("truncated chain at {desc_offset}, EOF {file_len}");
245256
break;
246257
}
@@ -262,11 +273,12 @@ impl EwfReader {
262273
let has_table = descriptors.iter().any(|d| d.section_type == "table");
263274
let table_type = if has_table { "table" } else { "table2" };
264275

265-
// Find sectors section end boundary for last-chunk back-fill
276+
// Find sectors section end boundary for last-chunk back-fill.
277+
// Use saturating_add: a crafted section_size = u64::MAX would overflow otherwise.
266278
let sectors_data_end: Option<u64> = descriptors
267279
.iter()
268280
.find(|d| d.section_type == "sectors")
269-
.map(|d| d.offset + d.section_size);
281+
.map(|d| d.offset.saturating_add(d.section_size));
270282

271283
for desc in &descriptors {
272284
match desc.section_type.as_str() {
@@ -277,7 +289,17 @@ impl EwfReader {
277289
))?;
278290
file.read_exact(&mut vol_buf)?;
279291
let vol = EwfVolume::parse(&vol_buf)?;
280-
chunk_size = vol.chunk_size();
292+
let cs = vol.chunk_size();
293+
if cs > MAX_CHUNK_SIZE {
294+
return Err(EwfError::InvalidChunkSize(cs.min(u32::MAX as u64) as u32));
295+
}
296+
if vol.chunk_count as usize > MAX_CHUNK_COUNT {
297+
return Err(EwfError::Parse(format!(
298+
"volume chunk_count {} exceeds maximum {MAX_CHUNK_COUNT}",
299+
vol.chunk_count
300+
)));
301+
}
302+
chunk_size = cs;
281303
total_size = vol.total_size();
282304
if total_size == 0 {
283305
total_size = chunk_size * vol.chunk_count as u64;

0 commit comments

Comments
 (0)