hhhizzz opened a new issue, #11516:
URL: https://github.com/apache/arrow-rs/issues/11516

   ### Describe the bug
   
   `RleDecoder::reload` computes the length of a bit-packed run from its header 
without checking for overflow (`parquet/src/encodings/rle.rs`):
   
   ```rust
   self.bit_packed_left = ((indicator_value >> 1) * BIT_PACK_GROUP_SIZE as i64) 
as u32;
   ```
   
   `indicator_value` is a varint of up to 10 bytes, so a corrupt header can 
decode to any `i64`. With overflow checks enabled (e.g. debug builds) the 
multiplication panics:
   
   ```
   thread 'main' panicked at parquet/src/encodings/rle.rs:676:40:
   attempt to multiply with overflow
   ```
   
   Without overflow checks the length wraps and decoding continues with a bogus 
run length, so the read fails later with an unrelated error, or panics 
elsewhere depending on the bytes that follow. The RLE branch truncates the run 
length to `u32` the same way, and `PackedDecoder::next_rle_block`, which parses 
run headers for the definition level fast path, has the same unchecked `len * 
8`.
   
   ### To Reproduce
   
   With `parquet = "60.0.0"`, `arrow-array = "60.0.0"` and `bytes = "1"`:
   
   ```rust
   use std::sync::Arc;
   
   use arrow_array::{ArrayRef, Int32Array, RecordBatch};
   use bytes::Bytes;
   use parquet::arrow::ArrowWriter;
   use parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder;
   
   fn main() {
       // 128 dictionary indices without repeats are written as a single 
bit-packed
       // run: bit width byte 0x03, then run header 0x21 (16 groups of 8 values)
       let values: ArrayRef = 
Arc::new(Int32Array::from_iter_values((0..128).map(|i| i % 8)));
       let batch = RecordBatch::try_from_iter_with_nullable([("v", values, 
false)]).unwrap();
       let mut file = Vec::new();
       let mut writer = ArrowWriter::try_new(&mut file, batch.schema(), 
None).unwrap();
       writer.write(&batch).unwrap();
       writer.close().unwrap();
   
       // Overwrite the run header with a 10 byte varint for 
0xa4ff_ffff_ffff_ffff,
       // a bit-packed run whose length in values overflows i64
       let header = file.windows(2).position(|w| w == [0x03, 0x21]).unwrap() + 
1;
       file[header..header + 10]
           .copy_from_slice(&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
0xa4, 0x01]);
   
       let mut reader = 
ParquetRecordBatchReaderBuilder::try_new(Bytes::from(file))
           .unwrap()
           .build()
           .unwrap();
       println!("{:?}", reader.next());
   }
   ```
   
   In a debug build this panics at `rle.rs:632` (`rle.rs:676` on `main`). In a 
release build it prints:
   
   ```
   Some(Err(ParquetError("Parquet error: insufficient values read from column - 
expected: 128, got: 104")))
   ```
   
   ### Expected behavior
   
   `next()` returns an error that points at the invalid run header. The 
[Parquet 
spec](https://github.com/apache/parquet-format/blob/master/Encodings.md#run-length-encoding--bit-packing-hybrid-rle--3)
 requires run lengths to be in the range `[1, 2^31 - 1]`, and Arrow C++ rejects 
longer runs.
   
   ### Additional context
   
   Found by mutation testing Parquet files in a downstream project. Reproduced 
with 60.0.0 and `main` (0706d317a). 
   
   This report was drafted with the help of AI Agent.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to