This is an automated email from the ASF dual-hosted git repository.

etseidl pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new d40cac73f4 Fix: Error rather than panic on invalid dictionary index 
bit width in Parquet reader (#10725)
d40cac73f4 is described below

commit d40cac73f4f6c65841d0619857b4bc81eeab8a5e
Author: Dhruv Vaishnav <[email protected]>
AuthorDate: Thu Aug 20 02:46:46 2026 +0530

    Fix: Error rather than panic on invalid dictionary index bit width in 
Parquet reader (#10725)
    
    # Which issue does this PR close?
    
    - Closes #10722.
    
    # Rationale for this change
    
    When reading dictionary-encoded Parquet data pages with DictIndexDecoder
    or ByteArrayDictionaryReader, the first byte was read via data[0]
    without checking if data is empty, and the index it_width was passed to
    RleDecoder without checking the
    
    ---------
    
    Co-authored-by: Ed Seidl <[email protected]>
---
 .../arrow/array_reader/byte_array_dictionary.rs    | 38 ++++++++++++++++++--
 parquet/src/arrow/decoder/dictionary_index.rs      | 41 ++++++++++++++++++++--
 parquet/src/encodings/decoding.rs                  |  7 ++--
 parquet/src/encodings/rle.rs                       | 11 ++++++
 4 files changed, 88 insertions(+), 9 deletions(-)

diff --git a/parquet/src/arrow/array_reader/byte_array_dictionary.rs 
b/parquet/src/arrow/array_reader/byte_array_dictionary.rs
index 14a2bd1241..f4d84503bb 100644
--- a/parquet/src/arrow/array_reader/byte_array_dictionary.rs
+++ b/parquet/src/arrow/array_reader/byte_array_dictionary.rs
@@ -31,7 +31,7 @@ use crate::arrow::schema::parquet_to_arrow_field;
 use crate::basic::{ConvertedType, Encoding};
 use crate::column::page::PageIterator;
 use crate::column::reader::decoder::ColumnValueDecoder;
-use crate::encodings::rle::RleDecoder;
+use crate::encodings::rle::{MAX_RLE_DICTIONARY_BIT_WIDTH, RleDecoder};
 use crate::errors::{ParquetError, Result};
 use crate::schema::types::ColumnDescPtr;
 use crate::util::bit_util::FromBitpacked;
@@ -298,7 +298,14 @@ where
     ) -> Result<()> {
         let decoder = match encoding {
             Encoding::RLE_DICTIONARY | Encoding::PLAIN_DICTIONARY => {
-                let bit_width = data[0];
+                let bit_width = *data
+                    .first()
+                    .ok_or_else(|| general_err!("dictionary index page is 
empty"))?;
+                if bit_width > MAX_RLE_DICTIONARY_BIT_WIDTH {
+                    return Err(general_err!(
+                        "Invalid or corrupted RLE bit width {bit_width}. Max 
allowed is {MAX_RLE_DICTIONARY_BIT_WIDTH}"
+                    ));
+                }
                 let mut decoder = RleDecoder::new(bit_width);
                 decoder.set_data(data.slice(1..))?;
                 MaybeDictionaryDecoder::Dict {
@@ -688,4 +695,31 @@ mod tests {
             assert_eq!(array.logical_null_count(), 8);
         }
     }
+
+    #[test]
+    fn test_dictionary_decoder_empty_data() {
+        let column_desc = utf8_column();
+        let mut decoder = DictionaryDecoder::<i32, i32>::new(&column_desc);
+        let err = decoder
+            .set_data(Encoding::RLE_DICTIONARY, Bytes::new(), 0, None)
+            .unwrap_err();
+        assert_eq!(
+            err.to_string(),
+            "Parquet error: dictionary index page is empty"
+        );
+    }
+
+    #[test]
+    fn test_dictionary_decoder_invalid_bit_width() {
+        let column_desc = utf8_column();
+        let mut decoder = DictionaryDecoder::<i32, i32>::new(&column_desc);
+        let data = Bytes::from_static(&[33, 0, 0, 0]);
+        let err = decoder
+            .set_data(Encoding::RLE_DICTIONARY, data, 1, None)
+            .unwrap_err();
+        assert_eq!(
+            err.to_string(),
+            "Parquet error: Invalid or corrupted RLE bit width 33. Max allowed 
is 32"
+        );
+    }
 }
diff --git a/parquet/src/arrow/decoder/dictionary_index.rs 
b/parquet/src/arrow/decoder/dictionary_index.rs
index 7a4b77f89d..6640fa7381 100644
--- a/parquet/src/arrow/decoder/dictionary_index.rs
+++ b/parquet/src/arrow/decoder/dictionary_index.rs
@@ -17,8 +17,8 @@
 
 use bytes::Bytes;
 
-use crate::encodings::rle::RleDecoder;
-use crate::errors::Result;
+use crate::encodings::rle::{MAX_RLE_DICTIONARY_BIT_WIDTH, RleDecoder};
+use crate::errors::{ParquetError, Result};
 
 /// Decoder for `Encoding::RLE_DICTIONARY` indices
 pub struct DictIndexDecoder {
@@ -43,7 +43,14 @@ impl DictIndexDecoder {
     /// Create a new [`DictIndexDecoder`] with the provided data page, the 
number of levels
     /// associated with this data page, and the number of non-null values (if 
known)
     pub fn new(data: Bytes, num_levels: usize, num_values: Option<usize>) -> 
Result<Self> {
-        let bit_width = data[0];
+        let bit_width = *data
+            .first()
+            .ok_or_else(|| general_err!("dictionary index page is empty"))?;
+        if bit_width > MAX_RLE_DICTIONARY_BIT_WIDTH {
+            return Err(general_err!(
+                "Invalid or corrupted RLE bit width {bit_width}. Max allowed 
is {MAX_RLE_DICTIONARY_BIT_WIDTH}"
+            ));
+        }
         let mut decoder = RleDecoder::new(bit_width);
         decoder.set_data(data.slice(1..))?;
 
@@ -119,3 +126,31 @@ impl DictIndexDecoder {
         Ok(values_skip)
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_dict_index_decoder_empty_data() {
+        let Err(err) = DictIndexDecoder::new(Bytes::new(), 0, None) else {
+            panic!("expected error");
+        };
+        assert_eq!(
+            err.to_string(),
+            "Parquet error: dictionary index page is empty"
+        );
+    }
+
+    #[test]
+    fn test_dict_index_decoder_invalid_bit_width() {
+        let data = Bytes::from_static(&[33, 0, 0, 0]);
+        let Err(err) = DictIndexDecoder::new(data, 1, None) else {
+            panic!("expected error");
+        };
+        assert_eq!(
+            err.to_string(),
+            "Parquet error: Invalid or corrupted RLE bit width 33. Max allowed 
is 32"
+        );
+    }
+}
diff --git a/parquet/src/encodings/decoding.rs 
b/parquet/src/encodings/decoding.rs
index 85f3bd495b..2b2bae302e 100644
--- a/parquet/src/encodings/decoding.rs
+++ b/parquet/src/encodings/decoding.rs
@@ -21,7 +21,7 @@ use bytes::Bytes;
 use num_traits::{FromPrimitive, WrappingAdd};
 use std::{cmp, marker::PhantomData, mem};
 
-use super::rle::RleDecoder;
+use super::rle::{MAX_RLE_DICTIONARY_BIT_WIDTH, RleDecoder};
 
 use crate::basic::*;
 use crate::data_type::private::ParquetValueType;
@@ -386,10 +386,9 @@ impl<T: DataType> Decoder<T> for DictDecoder<T> {
         }
 
         let bit_width = data.as_ref()[0];
-        if bit_width > 32 {
+        if bit_width > MAX_RLE_DICTIONARY_BIT_WIDTH {
             return Err(general_err!(
-                "Invalid or corrupted RLE bit width {}. Max allowed is 32",
-                bit_width
+                "Invalid or corrupted RLE bit width {bit_width}. Max allowed 
is {MAX_RLE_DICTIONARY_BIT_WIDTH}"
             ));
         }
         let mut rle_decoder = RleDecoder::new(bit_width);
diff --git a/parquet/src/encodings/rle.rs b/parquet/src/encodings/rle.rs
index a1994a4fe0..95538dfb41 100644
--- a/parquet/src/encodings/rle.rs
+++ b/parquet/src/encodings/rle.rs
@@ -41,6 +41,12 @@ use bytes::Bytes;
 use crate::errors::{ParquetError, Result};
 use crate::util::bit_util::{self, BitReader, BitWriter, FromBitpacked};
 
+/// Maximum bit width for dictionary page indices encoded with RLE / 
Bit-Packing hybrid encoding.
+///
+/// Parquet dictionary indices are represented as 32-bit signed integers, so 
dictionary
+/// index bit widths must not exceed 32.
+pub const MAX_RLE_DICTIONARY_BIT_WIDTH: u8 = 32;
+
 /// Number of values in one bit-packed group. The Parquet RLE/bit-packing 
hybrid
 /// format always bit-packs values in multiples of this count (see the
 /// [format 
spec](https://github.com/apache/parquet-format/blob/master/Encodings.md#run-length-encoding--bit-packing-hybrid-rle--3):
@@ -360,7 +366,12 @@ pub struct RleDecoder {
 }
 
 impl RleDecoder {
+    /// Creates a new `RleDecoder` with the specified bit width.
+    ///
+    /// Bit width must be between 0 and 64 (inclusive). Note that for 
dictionary indices
+    /// specifically, the bit width cannot exceed 
[`MAX_RLE_DICTIONARY_BIT_WIDTH`] (32).
     pub fn new(bit_width: u8) -> Self {
+        debug_assert!(bit_width <= 64, "Bit width must be <= 64");
         RleDecoder {
             bit_width,
             rle_left: 0,

Reply via email to