Skip to content

Fix: Error rather than panic on invalid dictionary index bit width in Parquet reader - #10725

Merged
etseidl merged 3 commits into
apache:mainfrom
dhruvxvaishnav:fix/parquet-dict-index-bit-width
Aug 19, 2026
Merged

Fix: Error rather than panic on invalid dictionary index bit width in Parquet reader#10725
etseidl merged 3 commits into
apache:mainfrom
dhruvxvaishnav:fix/parquet-dict-index-bit-width

Conversation

@dhruvxvaishnav

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

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

Validate that dictionary index data pages have non-empty value sections and bit widths bounded by 0..=32 in DictIndexDecoder and ByteArrayDictionary, returning a clean ParquetError instead of panicking on malformed or untrusted input.
@github-actions github-actions Bot added the parquet Changes to the parquet crate label Aug 18, 2026

@Rich-T-kid Rich-T-kid left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this make sense to me.

i'm not super familiar with the arrow-rs parquet decode code, are all offsets/indexes validated or is it assumed that the offsets are correct? for example for variable length data I would assume that we check the prefix length against a buffer length to make sure we don't have OOB errors.

@alamb alamb changed the title Fix: Validate dictionary index bit width in Parquet reader Fix: Error rather than panic on invalid dictionary index bit width in Parquet reader Aug 18, 2026

@alamb alamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me -- thank you @dhruvxvaishnav and @Rich-T-kid

I have one question


impl RleDecoder {
pub fn new(bit_width: u8) -> Self {
debug_assert!(bit_width <= 64, "Bit width must be <= 64");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The checks above say 32 bits is the max, but this assert allows 64 -- why the discrepancy?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's somehow related to delta binary, which uses bit packing up to 64 bits. But the current delta decoder doesn't use RleDecoder and instead uses BitReader directly. Kind of confusing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RleDecoder is a generic Parquet RLE / bit-packing hybrid decoder that supports unpacking values up to 64 bits wide (using a u64 internal accumulator). However, Parquet dictionary indices are 32-bit signed integers (i32), so dictionary index bit widths are bounded by MAX_RLE_DICTIONARY_BIT_WIDTH (32). I have added doc comments on RleDecoder::new and MAX_RLE_DICTIONARY_BIT_WIDTH explaining this distinction.

@etseidl etseidl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fine to me, just one suggestion.

let bit_width = *data
.first()
.ok_or_else(|| general_err!("dictionary index page is empty"))?;
if bit_width > 32 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a constant somewhere (MAX_RLE_DICTIONARY_BIT_WIDTH 🤷).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added MAX_RLE_DICTIONARY_BIT_WIDTH = 32 in parquet::encodings::rle and updated DictIndexDecoder, ByteArrayDictionaryReader (DictionaryDecoder), and DictDecoder to use it.

…oders

Define MAX_RLE_DICTIONARY_BIT_WIDTH in encodings::rle and use it across DictIndexDecoder, ByteArrayDictionary, and DictDecoder to avoid magic numbers and clarify the 32-bit bound for dictionary index bit widths.
@etseidl

etseidl commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

CI is backed up today...I'll merge this once it finishes

@etseidl
etseidl merged commit d40cac7 into apache:main Aug 19, 2026
18 checks passed
@etseidl

etseidl commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Thanks @dhruvxvaishnav (and @alamb and @Rich-T-kid for reviews)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

parquet Changes to the parquet crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

parquet: dictionary index bit width is not validated, panicking the reader on untrusted input

4 participants