Hi,
HAProxy 3.0.30 was released on 2026/10/14. It added 46 new commits
after version 3.0.29.
This release is mostly made of backports of the fixes shipped in 3.2.26. It
also carries the same two important security fixes. The first one concerns
the HTTP/2 multiplexer, where a truncated PING frame could prevent the CPU
from going back to idle, and the second one the HTTP/1 parser, where a
chunked request with a too long chunk-size line could lead to the same
behavior. CVEs were requested for both.
The rest of the release fixes issues in the configuration parser, the CLI,
the stick-table key exchange over peers, maps/ACLs, the H1 and H2
multiplexers, the compression filter and several sample converters, along
with the cipher and curve restrictions of crt-list lines on AWS-LC builds of
the SSL layer. The internal random generator, whose output could otherwise be
predicted by observers, is hardened as well.
* mux-h2: A truncated PING frame could waste the CPU of spurious wakeups.
An HTTP/2 PING frame cannot be entirely consumed until it is complete,
and the presence of an incomplete frame is sufficient to cause a wakeup,
preventing the CPU from going idle until the entire frame is received,
and resulting in a 100% CPU measurement for as long as the peer keeps the
connection open without completing the frame (or until "timeout
http-request" expired, when one is set). The performance impact is
moderate and depends on the ratio of such connections among all
concurrent connections. Partial PING frames are now detected as short
reads like the other frame types, so the tasklet goes back to sleep until
the rest of the frame arrives. The handling of short reads was fixed in
2.5, but PING was missed; it only became a problem in 3.1 when the
tasklet wake-up logic changed. As a workaround on an older build, setting
"timeout http-request" to a low value such as 1-5 seconds limits the CPU
consumption.
* h1-htx: A too long chunk-size line waste the CPU of spurious wakeups.
For chunked messages, when a chunk-size line was longer than the input
buffer, the parser made no progress, waiting for the end of the line. So
no data was consumed, the buffer was full and could not be extended: the
same bytes were retried endlessly with no request ever processed on it,
preventing, as for the H2 multiplexer issue, the CPU from going idle. Here
too, the impact is moderate and depends on the ratio of such connections
among all concurrent connections. A parsing error (HTTP 400) is now
reported as soon as the parser determines that the line cannot possibly
fit, using the same full-buffer detection that already exists for
oversized headers and trailers. Reported with a fix proposal by Adam
Crawford.
* tools: The random generator was hardened against state recovery.
The internal random generator was consumed in pairs and its output was
used directly, so enough of its internal state could be observed on
moderately idle systems to predict the values it produces: the UUIDs used
in logs and identifiers, the QUIC retry tokens, or the WebSocket keys
HAProxy generates. An XXH64 mixing layer seeded with the boot secret is
now applied to its output, which hides that state, for about 2% of the
UUID throughput when single-threaded (not measurable with threads).
* cfgparse: Several configuration parsing issues were fixed.
A backend section given extra arguments after its name (e.g. "backend b
junk") was silently ignored when it was the last section of the file; it
is now properly rejected with a clear message. The "email-alert" options
now really check that a value was provided, the proxy "description"
keyword no longer leaks the previous string when used twice, the global
"description" allocation is checked, and "max-spread-checks" no longer
stores an uninitialized value on a parse error. Finally, the list of
suggested options used when one is mistyped was completed and fixed.
* cli: The help of the "del server" command was misleading.
It stated that only dynamically added servers could be removed, while any
server can be removed at runtime since version 2.5.
* stick-table: Oversized keys could corrupt memory when replicated to peers.
The overhead and the stored data were not accounted for when building the
key sent over the peers protocol, although the whole message must fit in
one buffer. A key close to the buffer size could therefore crash the
sender, or overwrite a few bytes past the end of the buffer: this is
reachable by tracking a large body or by applying an expansion converter
(b64dec, hex, ...) to a key, or more easily with binary keys, which are
always sent fully padded. Keys are now sized against the available room:
string keys are trimmed, and binary keys too large to fit are refused at
configuration time with an explicit message. There is no protocol change,
so mixed-version peers still interoperate; realistic configurations are
unaffected. Reported with a reproducer by Dmitri Kaslov.
A stick-table counter was also fixed: the number of trackers returned
by "table_trackers()" no longer counts the lookup itself, in line with
the other fetches.
* map/acl: A use-after-free could be triggered with maps and ACLs.
When a lookup succeeded through the tree without filling the result (the
case of ACL matches), the returned sample still pointed at data left over
by an earlier lookup done on the same thread outside the normal path,
e.g. by the CLI's "get map" command. If that element was then freed ("del
map", "clear map", or the old generation purged after "commit map"), the
next matching request read freed memory, leading to random behaviour or a
worker crash. Configurations using both an ACL and a map loaded from a
virtual file are concerned. The returned pattern is no longer touched when
it is not filled.
* tcpcheck: A crash was possible with "tcp-check expect rstring".
The rule could be evaluated while the check's input buffer was not yet
allocated, passing a NULL buffer to the regular expression engine, which
expects a writable one. Only builds using the libc regex were affected
(PCRE/PCRE2 and the binary case return cleanly). Such a rule now simply
reports no match, like the string and binary cases already did.
* listener: A stuck accept loop could trigger the watchdog.
Listeners were woken up without checking whether "maxconn" had already
been reached. With many thread groups, a group could dequeue listeners,
fail to accept because another group had consumed the last slots, and
remain stuck long enough to trip the watchdog.
* ssl: Cipher and curve restrictions of crt-list lines were ignored with
AWS-LC.
On AWS-LC builds, the SSL object keeps its own copy of the cipher lists
and curves taken at creation, and switching to the certificate's SSL_CTX
during the handshake did not refresh them. As a result, the "ciphers",
"ciphersuites", "curves" and "ecdhe" options of a crt-list line were
silently ignored, and a client could negotiate a cipher or a curve that
the certificate explicitly excluded, as long as the bind line allowed
it. These settings are now reapplied after the context switch (OpenSSL
builds were not affected).
* muxes: Several crashes and connection-handling bugs were fixed in the H1
and H2 multiplexers.
A connection upgraded from HTTP/1 to HTTP/2 (via the H2 connection
preface) is only allowed to do so on its very first request, but the check
was defeated when the preface of a later request arrived split across
several reads: the connection could then be switched to H2 in the middle
of its life, dropping the end of the previous response that was still
pending in the output buffer. The first-request condition is now checked
reliably.
Other fixes in this area: the reverse HTTP gateway ("rhttp@") could crash
when an outgoing H2 connection failed, because the failure was not
reported to the caller that then used a freed connection (an experimental
feature); the H2 multiplexer could return a pointer to an already freed
tasklet when a deferred shutdown was scheduled, and the H1 multiplexer
passed an upgraded (thus no longer valid) connection to the traces when
destroying a stream. Also, when an error page (400, 408, 500, 501) was
mapped to an empty file or /dev/null, the H1 multiplexer failed to release
its embryonic stream, so the connection stayed open until the client
timeout instead of being closed immediately.
* h3/quic: Several protocol robustness issues were fixed.
HTTP/3 frames whose header itself was truncated are now rejected with
H3_FRAME_ERROR, closing the connection immediately instead of leaving the
stream to time out. A peer that resets (RESET_STREAM) its QPACK encoder or
decoder stream is now detected as a critical-stream violation, like a
close with FIN already was. In the QUIC transport, a STREAM frame that
sets FIN before the already-received offset is now rejected with
FINAL_SIZE_ERROR even when the final size is not yet known.
* flt-http-comp: The compression filter could truncate a message.
Since the filter produces a new payload and may hold data in its own
context, the end-of-message flag now stays cleared on the HTX message
until the compression is really complete, instead of being visible while
data is still buffered in the filter. Otherwise a message with no input
left and the EOM flag set was considered complete and the remaining
compressed data could be dropped, truncating the response.
* http-ana: "option checkcache" now returns a 502 as documented.
When "option checkcache" blocked a response, the status returned to the
client was still the server's original one (usually 200) instead of the
502 bad gateway that the documentation promises and that "http-response
deny" produces; the error files are now used as well.
* samples: Several converters and fetches returned wrong results.
Time converters ("ms_ltime", "ms_utime", "us_ltime", "us_utime") applied their
offset in the wrong unit, "http_date()" did not zero-pad the sub-second part
("23 ms" printed as ".23", same as "23 us"), the JWT part queries failed
without a path, "quic_enabled()" and "req.ssl_ec_ext" failed instead of
returning false, "res.cook_cnt()" miscounted Set-Cookie attributes, TCP-mode
fetches exposed the ":method"/":path" pseudo-headers, and "language" skipped
values after a truncated q-factor. All were fixed.
* spoe/stconn: A few internal robustness fixes.
A SPOE agent configuration file that defines no "spoe-message" section
corrupted the agent's message list during parsing, which could break the
agent's messages; the list is now spliced correctly. Streams caught in the
middle of a connection retry could crash the process while being dumped by
"show threads", the watchdog or "warn-blocked-traffic-after"; endpoints
are now replaced instead of being briefly detached, so an observer can
never see a stream without endpoint.
* doc: The documentation was corrected on several points.
The "url_ip" and "url_port" fetches are now documented as failing when the
authority part contains a host name rather than an address. The "hex2i"
converter is documented as failing (returning nothing) on unparsable input
instead of returning zero. "option checkcache" now mentions that responses
carrying neither a validator nor freshness information are not examined
and pass unfiltered. The diagnostic mode section explains that some
diagnostic messages interact with "zero-warning" mode (and thus must only
be used interactively).
Thanks 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 (4):
BUG/MINOR: server: fix "del server" description
BUG/MINOR: mux_quic: extend FINAL_SIZE_ERROR detection on stream
BUG/MINOR: h3: reject frames with truncated header
BUG/MINOR: h3: reject RS/SS for QPACK uni streams
Christopher Faulet (8):
BUG/MEDIUM: flt-http-comp: Block HTX_FL_EOM flag till the end of
compression
BUG/MINOR: flt-http-comp: Rely on htx_set_eom() to restore EOM flag
BUG/MINOR: spoe: Fix the message list splice with an empty list
BUG/MEDIUM: mux-h1: also release the embryonic stream when the error is
empty
BUG/MINOR: http-fetch: don't report pseudo-headers in TCP mode
BUG/MEDIUM: mux-h1: only accept the H2 preface on the first request
REGTESTS: http-messaging: Fix http_fail_cnt_body to succeed with small
timeouts
BUG/MAJOR: h1-htx: report an error on a too long chunk-size line
Frederic Lecaille (2):
BUG/MEDIUM: ssl: crt-list ciphers ignored with AWS-LC
BUG/MEDIUM: ssl: crt-list curves ignored with AWS-LC
Lukas Tribus (1):
MINOR: doc: elaborate on diagnostic mode behavior
Olivier Houchard (1):
BUG/MEDIUM: listener: Consider maxconn before waking listeners
William Lallemand (1):
REGTEST: ssl: require OpenSSL 1.1.1 for crt-list ciphersuites tests
Willy Tarreau (29):
BUG/MEDIUM: tools: insert an XXH64 layer on the PRNG output
MINOR: stconn: prefer to replace endpoints rather than detaching them
MINOR: stick-table: provide a function to estimate on-wire data size
MINOR: stick-table: calculate the on-wire size of each key
BUG/MEDIUM: stick-table: restrict key sizes to what fits in a buffer
BUG/MEDIUM: pattern: don't dereference static_pattern's data when not
filling it
BUG/MINOR: http: do not consume the delimiter of a truncated q-factor
BUG/MINOR: sample: apply the ms_/us_ time offsets in the input unit
BUG/MINOR: sample: zero-pad the fractional part emitted by http_date()
BUG/MINOR: sample: make quic_enabled() always succeed
BUG/MINOR: http-ana: return a 502 when checkcache blocks a response
BUG/MINOR: stick-table: do not count the lookup itself in table_trackers()
BUG/MINOR: payload: always report a boolean from req.ssl_ec_ext
BUG/MINOR: http-fetch: do not count Set-Cookie attributes as cookies
BUG/MEDIUM: tcpcheck: do not run a regex on an unallocated buffer
BUG/MINOR: sample: return the decoded JWT part when no path is given
DOC: config: mention other responses not blocked by option checkcache
DOC: config: fix doc for hex2i() converter on unparsable input
DOC: config: mention that url_ip and url_port fail when passed a name
BUG/MINOR: cfgparse-global: add missing gotos to error label in
max-spread-checks
BUG/MINOR: cfgparse-global: add missing calloc() check for the global
description
BUG/MINOR: cfgparse-listen: release the previous description of the proxy
BUG/MINOR: cfgparse-listen: fix the argument checked by "email-alert"
BUG/MINOR: cfgparse-listen: properly reject extra args in a backend
section
BUG/MINOR: cfgparse-listen: complete the list of options for the
suggestions
BUG/MEDIUM: mux-h2: don't return a freed tasklet from h2_deferred_shut()
BUG/MEDIUM: mux-h1: don't pass an upgraded connection to traces in
h1s_destroy()
BUG/MEDIUM: mux-h2: report a dead rhttp connection in h2_wake()
BUG/MAJOR: mux-h2: properly handle short reads on PING frames
--
Christopher Faulet