AVRO-4323: [Java] Bound DataFileStream block size against available input before allocating the block buffer - #3919
Conversation
…nput When reading a data (container) file, DataFileStream validated the declared block size only against the Integer range before allocating the block buffer. For a malformed, corrupted, or truncated file the declared size can greatly exceed the bytes actually present, so the reader eagerly allocated a large buffer before reading any block byte. Reject a declared block size that exceeds the number of bytes remaining in the input when that count is known (byte-array- or known-length-stream-backed decoders), so reading a malformed file fails fast with a clear IOException. The check is skipped when the remaining count is unknown (-1).
There was a problem hiding this comment.
Pull request overview
This PR hardens the Java DataFileStream container-file reader against malformed/truncated inputs that declare an unrealistically large block size, by validating the declared size against the decoder’s known remaining input bytes before allocating the block buffer.
Changes:
- Add a remaining-bytes guard in
DataFileStream.hasNextBlock()to fail fast on oversized declared block sizes when the remaining byte count is known. - Add a regression test that crafts a malformed container to ensure the oversized block size is rejected before allocation.
- Add a negative-control test to confirm valid single-record files still read successfully.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| lang/java/avro/src/main/java/org/apache/avro/file/DataFileStream.java | Adds remaining-bytes validation before block buffer allocation in hasNextBlock(). |
| lang/java/avro/src/test/java/org/apache/avro/TestDataFileReader.java | Adds tests covering oversized declared block sizes and a valid-read regression case. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| int remaining = vin.remainingBytes(); | ||
| if (remaining >= 0 && blockSize > remaining) { | ||
| throw new IOException("Block size " + blockSize + " exceeds the number of bytes remaining in the input (" | ||
| + remaining + "). The file is likely corrupted or truncated."); | ||
| } |
There was a problem hiding this comment.
Good catch. Updated the guard to require blockSize + DataFileConstants.SYNC_SIZE to fit in the remaining bytes, so a file truncated right before the sync marker is now rejected before the block buffer is allocated. (2a09e3c)
| AvroRuntimeException exception = assertThrows(AvroRuntimeException.class, () -> { | ||
| DataFileStream<Object> reader = new DataFileStream<>(new ByteArrayInputStream(malformed), | ||
| new GenericDatumReader<>()); | ||
| while (reader.hasNext()) { | ||
| reader.next(); | ||
| } | ||
| }); |
There was a problem hiding this comment.
Done — the regression test now wraps the DataFileStream in try-with-resources so it is closed even when the expected exception is thrown, matching the pattern used elsewhere in this class. (2a09e3c)
…d; close reader in test Include the trailing sync-marker length (DataFileConstants.SYNC_SIZE) in the remaining-bytes check so a file truncated right before the sync marker is rejected before allocating the block buffer, and use try-with-resources in the regression test.
…nput before allocating the block buffer (#3919) * AVRO-4323: [Java] Bound DataFileStream block size against available input When reading a data (container) file, DataFileStream validated the declared block size only against the Integer range before allocating the block buffer. For a malformed, corrupted, or truncated file the declared size can greatly exceed the bytes actually present, so the reader eagerly allocated a large buffer before reading any block byte. Reject a declared block size that exceeds the number of bytes remaining in the input when that count is known (byte-array- or known-length-stream-backed decoders), so reading a malformed file fails fast with a clear IOException. The check is skipped when the remaining count is unknown (-1). * AVRO-4323: Address review: account for sync marker in block-size guard; close reader in test Include the trailing sync-marker length (DataFileConstants.SYNC_SIZE) in the remaining-bytes check so a file truncated right before the sync marker is rejected before allocating the block buffer, and use try-with-resources in the regression test.
What changes were proposed in this pull request?
When reading an Avro data (container) file,
DataFileStreamreads each block's declared size as alongand validated it only against theIntegerrange before allocating the blockbyte[]buffer (inDataFileStream.DataBlock). For a malformed, corrupted, or truncated file, the declared block size can be much larger than the number of bytes actually present, so the reader eagerly allocated a very large buffer on the firsthasNext()/next()call before any block byte had been read.This adds a check: when the number of bytes remaining in the input is known (byte-array- or known-length-stream-backed decoders), a declared block size larger than the bytes remaining is rejected with a clear
IOExceptionbefore allocating. The check is skipped when the remaining count is unknown (-1), so non-seekable streams are unaffected.How was this patch tested?
TestDataFileReader:oversizedBlockSizeIsRejectedBeforeAllocation— a crafted file whose block header declares a size nearInteger.MAX_VALUEwith no block bytes now fails fast instead of attempting a large allocation.validFileWithSingleRecordStillReads— negative control confirming a valid file still reads.avromodule test suite passes.JIRA