You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by gi...@apache.org on 2026/10/19 18:41:37 UTC
(arrow-rs) 01/01: fix(parquet): return an error when the footer metadata length exceeds the file in ParquetMetaDataPushDecoder (#11697)
This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-11697-ee9f2a38f640f7147b2e849ea4fafa7f4333a45a
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
commit 837b6703f0ea8e33b891584511bb99d84794d2ef
Author: ump45nose <52...@users.noreply.github.com>
AuthorDate: Mon Oct 19 16:55:37 2026 +0000
fix(parquet): return an error when the footer metadata length exceeds the file in ParquetMetaDataPushDecoder (#11697)
# Which issue does this PR close?
- Closes #11690.
# Rationale for this change
`ParquetMetaDataPushDecoder::try_decode` computed the metadata start as
`file_len - footer_len - metadata_len` without checking the footer's
metadata length against the file length. A corrupt footer that claims
more metadata than the file holds made that subtraction underflow: it
panicked with overflow checks on, and without them the wrapped value
later panicked in `Bytes::slice`. `ParquetMetaDataReader` already
returns an EOF error for the same bytes.
# What changes are included in this PR?
`try_decode` in `parquet/src/file/metadata/push_decoder.rs` uses
`checked_sub` for the metadata start and returns the same `EOF: Parquet
file too small. Size is N but need M` error as `ParquetMetaDataReader`
when the footer and metadata don't fit in the file.
# Are these changes tested?
Yes. `test_metadata_decoder_metadata_length_exceeds_file` builds the
12-byte file from the issue (4 data bytes and a footer claiming 1000
bytes of metadata) and checks that `try_decode` returns `EOF: Parquet
file too small. Size is 12 but need 1008`. Without the fix the test
panics with `attempt to subtract with overflow`.
I also ran `cargo test -p parquet --lib push_decoder`, `cargo fmt -p
parquet -- --check` and `cargo clippy -p parquet --lib --tests -- -D
warnings`.
# Are there any significant user-facing changes?
No. Corrupt files that used to panic now return an error.
AI disclosure: I used an AI coding assistant to write the fix, the test
and this description, and I reviewed all of it. The part worth a
reviewer's look is that the decoder is left in its intermediate state
after this error, the same as for the other decode errors in
`try_decode` (for example a bad footer magic), rather than being
restored so the call can be retried.
---
parquet/src/file/metadata/push_decoder.rs | 26 +++++++++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
diff --git a/parquet/src/file/metadata/push_decoder.rs b/parquet/src/file/metadata/push_decoder.rs
index 202fa0d163..8ad49a557d 100644
--- a/parquet/src/file/metadata/push_decoder.rs
+++ b/parquet/src/file/metadata/push_decoder.rs
@@ -427,7 +427,13 @@ impl ParquetMetaDataPushDecoder {
DecodeState::ReadingMetadata(footer_tail) => {
let metadata_len: u64 = footer_tail.metadata_length() as u64;
- let metadata_start = file_len - footer_len - metadata_len;
+ // A corrupt footer can claim more metadata than the file holds
+ let needed = footer_len + metadata_len;
+ let Some(metadata_start) = file_len.checked_sub(needed) else {
+ return Err(eof_err!(
+ "Parquet file too small. Size is {file_len} but need {needed}"
+ ));
+ };
let metadata_end = metadata_start + metadata_len;
let metadata_range = metadata_start..metadata_end;
@@ -690,6 +696,24 @@ mod tests {
assert!(metadata.page_index().is_some_and(|idx| idx.is_complete()));
}
+ /// A footer whose metadata length exceeds the file length is an error, not a panic
+ #[test]
+ fn test_metadata_decoder_metadata_length_exceeds_file() {
+ // 4 bytes of data, then a footer claiming 1000 bytes of metadata
+ let mut file = vec![0u8; 4];
+ file.extend_from_slice(&1000u32.to_le_bytes());
+ file.extend_from_slice(b"PAR1");
+ let file_len = file.len() as u64;
+
+ let mut decoder = ParquetMetaDataPushDecoder::try_new(file_len).unwrap();
+ decoder.push_range(0..file_len, Bytes::from(file)).unwrap();
+ let err = decoder.try_decode().unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "EOF: Parquet file too small. Size is 12 but need 1008"
+ );
+ }
+
#[test]
fn test_range_for_page_index_respects_column_mask() {
let mut decoder = ParquetMetaDataPushDecoder::try_new(test_file_len()).unwrap();