Hi,

HAProxy 3.2.20 was released on 2026/06/25. It added 125 new commits
after version 3.2.19.

This large maintenance release addresses bugs across many subsystems. Many of
them were never encountered and were only found by AI-assisted scans.

The most notable areas covered are the HTTP/1 parser hardening, extensive
DNS/resolver fixes, QUIC and HTTP/3 correctness improvements, multiplexer
draining bugs, Lua and applet issues, cache correctness, dictionary locking
bugs, SSL/TLS fixes, server connection management, and health check
correctness. Users are encouraged to upgrade.

Several security-relevant fixes were applied to the HTTP/1 parser. Header
names containing invalid characters are now properly rejected to prevent
header injection attempts. Status codes are now limited to 3 digits to
prevent response splitting. All "h2c" values in Upgrade headers are now
filtered out during parsing to avoid protocol confusion. Additionally, the
handling of Upgrade headers when making outgoing requests was reworked to
properly sanitize and deduplicate upgrade tokens. The WebSocket protocol is
no longer masked when multiple protocols are listed in the Upgrade header.

A large number of bugs were fixed in the DNS resolver code. A validation bug
was fixed in resolv_read_name() where a name compression pointer was not
validated against the response bounds, potentially causing out-of-bounds
memory reads. A bug in resolv_dn_label_to_str() were fixed: the label size
check used an incorrect comparison. A related fix prevents appending garbage
past the domain name. When the xprt prepare_srv callback fails for a newly
resolved server, HAProxy now waits a bit before retrying to avoid
busy-looping. A memory leak of a sockaddr structure was fixed in the
dns_session_init() error path. Authority record parsing was relaxed to be
more tolerant of label sizes. Several minor error-path leaks and dangling
pointer issues were also fixed. The expression error is now reported in the
do-resolve() action parser. Query ID generation was switched to a better
PRNG for unpredictability.

Multiple bugs were fixed in the QUIC multiplexer. A potential infinite loop
was possible in qcc_io_recv() when a RESET_STREAM was received while the
stream was still in the recv_list; the loop is now correctly guarded. A
related issue where the recv loop was unnecessarily interrupted on
stream-level errors or incomplete data was also fixed. A transfer freeze
could occur after a QCS rxbuf realign operation because the next Rx buffer
was not reinserted into the QCS tree after the data move; this caused data
loss particularly on POST requests on the frontend or on most backend
transfers. Another issue in qcs_http_reset_buf() caused the entire buffer to
be reset unconditionally instead of draining only the requested amount,
which could discard unsent data when a filter was registered on the data
forwarding stage. A bug was fixed in qcc_is_dead() which was not accounting
for detached streams (streams whose upper layer had been removed but still
had data to emit), allowing connections to be freed prematurely with
remaining data. The QUIC connection inactivity timeout was being refreshed
on every I/O callback execution even when no actual send or receive had
occurred; the timeout is now only refreshed when I/O progress is made, which
helps detect and close stuck connections more reliably. Various smaller
fixes addressed ODCID lookup from derived values, Initial packet length
values, and ACK range pool deallocation.

The CUBIC congestion control algorithm received two fixes: the congestion
window was not reset in slow start on persistent congestion, and the
consecutive_losses counter was not reset when leaving the recovery period,
potentially causing premature congestion control triggering. An ECONNREFUSED
error on the receive side was not handled, causing the QUIC connection to
stall instead of being properly terminated.

Two HTTP/3 improvements were made. Client push streams are now properly
rejected, as they are not supported. CANCEL_PUSH frames sent by the client
are also now rejected as required.

Two fixes were applied to the hq-interop HTTP/0.9 transcoder: streams with
unknown payload length were incorrectly closed with RESET_STREAM instead of
FIN and responses exceeding the supported size are now properly rejected.

Numerous bugs were fixed in the QPACK decoder. Index calculation in debug
functions was incorrect. A potential null-pointer dereference was fixed in
qpack_dht_insert() when the memory pool was exhausted. A sign bit mask was
wrong in qpack_decode_fs_pfx(), potentially causing incorrect header value
decoding. Error handling in huff_dec() called from qpack_decode_fs() was not
properly propagating the error.

