From 16af88eb9db424284135635e149a5dc7e9ae305c Mon Sep 17 00:00:00 2001 From: michel Date: Sat, 18 Jul 2026 14:23:40 +0200 Subject: [PATCH] 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. --- soulseek-rs-lib/src/utils/zlib.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/soulseek-rs-lib/src/utils/zlib.rs b/soulseek-rs-lib/src/utils/zlib.rs index 599889b..3c20d63 100644 --- a/soulseek-rs-lib/src/utils/zlib.rs +++ b/soulseek-rs-lib/src/utils/zlib.rs @@ -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];