]> git.ipfire.org Git - thirdparty/haproxy.git/log
thirdparty/haproxy.git
31 hours agoBUG/MINOR: mux_quic: prevent BE reuse with an errored conn quic-interop flx04/quic-interop
Amaury Denoyelle [Thu, 28 May 2026 13:55:56 +0000 (15:55 +0200)] 
BUG/MINOR: mux_quic: prevent BE reuse with an errored conn

When a backend connection is reused, qcm_strm_attach() callback is used.
A BUG_ON() is present to ensure that the connection is not already on
error. This should be guaranteed by the fact that idle insertion is
skipped for such connections.

However, when a connection is flagged on error, it is not immediately
removed from its idle/avail pool. Thus, there is a risk that it is
reused, triggering the aformentioned BUG_ON() statement.

This issue should be avoided via avail_streams callback which should
return 0, forcing the caller to cancel the connection reuse. In QUIC,
this callback implementation relies on internal qcc_be_is_reusable().
However, it lacked checks for error status.

To fix this, extend qcc_be_is_reusable() to properly check connection
errors or an expired timeout.

Previously, these parameters were already checked by qcc_is_dead(). As
it also relies on qcc_be_is_reusable(), this patch also rearranges it to
avoid duplicate checks for backend connections.

This should be backported up to 3.3.

31 hours agoBUG/MINOR: mux_quic: fix BE conn removal on app shutdown
Amaury Denoyelle [Thu, 28 May 2026 14:44:03 +0000 (16:44 +0200)] 
BUG/MINOR: mux_quic: fix BE conn removal on app shutdown

When QUIC application layer is shut for a backend connection, the
connection is immediately removed from its idle pool. This is a nice
optimization as this prevents a future streams to try to reuse an
unusable connection. This is implemented since the following commit.

  00d668549e46b34d29ea3daa1f6dd42b5251a365
  MINOR: mux-quic: do not reuse connection if app already shut

However, this removal is not correctly performed as it is used
conn_delete_from_tree(). For private connections, this can cause crashes
as they are stored in the session instead. Thus, connection status is
now properly check, and alternatively session_unown_conn() is used if
stored in the session.

This must be backported up to 3.3.

31 hours agoBUG/MINOR: mux_quic: open an idle QCS on reset on BE side
Amaury Denoyelle [Thu, 23 Apr 2026 12:23:01 +0000 (14:23 +0200)] 
BUG/MINOR: mux_quic: open an idle QCS on reset on BE side

On the backend side, a QCS may be opened but resetted immediately. No
STREAM frame will be emitted prior to the RESET_STREAM. When the latter
is sent, qcs_close_local() will mark the QCS Tx channel as closed.

In this case, a BUG_ON() would be triggered as there is QCS Tx channel
is not yet marked as opened. To prevent this, add a qcs_idle_open() call
when the stream is resetted, but only for the backend side.

This should be backported up to 3.3.

31 hours agoMINOR: mux_quic/flags: add missing flags
Amaury Denoyelle [Thu, 28 May 2026 13:49:21 +0000 (15:49 +0200)] 
MINOR: mux_quic/flags: add missing flags

Add missing mux QUIC values for the dev flags utility, both for qcc and
qcs types.

32 hours agoBUILD: addons: convert WURFL addon to EXTRA_MAKE 20260528-extra-make flx04/20260528-extra-make
William Lallemand [Thu, 28 May 2026 14:33:30 +0000 (16:33 +0200)] 
BUILD: addons: convert WURFL addon to EXTRA_MAKE

Move the WURFL Makefile part to addons/wurfl/Makefile.mk so it can be
used with EXTRA_MAKE and allow to cleanup the main Makefile.

Shouldn't have impact on the build system, every build variable
previously used are the same.

32 hours agoBUILD: addons: convert deviceatlas addon to EXTRA_MAKE
William Lallemand [Thu, 28 May 2026 14:25:44 +0000 (16:25 +0200)] 
BUILD: addons: convert deviceatlas addon to EXTRA_MAKE

Move the deviceatlas Makefile.inc to Makefile.mk so it can be used with
EXTRA_MAKE and allow to cleanup the main Makefile.

EXTRA_MAKE paths are appended with /Makefile.mk via addsuffix, so the
path must not have a trailing slash.

Shouldn't have impact on the build system, every build variable
previously used are the same.

32 hours agoBUILD: addons: convert 51d addon to EXTRA_MAKE
William Lallemand [Thu, 28 May 2026 14:17:57 +0000 (16:17 +0200)] 
BUILD: addons: convert 51d addon to EXTRA_MAKE

Move the 51degrees Makefile part to addons/51degrees/Makefile.mk so it
can be used with EXTRA_MAKE and allow to cleanup the main Makefile.

EXTRA_MAKE paths are appended with /Makefile.mk via addsuffix, so the
path must not have a trailing slash.

Shouldn't have impact on the build system, every build variable
previously used are the same.

34 hours agoBUG/MINOR: mux-h2: Count padding for connection flow control on error path
Christopher Faulet [Thu, 28 May 2026 12:42:16 +0000 (14:42 +0200)] 
BUG/MINOR: mux-h2: Count padding for connection flow control on error path

When DATA frame are received, we take care to update the counter used to
send WINDOW_UPDATE for the connection. It is also performed on error path
when DATA frames are processed. However, when this happened, only the frame
length was accounted while the padding must also be considered.

To fix the issue, the full frame length (h2c->dfl), which include the
padding length, must be added to the amount of newly received data
(h2c->rcvd_c).

