fix: error on degenerate Huffman code in zlib inflate instead of panicking

decode_symbol walked the Huffman tree while either child existed, then
unwrapped the child selected by the current bit. A malformed/incomplete code
table (fully attacker-controlled, since search results arrive zlib-compressed
from peers) can leave a node with only one child; reading the opposite bit
unwrapped None and panicked, unwinding out of the actor job. It now returns a
decode error, which propagates as a CompressionError and is handled gracefully.
This commit is contained in:
michel
2026-07-18 14:23:40 +02:00
parent 96988f9f9e
commit 16af88eb9d
+18 -5
View File
@@ -170,11 +170,12 @@ fn decode_symbol(
let mut node = &t.root;
while node.left.is_some() || node.right.is_some() {
let b = r.read_bit()?;
node = if b != 0 {
node.right.as_ref().unwrap()
} else {
node.left.as_ref().unwrap()
};
let next = if b != 0 { &node.right } else { &node.left };
// A degenerate/incomplete Huffman code (attacker-controlled) can point
// at a missing child; error out instead of unwrap-panicking.
node = next
.as_ref()
.ok_or("Invalid Huffman code".to_string())?;
}
node.symbol.ok_or("No symbol found".to_string())
}
@@ -364,6 +365,18 @@ mod tests {
use super::*;
#[test]
fn decode_symbol_on_degenerate_tree_errors_instead_of_panicking() {
// A malformed Huffman table from an untrusted peer can produce a node
// with only one child. Inserting codeword "0" gives the root a left
// child but no right child; decoding a '1' bit must error rather than
// unwrap-panic.
let mut tree = HuffmanTree::new();
tree.insert(0, 1, 42);
let mut reader = BitReader::new(vec![0b0000_0001]); // first bit = 1
assert!(decode_symbol(&mut reader, &tree).is_err());
}
#[test]
fn test_bitreader_read_bits() {
let data = vec![0b11010010, 0b10110101];