@@ -2286,35 +2286,56 @@ fn process_blockwise(
22862286 match block_count. cmp ( & 0 ) {
22872287 Ordering :: Equal => break ,
22882288 Ordering :: Less => {
2289- let count = ( -block_count) as usize ;
2289+ // `unsigned_abs` avoids overflowing `-block_count` for `i64::MIN` (#10235)
2290+ let count = block_count. unsigned_abs ( ) as usize ;
22902291 // A negative count is followed by a long of the size in bytes
2291- let size_in_bytes = buf. get_long ( ) ? as usize ;
2292+ let raw_size = buf. get_long ( ) ?;
2293+ let size_in_bytes = usize:: try_from ( raw_size) . map_err ( |_| {
2294+ AvroError :: ParseError ( format ! ( "Block size cannot be negative, got {raw_size}" ) )
2295+ } ) ?;
22922296 match negative_behavior {
22932297 NegativeBlockBehavior :: ProcessItems => {
22942298 // Process items one-by-one after reading size
2295- for _ in 0 ..count {
2296- on_item ( buf) ?;
2297- }
2299+ total = process_block_items ( buf, count, total, & mut on_item) ?;
22982300 }
22992301 NegativeBlockBehavior :: SkipBySize => {
23002302 // Skip the entire block payload at once
23012303 let _ = buf. get_fixed ( size_in_bytes) ?;
2304+ total = total. saturating_add ( count) ;
23022305 }
23032306 }
2304- total += count;
23052307 }
23062308 Ordering :: Greater => {
23072309 let count = block_count as usize ;
2308- for _ in 0 ..count {
2309- on_item ( buf) ?;
2310- }
2311- total += count;
2310+ total = process_block_items ( buf, count, total, & mut on_item) ?;
23122311 }
23132312 }
23142313 }
23152314 Ok ( total)
23162315}
23172316
2317+ /// Decode `count` items, capping the running total at `i32::MAX` (the largest index
2318+ /// an Arrow list/map offset holds). Otherwise a crafted `i64::MAX` count of a zero-byte
2319+ /// item like `null` spins the loop forever (#10235); byte-consuming items self-terminate
2320+ /// on cursor exhaustion, so valid blocks (including `array<null>`) are unaffected.
2321+ #[ inline]
2322+ fn process_block_items (
2323+ buf : & mut AvroCursor ,
2324+ count : usize ,
2325+ total : usize ,
2326+ on_item : & mut impl FnMut ( & mut AvroCursor ) -> Result < ( ) , AvroError > ,
2327+ ) -> Result < usize , AvroError > {
2328+ let Some ( new_total) = total. checked_add ( count) . filter ( |& t| t <= i32:: MAX as usize ) else {
2329+ return Err ( AvroError :: ParseError (
2330+ "Capacity overflow when decoding array/map item blocks" . to_string ( ) ,
2331+ ) ) ;
2332+ } ;
2333+ for _ in 0 ..count {
2334+ on_item ( buf) ?;
2335+ }
2336+ Ok ( new_total)
2337+ }
2338+
23182339#[ inline]
23192340fn flush_values < T > ( values : & mut Vec < T > ) -> Vec < T > {
23202341 std:: mem:: replace ( values, Vec :: with_capacity ( DEFAULT_CAPACITY ) )
@@ -3434,6 +3455,60 @@ mod tests {
34343455 assert_eq ! ( values. value( 2 ) , 3 ) ;
34353456 }
34363457
3458+ /// Zig-zag + unsigned-LEB128 encode, correct for all `i64` including `MIN`/`MAX`
3459+ /// (`encode_avro_long` loops forever on those two values).
3460+ fn encode_avro_long_extreme ( value : i64 ) -> Vec < u8 > {
3461+ let mut n = ( ( value << 1 ) ^ ( value >> 63 ) ) as u64 ;
3462+ let mut out = Vec :: new ( ) ;
3463+ while n >= 0x80 {
3464+ out. push ( ( n as u8 ) | 0x80 ) ;
3465+ n >>= 7 ;
3466+ }
3467+ out. push ( n as u8 ) ;
3468+ out
3469+ }
3470+
3471+ // `array<null>` is the worst case: items consume no bytes, so an unbounded
3472+ // `block_count` spins the item loop without ever advancing the cursor (#10235).
3473+ fn array_of_null_decoder ( ) -> Decoder {
3474+ let list_dt = avro_from_codec ( Codec :: List ( Arc :: new ( avro_from_codec ( Codec :: Null ) ) ) ) ;
3475+ Decoder :: try_new ( & list_dt) . unwrap ( )
3476+ }
3477+
3478+ #[ test]
3479+ fn test_array_of_null_decodes ( ) {
3480+ let mut decoder = array_of_null_decoder ( ) ;
3481+ let mut data = encode_avro_long ( 3 ) ; // three null items
3482+ data. extend_from_slice ( & encode_avro_long ( 0 ) ) ; // empty-block terminator
3483+ decoder. decode ( & mut AvroCursor :: new ( & data) ) . unwrap ( ) ;
3484+ }
3485+
3486+ #[ test]
3487+ fn test_array_block_count_i64_max_errors ( ) {
3488+ // A positive `i64::MAX` block count must error rather than spin the item loop.
3489+ let mut decoder = array_of_null_decoder ( ) ;
3490+ let mut data = encode_avro_long_extreme ( i64:: MAX ) ; // item count
3491+ data. extend_from_slice ( & encode_avro_long ( 0 ) ) ; // empty-block terminator
3492+ let err = decoder. decode ( & mut AvroCursor :: new ( & data) ) . unwrap_err ( ) ;
3493+ assert ! (
3494+ err. to_string( ) . contains( "Capacity overflow" ) ,
3495+ "unexpected error: {err}" ,
3496+ ) ;
3497+ }
3498+
3499+ #[ test]
3500+ fn test_array_block_count_i64_min_errors ( ) {
3501+ // `i64::MIN` previously overflowed `-block_count` before spinning the loop.
3502+ let mut decoder = array_of_null_decoder ( ) ;
3503+ let mut data = encode_avro_long_extreme ( i64:: MIN ) ; // negative item count
3504+ data. extend_from_slice ( & encode_avro_long ( 0 ) ) ; // block size in bytes
3505+ let err = decoder. decode ( & mut AvroCursor :: new ( & data) ) . unwrap_err ( ) ;
3506+ assert ! (
3507+ err. to_string( ) . contains( "Capacity overflow" ) ,
3508+ "unexpected error: {err}" ,
3509+ ) ;
3510+ }
3511+
34373512 #[ test]
34383513 fn test_nested_array_decoding ( ) {
34393514 let inner_ty = avro_from_codec ( Codec :: List ( Arc :: new ( avro_from_codec ( Codec :: Int32 ) ) ) ) ;
0 commit comments