The issue was introduced with commit eeacca75d ("BUG/MINOR: mux-h2: count
rejected DATA frames against the connection's flow control") and backported
to 2.8.

So this patch must be backported as far as 2.8.

37 hours agoREGTESTS: lua: fix tune.lua.openlibs in Lua reg-tests
William Lallemand [Wed, 27 May 2026 19:06:04 +0000 (21:06 +0200)] 
REGTESTS: lua: fix tune.lua.openlibs in Lua reg-tests

These tests were using "tune.lua.openlibs none" with lua-load, which
was a no-op in the old code since Lua states 0 and 1 were always
initialised before config parsing with all standard libraries.

Now that the Lua VM is initialised lazily, the restriction correctly
applies to state 0 as well. Replace "none" with the minimal set of
libraries actually required by each test's Lua code:

  - lua_socket.vtc, h_txn_get_priv.vtc, lua_httpclient.vtc: string
  - txn_get_priv.vtc: string,table

37 hours agoBUG/MEDIUM: lua: defer Lua VM initialisation to the first Lua config keyword
William Lallemand [Wed, 27 May 2026 17:54:48 +0000 (19:54 +0200)] 
BUG/MEDIUM: lua: defer Lua VM initialisation to the first Lua config keyword

HAProxy used to call hlua_init() unconditionally from step_init_1(),
before any configuration file was parsed.  As a consequence, Lua states
0 and 1 were always created with hlua_openlibs_flags set to its default
value (HLUA_OPENLIBS_ALL), regardless of any tune.lua.openlibs directive
that appeared later in the global section.  With multiple threads, states
2..N were created correctly in hlua_post_init() after the config had been
parsed, while states 0 and 1 retained the full standard-library set.
This produced the observable bug reported in GitHub issue #3396: a script
loaded with lua-load-per-thread could see require() as a function on
thread 1 but nil on thread 2 when tune.lua.openlibs was used to restrict
the available libraries.

The initialisation is now lazy.  hlua_init() is idempotent: it returns
immediately if the states already exist (hlua_states[0] != NULL).  It is
called explicitly from the three config keyword handlers that need the
Lua states to be live before they can do their work (lua-load,
lua-load-per-thread, lua-prepend-path) and from tune.lua.openlibs, after
the hlua_openlibs_flags variable has been updated, so that the states are
always created with the correct library set.

hlua_post_init() calls hlua_init() unconditionally as a safety net,
covering the case where no Lua directive appeared in the configuration at
all (no global section, or only pure-tuning directives such as timeouts
and memory limits), and ensuring correct behaviour with multiple
consecutive global sections.

As a result of this change, tune.lua.openlibs must now appear before
lua-load, lua-load-per-thread, and lua-prepend-path in the configuration;
if any of those keywords is encountered first, the Lua states will already
be initialised and tune.lua.openlibs with a non-default value will return
a parse error.

No backport needed.

41 hours agoBUG/MINOR: quic: Fix memory leak in quic_deallocate_dghdlrs()
Frederic Lecaille [Tue, 19 May 2026 15:06:08 +0000 (17:06 +0200)] 
BUG/MINOR: quic: Fix memory leak in quic_deallocate_dghdlrs()

When deallocating the QUIC datagram handlers, the per-thread buffer
allocated inside quic_dghdlrs[i].buf.buffer was missing a free().
This led to a memory leak on exit or reload.

Fix this by freeing each thread buffer before releasing the main
quic_dghdlrs array.

41 hours agoBUG/MEDIUM: quic: handle ECONNREFUSED on RX side
Frederic Lecaille [Fri, 24 Apr 2026 09:01:37 +0000 (11:01 +0200)] 
BUG/MEDIUM: quic: handle ECONNREFUSED on RX side

Unlike the detection performed during sendto() for an unreachable peer,
ECONNREFUSED was not handled when received via recvmsg() as an ICMP
"host unreachable" message.

This patch tracks ECONNREFUSED errors on the receive path.

Note that this detection is entirely dependent on the remote host effectively
sending an ICMP "host unreachable" message and on the absence of any network
filtering (e.g., firewalls) that would drop such ICMP packets. Without
receiving this ICMP signal, the connection state cannot be updated through
this mechanism.

At a higher level, similar to how this error is handled on sendto(),
the connection is now terminated as soon as possible by calling
qc_kill_conn(). This triggers a call to qc_notify_err(). When the mux
does not exist, it attempts to create one via conn_create_mux(). While
the latter systematically fails if the connection is flagged with
CO_FL_ERROR, it has the useful side effect of waking the stconn stream
attached to the connection during a session opening without a mux
(e.g., for H3).

This issue was caught by haload (upcoming tool).

Must be backported as far as 2.6 because it impacts both the QUIC
frontends and backends.

2 days agoCLEANUP: qpack: move encoded macros to qpack-t.h to avoid duplication 20260527-fle-ia-review flx04/20260527-fle-ia-review
Frederic Lecaille [Wed, 27 May 2026 16:38:32 +0000 (18:38 +0200)] 
CLEANUP: qpack: move encoded macros to qpack-t.h to avoid duplication

QPACK_LFL_WLN_BIT and related encoded field line bitmasks were defined
in both qpack-enc.c and qpack-dec.c. Moved them to qpack-t.h where
they are shared between encoder and decoder, eliminating the duplicate
definitions.

Should be backported to ease any further commit to come.

2 days agoBUG/MINOR: qpack: fix huff_dec() error handling in qpack_decode_fs()
Frederic Lecaille [Wed, 27 May 2026 15:16:16 +0000 (17:16 +0200)] 
BUG/MINOR: qpack: fix huff_dec() error handling in qpack_decode_fs()

The <nlen> variable is a signed integer, but the check for a Huffman
decoding error was written as 'nlen == (uint32_t)-1'.

With standard compiler type promotion rules, this comparison happens to
work as intended when huff_dec() returns -1. However, relying on implicit
unsigned promotions for signed error checking is fragile. If a compiler
applies different promotion semantics, or if huff_dec() returns any other
negative error code, the failure would go undetected, leading to buffer
corruption or a crash via b_add() and ist2().

Fix this by using 'nlen < 0', removing any ambiguity regardless of the
compiler used.

Must be backported to all versions.

2 days agoCLEANUP: qpack: fix copy-paste typo in value Huffman debug string for WLN
Frederic Lecaille [Wed, 27 May 2026 15:07:26 +0000 (17:07 +0200)] 
CLEANUP: qpack: fix copy-paste typo in value Huffman debug string for WLN

In qpack_decode_fs(), inside the QPACK_LFL_WLN_BIT branch (Literal field
line with literal name), the debug message printed "[name huff ...]" instead
of "[value huff ...]" after decoding the value string.

This is a harmless copy-paste typo from the preceding name decoding block.

Even if this is a cleanup, should be easily backported to ease any further
backport.

2 days agoBUG/MINOR: qpack: fix sign bit mask in qpack_decode_fs_pfx()
Frederic Lecaille [Wed, 27 May 2026 14:40:52 +0000 (16:40 +0200)] 
BUG/MINOR: qpack: fix sign bit mask in qpack_decode_fs_pfx()

The sign bit of the Delta Base integer encoding was extracted using
mask 0x8 (bit 3) instead of 0x80 (bit 7). This was likely a copy-paste
error from other QPACK instructions using 3-bit varints.

According to RFC 9204 Section 5.2.1, for prefix instructions, the sign
bit 'S' is the most significant bit (bit 7) of the first byte, followed
by a 7-bit varint.

This fix is harmless for current HTTP/3 traffic: per RFC 9204, the Delta
Base calculation is strictly used for dynamic table entry references.
Since HAProxy's QPACK dynamic table is currently disabled and the extracted
sign bit is not yet used in the decoding logic (only in debug prints),
this code path has no impact on production for now.

Must be backported to all versions.

2 days agoCLEANUP: qpack: fix copy-paste typo in value Huffman debug string
Frederic Lecaille [Wed, 27 May 2026 13:18:40 +0000 (15:18 +0200)] 
CLEANUP: qpack: fix copy-paste typo in value Huffman debug string

In qpack_decode_fs(), when decoding a literal field line with a literal
value, the debug message mistakenly printed "[name huff ...]" instead of
"[value huff ...]" after a successful Huffman decoding of the value string.

This is a harmless copy-paste typo from the field name decoding block
just above, fix it to prevent confusion when debugging QPACK streams.

Should be easily backported to all versions to ease further modifications
into the QPACK code.

2 days agoBUG/MINOR: qpack: fix potential null-pointer dereference in qpack_dht_insert()
Frederic Lecaille [Wed, 27 May 2026 13:00:30 +0000 (15:00 +0200)] 
BUG/MINOR: qpack: fix potential null-pointer dereference in qpack_dht_insert()

When defragmenting the QPACK dynamic header table upfront during an
insertion, qpack_dht_defrag() can fail and return NULL if memory
allocation or re-allocation fails.

However, qpack_dht_insert() was blindly using the returned pointer
without validation, immediately leading to a null-pointer dereference
on 'dht->wrap'.

Fix this by checking if 'dht' is NULL after the defrag call and return
an error (-1).

Note that this has no impact on production yet because the QPACK dynamic
table is currently not enabled/used, so qpack_dht_insert() is never called.

Should be easily backported to all versions.

2 days agoBUG/MINOR: qpack: Fix index calculation in debug functions
Frederic Lecaille [Tue, 26 May 2026 09:26:23 +0000 (11:26 +0200)] 
BUG/MINOR: qpack: Fix index calculation in debug functions

Although qpack_idx_to_name and qpack_idx_to_value are currently only
called within uncompiled debug code, they contained an index bug. They
passed absolute indexes directly to qpack_get_dte instead of relative
dynamic table indexes.

This patch fixes the logic by subtracting QPACK_SHT_SIZE and guarding
against static table index lookups.

Should be easily backported to all versions.

2 days agoRevert "BUG/MEDIUM: dns: fix long loops in additional records parse on name failure"
Christopher Faulet [Wed, 27 May 2026 13:37:35 +0000 (15:37 +0200)] 
Revert "BUG/MEDIUM: dns: fix long loops in additional records parse on name failure"

This reverts commit fefce297ab5d0c36d6d6773092c976ea6166dc1e.

The commit broke the resolvers. All responses are marked as invalid. The
resolv_read_name() function can return 0 on error, but it seems also
possible to return 0 when no label name was found. And depending on the
caller, it can be an error... or not.

So, let's revert it. This might trigger a watchdog but doesn't seem to and
once fixed it makes things worse.

Must be backported as far as 2.4.

2 days agoBUG/MINOR: qmux: reject too large initial record
Amaury Denoyelle [Wed, 27 May 2026 13:34:01 +0000 (15:34 +0200)] 
BUG/MINOR: qmux: reject too large initial record

Initial max_record_size is set to 16382. If the first received record
size is larger, abort xprt_qmux layer immediately without having to wait
for the timeout.

No need to backport.

2 days agoBUG/MEDIUM: qmux: do not crash on receiving an invalid first frame
Amaury Denoyelle [Wed, 27 May 2026 13:35:34 +0000 (15:35 +0200)] 
BUG/MEDIUM: qmux: do not crash on receiving an invalid first frame

With QMux, each peer has to first emit a transport parameters frame. If
the received frame is different, xprt_qmux handshake cannot proceed.
This patch removes the BUG_ON() in this case, replacing it with a safer
connection closure.

In the future, a graceful close with CONNECTION_CLOSE frame should be
implemented.

No need to backport.

2 days agoBUG/MEDIUM: qmux: do not crash on too large record
Amaury Denoyelle [Wed, 27 May 2026 13:30:04 +0000 (15:30 +0200)] 
BUG/MEDIUM: qmux: do not crash on too large record

Remove BUG_ON() when reading a QMux record larger than the buffer. It is
now replaced by a safer error handling. In the future, a proper
CONNECTION_CLOSE emission should be implemented for this case.

No need to backport.

2 days agoBUG/MEDIUM: cpu-topo: Enforce thread-hard-limit on policy
Olivier Houchard [Wed, 27 May 2026 09:13:52 +0000 (11:13 +0200)] 
BUG/MEDIUM: cpu-topo: Enforce thread-hard-limit on policy

When a policy is set, and the number of threads is calculated
dynamically, make sure we enforce thread-hard-limit, and do not create
thread groups based on how many thread we would have created without
the limit.
This should be backported to 3.3 and 3.2. The patch won't apply cleanly
there, because the code has changed since then, but it should be very
similar, only we'll have to check "cpu_count" there, where in 3.4 we
check "thr_count".

2 days agoBUG/MINOR: mux-h1: H2 preface rejection doesn't update stick-table glitches
Chad Lavoie [Fri, 22 May 2026 17:58:38 +0000 (13:58 -0400)] 
BUG/MINOR: mux-h1: H2 preface rejection doesn't update stick-table glitches

commit 72fd357814e1 ("MEDIUM: mux-h1: Return an error on h2 upgrade
attempts if not allowed") added an h1_report_glitch() call on the new
405 path but exits via "goto no_parsing", which skips the
session_add_glitch_ctr() call at the end of the parse block. As a
result fc_glitches increments correctly but the per-session stick
counters never see it, breaking sc_glitch_cnt-based rate limiting of
the H2-preface-over-H1 abuse pattern.

No backport needed beyond the branches that took 72fd357814.

[cf: Patch was edited to move the goto label instead of duplicating
     the call to session_add_glitch_ctr]

2 days agoBUG/MINOR: ssl-gencert: validate SNI characters to prevent SAN certificate injection
William Lallemand [Mon, 25 May 2026 12:55:18 +0000 (12:55 +0000)] 
BUG/MINOR: ssl-gencert: validate SNI characters to prevent SAN certificate injection

ssl_sock_add_san_ext() builds the Subject Alternative Name extension by
concatenating "DNS:" + servername and passing the result to
X509V3_EXT_nconf_nid(). OpenSSL's nconf parser splits the value string on
commas into multiple type:value SAN entries. The SNI comes from unauthenticated
TLS ClientHello data -- an attacker can embed commas and colons (e.g.,
"host,dns:internal.corp,ip:10.0.0.1") to inject arbitrary GENERAL_NAME entries
into certificates signed by HAProxy's configured CA.

This is a CA issuance-policy violation: the operator expects one certificate
per SNI hostname, but an attacker can obtain certificates containing additional
hostnames/IPs/emails without access to the CA private key.

Fix by adding ssl_sock_sni_is_valid() that validates the SNI contains only
DNS-label-legal characters (alphanumeric, hyphens, dots). The check is
performed at the start of ssl_sock_do_create_cert() before any allocation.
Commas, colons, spaces, and other special characters cause certificate
generation to fail, preventing SAN injection while allowing all valid
hostname values.

Must be backported in every maintained branches.

2 days agoBUG/MINOR: tcpcheck: Check LDAP response to not read more data than available
Christopher Faulet [Wed, 27 May 2026 07:16:37 +0000 (09:16 +0200)] 
BUG/MINOR: tcpcheck: Check LDAP response to not read more data than available

tcpcheck_ldap_expect_bindrsp() parses ASN.1 BER-encoded LDAP responses from
the health check target. After reading the outer message size and validating
protocol fields, it encounters a long-form BER length for the bindResponse
value (high bit set in the length byte). The code reads nbytes = (*ptr &
0x7f) then advances ptr by 1 + nbytes without checking that enough bytes
remain in the receive buffer. So, it is possible to read more data than
available.

Note that it is only possible if the LDAP response was forged because the
message length was already checked. LDAP response remains quite short and it
is not possible to read outside the buffer area. So at worst, garbage are
parsed and a wrong result is reported by the LDAP health-check. Most
probably an error will be reported.

This patch could be backported to all stable versions.

3 days ago[RELEASE] Released version 3.4-dev14 v3.4-dev14
Willy Tarreau [Tue, 26 May 2026 19:56:40 +0000 (21:56 +0200)] 
[RELEASE] Released version 3.4-dev14

Released version 3.4-dev14 with the following main changes :
    - MINOR: config: shm-stats-file is no longer experimental
    - BUILD: proxy: unstatify the proxies_del_lock to avoid a warning without threads
    - BUG/MEDIUM: net_helper: fix a remaining possibly infinite loop in converters
    - MINOR: ssl_sock: remove unneeded check on QMux flags
    - MINOR: connection: define xprt_add_l6hs()
    - MINOR: xprt_qmux: define default value for get_alpn
    - MINOR: connection: define mask CO_FL_WAIT_XPRT_L6
    - MINOR: session: support QMux in clear on FE side
    - MINOR: backend: support QMux in clear for BE side
    - BUG/MINOR: ocsp: Manage date too far away in the future
    - MINOR: mux_quic: handle STOP_SENDING in QMux
    - MINOR: mux_quic: handle MAX_STREAMS for uni stream in QMux
    - MINOR: mux_quic: do not crash on unhandled QMux frame reception
    - 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/MINOR: quic: fix ODCID lookup from derived value
    - 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: cli: Fix parsing of pattern finishing a command payload
    - BUG/MEDIUM: acme: NUL terminate response buffer before PEM parsing
    - BUILD: intops: mask the fail value in array_size_or_fail()
    - BUG/MEDIUM: log-forward: make sure the month is unsigned
    - BUG/MEDIUM: regex: allocate a large enough pcre2 match for all matches
    - 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: log: free logformat expr on compile failure in cfg_parse_log_profile
    - 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
    - CLEANUP: resolvers: use read_n32() instead of open-coded big-endian read
    - CLEANUP: resolvers: remove pool_free(NULL) in SRV additional record matching
    - CLEANUP: resolvers: fix comment typos and wrong filenames in file headers
    - BUG/MINOR: haterm: fix the random suffix multiplication
    - MINOR: haterm: enable h3 for TCP bindings
    - MINOR: haterm: do not emit a warning when not using SSL
    - 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: sample: request an extra output byte for the url_dec converter
    - 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/MEDIUM: tools: insert an XXH64 layer on the PRNG output
    - MINOR: tools: provide a function to generate a hashed random pair
    - MEDIUM: init: fall back to ha_random64_pair_hashed() for the cluster secret
    - MEDIUM: tools: use the hashed random pair for UUID generation
    - MEDIUM: h1: use ha_random64_pair_hashed() for the WebSocket key
    - MEDIUM: quic: use ha_random64_pair_hashed() to generate the QUIC retry tokens
    - MEDIUM: tools: switch the main PRNG to a thread-local xoshiro256**
    - BUG/MEDIUM: h3: reject client push stream
    - BUG/MINOR: h3: reject server push stream
    - BUG/MINOR: h3: reject client CANCEL_PUSH frame
    - BUG/MINOR: h3: adjust error on PUSH_PROMISE frame reception
    - BUG/MINOR: h3: reject server MAX_PUSH_ID frame
    - BUG/MEDIUM: auth: fix unconfigured password NULL deref
    - BUG/MINOR: h3: add missing break on rcv_buf()
    - BUG/MINOR: hlua: prevent Lua from passing CR/LF/NUL in HTTP headers
    - BUG/MINOR: qmux: do not crash on frame parsing issue
    - BUG/MINOR: quic: reject packet too short for HP decryption
    - BUG/MINOR: jwe: enforce GCM tag length to 128 bits
    - BUG/MEDIUM: jwe: substitute random CEK on RSA1_5 decryption failure per RFC 7516 #11.5
    - 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
    - CLEANUP: mux-fcgi/mux-spop: Remove copy/pasted comment about slow realign

3 days agoCLEANUP: mux-fcgi/mux-spop: Remove copy/pasted comment about slow realign
Christopher Faulet [Tue, 26 May 2026 16:20:01 +0000 (18:20 +0200)] 
CLEANUP: mux-fcgi/mux-spop: Remove copy/pasted comment about slow realign

A comment about the condition to perform a slow realign of the demux buffer
was abusively copy/pasted from the FCGI multiplexer at different places in
the FCGI and SPOP multiplexers. Let's remove these comments.

3 days agoBUG/MINOR: mux-spop: Use relative offset to compute contig data in demux buf
Christopher Faulet [Tue, 26 May 2026 16:15:25 +0000 (18:15 +0200)] 
BUG/MINOR: mux-spop: Use relative offset to compute contig data in demux buf

b_contig_data() should be called with a head-relative offset (0 for the
beginning of readable data). However, in the SPOP multiplexer, to get
contiguous data available in the demux buffer, it is called with
b_head_ofs(dbuf) which returns an absolute buffer position (b->head). So
b->head is counted twice. Because of this bug, the demux buffer could be
realigned while it should not and conversely.

Instead, the offset 0 must be used. So let's fix it.

This patch must be backported as far as 3.2.

3 days agoBUG/MINOR: mux-fcgi: Use relative offset to compute contig data in demux buf
Christopher Faulet [Tue, 26 May 2026 14:31:32 +0000 (16:31 +0200)] 
BUG/MINOR: mux-fcgi: Use relative offset to compute contig data in demux buf

b_contig_data() should be called with a head-relative offset (0 for the
beginning of readable data). However, in the FCGI multiplexer, to get
contiguous data available in the demux buffer, it is called with
b_head_ofs(dbuf) which returns an absolute buffer position (b->head). So
b->head is counted twice. Because of this bug, the demux buffer could be
realigned while it should not and conversely.

Instead, the offset 0 must be used. So let's fix it.

This patch must be backported as far as 2.4.

3 days agoBUG/MEDIUM: h1-htx: Sanitize parsing to properly handle upgrade requests
Christopher Faulet [Tue, 26 May 2026 13:35:46 +0000 (15:35 +0200)] 
BUG/MEDIUM: h1-htx: Sanitize parsing to properly handle upgrade requests

Thanks to previous patches, the request messages are now sanitized to
properly handle Upgrade requests. Now, if a 'connection: upgrade' header
value was found while no 'Upgrade' header, the 'upgrade' values is removed
from the 'connection' header. Conversely the opposite is also performed. If
'Upgrade' header was found, but no "conneciotn: upgrade" header value, all
occurrences of 'Upgrade' header are refused.

This patch depends on following ones:
  * MINOR: h1: Add  a H1M flag to specify a non-empty 'Upgrade:' header was parsed
  * MINOR: http: Add function to remove all occurrences of a value in a header

It should fix the issue 3397. But the H2 part should be reviewed too, and
probably the H1 response parsing, to be consistent with this change.

The series should be backported as far as 2.4.

3 days agoMINOR: h1: Add a H1M flag to specify a non-empty 'Upgrade:' header was parsed
Christopher Faulet [Tue, 26 May 2026 13:30:33 +0000 (15:30 +0200)] 
MINOR: h1: Add  a H1M flag to specify a non-empty 'Upgrade:' header was parsed

H1_MF_UPG_HDR flags was introduced to let H1 parser knwon a non-empty 'Upgrade:'
header was parsed.

This patch is mandatory to fix a bug.

3 days agoMINOR: http: Add function to remove all occurrences of a value in a header
Christopher Faulet [Tue, 26 May 2026 13:27:18 +0000 (15:27 +0200)] 
MINOR: http: Add function to remove all occurrences of a value in a header

http_remove_header_value() function was added to parse a header value and
remove all occurrences of a specific value.

This patch is mandatory to fix a bug.

3 days agoBUG/MEDIUM: mux-fcgi: reject stream ID 0 for application records
Christopher Faulet [Tue, 26 May 2026 11:56:12 +0000 (13:56 +0200)] 
BUG/MEDIUM: mux-fcgi: reject stream ID 0 for application records

Records with a stream ID set to 0 are reserved to management records.
However there was no check to trigger an error if an application record is
received with a stream ID to 0. This could lead to crash becausqe management
streams (which are static and immutable) can be modified while processing
application records (STDOUT/STDERR/END_REQUEST).

To fix the issue, An error is returned if the stream ID 0 is set on
GET_VALUES_RESULT or UNKNOWN_TYPE records.

This patch must be backported to all stable versions.

3 days agoBUG/MEDIUM: jwe: substitute random CEK on RSA1_5 decryption failure per RFC 7516...
Remi Tricot-Le Breton [Tue, 26 May 2026 15:26:04 +0000 (17:26 +0200)] 
BUG/MEDIUM: jwe: substitute random CEK on RSA1_5 decryption failure per RFC 7516 #11.5

do_decrypt_cek_rsa() calls EVP_PKEY_decrypt with RSA_PKCS1_PADDING for
RSA1_5 and returns failure (goto end) on decrypt error. This creates a
measurable timing difference between "padding invalid" (fast exit before
content decryption) and "padding valid + AEAD tag fail" (full AES-GCM/CBC
decryption path), exposing the RSA private key to a Bleichenbacher-style
adaptive attack requiring ~10^4-10^6 queries.

Fix: On RSA_PKCS1_PADDING failure, fill decrypted_cek with random bytes
of the buffer size and return success (retval=0). This forces execution
into decrypt_ciphertext() regardless of padding validity, so the attacker
cannot distinguish valid from invalid padding via timing. The AEAD tag
check in decrypt_ciphertext() will still reject the wrong CEK, but the
timing profile is identical for both branches.

RSA-OAEP variants are not affected (mathematically infeasible to craft
valid ciphertext without the private key).

Introduced by RSA1_5 path lacking constant-time fallback.

3 days agoBUG/MINOR: jwe: enforce GCM tag length to 128 bits
Remi Tricot-Le Breton [Tue, 26 May 2026 14:20:52 +0000 (16:20 +0200)] 
BUG/MINOR: jwe: enforce GCM tag length to 128 bits

Two fixes addressing cryptographic and parsing correctness issues:

1. Enforce 16-byte GCM authentication tag in decrypt_ciphertext()

   The base64url-decoded 5th JWE component (authentication tag) was passed
   directly to EVP_CTRL_AEAD_SET_TAG with its attacker-controlled length.
   OpenSSL accepts 1-16 byte GCM tags and only verifies that many bytes, so
   a 1-byte tag reduces forgery work factor to ~256. RFC 7518 mandates 128-bit
   (16 byte) tags for A*GCM. The CBC-HMAC path already enforced correct length,
   confirming this was an oversight.

   Fix: Add (*aead_tag)->data != 16 check before the GCM branch in
   decrypt_ciphertext(), rejecting any non-16-byte tag.

   Introduced by 416b87d5db (JWE A*GCM support).

2. Enforce 16-byte GCMKW tag in parse_jose() decode_jose_field()

   The $.tag field from the attacker-supplied protected header in A*GCMKW
   key-wrap was similarly decoded without length enforcement. Fix: Add a
   size != 16 check for fields named ".tag" in decode_jose_field() when
   called from the GCMKW path.

   Introduced by 026652a7eb (GCMKW tag field parsing).

3 days agoBUG/MINOR: quic: reject packet too short for HP decryption
Amaury Denoyelle [Tue, 26 May 2026 15:21:07 +0000 (17:21 +0200)] 
BUG/MINOR: quic: reject packet too short for HP decryption

Header protection can only be performed on a packet of a minimal size.
There was already a check for this in qc_do_rm_hp() but it did not use
the correct value.

Fix this by using the correct minimal size which is 20 bytes starting
from the packet number offset. This is enough to decrypt 4 bytes (PN max
size) and 16 bytes of IV. If the packet is not big enough, it is
still silently discarded.

This must be backported up to 2.6.

3 days agoBUG/MINOR: qmux: do not crash on frame parsing issue
Amaury Denoyelle [Tue, 26 May 2026 12:25:32 +0000 (14:25 +0200)] 
BUG/MINOR: qmux: do not crash on frame parsing issue

Ensure frame parsing error does not cause a crash by removing the
associated BUG_ON()/ABORT_NOW().

For now, connection is flagged on error, which ensures that any
send/receive future operations are prevented and connection is closed
asap. In the future, a proper CONNECTION_CLOSE will be required as
defined by QMux protocol.

No need to backport.

3 days agoBUG/MINOR: hlua: prevent Lua from passing CR/LF/NUL in HTTP headers
Willy Tarreau [Tue, 26 May 2026 11:49:49 +0000 (13:49 +0200)] 
BUG/MINOR: hlua: prevent Lua from passing CR/LF/NUL in HTTP headers

hlua_http_add_hdr() passes Lua string values directly to htx_add_header()
without validation. This can be an issue for user-controlled data, but as
well when relying on poorly written scripts. This patch makes sure that
neither the name nor the value may contain any of these forbidden chars.

This should be backported to all versions since the issue has been there
since at least 2.4.

3 days agoBUG/MINOR: h3: add missing break on rcv_buf()
Amaury Denoyelle [Tue, 26 May 2026 12:06:23 +0000 (14:06 +0200)] 
BUG/MINOR: h3: add missing break on rcv_buf()

The following patch ensures server MAX_PUSH_ID are rejected as a client.
This has been implemented by extending h3_rcv_buf().

  e4a5a64198bb084eaef2e71bfde65704a5db3931
  BUG/MINOR: h3: reject server MAX_PUSH_ID frame

Case label for MAX_PUSH_ID has been moved in the function, however the
break instruction was removed by error. Fix this by adding the missing
break statement.

This must be backported to every version the above fix is. Currently, it
is scheduled to 3.3.

3 days agoBUG/MEDIUM: auth: fix unconfigured password NULL deref
William Lallemand [Tue, 26 May 2026 12:08:38 +0000 (14:08 +0200)] 
BUG/MEDIUM: auth: fix unconfigured password NULL deref

Fix a case of dereference NULL pointer when trying to use an user from
an userlist which does not have a password configured.

The check_user() function tries to do an strcmp of the password, howver
u->pass is NULL and the strcmp would crash when trying.

Must be backported in every stable branches.

3 days agoBUG/MINOR: h3: reject server MAX_PUSH_ID frame
Amaury Denoyelle [Tue, 26 May 2026 08:36:06 +0000 (10:36 +0200)] 
BUG/MINOR: h3: reject server MAX_PUSH_ID frame

Previously, MAX_PUSH_ID frames were silently ignored both on client and
server sides. However, such frame cannot be emitted by the server.

This patch fixes this by properly issuing connection error
FRAME_UNEXPECTED when receiving a MAX_PUSH_ID frame as a client. This is
implemented by extending h3_check_frame_valid().

This must be backported up to 3.3.

3 days agoBUG/MINOR: h3: adjust error on PUSH_PROMISE frame reception
Amaury Denoyelle [Tue, 26 May 2026 08:53:48 +0000 (10:53 +0200)] 
BUG/MINOR: h3: adjust error on PUSH_PROMISE frame reception

HTTP/3 PUSH_PROMISE frames are systematically rejected with H3 error
FRAME_UNEXPECTED. This is adapted on the server side as a client can
never emit them.

This patch adapts error reporting when haproxy runs as a client. In this
case, server is still forbidden to emit any PUSH_PROMISE as MAX_PUSH_ID
frames are never emitted. In this case, ID_ERROR must be used as an
error code.

This must be backported up to 3.3.

3 days agoBUG/MINOR: h3: reject client CANCEL_PUSH frame
Amaury Denoyelle [Tue, 26 May 2026 09:22:16 +0000 (11:22 +0200)] 
BUG/MINOR: h3: reject client CANCEL_PUSH frame

CANCEL_PUSH frames are silently ignored on both client and server sides.
However, as push support is not implemented by haproxy, clients are thus
forbidden to emit any of those frames.

Fix this by closing the connection with ID_ERROR when receiving a client
CANCEL_PUSH as a server. On client side, the frame is still silently
discarded.

This must be backported up to 2.6.

3 days agoBUG/MINOR: h3: reject server push stream
Amaury Denoyelle [Tue, 26 May 2026 08:41:07 +0000 (10:41 +0200)] 
BUG/MINOR: h3: reject server push stream

Push streams are not supported by haproxy as a client. Thus, it never
emits any MAX_PUSH_ID frame. In this case, the server is not allowed to
initiate any push stream.

This patch ensures that such stream is closed with error H3_ID_ERROR, as
specified by HTTP/3 RFC.

This must be backported up to 3.3.

3 days agoBUG/MEDIUM: h3: reject client push stream
Amaury Denoyelle [Tue, 26 May 2026 08:25:54 +0000 (10:25 +0200)] 
BUG/MEDIUM: h3: reject client push stream

HTTP/3 push streams can only be opened by a server instance. The
specification mandates that the connection must be closed if a server
receives a client-initiated push stream.

This patch should ensure that it is not possible to exploit
unidirectional streams for an unexpected usage.

This must be backported up to 2.6.

3 days agoMEDIUM: tools: switch the main PRNG to a thread-local xoshiro256**
Willy Tarreau [Mon, 25 May 2026 13:38:49 +0000 (15:38 +0200)] 
MEDIUM: tools: switch the main PRNG to a thread-local xoshiro256**

The current PRNG is xoroshiro128**, it was introduced in 2.2 with
commit 52bf83939 ("BUG/MEDIUM: random: implement a thread-safe and
process-safe PRNG").  It features a 2^128 sequence and can perform
2^64 or 2^96 jumps, though only the 2^96 jump is implemented. It
was initially designed to support both processes and threads, and
implements a shared state between threads instead of allocating
distinct sequences based on PID and thread numbers.

Since then, the PRNG's usage grew and processes have disappeared,
but the lock or the DWCAS are still there due to its shared nature,
and it's possible to trigger watchdog warnings by issuing 100 UUIDs
in a single log-format string.

Also, UUID and QUIC retry tokens now consume 128 bits from the PRNG
in two 64-bit calls, and used to weaken the PRNG by rapidly disclosing
its internal state on reasonably idle systems. This indicates that
most of the time we now need 128 bits.

This patch modernizes the internal generator by switching to xoshiro256**,
which has comparable properties (it's even faster), and features even
longer 2^256 periods, still returning 64 bits per call. It can be
initialized with 2^128 and 2^192 jumps. More details here:

   https://prng.di.unimi.it/
   https://prng.di.unimi.it/xoshiro256starstar.c

Here we implement a thread-local state instead of the old shared one,
so there is no more need for synchronization. The state is seeded at
boot, and each thread performs as many 2^192 jumps as their TID is
large. The master process performs a 2^128 jump where it used to
perform a 2^96 jump so that it doesn't overlap with any worker thread.
However a cleaner approach could be to perform a 2^128 jump for each
fork() (here the worker) and 2^192 for each thread. This might be for
a future improvement.

ha_random64_internal() is now the new PRNG, so that everything else
remains totally transparent. _ha_random64_pair_hashed() continues to
hash the first 128 bits of the state.

A simple config generating 100 UUID on 20 threads jumps from 135k to
1.25M req/s, which translates to a bump from 13.5M to 125M UUID/s,
or 9 times faster. And there is no more DWCAS can be seen anymore
in perf top:

Before: 13.5M/s
Overhead  Shared Object            Symbol
  99.04%  haproxy       [.] ha_random64_internal
   0.66%  haproxy       [.] _ha_random64_pair_hashed
   0.03%  libc-2.42.so  [.] __printf_buffer
   0.02%  [kernel]      [k] _raw_spin_lock
   0.01%  libc-2.42.so  [.] __strchrnul_avx2
   0.01%  [kernel]      [k] ktime_get
   0.01%  [kernel]      [k] lapic_next_deadline
   0.01%  haproxy       [.] sample_process
   0.01%  haproxy       [.] chunk_printf
   0.01%  libc-2.42.so  [.] __printf_buffer_write
   0.01%  [kernel]      [k] hrtimer_active
   0.01%  libc-2.42.so  [.] __memmove_avx_unaligned_erms
   0.01%  libc-2.42.so  [.] _itoa_word

After: 125M/s
  18.84%  libc-2.42.so      [.] __printf_buffer
   9.84%  haproxy           [.] sample_process
   8.33%  libc-2.42.so      [.] __strchrnul_avx2
   6.61%  libc-2.42.so      [.] __memmove_avx_unaligned_erms
   6.06%  libc-2.42.so      [.] __printf_buffer_write
   4.43%  haproxy           [.] strlcpy2
   4.09%  libc-2.42.so      [.] _itoa_word
   2.62%  haproxy           [.] sess_build_logline_orig
   2.12%  haproxy           [.] _ha_random64_pair_hashed
   1.28%  haproxy           [.] pool_put_to_cache
   1.06%  haproxy           [.] __pool_alloc
   1.00%  haproxy           [.] smp_fetch_uuid
   0.93%  haproxy           [.] lf_text_len
   0.82%  haproxy           [.] ha_generate_uuid_v4

3 days agoMEDIUM: quic: use ha_random64_pair_hashed() to generate the QUIC retry tokens
Willy Tarreau [Mon, 25 May 2026 16:31:02 +0000 (18:31 +0200)] 
MEDIUM: quic: use ha_random64_pair_hashed() to generate the QUIC retry tokens

The QUIC retry tokens used to directly return ha_random64(), making the
next tokens easily predictable on low-load systems before the XXH64 call.
Let's now switch to the faster and safer ha_random64_pair_hashed() instead.

3 days agoMEDIUM: h1: use ha_random64_pair_hashed() for the WebSocket key
Willy Tarreau [Mon, 25 May 2026 16:23:30 +0000 (18:23 +0200)] 
MEDIUM: h1: use ha_random64_pair_hashed() for the WebSocket key

Instead of using two consecutive calls to ha_random64(), let's use the
cleaner and safer ha_random64_pair_hashed(). This way the internal
PRNG state will not leak into the emitted headers.

3 days agoMEDIUM: tools: use the hashed random pair for UUID generation
Willy Tarreau [Mon, 25 May 2026 15:56:01 +0000 (17:56 +0200)] 
MEDIUM: tools: use the hashed random pair for UUID generation

The UUID generation used to emit the internal PRNG state, which allows
to predict previous and next ones, or disclose the internal PRNG state.
While not critical, it may eventually become an issue.

This patch uses the new ha_random64_pair_hashed() function that returns
a pair of u64 that are hashed from the internal PRNG state. It's almost
twice as fast on 20 threads (14.1M UUID/s vs 7.8M/s).

3 days agoMEDIUM: init: fall back to ha_random64_pair_hashed() for the cluster secret
Willy Tarreau [Mon, 25 May 2026 16:20:29 +0000 (18:20 +0200)] 
MEDIUM: init: fall back to ha_random64_pair_hashed() for the cluster secret

The cluster secret, when SSL is not working, used to involve a mix of
calls to ha_random64() and random() to mask the bits that we didn't want
to see leaked. Let's now simply fall back to ha_random64_pair_hashed()
that does a much better job.

3 days agoMINOR: tools: provide a function to generate a hashed random pair
Willy Tarreau [Mon, 25 May 2026 15:30:58 +0000 (17:30 +0200)] 
MINOR: tools: provide a function to generate a hashed random pair

A lot of places call two ha_random64() in a row to generate a 128-bit
random. While it's now safe against linear analysis thanks to the XXH64
call, it's still particularly expensive due to the lock.

Here we introduce a new function ha_random64_pair_hashed(), that feeds
two uint64_t with a hash of the PRNG's internal state, and make it
advance. This will cut in half the number of calls to ha_random64()
and should recover a part of the performance lost in the lock. For
now it's not used.

3 days agoBUG/MEDIUM: tools: insert an XXH64 layer on the PRNG output
Willy Tarreau [Mon, 25 May 2026 15:30:58 +0000 (17:30 +0200)] 
BUG/MEDIUM: tools: insert an XXH64 layer on the PRNG output

Consuming randoms in pairs directly exposes the internal PRNG's state
on moderately idle system. It can allow to predict next (or previous)
UUIDs, QUIC retry tokens, and WS keys for example. Let's insert an XXH64
call on the ha_random64() output to avoid this. We expand the boot seed
as the secret at boot, and use now_ns as the seed for each call. The
original ha_random64() function was renamed to ha_random64_internal()
for use cases where it's not a problem to directly use the internal
state.

The performance loss is only measurable when single-threaded. It drops
from 7.32M UUID per second to 7.16M. Above that there is no longer any
difference due to the DWCAS loop which reaches up to 98.5% CPU at 20
threads.

This will need to be backported to stable releases after a period of
observation.

3 days agoBUG/MINOR: addons/51d: NUL-terminate headers before passing them to Trie API
Willy Tarreau [Tue, 26 May 2026 07:36:12 +0000 (09:36 +0200)] 
BUG/MINOR: addons/51d: NUL-terminate headers before passing them to Trie API

_51d_set_device_offsets() passes ctx.value.ptr directly to
fiftyoneDegreesGetDeviceOffset() which expects a null-terminated string.
Let's copy it through the trash first, to avoid possibly surronding
garbage.

This can be backported to all versions.

3 days agoBUG/MINOR: resolvers: switch to a better PRNG for query IDs
Willy Tarreau [Tue, 26 May 2026 07:30:12 +0000 (09:30 +0200)] 
BUG/MINOR: resolvers: switch to a better PRNG for query IDs

The PRNG used by the DNS currently is easily predictable once an
observer can collect a few consecutive IDs from the same thread, since
it's a 32-bit xorshift reduced to 16 bits output. Let's switch it to
ha_random32() instead.

This should be backported, however on older releases the ha_random32()
cost is higher due to the lock involved.

3 days agoBUG/MINOR: ssl-hello: make use of the null-terminated servername
Willy Tarreau [Tue, 26 May 2026 07:23:48 +0000 (09:23 +0200)] 
BUG/MINOR: ssl-hello: make use of the null-terminated servername

In ssl_sock_switchctx_cbk(), the servername is copied into the trash
and null-terminated, but later in the call to strncpy() it's still used
as-is, so anything that follows it will be copied as well, which is not
really expected. Let's make the servername point to the trash after
sanitizing it, like ssl_sock_switchcbk_wolfSSL_cbk() does.

This can be backported to 2.6 since it was introduced with commit
a996763619 ("BUG/MINOR: ssl: Store client SNI in SSL context in case
of ClientHello error").

3 days agoBUG/MINOR: payload: fix the handshake length bounds check smp_client_hello_parse()
Willy Tarreau [Tue, 26 May 2026 07:06:03 +0000 (09:06 +0200)] 
BUG/MINOR: payload: fix the handshake length bounds check smp_client_hello_parse()

After reading the handshake length, which is covered by the previous
4 bytes check, the size was not subtracted before being compared to the
retrieved handshake length, making it possible to accept a handshake
that claims to be 4 bytes larger than it really is. Similarly, a few
lines later, data[34] is accessed without checking that it is present,
because the test is made on the second hs_len, which doesn't guarantee
that the data are there.

This fix adds both tests. It can be backported to all stable versions
as it was introduced in 1.6 with commit bb2acf589f ("MINOR: payload:
add support for tls session ticket ext").

3 days agoBUG/MINOR: base64: return empty string for empty input in base64dec()
Willy Tarreau [Tue, 26 May 2026 06:54:15 +0000 (08:54 +0200)] 
BUG/MINOR: base64: return empty string for empty input in base64dec()

Right now no special case is made of size zero and the parser assumes
that it can read the last two chars, which do not exist in this case.
Let's check for this empty string situation and return zero (empty) as
well.

This should be backported to all versions.

3 days agoBUG/MINOR: http-ext: always check remaining data when reading rfc7239 nodeport
Willy Tarreau [Tue, 26 May 2026 06:47:11 +0000 (08:47 +0200)] 
BUG/MINOR: http-ext: always check remaining data when reading rfc7239 nodeport

http_7239_extract_nodeport() reads the first byte of the passed string
but the caller doesn't check that it's not empty, which can happen if
passed as 'host="127.0.0.1:"'. In that case the function would read and
return garbage that is present in the buffer after the colon. Let's just
check the remaining length before reading.

This can be backported to 2.8 as it was introduced with commit b2bb9257d2
("MINOR: proxy/http_ext: introduce proxy forwarded option").

3 days agoBUG/MEDIUM: acme: protect against risk of null-deref on connection failure
Willy Tarreau [Tue, 26 May 2026 06:36:36 +0000 (08:36 +0200)] 
BUG/MEDIUM: acme: protect against risk of null-deref on connection failure

7 ACME state handlers iterate over hc->res.hdrs, but they can be called
after an error was detected, and the HTTP client will leave res.hdrs NULL
on connection errors before headers are received. Let's check this inside
the loop, like the chkorder handler already does.

Most of them, if not all, need to be backported to 3.2.

3 days agoBUG/MINOR: http-fetch: check against the whole token in get_http_auth()
Willy Tarreau [Tue, 26 May 2026 06:28:33 +0000 (08:28 +0200)] 
BUG/MINOR: http-fetch: check against the whole token in get_http_auth()

In 1.4, Basic authentication support was added by commit f9423ae43a
("[MINOR] acl: add http_auth and http_auth_group"). Interestingly,
a mistake there consisted in taking the length of the comparison from
the input token, so "b" matches "Basic". It was later propagated to
Bearer in 2.5 with commit f5dd337b12 ("MINOR: http:
Add http_auth_bearer sample fetch"). Let's just compare the entire
tokens.

This may be backported though it is very minor.

3 days agoBUG/MINOR: sample: request an extra output byte for the url_dec converter
Willy Tarreau [Tue, 26 May 2026 06:24:04 +0000 (08:24 +0200)] 
BUG/MINOR: sample: request an extra output byte for the url_dec converter

A dynamic chunk size is now being allocated for output since commit
dfc4085413 ("MEDIUM: sample: Get chunks with a size dependent on input
data when necessary"). However this one missed the need for the trailing
zero when specifying the size, let's add it.

No backport is needed, this is only in 3.4.

3 days agoBUG/MINOR: resolvers: relax size checks in authority record parsing
Willy Tarreau [Fri, 22 May 2026 06:47:39 +0000 (06:47 +0000)] 
BUG/MINOR: resolvers: relax size checks in authority record parsing

Both boundary checks in the authority record parsing loop of
resolv_validate_dns_response() use >= bufend where they should use
> bufend, causing valid DNS responses with exactly enough bytes to be
rejected as invalid.

The first one, "reader + offset + 10 >= bufend" is too strict since it
prevents 10-byte responses from being accepted as valid while they
are. The second one, "reader + len >= bufend" has the same issue, when
exactly len bytes remain, the check rejects it even though dns_max_name()
already validated it. It may be backported though it is unlikely to ever
be noticed.

3 days agoBUG/MINOR: cache: also recognize directives in the form "token="
Willy Tarreau [Sun, 24 May 2026 19:52:54 +0000 (21:52 +0200)] 
BUG/MINOR: cache: also recognize directives in the form "token="

The caching RFC (9111, but was present since 2616) indicate that
cache-control supports both the "token" and "token=..." forms and that
consumers are supposed to recognize both. In addition, "private=..." is
explicitly mentioned, so servers could very well emit it. However,
haproxy only recognizes the short form without argument, except for
"no-cache" where it also supports it followed by the beginning of a
set-cookie argument. Thus it could miss "private=" or "no-store=".

Let's refine the checks. Now we explicitly recognize the form
no-cache="set-cookie", and all variants of "token" or "token=" as
identical to disable caching. It will more reliably catch such edge
cases and make sure we never cache a response marked like this.

This should be backported, at least to the latest LTS (3.2), maybe
further after some observation.

3 days agoBUG/MEDIUM: cache: always verify the primary hash in get_secondary_entry()
Willy Tarreau [Sun, 24 May 2026 16:48:52 +0000 (18:48 +0200)] 
BUG/MEDIUM: cache: always verify the primary hash in get_secondary_entry()

When checking for secondary entries, the tree is walked within duplicates
of the primary key, only indexed on the first 32 bits, which means that
in case of hash collision, we could start looking for an object and
switch to another one while visiting secondaries. In order to avoid this
we simply need to always check the full primary hash of the entry that
was found.

This should be backported to all stable versions.

3 days agoBUG/MEDIUM: h1: limit status codes to 3 digits by default
Willy Tarreau [Sun, 24 May 2026 17:58:52 +0000 (19:58 +0200)] 
BUG/MEDIUM: h1: limit status codes to 3 digits by default

By default, HTTP/1 status codes are not limited in the parser. However,
the value is stored in a 16-bit field, meaning that it may be truncated
if too large. Let's just restrict to 3-digits by default, and permit to
relax the check when accept-unsafe-violations is set, provided that the
value still fits in 16 bits.

This could be backported to latest LTS release.

3 days agoBUG/MEDIUM: h1: drop headers whose names contain invalid chars
Willy Tarreau [Sun, 24 May 2026 11:59:21 +0000 (13:59 +0200)] 
BUG/MEDIUM: h1: drop headers whose names contain invalid chars

Originally with "option accept-invalid-http-request", we couldn't really
edit the request on the fly to remove offending headers. But since we
have HTX and the headers are indexed one at a time, it has become
trivial. A non-negligible number of violations are conditioned by the
now renamed "option accept-unsafe-violations-in-http-request", and a
controversial one could definitely be reporting and passing invalid
header names containing control chars or spaces. The option was placed
so as not to block requests/responses containing them, but there's no
point in passing them to the other side. Most of the time it will be
totally harmless since the other side will reject them. But in case
haproxy is placed in front of a non-compliant server, it would fail
to protect it.

This patch implements a name check for all headers when a parsing
error was detected. It's cheap enough (especially since only done
after an error), and will skip the header if its name is invalid.
This may also remove some possibilities of confusion in logs, or
when encoding headers names for example.

This should be backported at least till the latest LTS.

3 days agoMINOR: haterm: do not emit a warning when not using SSL
Willy Tarreau [Tue, 26 May 2026 11:11:35 +0000 (13:11 +0200)] 
MINOR: haterm: do not emit a warning when not using SSL

Latest commit 04811943b5 ("MINOR: haterm: enable h3 for TCP bindings")
produces a warning when SSL is not enabled due to the addition of
expose-experimental-directives. Let's condition it to the use of SSL.

3 days agoMINOR: haterm: enable h3 for TCP bindings
Frederic Lecaille [Tue, 26 May 2026 08:38:20 +0000 (10:38 +0200)] 
MINOR: haterm: enable h3 for TCP bindings

Add "h3" as ALPN identifier to be supported by TCP "bind" lines. So, QMUX is
transparently enabled for such bindings.

4 days agoBUG/MINOR: haterm: fix the random suffix multiplication
Willy Tarreau [Mon, 25 May 2026 18:43:38 +0000 (20:43 +0200)] 
BUG/MINOR: haterm: fix the random suffix multiplication

Passing a size or anything with suffix "r" is supposed to apply a
random factor form 0 to 1. However due to the replacement of random()
with ha_random64(), all 64 bits are random before the divide, so the
end result is a random 32-bit value. In addition, ha_random64() is
slow since shared between threads.

Let's use statistical_prng() which is designed for this purpose and
is much cheaper. No backport is needed, this is only in 3.4.

4 days agoCLEANUP: resolvers: fix comment typos and wrong filenames in file headers
Willy Tarreau [Fri, 22 May 2026 06:34:31 +0000 (06:34 +0000)] 
CLEANUP: resolvers: fix comment typos and wrong filenames in file headers

A few asorted comment fixes for resolvers (incorrect file name etc).

4 days agoCLEANUP: resolvers: remove pool_free(NULL) in SRV additional record matching
Willy Tarreau [Fri, 22 May 2026 07:49:06 +0000 (07:49 +0000)] 
CLEANUP: resolvers: remove pool_free(NULL) in SRV additional record matching

In resolv_validate_dns_response(), when matching an additional A/AAAA
record to an SRV record, the code checked tmp_record->ar_item == NULL
then called pool_free(resolv_answer_item_pool, tmp_record->ar_item).
This is a copy-paste mistake from similar patterns elsewhere since
the pointer is confirmed to be NULL a few lines above, so let's just
drop the confusing pool_free.

4 days agoCLEANUP: resolvers: use read_n32() instead of open-coded big-endian read
Willy Tarreau [Fri, 22 May 2026 06:01:49 +0000 (06:01 +0000)] 
CLEANUP: resolvers: use read_n32() instead of open-coded big-endian read

In resolv_validate_dns_response(), the second DNS record parsing path
manually constructs a 32-bit big-endian TTL value from four individual
bytes using the expression:

  reader[0] * 16777216 + reader[1] * 65536 + reader[2] * 256 + reader[3]

We have read_n32() to do this, and it's more robust against unexpected
signedness surprises (which should not happen right here since reader is
unsigned char and we use -fwrapv so the result is defined). Also, let's
make the ttl an uint instead of an int. The TTL is only retrieved and not
used for now, so better clean it now.

4 days agoBUG/MINOR: sample: limit the be2hex converter's chunk size
Willy Tarreau [Sat, 23 May 2026 17:59:31 +0000 (19:59 +0200)] 
BUG/MINOR: sample: limit the be2hex converter's chunk size

In 2.5, commit da0264a96 ("MINOR: sample: Add be2hex converter")
introduced the be2hex() converter, which reads input data of a given
chunk size, processes it as a big endian block and turns it to hex
output.

There's an issue if the configured chunk_size (2nd argument) is larger
than tune.bufsize/2, because the max_size calculation will underflow,
and the later loop will always match since it compares a size_t to an
int (BTW, compilers love to annoy us with useless warnings but I never
found how to see some for these ones). This can result in overflowing
the output trash if  the input sample is at least as large as half a
buffer.

Let's add an explicit check for this, and change the max_size type to
size_t so that the comparison is always right. While we're at it, let's
ask the trash buffer to be twice as large, just like bin2hex() does, as
it may result in offering a larger buffer in 3.4. thanks to the large
buffers support.

Despite the risk, this is marked as minor because a config with that
large an argument in the converter makes absolutely no sense.

This should be backported to 2.6. The *2 for the trash allocation will
conflict and have to be dropped in stable versions, which is safe.

4 days agoBUG/MINOR: init: use more than ha_random64() for the cluster secret
Willy Tarreau [Sun, 24 May 2026 09:06:31 +0000 (11:06 +0200)] 
BUG/MINOR: init: use more than ha_random64() for the cluster secret

When not set, the cluster secret is randomly generated by two
consecutive calls to ha_random64(). However, the random64 PRNG may be
partially observed on a fully idle machine (QUIC retry tokens, UUID,
WS key), and it could be rolled back to the initial call that produced
the secret. This is purely theoretical as a normally loaded system
wouldn't reveal meaningful sequences, but better address this while
it's still easy.

The first here consists in isolating the cluster_secret from the PRNG
sequence. When RAND_bytes() is available and works, it's used. Otherwise
ha_random64() is mixed with uncorrelated bits from random().

This could be backported to stable releases.

4 days agoBUG/MINOR: dict: fix refcount race on insert collision
Willy Tarreau [Sat, 23 May 2026 20:17:10 +0000 (22:17 +0200)] 
BUG/MINOR: dict: fix refcount race on insert collision

In dict_insert(), when ebis_insert() returns an existing node n indicating
that another thread inserted the same key concurrently, the code freed its
own newly-allocated entry and returned the winner without bumping its
refcount. Both callers then held a reference with refcount=1 instead of 2,
so when one expires the other becomes a use-after-free or double-free.

The bug likely comes from the fact that new_dict_entry() creates an entry
with a refcount preset to 1 (saves an atomic op) and that because of this
there is no refcount increment upon a successful insertion in the tree,
resulting in requiring different code paths for collision and normal
insertion.

A simple fix consists in bumping the refcount under the lock and unlocking
only at the end, but this would mean performing two free() calls under a
lock, which we always try to avoid. The code was slightly rearranged so
that we can now bump the existing entry's refcount under the lock in case
of duplicate, or unlock immediately in the common case, so that the free()
call is done out of the lock.

The probably of the race is very low (at peers connection setup only),
reason why it's marked low. This should be backported to all versions.

4 days agoBUG/MINOR: log: look for the end of priority before the end of the buffer
Willy Tarreau [Sat, 23 May 2026 19:14:04 +0000 (21:14 +0200)] 
BUG/MINOR: log: look for the end of priority before the end of the buffer

In parse_log_message(), the first loop looks for '>' that finishes the
priority field, and unfortunately it stops once it has checked the first
byte after the end of the buffer. This means that a priority made only
of digits for the whole buffer would read one extra byte. In practice
since pools have a tag at the end this is only detectable when using ASAN,
but this should be fixed nevertheless.

This can be backported to all versions.

It's worth noting that RFC5424 now says that the PRI field is 1..3
digits only, so maybe at some point we could seriously limit the
length as well.

4 days agoBUG/MINOR: mux-h2: validate HEADERS frame length before reading stream dep
Willy Tarreau [Sat, 23 May 2026 17:42:43 +0000 (19:42 +0200)] 
BUG/MINOR: mux-h2: validate HEADERS frame length before reading stream dep

When the PRIORITY flag is present on a HEADERS frame, the frame must
contain a stream dependency and a weight, for a total of 5 bytes. The
length is checked after reading the stream dep field so theoretically
such a frame could cause up to 4-byte OOB read at the end of the buffer,
though in practice buffers allocated from pools never end on a page
boundary (one extra word at the end) and the anomaly is still detected
after reading the stream ID and the connection aborted with the glitch
count incremented. Thus while not technically correct, practically
speaking it's harmless.

This should be backported to all stable releases.

4 days agoBUG/MINOR: resolvers: fix risk of appending garbage past the domain name
Willy Tarreau [Sat, 23 May 2026 16:55:22 +0000 (18:55 +0200)] 
BUG/MINOR: resolvers: fix risk of appending garbage past the domain name

The previous fix 75f72c2eb ("BUG/MEDIUM: resolvers: Fix test on dn label
size in resolv_dn_label_to_str()") may still leave garbage from the input
buffer into the response: if a component length is passed as zero, it
should mark the end, but instead a dot will be emitted, and whatever
follows it in the input buffer would continue to be appended as extra
components. While having no direct consequences beyond the domain not
being properly decoded, it could at least complicate troubleshooting.

This should be backported where the fix above is backported.

4 days agoBUG/MINOR: resolvers: fix room for trailing zero in resolv_dn_label_to_str()
Willy Tarreau [Sat, 23 May 2026 16:53:27 +0000 (18:53 +0200)] 
BUG/MINOR: resolvers: fix room for trailing zero in resolv_dn_label_to_str()

The previous fix 75f72c2eb ("BUG/MEDIUM: resolvers: Fix test on dn label
size in resolv_dn_label_to_str()") can still be fooled by an input exactly
the size of str_len, in which case the trailing zero appended at the end
was not being accounted for. Let's add 1 to the condition to prepare for
it.

This needs to be backported wherever the fix above is backported.

4 days agoBUG/MINOR: log: free logformat expr on compile failure in cfg_parse_log_profile
Willy Tarreau [Fri, 22 May 2026 08:49:17 +0000 (08:49 +0000)] 
BUG/MINOR: log: free logformat expr on compile failure in cfg_parse_log_profile

When lf_expr_compile() fails in cfg_parse_log_profile, the code leaves
without freeing the previously strdup()'d strings in target_lf->str and
target_lf->conf.file. Let's add a call to lf_expr_deinit() there to
release it.

It was harmless anyway since the startup will abort when this happens,
but better clean it because with increasingly dynamic setups, one day
it could become a runtime leak.

No backport is needed.

4 days agoBUG/MEDIUM: cache: fix a refcount leak for missed secondary entries
Willy Tarreau [Sat, 23 May 2026 20:31:58 +0000 (22:31 +0200)] 
BUG/MEDIUM: cache: fix a refcount leak for missed secondary entries

When a primary cache hit has a Vary secondary_key_signature, the code calls
retain_entry() and shctx_row_detach() before performing the secondary lookup.
If get_secondary_entry() returns NULL (no stored variant matches), res is set
to NULL and the function falls through to return ACT_RET_CONT without calling
release_entry() or shctx_row_reattach(). Each such request leaks one refcount
and pins one shctx row permanently, eventually exhausting the cache if this
happens to all objects. This is visible when requesting a secondary key
covered by vary for an object that is already stored without that key.
"show cache" then shows the object's refcount increasing after each request.

In order to fix this we must do like when no secondary key could be built
and release everything. We only reattach to the row if we previously
detached.

The issue was introduced in 2.4 with commit 1785f3dd9 ("MEDIUM: cache: Add
the Vary header support"). The code changed a bit in 2.9 with commit
48f81ec09 ("MAJOR: cache: Delay cache entry delete in reserve_hot function"),
so in order to backport to 2.8 and older, the patch will have to be manually
applied (no test on detached).

4 days agoBUG/MEDIUM: tcpcheck/spoe: bound the SPOP error code to valid values
Willy Tarreau [Sun, 24 May 2026 16:14:50 +0000 (18:14 +0200)] 
BUG/MEDIUM: tcpcheck/spoe: bound the SPOP error code to valid values

tcpcheck_spop_expect_hello() stores the SPOA agent-supplied status-code
varint directly into check->code (signed short) without range validation.
The code is later used as an index into spop_err_reasons[100]. Let's
just replace invalid status codes with SPOP_ERR_UNKNOWN to avoid any
problem.

The SPOP tcp-check was introduced in 3.1 so this fix must be backported
to 3.2.

4 days agoBUG/MEDIUM: regex: allocate a large enough pcre2 match for all matches
Willy Tarreau [Sun, 24 May 2026 11:11:09 +0000 (13:11 +0200)] 
BUG/MEDIUM: regex: allocate a large enough pcre2 match for all matches

In 3.3 with commit fda6dc959 ("MINOR: regex: use a thread-local match
pointer for pcre2") we got a thread-local match that saves us from having
to allocate a match array with each match. However something was clearly
overlooked or misunderstood in the pcre2 API because the local match
array was initialized via pcre2_match_data_create() for MAX_MATCH-1
entries instead of MAX_MATCH, despite the commit message mentioning
MAX_MATCH entries. It was possibly confused with an index. Due to this
there is a risk of crash when matching more than 9 groups in a regex.

This fix must be backported to 3.3.

4 days agoBUG/MEDIUM: log-forward: make sure the month is unsigned
Willy Tarreau [Sat, 23 May 2026 18:17:16 +0000 (20:17 +0200)] 
BUG/MEDIUM: log-forward: make sure the month is unsigned

In 2.3, in preparation for log forwarding, commit 546488559 ("MEDIUM:
log/sink: re-work and merge of build message API.") extended the log
send API to be able to use metadata from an existing header. However
the month number is parsed from the passed meta-data and compared
against 11 but there's no check for negative values which could in
theory cause a negative monthname[] index.

It can be a problem when the date is received as RFC5424 and forced
to RFC3164 because certain characters in the month field could result
in a negative month value. Let's fix it by turning the month to unsigned
to make sure we only accept months 0..11.

This should be backported to all branches.

4 days agoBUILD: intops: mask the fail value in array_size_or_fail()
Willy Tarreau [Mon, 25 May 2026 05:23:49 +0000 (07:23 +0200)] 
BUILD: intops: mask the fail value in array_size_or_fail()

Cross-compilation on m68k fails in ssl_sock_resize_passphrase_cache()
where the compiler noticed the SIZE_MAX passed to realloc() in the
error path and complained that it's larger than PTRDIFF_MAX. This can
be disabled with -Walloc-size-larger-than=SIZE_MAX but in practice we
can simply hide the value and keep the warning to detect real failures
elsewhere. Let's pass it through DISGUISE() and also take this
opportunity for doing that inside an unlikely() clause since it's never
supposed to happen.

6 days agoBUG/MEDIUM: acme: NUL terminate response buffer before PEM parsing
CyberpsychoJacob [Fri, 15 May 2026 07:27:15 +0000 (11:27 +0400)] 
BUG/MEDIUM: acme: NUL terminate response buffer before PEM parsing

acme_res_certificate() passes the httpclient response buffer to
ssl_sock_load_pem_into_ckch(), which will then call BIO_new_mem_buf(buf, -1).
The "-1" flag will make the OpenSSL PEM parser determine the length by
using strlen(). However, the httpclient populates the response buffer with
__b_putblk() without writing a trailing NUL to it. The byte at area[data]
is whatever data previously resided there in the memory pool.

Thus, a malicious or compromised ACME CA can perform an arbitrary-length
out-of-bounds read until hitting the first NULL byte past the response
body. The OpenSSL PEM loader will try to iterate to load the chain
certificates, thus the PEM-looking garbage found in freed memory chunks
can be erroneously loaded as additional intermediate certificates. The
presence of a single NUL inside the valid response body will result in
silent truncation of the certificate.

Make sure that the area[data] contains a terminating NULL before passing
the buffer to the parser. Fail on insufficient room for the NUL terminator.

No backport required: The ACME client has been added in 3.x and this
code path didn't exist in 2.x.

7 days agoBUG/MEDIUM: cli: Fix parsing of pattern finishing a command payload
Christopher Faulet [Fri, 22 May 2026 15:10:28 +0000 (17:10 +0200)] 
BUG/MEDIUM: cli: Fix parsing of pattern finishing a command payload

When the dedidacted buffer to store the command payload was added (c5ae0da62
"MEDIUM: cli: Make a buffer for the command payload"), an bug was
introduced. When the pattern finishing the command payload is found, it is
removed from the buffer. A NULL-bytes is added before it, skipping the
previous newline character.

It worked well in all cases before the commit above, because the commandline
was already parsed and was placed at the beginning of the cmdline buffer.
So, there is always a line before the payload.

Now, the payload is stored in a dedicated buffer. So there is nothing
preceeding it in a buffer. If the payload is empty, we cannot rewind to the
previous line to set the NULL-byte character. We must handle this case to
avoid integer underflow on the payload buffer length.

It is a 3.4-specific bug. No backport needed.

7 days agoBUG/MEDIUM: hlua: Fix integer underflow when receiving line from lua cosocket
Christopher Faulet [Fri, 22 May 2026 14:11:52 +0000 (16:11 +0200)] 
BUG/MEDIUM: hlua: Fix integer underflow when receiving line from lua cosocket

In hlua_socket_receive_yield(), when we try to get a line, the trailing CRLF is
stripped by decrementing the block length. The '\n' is first skipped, then,
possible a preceeding '\r'. But the block lenght is never checked. If an empty
line is returned, this leads to an integer underflow and most probably to a
crash because this length is used to copy data into a LUA string.

To fix the issue, the block length is now properly tested against 0 before
decrementing it.

This patch must be backported to all stable versions.

7 days agoBUG/MINOR: tcpchecks: Limit parsing of agent-check reply to the buffer
Christopher Faulet [Fri, 22 May 2026 13:22:28 +0000 (15:22 +0200)] 
BUG/MINOR: tcpchecks: Limit parsing of agent-check reply to the buffer

When parsing the agent-check reply, we first loop on the response to find
the newline character, to add a NULL-byte at the end of the line. However,
this loop is not bounded to the data available in the buffer. So it is
possible to read bytes outside the buffer and eventually write a NULL-byte
ouside the buffer.

So let's check for the end of the buffer when looping on the agent-check
reply.

This patch must be backported to all stable versions.

7 days agoBUG/MEDIUM: dict: hold lock while decrementing refcount in dict_entry_unref
Christopher Faulet [Fri, 22 May 2026 12:08:27 +0000 (14:08 +0200)] 
BUG/MEDIUM: dict: hold lock while decrementing refcount in dict_entry_unref

In dict_entry_unref(), the write lock on d->rwlock was only acquired after
decrementing the refcount. However, between the decrement and the lock,
another thread could increment it by calling dict_insert(). That could lead
to a UAF.

To fix the issue, the call to HA_ATOMIC_SUB_FETCH is moved inside the write
lock.

This patch must be backported to all stable versions.

7 days agoBUG/MINOR: quic: fix ODCID lookup from derived value
Amaury Denoyelle [Fri, 22 May 2026 13:49:44 +0000 (15:49 +0200)] 
BUG/MINOR: quic: fix ODCID lookup from derived value

In haproxy, when an Initial packet is received, a new connection may be
created and a DCID must be attributed. This CID is derived from the
original DCID used by the client in its first packet. This is an
optimization to avoid storing two CIDs values in the CID tree.

On CID lookup, if the DCID used is not found, derivation is performed
again. This should permit to retrieve the DCID node. However, this
operation is not performed as expected in quic_get_cid_tid(), as the
wrong value is used on the second lookup. Fix this function by using
derive CID for it. Note that retrieve_qc_conn_from_cid() performs the
same lookup but the bug was not present there.

The impact of this bug is relatively low as most clients send a single
Initial packet. Even in case of multiple packets in a single datagram,
this does not cause any issue as the current thread is assigned as
default.

This should be backported up to 2.8.

7 days agoBUG/MEDIUM: ssl-gencert: Unlock LRU cache if failing to generate certificate
Christopher Faulet [Fri, 22 May 2026 09:36:59 +0000 (11:36 +0200)] 
BUG/MEDIUM: ssl-gencert: Unlock LRU cache if failing to generate certificate

In ssl_sock_generate_certificate(), if the LRU cache for generated
certificates is used, the LRU tree is not unlocked on cache miss if the
certificate generation failed. So let's unlock it on error path.

The bug was introduced by the commit fbc98ebcd ("BUG/MEDIUM: ssl: fix error
path on generate-certificates"). So this patch must be backported with the
commit above, so to all stable versions.

7 days agoBUG/MEDIUM: resolvers: Fix test on dn label size in resolv_dn_label_to_str()
Christopher Faulet [Fri, 22 May 2026 09:13:30 +0000 (11:13 +0200)] 
BUG/MEDIUM: resolvers: Fix test on dn label size in resolv_dn_label_to_str()

In resolv_dn_label_to_str(), size for a dn label was stored into an integer
from a signed char without a cast to unsigned. So dn label with a size of
128 bytes or more become negative, skipping this way the copy loop and
desynchronizing input vs output.

In addition, the size of the destination string was only checked at the
begining, against the dn string length. But it must also be checked for
every dn label, to be sure. The dn string can be forged to copied more bytes
than expected.

This patch must be backported to all stable versions.

7 days agoBUG/MEDIUM: applet: Properly handle receives of size 0
Christopher Faulet [Thu, 21 May 2026 14:16:04 +0000 (16:16 +0200)] 
BUG/MEDIUM: applet: Properly handle receives of size 0

when appctx_rcv_buf() function was called to get data from the applet, but
to get zero bytes, nothing was performed and the function early
returned. However, we must at least take care to set SE_FL_WANT_ROOM if
necessary. Otherwise, if data are still blocked in the applet's output
buffer while the EOI/EOS are pending, the information can be reported to the
upper layer and remaining data can be lost.

Indeed, in such case, SE_FL_WANT_ROOM flag is here to specify the applet has
more data to deliver. Thanks to this flag, the stream will wait before
closing. But when appctx_rcv_buf() function is called, this flag is removed by
the stconn. It is the function responsibility to set it again when necessary.

This patch should fix second part of the issue #3366. It must be backported
to 3.0.

8 days agoMINOR: mux_quic: do not crash on unhandled QMux frame reception
Amaury Denoyelle [Thu, 21 May 2026 13:51:34 +0000 (15:51 +0200)] 
MINOR: mux_quic: do not crash on unhandled QMux frame reception

Completes qmux_parse_frm() to ensure every frames allowed by QMux
protocol are listed. For now, nothing is implemented except a CHECK_IF()
to report such events.

This is necessary to prevent a crash on abort. Frames not supported by
QMux should already have been rejected prior via qmux_is_frm_valid().

8 days agoMINOR: mux_quic: handle MAX_STREAMS for uni stream in QMux
Amaury Denoyelle [Thu, 21 May 2026 13:50:06 +0000 (15:50 +0200)] 
MINOR: mux_quic: handle MAX_STREAMS for uni stream in QMux

Handle reception of a MAX_STREAMS frame for unidirectional stream usage
when using QMux. This simply consists in using qcc_recv_max_streams() as
with QUIC protocol.

8 days agoMINOR: mux_quic: handle STOP_SENDING in QMux
Amaury Denoyelle [Thu, 21 May 2026 13:48:31 +0000 (15:48 +0200)] 
MINOR: mux_quic: handle STOP_SENDING in QMux

Ensure reception of STOP_SENDING via QMux protocol is properly handled.
This simply consists in using qcc_recv_stop_sending() which will update
the associated QCS if found.

8 days agoBUG/MINOR: ocsp: Manage date too far away in the future
Remi Tricot-Le Breton [Thu, 21 May 2026 13:18:06 +0000 (15:18 +0200)] 
BUG/MINOR: ocsp: Manage date too far away in the future

The check on the OCSP response expire time is based on the "Next Update"
field of the response, converted by my_timegm function that returns a
time_t (signed long). It is then stored in the 'expire' field of the
certificate_ocsp structure which is typed as a signed long.
When loading an OCSP response, if the "Next Update" time is too far in
the future and we are running on a 32 bits machine, we might end up with
negative times ireturned by my_timegm, which make the comparison with
the current date fail and raises the "OCSP single response: no longer
valid." error message.

This problem typically happens in the ocsp_auto_update.vtc regtest since
the loaded OCSP response have a "Next Update" field in 2050.

This patch simply changes the type of the expire field to an unsigned
long since the 'my_timegm' function does not return '-1' in case of
error, contrary to the standard 'timegm' one.

Ths patch can be backported to all stable branches.