Three multiplexers were affected by the same draining bug: when trying to
send data to a server whose stream was closed (in error or half-closed
state), the mux claimed to have consumed the buffered data without actually
draining them. This means the upper stream would keep trying to send these
data in a loop, potentially triggering the watchdog. The FCGI, H2, and SPOP
multiplexers were all fixed.

In the FCGI mux, an integer overflow was fixed: the unsigned 16-bit integer
used for the record length overflowed to 0 when the record length was 65535
and padding was non-zero, causing the state machine to skip consuming buffer
data and parse the remaining bytes as a new FCGI record header. This could
only happen for unknown records or for STDOUT records received after the end
of the response. CVE-2026-55203 was assigned to this overflow. It must be
noted however that the assigned severity is excessively high relative to the
actual exploitability: triggering this bug requires the FCGI backend server
itself to be compromised and to craft a malicious response with exactly
those values, which is well outside a normal threat model where the backend
is trusted. Additionally, stream ID 0 was not rejected for application
records as required by the FCGI specification, potentially causing incorrect
demultiplexing. A minor fix corrected the computation of contiguous data in
the demux buffer by using a relative offset. The same relative offset fix
was applied to the SPOP multiplexer demux buffer, and a possible off-by-one
OOB read was fixed in spop_get_varint().

The H1 multiplexer received two fixes. The connection/upgrade header value
is now duplicated before being parsed when building outgoing request
headers, as the parsing was modifying the value in-place which could corrupt
shared data. The 'h1-case-adjust-file' directive was not resolving a
relative file path to an absolute path at parse time, causing failures when
multiple configuration files were loaded. The H2 multiplexer received two
minor fixes: padding was not correctly accounted for in connection-level
flow control on certain error paths, and the HEADERS frame length was not
validated before reading the stream dependency field.

Several bugs were fixed in the Lua and applet subsystems. An integer
underflow was possible in the Lua cosocket line-read path, causing memory
corruption when fewer bytes than expected were received. The HTX data
transfer to applets was reworked: data were not consumed from the channel
buffer after being copied to the applet's input buffer, and changes to the
input buffer were not committed back after sending HTX data. Receives of
size 0 were not properly handled, potentially leaving the channel in an
inconsistent state. Lua was also not filtering CR, LF, and NUL characters
from HTTP headers, allowing header injection.

Multiple bugs were fixed in the cache subsystem. The primary hash was not
always being verified in get_secondary_entry(), which could cause incorrect
cache hits. A refcount leak was fixed for missed secondary entries where the
reference count was incremented on lookup but not decremented when the
matching entry was not found. Cache tree iteration was broken due to an
incorrect traversal condition. The maxage value was being overwritten
instead of copied during parsing of cache-control directives. Cache-control
directives in the "token=" form (without value) were not recognized.

Two locking bugs were fixed in the dictionary subsystem. The read lock was
not held while incrementing the refcount in dict_insert(), creating a race
condition where two concurrent inserts could corrupt the refcount of a
shared entry. The write lock was not held while decrementing the refcount in
dict_entry_unref(), allowing a concurrent lookup to observe an inconsistent
state. A further race was fixed on insert collision where the refcount was
not properly updated when two threads inserted the same key simultaneously.

Several SSL/TLS fixes are included. With TLS 1.3 0-RTT, the early data
buffer was freed when emptied, but more early data could still arrive; the
buffer is now reset rather than freed until all early data has been
received. The LRU certificate cache lock was not released on error in the
SSL certificate generation path. SNI character validation was added to the
dynamic certificate generation code to prevent SAN certificate injection
through crafted SNI values. The null-terminated servername is now properly
used in the SSL hello parser. Various fixes were also made in the JWT and
JWS code: a missing return value check for EVP_PKEY_get_bn_param(), an
incorrect OpenSSL version check (> instead of >=) for OpenSSL 3.0, and a
memory leak in the ECDSA signature conversion error path. The OCSP responder
now handles dates that are too far in the future.

