Hi,

HAProxy 3.0.24 was released on 2026/06/25. It added 101 new commits
after version 3.0.23.

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.

The HTTP/1 parser received several hardening fixes around Upgrade header
handling. 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 bugs 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.

Several bugs were fixed in the QUIC multiplexer. An 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 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.

Two 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 and H2 multiplexers were
both 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 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. A possible failure from strdup()
was also left unhandled in one code path. 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.

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. A memory leak in the ECDSA signature
conversion error path was also fixed. 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.

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 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 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().

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.

A NULL pointer dereference was fixed in the authentication code when an
unconfigured password entry (NULL) was accessed during authentication. 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.0/src/
   Git repository   : https://git.haproxy.org/git/haproxy-3.0.git/
   Git Web browsing : https://git.haproxy.org/?p=haproxy-3.0.git
   Changelog        : https://www.haproxy.org/download/3.0/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 (11):
      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/MINOR: quic: fix Initial length value in sent packets
      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
      MINOR: mux-quic: adapt return value of qcc_decode_qcs()
      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 (30):
      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/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: 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/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/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-quic: Drain the given amount of data in 
qcs_http_reset_buf()

Frederic Lecaille (10):
      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)

Ilia Shipitsin (2):
      BUILD: 51d.c: cleanup, fix preprocessor ifdefs
      BUG/MINOR: mux_h1: handle a possible strdup() failure

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

Olivier Houchard (4):
      BUG/MEDIUM: resolvers: Wait a bit before calling the xprt prepare_srv
      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 (1):
      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 (36):
      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/MEDIUM: server/cli: unlock server lock on failure in 
cli_parse_set_server
      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: 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: backend: fix balance hash calculation when using hash-type none
      BUG/MEDIUM: log-forward: make sure the month is unsigned
      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: cache: always verify the primary hash in get_secondary_entry()
      BUG/MINOR: resolvers: relax size checks in authority record parsing
      BUG/MINOR: http-fetch: check against the whole token in get_http_auth()
      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
      CLEANUP: sessions: simplify the sess_priv_conns pool name
      BUG/MINOR: acl: report "ACL" not "map" in ACL ID lookup failures

--
Christopher Faulet



Reply via email to