]> git.kaiwu.me - haproxy.git/commit
IMPORT: slz/uslz: don't lose the bytes consumed by the header fast paths
authorAurelien DARRAGON <adarragon@haproxy.com>
Tue, 11 Aug 2026 16:21:28 +0000 (18:21 +0200)
committerAurelien DARRAGON <adarragon@haproxy.com>
Wed, 12 Aug 2026 07:14:07 +0000 (09:14 +0200)
commit7f349d2921393d34926f76a7b39c19740c287d1c
treea63b756f2ee7d02c89e21f647647812bc035723c
parentdba9ea66c06a1ca77ad9ce6d3727d9b416424bd5
IMPORT: slz/uslz: don't lose the bytes consumed by the header fast paths

The format autodetection in uslz_decode() has a fast path for the common
case where the first call carries enough data to parse the header directly
from the caller's buffer, and a slow path that accumulates the header into
hdr_detect.buf across calls. The fast path consumes bytes from the caller's
buffer *before* it knows whether it will be able to complete, and it had no
way to give them back, so three things went wrong:

1) Raw deflate (rfc1951) streams were losing their first two bytes. The
   fast path consumes two bytes to sniff the magic, and the fallback that
   feeds them back to the block decoder is guarded by "else if (buf_len)",
   which is false on exactly that path. Every raw stream fed more than 2
   bytes at a time therefore failed with E_CORRUPT (or E_INVALID_BLOCK_CODE,
   depending on what the two lost bytes happened to decode as), i.e. raw
   deflate was simply unusable:

       ./zdec < raw.deflate     ->  error (14)

   This is fixed by rewinding in_ptr to the start of the header, which
   also lets the fast path skip the local copy the accumulating path
   needs.

2) A gzip stream whose first call carried 3 to 9 bytes was mis-parsed.
   That is more than 2, so the fast path consumed the magic, but less
   than 10, so the 10-byte header could not be parsed, and it fell back
   to need_more_header, which starts filling hdr_detect.buf at buf_len=0,
   dropping the two bytes already consumed. The next call then parsed the
   header two bytes too far. This is fixed by rewinding before the
   fallback.
3) The tests on the remaining input used <compressed_size>, which is only
   decremented by the fast paths and never by the accumulating ones, so it
   overstates what is left in the caller's buffer as soon as the two are
   mixed. "if (compressed_size >= 2)" and "if (compressed_size >= xlen)"
   could therefore both succeed with fewer bytes actually available, read
   the FEXTRA length or payload **past in_top** and advance in_ptr beyond
   it. Fixed by testing state->in_top - state->in_ptr, which is exact on
   every path; <compressed_size> is no longer modified.

tests/uslztest.sh goes from 1108/1514 to 1438/1514, and raw, gzip, zlib
and slz streams now decode correctly at every input chunk size from 1 to
24 and beyond, for every supported ring size. The gzip variant carrying
FEXTRA is still broken at some chunk sizes, this is for a future fix.

This is libslz upstream commit cea782efdd111bf476b625051fe4d37cf28be92d
src/uslz.c