A few server management bugs were fixed. The server lock was not properly
released on failure in cli_parse_set_server(), potentially causing a
deadlock when a "set server" CLI command failed. Consistent hash balancing
for newly added servers was broken. The init-state keyword was not properly
applied during startup. The wrong source was used for pool_conn_name when
copying server settings.

When L7 retries were configured and the maximum retry count was reached on a
reused connection, the stream was silently closed on the client side to let
it safely retry the request instead of returning a 502 or 425 error. The
SF_SRV_REUSED flag is now ignored in this path so that the error is properly
reported to the client. The set-var-fmt action was not properly evaluating
empty log-format strings. The final evaluation in the HTTP pause action was
not properly handled. A leftover debugging memset was removed from the
redirect path. The Forwarded header parser did not check whether sufficient
data remained when reading the nodeport field. The HTTP authentication token
checker was not verifying against the whole token, allowing partial matches.
The HTTP client CLI now properly destroys its context if it fails to start.

Several health check bugs were fixed. When a server configured with
tune.max-checks-per-thread was deleted while its check was still queued, a
use-after-free would occur; the fix dequeues the check before freeing the
server. External checks were not being excluded from the tcpcheck
post-config phase, which could cause a NULL pointer dereference. The SPOP
error code used in tcpcheck/SPOE responses was not bounded to valid
values. The LDAP health check could read more data than available from the
buffer. The agent-check reply parser could read past the buffer end. The
error code was not properly reported via errno in chk_report_conn_err().

CPU topology and thread handling received a few fixes. The thread-hard-limit
was not being enforced when applying the CPU affinity policy, potentially
allowing more threads than permitted. The minimum value of grp_max was not
considered when mtpg was configured too small. A new ha_diag_notice()
function was introduced to report diagnostic-level notifications, and thread
creation count reporting was switched to use it instead of
ha_diag_warning(), which was causing false positives with -dW.

A NULL pointer dereference was fixed in the HPACK table code: when the
memory pool was exhausted, hpack_dht_defrag() could return NULL and be
immediately dereferenced, crashing the worker process. CVE-2026-55204 was
assigned to this issue. Here again the assigned severity is excessively
high: the condition only arises when the process is already out of memory,
at which point the allocator failure itself is likely to cause other
problems before this specific code path is even reached. The probability of
hitting this particular NULL dereference in a production environment is
extremely thin, and a crash under memory exhaustion, while undesirable, is
not a meaningful security primitive an attacker could reliably control or
exploit.

Several other medium-severity fixes are worth noting. A NULL pointer
dereference was fixed in the authentication code when an unconfigured
password entry (NULL) was accessed during authentication. In the ACME
client, a connection failure could trigger a NULL dereference when
attempting to cancel the request. A deadlock was fixed in the leastconn
balancing algorithm: the write lock was not released on allocation
failure. The log-forward module treated the month field as a signed integer,
which could cause incorrect log timestamps when the high bit was set.

Finally, a few minor issues were also addressed: the error message for ACL
identifier lookup failures incorrectly said "map" instead of "ACL"; the
niced_task counter was incremented for the wrong counter; the be2hex sample
converter chunk size was not bounded; base64dec() returned NULL for empty
input instead of an empty string; the client hello parser had incorrect
bounds checks for the handshake length; the addons/51d module was not
NUL-terminating headers before passing them to the Trie API; the cluster
secret was initialized using only ha_random64() which does not provide
sufficient entropy; parameter value validation in get_server_ph_post() had
an incorrect comparison; hash calculation was wrong when using hash-type
none; and log priority parsing did not check for the end of buffer properly.

Some documentation improvements were also included: the documentation now
clarifies that a "default" resolver exists, and incorrect init tags were
removed from the Lua documentation.

Thanks to everyone for your help on this release!

