@@ -239,20 +239,51 @@ impl<'a> Iterator for IterChunkRecords<'a> {
239239 type Item = std:: result:: Result < EvtxRecord < ' a > , EvtxError > ;
240240
241241 fn next ( & mut self ) -> Option < <Self as Iterator >:: Item > {
242- if self . exhausted
243- || self . offset_from_chunk_start >= u64:: from ( self . chunk . header . free_space_offset )
244- {
242+ // Be resilient to corrupted chunk headers: `free_space_offset` is user-controlled data
243+ // coming from the EVTX stream, and may point past the end of the chunk.
244+ let effective_free_space_offset = u64:: from ( self . chunk . header . free_space_offset )
245+ . min ( self . chunk . data . len ( ) . try_into ( ) . unwrap_or ( u64:: MAX ) ) ;
246+
247+ if self . exhausted || self . offset_from_chunk_start >= effective_free_space_offset {
248+ return None ;
249+ }
250+
251+ let start = self . offset_from_chunk_start as usize ;
252+ if start >= self . chunk . data . len ( ) {
253+ // Avoid panicking on an out-of-bounds slice if the header is corrupted.
254+ self . exhausted = true ;
255+ return None ;
256+ }
257+
258+ let remaining = & self . chunk . data [ start..] ;
259+ if remaining. len ( ) < 4 {
260+ // Not enough bytes for the record header magic, treat as end-of-chunk.
261+ self . exhausted = true ;
245262 return None ;
246263 }
247264
248- let mut cursor = Cursor :: new ( & self . chunk . data [ self . offset_from_chunk_start as usize .. ] ) ;
265+ let mut cursor = Cursor :: new ( remaining ) ;
249266
250267 let record_header = match EvtxRecordHeader :: from_reader ( & mut cursor) {
251268 Ok ( record_header) => record_header,
269+ Err ( DeserializationError :: InvalidEvtxRecordHeaderMagic { magic } ) => {
270+ // Some producers write incorrect `free_space_offset` / `last_event_record_id`.
271+ // In such cases we may attempt to parse the chunk slack area, which is typically
272+ // zero-padded. Treat an all-zero "magic" as a clean end-of-chunk instead of
273+ // emitting an error (see issue #197).
274+ if magic == [ 0 , 0 , 0 , 0 ] {
275+ self . exhausted = true ;
276+ return None ;
277+ }
278+
279+ self . exhausted = true ;
280+ return Some ( Err ( EvtxError :: DeserializationError (
281+ DeserializationError :: InvalidEvtxRecordHeaderMagic { magic } ,
282+ ) ) ) ;
283+ }
252284 Err ( err) => {
253285 // We currently do not try to recover after an invalid record.
254286 self . exhausted = true ;
255-
256287 return Some ( Err ( EvtxError :: DeserializationError ( err) ) ) ;
257288 }
258289 } ;
@@ -449,4 +480,41 @@ mod tests {
449480 let chunk = EvtxChunkData :: new ( chunk_data, false ) . unwrap ( ) ;
450481 assert ! ( chunk. validate_checksum( ) ) ;
451482 }
483+
484+ #[ test]
485+ fn test_iter_ends_cleanly_when_chunk_header_offsets_are_too_large ( ) {
486+ ensure_env_logger_initialized ( ) ;
487+
488+ let evtx_file = include_bytes ! ( "../samples/security.evtx" ) ;
489+ let chunk_data =
490+ evtx_file[ EVTX_FILE_HEADER_SIZE ..EVTX_FILE_HEADER_SIZE + EVTX_CHUNK_SIZE ] . to_vec ( ) ;
491+
492+ // Parse once to get a baseline count.
493+ let mut baseline = EvtxChunkData :: new ( chunk_data. clone ( ) , false ) . unwrap ( ) ;
494+ let settings = Arc :: new ( ParserSettings :: new ( ) ) ;
495+ let baseline_count = {
496+ let mut chunk = baseline. parse ( Arc :: clone ( & settings) ) . unwrap ( ) ;
497+ chunk
498+ . iter ( )
499+ . try_fold ( 0usize , |acc, record| record. map ( |_| acc + 1 ) )
500+ . unwrap ( )
501+ } ;
502+
503+ // Now simulate a broken chunk header like in issue #197: `last_event_record_id` and
504+ // `free_space_offset` are larger than the actual number of records/data.
505+ let mut corrupted = EvtxChunkData :: new ( chunk_data, false ) . unwrap ( ) ;
506+ corrupted. header . last_event_record_id =
507+ corrupted. header . last_event_record_id . saturating_add ( 100 ) ;
508+ corrupted. header . free_space_offset = EVTX_CHUNK_SIZE as u32 ;
509+
510+ let corrupted_count = {
511+ let mut chunk = corrupted. parse ( settings) . unwrap ( ) ;
512+ chunk
513+ . iter ( )
514+ . try_fold ( 0usize , |acc, record| record. map ( |_| acc + 1 ) )
515+ . unwrap ( )
516+ } ;
517+
518+ assert_eq ! ( corrupted_count, baseline_count) ;
519+ }
452520}
0 commit comments