Hi,
HAProxy 3.4.7 was released on 2026/10/14. It added 71 new commits
after version 3.4.6.
This release contains two important security fixes, in addition to the usual
set of bug 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
muxes (H1, H2 and FCGI), the CLI, the syslog applet used by "log-forward",
the stick-table key exchange over peers, maps/ACLs, and AWS-LC builds of the
SSL layer. Two behaviour changes deserve attention: the "none" JWT algorithm
is now rejected by default, and server-sent event responses (SSE) no longer
suffer from Nagle-induced delays.
* 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.
* cfgparse: Several configuration parsing issues were fixed, one of which
could randomly prevent startup.
The most severe one concerns the "userlist" section. Its internal section
identifier overlapped with the "global" and "listen" ones, so depending on
build options and the linker, the wrong parser could be invoked for the
"user" and "group" keywords. A configuration that was accepted by one
build could then fail to start on another with errors such as "cannot find
group id for 'admins'", with no configuration-side workaround.
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. The warning printed for
conflicting ACL conditions no longer emits a stray quote on the next
line. Also, setting "localpeer" in the global section while passing "-L"
on the command line used to make HAProxy refuse to load its configuration
as a fatal error; it is now a warning again as documented, and the name
from "-L" wins, so instances sharing a configuration file start
again. Finally, the list of suggested options used when one is mistyped
was completed and fixed.
* cli: A command line filling the buffer exactly could loop forever.
Since the pipelining refactor, the last command of a line is detected by
comparing its end with the tail of an internal buffer. When a command line
(including its final LF) was exactly "tune.bufsize" bytes long, the tail
wrapped and the last command was never detected: its words were then
re-parsed as new commands, the buffer length underflowed, and the CLI kept
answering "Unknown command" errors, keeping a thread busy as long as the
client read them. With DEBUG_STRICT=2, HAProxy crashed instead. Reported
and fixed by Yeonggi Kim.
* log: Syslog sessions in "log-forward" sections never ended.
The syslog applet stopped reading its input as soon as the buffer was
empty, which was also where the client's shutdown was noticed. When a
client closed the connection after its last message had been forwarded,
the session stayed open forever. A "log-forward" section does not inherit
"timeout client" from the defaults section, so without an explicit timeout
nothing ever closed these sessions: they accumulated, and a soft-stop then
waited for "hard-stop-after" (forever if it is not set). This also
happened when a "ring" forwarded to a local "log-forward" section, causing
the previous process to survive a reload. Sessions now end correctly on
client close and are accounted in "cli_aborts" as before. Reported and
fixed by Yeonggi Kim.
* 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, and a few crashes were fixed.
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).
In addition, "dump ssl cert" could crash on AWS-LC when a certificate in
crt-store had no private key. Two crashes in the SSL I/O callback could
also be hit when traces were enabled and a connection was destroyed
instead of being returned to an idle pool or its session. Finally, X25519
and X448 are now reported under their standard IANA names in traces, and
"curves2nid()" can resolve them; the old "ecdh_x25519"/"ecdh_x448"
spellings are kept as aliases.
* muxes: Several crashes and connection-handling bugs were fixed in the H1,
H2 and FCGI 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: when the H2 multiplexer could not allocate a
buffer to receive a large request body (with "tune.buffers.limit" for
instance), it could crash while switching buffers; 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 and FCGI multiplexers 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/hq-interop: 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 GOAWAY frame whose length does not match its single
stream ID is likewise a connection error. 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. The HTTP/0.9 parser no longer sets the end-of-message flag twice
when the final FIN arrives in its own empty frame, which could trip an
internal consistency check. Finally, the QUIC stack now implements the
AEAD integrity limit required by RFC 9001 ยง6.6: failed packet
authentications are counted per connection and the connection is closed
with AEAD_LIMIT_REACHED once the limit of the cipher is exceeded (2^52 for
AES-GCM, 2^36 for ChaCha20-Poly1305). This is a hardening measure with no
practical impact.
* 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.
* jwt: Tokens using the "none" algorithm are now rejected by default.
Following RFC 8725, "jwt_verify" no longer accepts an unsigned token whose
algorithm is "none" unless the caller explicitly allows it. If some of
your checks rely on such tokens, they will now fail and you must add the
new global directive "jwt.allow_none_alg" to restore the previous
behaviour.
* jwe: Two buffer overflows were fixed in the RSA1.5 decryption path. Found
by Dmitri Kaslov.
* http-ana: SSE responses are no longer delayed, and "option checkcache" now
returns a 502.
Responses advertising "Content-Type: text/event-stream" now automatically
have the equivalent of "option http-no-delay" enabled for the direction
towards the client, forwarding this way small events immediately instead
of dealying them by up to ~200 ms.
Independently, 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/proxy/hlua: A few internal robustness fixes.
Adding a backend at runtime through the CLI did not set the load-balancing
capability on it, which is only used today to validate a server's
compatibility with the proxy's mode and could matter later. 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. Finally, a Lua event handler looking up a server
could hit a race with dynamic backends: if the backend was removed between
the event being queued and processed, an internal consistency check killed
the process. The lookup now tolerates a gone backend.
* 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). Finally, the "log-steps" parser error message now
states the comma-separated syntax it actually expects.
* build: The "haring" developer tool no longer fails to build on a warning.
A statement with no effect (and a static variable that was never set) made
the "haring" tool emit a build warning, and prevented it from building where
warnings are treated as errors.
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.4/src/
Git repository : https://git.haproxy.org/git/haproxy-3.4.git/
Git Web browsing : https://git.haproxy.org/?p=haproxy-3.4.git
Changelog : https://www.haproxy.org/download/3.4/src/CHANGELOG
Dataplane API :
https://github.com/haproxytech/dataplaneapi/releases/latest
OpenTelemetry : https://github.com/haproxytech/haproxy-opentelemetry
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 (10):
BUG/MINOR: proxy: add PR_CAP_LB for dynamically added backend
BUG/MINOR: server: fix "del server" description
BUG/MINOR: cli: update wait help usage
BUG/MINOR: hq-interop: prevent double EOM on request with standalone FIN
BUG/MINOR: ssl: fix trace crash on FE conn release in io cb
BUG/MINOR: ssl: fix trace crash on BE conn release in io cb
BUG/MINOR: mux_quic: extend FINAL_SIZE_ERROR detection on stream
BUG/MINOR: h3: reject frames with truncated header
BUG/MINOR: h3: close connection on GOAWAY frame too long
BUG/MINOR: h3: reject RS/SS for QPACK uni streams
Aurelien DARRAGON (2):
BUG/MINOR: log: fix erroneous error message in log-steps parser
BUG/MINOR: hlua: assume proxy_find_by_id() may return NULL on previously
existing server
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
Emeric Brun (1):
BUILD: haring: fix compile issue due to nop warning.
Frederic Lecaille (3):
MINOR: quic: implement the AEAD integrity limit
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
Remi Tricot-Le Breton (4):
MEDIUM: jwt: Reject "none" alg in jwt_verify by default
BUG/MINOR: jwe: Buffer overflow when filling fake cek in case of RSA1.5
BUG/MINOR: jwe: Avoid buffer overflow in fake cek (RSA1.5)
BUG/MINOR: ssl: Fix potential NULL-deref in "dump ssl cert" with AWS-LC
William Lallemand (3):
BUG/MINOR: ssl: use standard names for X25519 and X448 groups
REGTEST: ssl: require OpenSSL 1.1.1 for crt-list ciphersuites tests
BUG/MINOR: cfgparse-global: only warn when "localpeer" is set together
with -L
Willy Tarreau (34):
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
MINOR: http-ana: automatically enable http-no-delay for streaming events
(SSE)
BUG/MAJOR: cfgparse: properly check userlist section when scanning
keywords
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: fix quoting of the condition conflict warning
BUG/MINOR: cfgparse-listen: properly reject extra args in a backend
section
BUG/MINOR: proxy: also look for the closest option among cfg_opts3
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-fcgi: don't return a freed tasklet from
fcgi_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/MEDIUM: mux-h2: fix a possible crash on rxbuf allocation failure
BUG/MAJOR: mux-h2: properly handle short reads on PING frames
Yeonggi Kim (4):
BUG/MEDIUM: cli: fix the last command detection on a full command line
REGTESTS: cli: test a command line of exactly tune.bufsize bytes
BUG/MEDIUM: log: do not ignore the peer's shutdown in the syslog applet
REGTESTS: log: check that a log-forward TCP session ends on client close
--
Christopher Faulet