Please find the usual URLs below :
   Site index       : https://www.haproxy.org/
   Documentation    : https://docs.haproxy.org/
   Wiki             : https://github.com/haproxy/wiki/wiki
   Discourse        : https://discourse.haproxy.org/
   Slack channel    : https://slack.haproxy.org/
   Issue tracker    : https://github.com/haproxy/haproxy/issues
   Q&A from devs    : https://github.com/orgs/haproxy/discussions
   Sources          : https://www.haproxy.org/download/3.2/src/
   Git repository   : https://git.haproxy.org/git/haproxy-3.2.git/
   Git Web browsing : https://git.haproxy.org/?p=haproxy-3.2.git
   Changelog        : https://www.haproxy.org/download/3.2/src/CHANGELOG
   Dataplane API    : 
https://github.com/haproxytech/dataplaneapi/releases/latest
   Pending bugs     : https://www.haproxy.org/l/pending-bugs
   Reviewed bugs    : https://www.haproxy.org/l/reviewed-bugs
   Code reports     : https://www.haproxy.org/l/code-reports
   Latest builds    : https://www.haproxy.org/l/dev-packages


---
Complete changelog :
Amaury Denoyelle (13):
      BUG/MEDIUM: mux_quic: adjust qcc_is_dead() to account detached streams
      BUG/MINOR: quic: fix ODCID lookup from derived value
      BUG/MEDIUM: h3: reject client push stream
      BUG/MINOR: h3: reject client CANCEL_PUSH frame
      BUG/MINOR: quic: reject packet too short for HP decryption
      BUG/MEDIUM: mux_quic: prevent risk of infinite loop on recv
      BUG/MINOR: mux_quic: do not interrupt recv on error/incomplete data
      BUG/MINOR: quic: fix Initial length value in sent packets
      BUG/MEDIUM: mux_quic: fix freeze transfer after QCS rxbuf realign
      BUG/MINOR: server: fix add server with consistent hash balancing
      BUG/MINOR: hq-interop: reject too big content
      BUG/MINOR: hq-interop: prevent reset if missing content-length
      BUG/MINOR: mux_quic: refresh timeout only if I/O performed

Christian Ruppert (1):
      REGTESTS: Don't try to use real nameservers for testcases

Christopher Faulet (36):
      MINOR: htx: Add htx_move_blks() to move blocks from a message to another
      BUG/MEDIUM: applet: Fix transfer of HTX data to the applet
      BUG/MINOR: httpclient-cli: Destroy http-client context if failing to 
start it
      BUG/MEDIUM: h1: Skip all h2c values from Upgrade headers during parsing
      BUG/MINOR: h1: Don't mask websocket protocol if multiple protocols used
      BUG/MINOR: server: Properly handle init-state value during haproxy startup
      BUG/MEDIUM: applet: Properly handle receives of size 0
      BUG/MEDIUM: resolvers: Fix test on dn label size in 
resolv_dn_label_to_str()
      BUG/MEDIUM: ssl-gencert: Unlock LRU cache if failing to generate 
certificate
      BUG/MEDIUM: dict: hold lock while decrementing refcount in 
dict_entry_unref
      BUG/MINOR: tcpchecks: Limit parsing of agent-check reply to the buffer
      BUG/MEDIUM: hlua: Fix integer underflow when receiving line from lua 
cosocket
      BUG/MEDIUM: mux-fcgi: reject stream ID 0 for application records
      MINOR: http: Add function to remove all occurrences of a value in a header
      MINOR: h1: Add  a H1M flag to specify a non-empty 'Upgrade:' header was 
parsed
      BUG/MEDIUM: h1-htx: Sanitize parsing to properly handle upgrade requests
      BUG/MINOR: mux-fcgi: Use relative offset to compute contig data in demux 
buf
      BUG/MINOR: mux-spop: Use relative offset to compute contig data in demux 
buf
      BUG/MINOR: tcpcheck: Check LDAP response to not read more data than 
available
      Revert "BUG/MEDIUM: dns: fix long loops in additional records parse on name 
