From 4bd94729ce249c9cd9b685cc04eb3420013ae95a Mon Sep 17 00:00:00 2001 From: "Chris G." Date: Tue, 13 Oct 2026 11:15:56 +0100 Subject: [PATCH 1/2] test: reproduce malformed hintsfile parser panic Add an 11-byte regression test expecting an error. Currently, parsing panics when ilog2() is called on zero --- src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index f0d60df..f95989b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -479,6 +479,13 @@ mod tests { assert_eq!(want, got); } + #[test] + fn malformed_hintsfile_returns_error() { + // Header: version 0, one block. Elias-Fano: n = 5, m = 0. + let bytes = [0x55, 0x54, 0x58, 0x4f, 0, 1, 0, 0, 0, 5, 0]; + assert!(Hintsfile::from_reader(&mut bytes.as_slice()).is_err()); + } + #[test] fn hintsfile_roundtrip() { let want = Vec::new(); From 43de0a775dd5517f5279fc797b559c054921830d Mon Sep 17 00:00:00 2001 From: "Chris G." Date: Tue, 13 Oct 2026 11:31:50 +0100 Subject: [PATCH 2/2] fix: return errors for malformed hintsfile input --- src/lib.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f95989b..48014c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -159,7 +159,6 @@ impl EliasFano { /// the size of the list and `m` is the largest element. #[inline] const fn l(m: u32, n: u32) -> u8 { - debug_assert!(m != u32::MAX); ((m + 1) / n).ilog2() as u8 } @@ -175,14 +174,19 @@ impl EliasFano { }); } let m = read_compact_size(reader)?; + if m == u32::MAX || n > m + 1 { + return Err(invalid_data("invalid Elias-Fano parameters")); + } let l = Self::l(m, n); let low_bytes = (n as usize * l as usize).div_ceil(8); let mut low_buf = vec![0u8; low_bytes]; reader.read_exact(&mut low_buf)?; - debug_assert!(m != u32::MAX); let high_bytes = (n as usize + (m >> l) as usize).div_ceil(8); let mut high_buf = vec![0u8; high_bytes]; reader.read_exact(&mut high_buf)?; + if high_buf.iter().map(|b| b.count_ones()).sum::() != n { + return Err(invalid_data("inconsistent Elias-Fano high bits")); + } Ok(Self { n, m, @@ -221,6 +225,10 @@ fn write_compact_size(value: u32, writer: &mut W) -> Result<(), std::i } } +fn invalid_data(msg: &str) -> std::io::Error { + std::io::Error::new(std::io::ErrorKind::InvalidData, msg) +} + #[inline] fn read_compact_size(reader: &mut R) -> Result { let mut buf: [u8; 1] = [0; 1]; @@ -238,7 +246,7 @@ fn read_compact_size(reader: &mut R) -> Result { Ok(u32::from_le_bytes(buf)) } 0..=0xFC => Ok(prefix as u32), - _ => panic!("unexpected large offset"), + _ => Err(invalid_data("unexpected large offset")), } }