From 3070e9594999ba5a18ea7f36cde29783aad48129 Mon Sep 17 00:00:00 2001 From: XananasX7 Date: Thu, 3 Sep 2026 20:52:15 -0400 Subject: [PATCH] Make deflateBound() account for a preset dictionary set after the call zlib.h documents that deflateBound() returns an upper bound on the compressed size such that a first deflate() call given all of the input at once, Z_FINISH, and an output buffer of that bound, is guaranteed to return Z_STREAM_END. deflateSetDictionary() may legally be called after deflateBound() and before that first deflate() (its only requirement is that it precede any deflate() call). When it is, the emitted zlib header carries the PRESET_DICT flag followed by the four bytes of the dictionary's Adler-32 (deflate.c, header emission), but deflateBound() only included those four bytes when s->strstart was already non-zero at bound time, i.e. when the dictionary had been set before deflateBound(). Callers following the documented single-pass pattern therefore received a bound four bytes too small: the first deflate() returned Z_OK with the output buffer full instead of Z_STREAM_END, truncating the stream (the dictionary Adler-32 and the first bytes of compressed data were lost). The compressed stream was still syntactically valid deflate but was no longer the Adler-32-verified stream the caller expected. Fix: include the four-byte preset-dictionary Adler-32 whenever the zlib header has not yet been emitted (stream status is still INIT_STATE), not only when a dictionary has already been loaded. Once the header has been written the bytes are already accounted for in the output. This makes the bound conservative by at most four bytes for streams that never use a dictionary. --- ChangeLog | 4 +++- deflate.c | 10 +++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4e86c26883..464ab859c5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,7 +2,9 @@ ChangeLog file for zlib Changes in 1.3.2.1 (xx Feb 2026) -- +- deflateBound() now includes the preset-dictionary Adler-32 when the stream + header has not yet been emitted, so the bound holds when a dictionary is + provided after deflateBound() and before the first deflate() Changes in 1.3.2 (17 Feb 2026) - Continued rewrite of CMake build [Vollstrecker] diff --git a/deflate.c b/deflate.c index a5be5e6947..c37a1e980a 100644 --- a/deflate.c +++ b/deflate.c @@ -885,7 +885,15 @@ z_size_t ZEXPORT deflateBound_z(z_streamp strm, z_size_t sourceLen) { wraplen = 0; break; case 1: /* zlib wrapper */ - wraplen = 6 + (s->strstart ? 4 : 0); + /* A preset dictionary adds four bytes of its Adler-32 right after + the two-byte header. This can happen if a dictionary was already + provided (strstart is then non-zero), or if deflateBound() was + called before deflateSetDictionary() and the header has not yet + been emitted (status is still INIT_STATE, as no data has been + compressed). Include the four bytes in the latter case as well, + so the returned bound covers the documented use of allocating an + output buffer for a single-pass deflate(). */ + wraplen = 6 + (s->strstart || s->status == INIT_STATE ? 4 : 0); break; #ifdef GZIP case 2: /* gzip wrapper */