failure"
      BUG/MINOR: mux-h2: Count padding for connection flow control on error path
      BUG/MINOR: cache: Fix copy of value when parsing maxage
      BUG/MEDIUM: mux-h1: Dup connection/upgrade value to parse it when making 
headers
      BUG/MINOR: applet: Commit changes into input buffer after sending HTX data
      BUG/MINOR: mux-spop: Fix possible off-by-one OOB read in spop_get_varint()
      BUG/MINOR: http-act: Properly handle final evaluation in pause action
      BUG/MEDIUM: vars: Properly eval set-var-fmt action for emtpy log-format 
string
      BUG/MEDIUM: check: Skip tcpcheck post-config for external checks
      MINOR: check: Don't dump buffers state in check traces for external checks
      BUG/MINOR: http-ana: Remove a debugging memset on redirect
      BUG/MEDIUM: http-ana: Don't ignore L7 retry errors
      BUG/MINOR: mux-h1: Properly resolve file path for 'h1-case-adjust-file'
      BUG/MEDIUM: mux-fcgi: Truly drain outgoing HTX data when the stream is 
closed
      BUG/MEDIUM: mux-h2: Truly drain outgoing HTX data when the stream is 
closed
      BUG/MEDIUM: mux-spop: Truly drain outgoing data when the stream is closed
      BUG/MEDIUM: mux-quic: Drain the given amount of data in 
qcs_http_reset_buf()

Frederic Lecaille (12):
      BUG/MINOR: qpack: Fix index calculation in debug functions
      BUG/MINOR: qpack: fix potential null-pointer dereference in 
qpack_dht_insert()
      CLEANUP: qpack: fix copy-paste typo in value Huffman debug string
      BUG/MINOR: qpack: fix sign bit mask in qpack_decode_fs_pfx()
      CLEANUP: qpack: fix copy-paste typo in value Huffman debug string for WLN
      BUG/MINOR: qpack: fix huff_dec() error handling in qpack_decode_fs()
      CLEANUP: qpack: move encoded macros to qpack-t.h to avoid duplication
      BUG/MEDIUM: quic: handle ECONNREFUSED on RX side
      BUG/MINOR: quic: fix ack range node pool_free call passing wrong pointer 
type
      BUG/MEDIUM: quic: reset cwnd in slow_start on persistent congestion 
(cubic)
      BUG/MEDIUM: quic: reset consecutive_losses on exit from recovery period 
(cubic)
      BUG/MINOR: quic: update drs->lost before calling on_ack_recv

Ilia Shipitsin (1):
      BUILD: 51d.c: cleanup, fix preprocessor ifdefs

Maxime Henrion (1):
      BUG/MINOR: cache: fix cache tree iteration

Olivier Houchard (6):
      BUG/MEDIUM: cpu-topo: Enforce thread-hard-limit on policy
      BUG/MEDIUM: resolvers: Wait a bit before calling the xprt prepare_srv
      BUG/MEDIUM: leastconn: Unlock the write lock on allocation failure
      BUG/MINOR: tasks: Increase the right niced_task counter
      BUG/MEDIUM: checks: Dequeue checks on purge
      BUG/MEDIUM: ssl: Don't free the early data buffer too early

Remi Tricot-Le Breton (2):
      BUG/MINOR: jws: Add missing return value check (EVP_PKEY_get_bn_param)
      BUG/MINOR: ocsp: Manage date too far away in the future

Thayne McCombs (1):
      DOC: lua: remove incorrect init tags

Tristan Madani (2):
      BUG/MINOR: hpack-tbl: add missing NULL check after hpack_dht_defrag()
      BUG/MEDIUM: mux-fcgi: fix uint16_t overflow in drl += drp

William Lallemand (2):
      BUG/MEDIUM: auth: fix unconfigured password NULL deref
      BUG/MINOR: ssl-gencert: validate SNI characters to prevent SAN 
certificate injection

Willy Tarreau (48):
      BUG/MEDIUM: dict: hold read lock while incrementing refcount in 
