diff --git a/crates/raw/src/vendor/pef.rs b/crates/raw/src/vendor/pef.rs index abb928e68..a52154705 100644 --- a/crates/raw/src/vendor/pef.rs +++ b/crates/raw/src/vendor/pef.rs @@ -103,8 +103,10 @@ impl<'a> Bits<'a> { pub fn consumed_bits(&self) -> usize { self.pos * 8 - self.n as usize } + /// Whether more bits were handed out than the source holds (the zeros read past its end are lookahead padding, + /// not data; using them would fabricate samples). pub fn overrun(&self) -> bool { - self.pos > self.src.len() + 8 + self.consumed_bits() > self.src.len() * 8 } } @@ -390,6 +392,21 @@ mod tests { assert!(table(&tb[..20], ByteOrder::Big).is_err()); } + /// Zero-padded lookahead past the end must not be decoded as samples: with no entropy at all, the zero bits + /// are a valid code (category 1) in this table, so a reader that only limits the refill position accepts them. + #[test] + fn symbols_decoded_from_padding_past_the_end_are_rejected() { + let (tb, codes) = test_table(); + let h = table(&tb, ByteOrder::Big).unwrap(); + assert!(decode_huffman(&[], &h, 2, 1, 14).is_err()); + // the same image with its real entropy still decodes, including the last byte's padding bits + let px = [5u16, 9]; + let src = encode(&px, 2, &codes); + assert_eq!(decode_huffman(&src, &h, 2, 1, 14).unwrap(), px); + // one byte short of the real entropy is rejected + assert!(decode_huffman(&src[..src.len() - 1], &h, 2, 1, 14).is_err()); + } + /// A big-endian one-strip PEF-style TIFF of `w × h` 12-bit samples with the given compression tag and strip /// byte count, around `payload`. fn tiny_pef(w: u16, h: u16, compression: u16, strip_len: u32, payload: &[u8]) -> Vec {