viirya opened a new pull request, #59117:
URL: https://github.com/apache/spark/pull/59117

   ### What changes were proposed in this pull request?
   
   This is a follow-up of SPARK-59812. It makes `VectorizedRleValuesReader`, 
which decodes definition/repetition levels, dictionary ids and RLE booleans, 
fail with a `ParquetDecodingException` when the encoded data ends early or a 
header is invalid.
   
   - `readNextGroup()` throws when there is no encoded data left, instead of 
returning false. Callers never read more values than the page declares, so 
running out of data always means a corrupt page. All the `if (currentCount == 0 
&& !readNextGroup()) break;` loop guards become `if (currentCount == 0) 
readNextGroup();`. With a bit width of 0, `initFromPage` already provides every 
value of the page, so reaching `readNextGroup()` also means reading past the 
end.
   - `readNextGroup()` skips runs of length 0, so `currentCount` is always 
positive when it returns.
   - For a bit-packed run, check that `numGroups * 8` does not overflow and 
that `numGroups * bitWidth` bytes are left before allocating the value buffer.
   - `initFromPage` rejects a level length that is negative or larger than the 
bytes left in the page.
   - A dictionary-encoded page with no data at all (not even the bit width 
byte) no longer decodes every value as id 0. Reading from it fails, like 
parquet-java's `DictionaryValuesReader` ("Attempt to read from empty page"). An 
all-null page, which reads no ids, still works.
   
   The added checks run once per run or page, not per value.
   
   ### Why are the changes needed?
   
   SPARK-34863 made `readNextGroup()` return false at the end of the stream. 
Before that, it called `in.read()`, which throws an `EOFException` that was 
wrapped in a `ParquetDecodingException`. Since then, on a corrupt page:
   
   - The batch read and skip loops stop early, so dictionary ids are only 
partially read or skipped, and the query returns wrong results without an error.
   - For definition and repetition levels, `readBatch` and `readBatchRepeated` 
return without making progress, and `VectorizedColumnReader.readBatch` loops 
forever because `rowsToReadInBatch` and `valuesToReadInPage` never change.
   - `readBooleans` ignores the return value, and loops forever at the end of 
the stream.
   - `readInteger` ignores the return value and decrements `currentCount` below 
0, returning the last run's value again. It also returns the value of a run of 
length 0 instead of skipping it.
   - A bit-packed header with a large group count overflows `numGroups * 8` and 
`numGroups * bitWidth` (the latter was added by SPARK-56895). `in.slice` with a 
negative length moves a `SingleBufferInputStream` backwards, and a large count 
allocates up to an `int[2^31 - 8]` buffer before noticing that the bytes are 
missing.
   - In Parquet 1.18.1, `SingleBufferInputStream.sliceBuffers` only rejects 
`length > remaining`, so a negative level length moves the stream backwards and 
the next section of the page is decoded from the wrong offset.
   
   The non-vectorized reader already fails on these pages: parquet-java's 
`RunLengthBitPackingHybridDecoder` throws "Reading past RLE/BitPacking stream.".
   
   ### Does this PR introduce _any_ user-facing change?
   
   Only for corrupt Parquet files: reading a page whose levels, dictionary ids 
or RLE booleans end early, or have an invalid length or bit-packed header, now 
fails with a `ParquetDecodingException` instead of returning wrong values, 
spinning forever or running out of memory. Valid files are not affected.
   
   ### How was this patch tested?
   
   New unit tests in `VectorizedRleValuesReaderSuite`. Most run both on a 
single buffer and on the same data split across multiple buffers. Tests that 
could loop forever before this change run with a timeout.
   - Truncated dictionary ids on `readIntegers`, `skipIntegers` and 
`readInteger`; truncated booleans on `readBooleans`; truncated definition 
levels on `readBatch` (with and without definition level output, and with row 
ranges that skip past the truncation); truncated repetition levels on 
`readBatchRepeated`; reading past a page with bit width 0.
   - Negative and oversized (up to `Int.MaxValue`) level lengths.
   - Bit-packed runs that are truncated, that overflow `currentCount` to 0 or a 
negative value, or that would allocate a huge buffer.
   - A dictionary-encoded page with no data, and runs of length 0.
   - Positive cases: reads and skips that end exactly at the end of the encoded 
values for mixed RLE and bit-packed dictionary ids and booleans, with a level 
length that covers exactly the rest of the page.
   
   A new end-to-end test in `ParquetIOSuite` shortens the definition level run 
of a written file and checks that the vectorized reader fails instead of 
hanging.
   
   All the new negative tests fail without this change. Existing suites also 
pass: `ParquetEncodingSuite`, `ParquetVectorizedSuite`, 
`ParquetColumnIndexSuite`, `ParquetIOSuite`, `ParquetV1QuerySuite` and 
`ParquetV2QuerySuite`.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Code (Claude Opus 5.5)
   
   This pull request and its description were written by Isaac.
   


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


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to