dict_insert
      BUG/MINOR: backend: correct parameter value validation in 
get_server_ph_post()
      BUG/MINOR: config/dns: properly fail on duplicate nameserver name 
detection
      BUG/MEDIUM: dns: fix long loops in additional records parse on name 
failure
      BUG/MEDIUM: resolvers: fix name compression pointer validation in 
resolv_read_name()
      BUG/MEDIUM: dns: fix memory leak of sockaddr in dns_session_init() error 
path
      CLEANUP: proxy: fix tiny mistakes in parse error messages
      BUG/MINOR: servers: use proper source of pool_conn_name in 
srv_settings_cpy()
      BUG/MEDIUM: server/cli: unlock server lock on failure in 
cli_parse_set_server
      BUG/MINOR: resolvers: fix dangling list pointer in resolvers_new() error 
paths
      BUG/MINOR: dns: fix dangling dgram pointer on dns_dgram_init() failure 
path
      BUG/MINOR: resolvers: report the expression error in the do-resolve() 
action parser
      BUG/MINOR: resolvers: fix leaked dgram and dns_ring struct in 
parse_resolve_conf()
      BUG/MINOR: session/trace: use distinct flags for SESS_EV_END and _ERR
      BUG/MINOR: check: properly report errno in chk_report_conn_err()
      BUG/MINOR: jwt: fix possible memory leak in convert_ecdsa_sig() error path
      DOC: config: further clarify that resolvers "default" exists
      BUG/MINOR: jws: fix OpenSSL 3.0 version check from > to >=
      BUG/MINOR: backend: fix balance hash calculation when using hash-type none
      BUG/MEDIUM: log-forward: make sure the month is unsigned
      BUG/MEDIUM: tcpcheck/spoe: bound the SPOP error code to valid values
      BUG/MEDIUM: cache: fix a refcount leak for missed secondary entries
      BUG/MINOR: resolvers: fix room for trailing zero in 
resolv_dn_label_to_str()
      BUG/MINOR: resolvers: fix risk of appending garbage past the domain name
      BUG/MINOR: mux-h2: validate HEADERS frame length before reading stream dep
      BUG/MINOR: log: look for the end of priority before the end of the buffer
      BUG/MINOR: dict: fix refcount race on insert collision
      BUG/MINOR: init: use more than ha_random64() for the cluster secret
      BUG/MINOR: sample: limit the be2hex converter's chunk size
      BUG/MEDIUM: h1: drop headers whose names contain invalid chars
      BUG/MEDIUM: h1: limit status codes to 3 digits by default
      BUG/MEDIUM: cache: always verify the primary hash in get_secondary_entry()
      BUG/MINOR: cache: also recognize directives in the form "token="
      BUG/MINOR: resolvers: relax size checks in authority record parsing
      BUG/MINOR: http-fetch: check against the whole token in get_http_auth()
      BUG/MEDIUM: acme: protect against risk of null-deref on connection failure
      BUG/MINOR: http-ext: always check remaining data when reading rfc7239 
nodeport
      BUG/MINOR: base64: return empty string for empty input in base64dec()
      BUG/MINOR: payload: fix the handshake length bounds check 
smp_client_hello_parse()
      BUG/MINOR: ssl-hello: make use of the null-terminated servername
      BUG/MINOR: resolvers: switch to a better PRNG for query IDs
      BUG/MINOR: addons/51d: NUL-terminate headers before passing them to Trie 
API
      BUG/MINOR: hlua: prevent Lua from passing CR/LF/NUL in HTTP headers
      BUG/MINOR: threads: set at least grp_max when mtpg is too small
      CLEANUP: sessions: simplify the sess_priv_conns pool name
      BUG/MINOR: acl: report "ACL" not "map" in ACL ID lookup failures
      MINOR: errors: add ha_diag_notice() to report diag-level notifications
      BUG/MINOR: cpu-topo: use ha_diag_notice() to report thread creations

--
Christopher Faulet



Reply via email to