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

   ### Describe the bug
   
   Reading a primitive (e.g. `INT32`) column whose data page is 
`RLE_DICTIONARY`-encoded, when no dictionary page was read for that column 
chunk, panics instead of returning an error:
   
   ```
   thread 'main' panicked at parquet-60.1.0/src/column/reader/decoder.rs:374:18:
   Decoder for dict should have been set
   ```
   
   `ColumnValueDecoderImpl::set_data` calls `.expect("Decoder for dict should 
have been set")` on the dictionary decoder slot. The byte-array readers already 
handle this case and return `missing dictionary page for column` 
(`arrow/array_reader/byte_array.rs`), so this looks like the same check missing 
from the generic decoder.
   
   A malformed or truncated file can trigger this, so any application reading 
untrusted Parquet files can be crashed by it. We found it by fuzzing a Parquet 
viewer with libFuzzer.
   
   ### To Reproduce
   
   Write a dictionary-encoded `Int32` column, then change the dictionary page 
header's type from `DICTIONARY_PAGE` (2) to `INDEX_PAGE` (1). The reader skips 
the page as an index page, and the following data page has no dictionary.
   
   ```toml
   [dependencies]
   parquet = { version = "60.1.0", features = ["arrow"] }
   arrow-array = "60.1.0"
   bytes = "1"
   ```
   
   ```rust
   use std::sync::Arc;
   
   use arrow_array::{Int32Array, RecordBatch};
   use bytes::Bytes;
   use parquet::{
       arrow::{ArrowWriter, arrow_reader::ParquetRecordBatchReaderBuilder},
       file::reader::{FileReader, SerializedFileReader},
   };
   
   fn main() {
       let column = Int32Array::from(vec![7, 8, 7, 8]);
       let batch = RecordBatch::try_from_iter([("i", Arc::new(column) as 
_)]).unwrap();
       let mut bytes = Vec::new();
       let mut writer = ArrowWriter::try_new(&mut bytes, batch.schema(), 
None).unwrap();
       writer.write(&batch).unwrap();
       writer.close().unwrap();
   
       let reader = 
SerializedFileReader::new(Bytes::from(bytes.clone())).unwrap();
       let offset = reader
           .metadata()
           .row_group(0)
           .column(0)
           .dictionary_page_offset()
           .unwrap() as usize;
       // Compact-protocol PageHeader starts with field 1 (type, i32): 0x15, 
then zigzag(2) = 0x04.
       assert_eq!(&bytes[offset..offset + 2], &[0x15, 0x04], "dictionary page 
header");
       bytes[offset + 1] = 0x02; // zigzag(1) = INDEX_PAGE
   
       let reader = ParquetRecordBatchReaderBuilder::try_new(Bytes::from(bytes))
           .unwrap()
           .build()
           .unwrap();
       for batch in reader {
           println!("{:?}", batch.map(|b| b.num_rows())); // expected: 
Err(...), actual: panic
       }
   }
   ```
   
   The same program with a `StringArray` column returns 
`Err(ParquetError("Parquet error: missing dictionary page for column"))`, which 
is the expected behavior.
   
   ### Expected behavior
   
   `Err(ParquetError::General("missing dictionary page for column"))` (or 
similar), not a panic.
   
   ### Additional context
   
   - Reproduces on 60.1.0 and 59.2.0 (`decoder.rs:213` there), Windows x86_64, 
rustc 1.97.1.
   - Found with cargo-fuzz; the fuzzer's original input was a mutated file, and 
the program above is a minimal deterministic equivalent.
   


-- 
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