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

   **Describe the bug**
   
   The arrow-side `DeltaByteArrayDecoder` 
(`parquet/src/arrow/decoder/delta_byte_array.rs`, `read`) panics on a corrupt 
`DELTA_BYTE_ARRAY` page instead of returning an error. A suffix length decoded 
from the page is cast from `i32` to `usize`:
   
   ```rust
   let suffix_length = *suffix_length as usize;
   if self.data_offset + suffix_length > self.data.len() {
       return Err(ParquetError::EOF("eof decoding byte array".into()));
   }
   self.last_value.truncate(prefix_length);
   self.last_value
       .extend_from_slice(&data[self.data_offset..self.data_offset + 
suffix_length]);
   ```
   
   A negative value becomes a huge `usize`, `self.data_offset + suffix_length` 
wraps (release builds), the bounds check passes, and the slice panics:
   
   ```
   thread 'main' panicked at 
parquet/src/arrow/decoder/delta_byte_array.rs:88:41:
   slice index starts at 124 but ends at 120
   ```
   
   (`delta_byte_array.rs:101` in 60.1.0; `:88` on `main` at 39ae0f7c, which has 
the same code.) #9796 fixed the same kind of problem for prefix lengths in 
`encodings/decoding.rs`; this decoder has the same unchecked cast for both 
prefix and suffix lengths (a too-large prefix is harmless because of 
`truncate`, a negative suffix is not).
   
   **To Reproduce**
   
   Writes a 630-byte file with one non-null string column (64 synthetic values, 
`DELTA_BYTE_ARRAY`, no dictionary, uncompressed), changes one byte of the data 
page, and reads it back. Reproduces with 60.1.0 and with `main` at 39ae0f7c, in 
`--release`.
   
   ```rust
   use std::sync::Arc;
   use arrow_array::{ArrayRef, RecordBatch, StringArray};
   use bytes::Bytes;
   use parquet::arrow::{arrow_reader::ParquetRecordBatchReaderBuilder, 
ArrowWriter};
   use parquet::basic::{Compression, Encoding};
   use parquet::file::properties::WriterProperties;
   
   fn main() {
       let vals: Vec<String> = (0..64).map(|i| format!("value-{:04}", i * 
7)).collect();
       let col: ArrayRef = Arc::new(StringArray::from(vals));
       let batch = RecordBatch::try_from_iter_with_nullable([("s", col, 
false)]).unwrap();
       let props = WriterProperties::builder()
           .set_dictionary_enabled(false)
           .set_encoding(Encoding::DELTA_BYTE_ARRAY)
           .set_compression(Compression::UNCOMPRESSED)
           .build();
       let mut file = Vec::new();
       let mut w = ArrowWriter::try_new(&mut file, batch.schema(), 
Some(props)).unwrap();
       w.write(&batch).unwrap();
       w.close().unwrap();
       assert_eq!(file.len(), 630);
   
       file[64] = 1; // one byte of the suffix-length stream of the data page
   
       let reader = 
ParquetRecordBatchReaderBuilder::try_new(Bytes::from(file)).unwrap().build().unwrap();
       for batch in reader {
           let _ = batch; // panics here instead of returning Err
       }
   }
   ```
   
   **Expected behavior**
   
   An `Err` (for example `ParquetError::General("invalid negative suffix 
length")`), like the other decoders return for invalid lengths. A possible fix: 
reject negative prefix and suffix lengths before the cast (or when the lengths 
are decoded in `DeltaByteArrayDecoder::new`), and use `checked_add` in the 
bounds check.
   
   **Additional context**
   
   Found by byte-flip fuzzing of valid files (the reader of a downstream 
project catches the panic at a task boundary and reports an error, but a panic 
on untrusted input is still a crash for any caller that does not). The same 
fuzzing also hit the BitChunks panic fixed in #11691 and the `byte_range` 
assertion of #6255; this one does not appear to be reported.
   


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