]> git.kaiwu.me - haproxy.git/commitdiff
IMPORT: uslz: do not replay the header-detection bytes on the raw deflate path
authorWilly Tarreau <w@1wt.eu>
Sun, 23 Aug 2026 10:42:34 +0000 (12:42 +0200)
committerWilliam Lallemand <wlallemand@haproxy.com>
Tue, 1 Sep 2026 11:47:07 +0000 (13:47 +0200)
When a stream turns out to be raw deflate and the first two bytes had to
be accumulated into hdr_detect.buf across calls, those bytes are replayed
to uslz_decode_block() from a local array, after which the code did:

state->in_ptr = (const unsigned char *)compressed_data;
state->in_top = state->in_ptr + compressed_size;

That rewinds to the start of the caller's buffer, but the loop that
filled hdr_detect.buf consumed from that same buffer, so the bytes just
replayed were handed to the decoder a second time. Saving the real
in_ptr across the replay fixes it; in_top never moved, and is saved with
it only for symmetry.

The problem happens whenever a raw deflate stream is fed in chunks of 1
or 2 bytes, since 3 or more takes the fast path at "in_top - in_ptr > 2"
and never enters this branch. Feeding 5000 bytes of source through
uslz_decode() one byte at a time failed with USLZ_DECODE_E_GEN_HUFF and
no output at all: the duplicated bytes shift the bit stream, so the first
dynamic block's code lengths no longer form a valid tree. gzip and zlib
streams are unaffected, they leave this branch by a different location.

The issue was spotted by Qwen-3.8-27B. This fix must be backported to
1.3.

This is libslz upstream commit f052863a91aeb44021ed1bd52f680df45667496a

src/uslz.c

index 84992f7ad4f1e669deda146e2e9734c7780764db..1e7a8f9e929e83681d34d6ca5afa6d6dba09eeb1 100644 (file)
@@ -1434,6 +1434,8 @@ enum uslz_decode_ret uslz_decode(struct uslz_stream *state,
                         * will be detected by tinflate_block().
                         */
                        unsigned char bytes[2];
+                       const unsigned char *save_ptr = state->in_ptr;
+                       const unsigned char *save_top = state->in_top;
                        int it = 0;
 
                        if (state->hdr_detect.buf_len > 2)
@@ -1452,9 +1454,15 @@ enum uslz_decode_ret uslz_decode(struct uslz_stream *state,
                        state->in_top = bytes + state->hdr_detect.buf_len;
                        res = uslz_decode_block(state);
 
-                       /* restore original state */
-                       state->in_ptr   = (const unsigned char *)compressed_data;
-                       state->in_top   = state->in_ptr + compressed_size;
+                       /* Restore where the real input had got to, which is
+                        * *not* the start of the caller's buffer: the loop
+                        * that filled hdr_detect.buf above consumed from it,
+                        * and those bytes have just been replayed from <bytes>.
+                        * Rewinding to <compressed_data> would hand them to the
+                        * decoder a second time.
+                        */
+                       state->in_ptr   = save_ptr;
+                       state->in_top   = save_top;
 
                        goto next_block;
                }// else raw format without pending bytes