Compare commits

..

197 commits

Author SHA1 Message Date
Olivier Houchard
3c923d075c MEDIUM: servers: Move to a per-thread idle connection cleanup task
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
Having a single task to take care of idle connection cleanup across all
servers leads to high contention. It uses a lock to maintain its tree of
servers to track, and then can acquire the idle_conns lock for each thread.
Instead, have one task per thread. Each thread will maintain its own
tree, so there will be no need for any lock, and it will just acquire
its own idle_conns lock, so it will lead to less contention.
This is a performance improvement, so backporting is optional, but may be
considered if it is worth it. That would require backporting commit
6f8dab2583 too.
2026-06-08 15:38:22 +02:00
Olivier Houchard
6f8dab2583 MINOR: servers: Add a back-pointer to the server in srv_per_thread
In struct srv_per_thread, add a pointer to the server, as with just a
pointer to srv_per_thread, we can't figure out the related server.
2026-06-08 15:37:50 +02:00
Olivier Houchard
a4520229a7 BUG/MEDIUM: checks: Dequeue checks on purge
When tune.max-checks-per-thread is used, checks that should run are
queued, to avoid having too many checks running at the same time.
But if the check is about to be purged, because the server is being
deleted, we have to explicitly remove it from the queue as that memory is
about to be freed, otherwise it will cause a use-after-free.
Also, queued checks have not yet incremented th_ctx->running_checks, so
don't decrement it if we're queued.

This should be backported up to 3.0.
2026-06-08 15:06:09 +02:00
Willy Tarreau
3fa818c78f MINOR: memprof: be careful to account allocations only once
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
For certain calls like strdup(), certain libc call the malloc() symbol
themselves, resulting in both strdup() and malloc() accounting for the
allocation while a single free() call is accounted for. Usually it's
not very hard to spot as these allocations are done inside libc, but
yet they complicate the tracing of allocations.

Let's note when we enter a handler and refrain from doing the accounting
again in this case. This way, the strdup() call place will be accountable
for the allocation and the libc's internal malloc() will not be seen.
2026-06-08 13:46:18 +02:00
Willy Tarreau
a7888f0373 MINOR: memprof: make in_memprof a bitfield instead of a counter
It's not convenient to use it as it is now because it may only be
used to count passes via the memprof init code. Let's turn it to
a bitfield instead so that we can also check what we're doing there.
This is safe because all callers of memprof_init() check for the
bit being zero first so it's not reentrant.
2026-06-08 13:46:18 +02:00
Willy Tarreau
ef191c46d7 BUG/MINOR: acl: report "ACL" not "map" in ACL ID lookup failures
As reported by @broxio in issue #3411, when trying to delete an ACL by
its name, in case of error the message says "unknown map identifier".
We need to check the type to decide between map and ACL as in other
messages.

This can be backported to all stable branches. Thanks to @broxio for
reporting the issue with a reproducer and providing this tested fix.
2026-06-08 13:45:39 +02:00
Willy Tarreau
b9fa07bd20 MINOR: pools: reject creation of pools containing invalid chars in their name
In order to preventively avoid issues that complicate debugging, let's
report to developers early if a pool name is not acceptable. This patch
does it in create_pool_from_reg() which catches both direct and declared
registrations. Aside the previous case, this didn't catch any other
occurrence.
2026-06-08 08:54:37 +02:00
Willy Tarreau
172306c308 CLEANUP: sessions: simplify the sess_priv_conns pool name
Using "show pools detailed" on the CLI breaks the column alignment on
"sess_priv_conns" because the pool name contains spaces: "session priv
conns list", which is not welcome as pool names are truncated after the
12th chars anyway. Let's shorten it to the pool's name as done for many
other ones: sess_priv_conns.

This can be backported as far as 3.0 where this name was introduced,
because it helps when trying to sum or graph certain metrics during
debugging.
2026-06-08 08:44:25 +02:00
Willy Tarreau
e51ae5ce66 BUG/MEDIUM: xprt_qmux: implement ->get_ssl_sock_ctx() to get the SSL laye
conn_get_ssl_sock_ctx() retrieves the ssl_sock_ctx of a connection by
calling conn->xprt->get_ssl_sock_ctx(). Only ssl_sock implements this
method, and it returns conn->xprt_ctx. This works because for every
existing XPRT combination the SSL layer is the topmost one: even
xprt_handshake (SOCKS4, PROXY, NetScaler CIP) is installed *below*
ssl_sock, so conn->xprt keeps pointing to ssl_sock.

Qmux changes this assumption: xprt_qmux is stacked *on top of* ssl_sock
and keeps the SSL layer as its lower layer to exchange the QUIC transport
parameters over the established TLS stream. During the qmux handshake,
conn->xprt therefore points to xprt_qmux, which does not implement
get_ssl_sock_ctx(), making conn_get_ssl_sock_ctx() return NULL for the
whole connection, affecting every caller that inspects the SSL layer
(sample fetches, logging, ssl_sock_infocbk(), ...).

The visible consequence was a crash: when the peer sends a TLS alert
during the qmux handshake, the SSL library calls ssl_sock_infocbk(),
which recovers a valid connection but a NULL ctx, rightfully triggering
the "BUG_ON(!ctx)" early in the function.

This patch implements xprt_qmux_get_ssl_sock_ctx() so that it returns
the ssl_sock_ctx of the lower layer when it is the SSL layer, just like
ssl_sock_get_ctx() does. conn_get_ssl_sock_ctx() then works again for
all callers while the qmux handshake is in progress. After the handshake,
conn->xprt is restored to the SSL layer so nothing else changes.

This should be backported to 3.4.
2026-06-08 08:31:20 +02:00
Olivier Houchard
45a64123d6 BUG/MEDIUM: threads: Fiw build when using no thread
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
In thread_detect_count(), avoid any usage of thread_cpu_enable_at_boot
if we're building without thread support. That variable is only defined
when building with threads, and those tests make little sense when
building with no thread, anyway.
This was submitted by: ririnto <ririnto@kakao.com>
This should fix github issue #3408.
This should be backported to 3.4.
2026-06-08 01:16:49 +02:00
Willy Tarreau
ac776e3819 BUG/MEDIUM: regex: initialize the match array earlier during boot
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
As reported by @zhanhb in github issue #3410, since 3.3 with commit
fda6dc959 ("MINOR: regex: use a thread-local match pointer for pcre2"),
the local_pcre2_match array is initialized too late for use by Lua. If
a lua-load makes use of regex, it may segfault (actually using PCRE2
is fine but PCRE2_JIT will crash):

Let's change the init sequence so that the first thread's context is
initialized early at boot and other threads are initialized when they
are created. For lua-load-per-thread, all extra threads will run on
the first thread's temporary storage during init but that's not a
problem since the sole purpose is to avoid concurrent accesses.

Thanks to @zhanbb for the detailed report and quick tests. This needs
to be backported to 3.3.
2026-06-07 07:46:32 +02:00
Christopher Faulet
1e00743520 REGTESTS: checks: Add script for external healthchecks
Some checks failed
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
FreeBSD / clang (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
This script is quite basic but it should validate the external healthchecks
are working well.
2026-06-05 17:15:31 +02:00
Christopher Faulet
b227ad2dc7 BUG/MINOR: tcpcheck: Override external check if healthcheck section is set
When an external check was configured at the proxy level, the healthcheck
section set on a server was not considered. The main reason was that the
check type of the server was always inherited for the proxy one.

To fix the issue, when a healthcheck section is set on a server line, the
check type for the server is forced to TCPCHK.

This patch must be backported to 3.4.
2026-06-05 17:15:31 +02:00
Amaury Denoyelle
07deafa104 BUG/MINOR: mux_quic: do not interrupt recv on error/incomplete data
Some checks failed
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
Prior to this patch, qcc_io_recv() stream decoding loop was interrupted
on the first decoding error or if incomplete data could not be parsed.

This patch adjusts this part so that loop is stopped only on a
connection level error. In case of a stream level error or on incomplete
data, decoding continues on the next QCS entry.

Without this patch, there is a risk that a QCS decode is not performed
as expected, with a possible client timeout firing. This is pretty
unlikely though. However this patch is still necessary to remove
completely this possibility.

This should be backported up to 3.2.
2026-06-05 16:27:10 +02:00
Amaury Denoyelle
a39b1a40ad OPTIM: mux_quic: remove QCS from recv_list on reset
When a RESET_STREAM is received, QCS Rx channel is closed and pending Rx
data and buf are cleared without being transmitted to upper stream
layer.

This patch complements this by removing the QCS from recv_list if
present in it. This is a small optimization nothing would be performed
for such QCS on qcc_io_recv().
2026-06-05 15:42:44 +02:00
Amaury Denoyelle
83ae0c250c BUG/MEDIUM: mux_quic: prevent risk of infinite loop on recv
When a RESET_STREAM is received, QCS Rx channel is closed and pending Rx
data and buf are cleared without being transmitted to upper stream
layer.

This can cause an issue if this QCS instance is present in the QCC
recv_list. When qcc_io_recv() is executed after reset handling, an
infinite loop is triggered for the QCS instance as qcs_rx_avail_data()
always return 0.

This issue happened due to the poor writing of the while loop in
qcc_io_recv() which is not correctly protected against infinite
execution.

To prevent this issue, this patch rewrites the loop. Crucially,
LIST_DEL_INIT() is now performed unconditionally outside of the inner
loop. This guarantees that even if the inner loop is not executed, the
stream will be removed from QCC recv_list and iteration will progress.

This is functionally correct as a QCS should not be present in recv_list
if there is no avail data or demux is currently blocked. For the first
condition, qcc_decode_qcs() will be called again when new data is read
unless demux is blocked. In this case, QCS will be reinserted in the
list on unblocking, with a rescheduling to invoke qcc_decode_qcs().

In the context of the currently found reproducer linked to stream reset,
the QCS instance can be safely removed from the recv_list without
implication.

This must be backported up to 3.2.
2026-06-05 15:32:55 +02:00
Christopher Faulet
f7bc8246ee BUG/MEDIUM: server/checks: Support healtcheck keyword on default-server lines
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
The healthcheck keyword could be parsed on default-server lines but not
copied during server initialization, making it ineffective. But there is
also a true issue by setting it on a default-server. The pseudo server used
to parse the default-server line is not initialized via the new_server()
function, as regular servers. So there is no tcpcheck information inherited
from the proxy. We must take care of that when the "healthcheck" keyword is
parsed to avoid crashes.

This patch must be backported to 3.4.
2026-06-04 21:53:32 +02:00
Christopher Faulet
3daf4498f3 MINOR: check: Don't dump buffers state in check traces for external checks
In healthcheck trace messages, there is no reason to dump the in/out buffers
state for external checks. So let's skip this part in that case.
2026-06-04 21:50:12 +02:00
Christopher Faulet
4b9c8b24c5 BUG/MEDIUM: check: Ignore small-buffer option when starting an external check
When an external check is started for a server, there is no tcpcheck
ruleset. The pointer is NULL. It was an issue leading to a crash if the
small-buffer option was enabled on the healthchecks. However, it is
irrelevant for external checks because it is only usefull to tcp checks.

So, the option must be ignored if there is no tcpcheck ruleset.

This patch must be backported to 3.4.
2026-06-04 19:19:02 +02:00
Christopher Faulet
6a7b27a0a4 BUG/MEDIUM: check: Skip tcpcheck post-config for external checks
When an external check was configured on a backend, the tcpcheck post config
for backend's servers was still performed instead to be skipped. The led to
a NULL-deref on the tcpcheck ruleset pointer and so to a segfault.

It seems to be only an issue for the 3.4 and higher. However, for older
versions, the tcpcheck post-config is still performed for external checks
and it is not really clean. This can hide some bugs.

For the 3.4, a workaround consists in configuring the backend to use a
tcp-check before configuring the external check:

  backend be
    option tcp-check
    option external-check
    ...

This patch should fix the issue #3407. It could be good to backport it to
all supported versions.
2026-06-04 18:52:25 +02:00
Willy Tarreau
7835e1fcbe [RELEASE] Released version 3.5-dev0
Some checks failed
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
FreeBSD / clang (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
Released version 3.5-dev0 with the following main changes :
    - MINOR: version: mention that it's development again
2026-06-03 15:26:45 +02:00
Willy Tarreau
02f0101cde MINOR: version: mention that it's development again
This essentially reverts 1cf7dc07e9.
2026-06-03 15:25:53 +02:00
Willy Tarreau
64a335366d [RELEASE] Released version 3.4.0
Released version 3.4.0 with the following main changes :
    - BUG/MINOR: tcpcheck: Check LDAP response to not read more data than available
    - BUG/MINOR: ssl-gencert: validate SNI characters to prevent SAN certificate injection
    - BUG/MINOR: mux-h1: H2 preface rejection doesn't update stick-table glitches
    - BUG/MEDIUM: cpu-topo: Enforce thread-hard-limit on policy
    - BUG/MEDIUM: qmux: do not crash on too large record
    - BUG/MEDIUM: qmux: do not crash on receiving an invalid first frame
    - BUG/MINOR: qmux: reject too large initial record
    - Revert "BUG/MEDIUM: dns: fix long loops in additional records parse on name failure"
    - BUG/MINOR: qpack: Fix index calculation in debug functions
    - BUG/MINOR: qpack: fix potential null-pointer dereference in qpack_dht_insert()
    - CLEANUP: qpack: fix copy-paste typo in value Huffman debug string
    - BUG/MINOR: qpack: fix sign bit mask in qpack_decode_fs_pfx()
    - CLEANUP: qpack: fix copy-paste typo in value Huffman debug string for WLN
    - BUG/MINOR: qpack: fix huff_dec() error handling in qpack_decode_fs()
    - CLEANUP: qpack: move encoded macros to qpack-t.h to avoid duplication
    - BUG/MEDIUM: quic: handle ECONNREFUSED on RX side
    - BUG/MINOR: quic: Fix memory leak in quic_deallocate_dghdlrs()
    - BUG/MEDIUM: lua: defer Lua VM initialisation to the first Lua config keyword
    - REGTESTS: lua: fix tune.lua.openlibs in Lua reg-tests
    - BUG/MINOR: mux-h2: Count padding for connection flow control on error path
    - BUILD: addons: convert 51d addon to EXTRA_MAKE
    - BUILD: addons: convert deviceatlas addon to EXTRA_MAKE
    - BUILD: addons: convert WURFL addon to EXTRA_MAKE
    - MINOR: mux_quic/flags: add missing flags
    - BUG/MINOR: mux_quic: open an idle QCS on reset on BE side
    - BUG/MINOR: mux_quic: fix BE conn removal on app shutdown
    - BUG/MINOR: mux_quic: prevent BE reuse with an errored conn
    - BUG/MINOR: quic: fix ack range node pool_free call passing wrong pointer type
    - MEDIUM: quic: optimize HKDF operations by reusing per-thread contexts
    - BUG/MEDIUM: quic: reset cwnd in slow_start on persistent congestion (cubic)
    - BUG/MEDIUM: quic: reset consecutive_losses on exit from recovery period (cubic)
    - BUG/MINOR: quic: update drs->lost before calling on_ack_recv
    - Revert "MEDIUM: quic: optimize HKDF operations by reusing per-thread contexts"
    - BUG/MEDIUM: lua: register hlua_init() as a pre-check to fix crash without Lua config
    - REGTESTS: quic: disable quic/ocsp_auto_update for now
    - BUG/MINOR: threads: set at least grp_max when mtpg is too small
    - BUG/MEDIUM: threads: ignore max-threads-per-group when thread-groups is set
    - CLEANUP: thread: indicate when max-threads-per-group is ignored
    - MINOR: cpu-topo: notify when cpu-policy is ignored due to other settings
    - MINOR: thread: report when thread-groups or nbthread results in less threads
    - BUILD: makefile: include EXTRA_MAKE in the .build_opts construction
    - BUG/MINOR: quic: Fix another buffer overflow with sockaddr_in46
    - MINOR: quic: Copy sin6_flowinfo and sin6_scope_id too
    - BUILD: Makefile: put EXTRA_MAKE help at the right place
    - BUG/MINOR: cache: fix cache tree iteration
    - BUG/MEDIUM: resolvers: Wait a bit before calling the xprt prepare_srv
    - CLEANUP: addons/51degrees: initialize variables
    - MINOR: addons/51degrees: handle memory allocation failures
    - CLEANUP: ncbmbuf: improve handling of memory allocation errors in unit tests
    - CLEANUP: admin/halog: improve handling of memory allocation errors
    - DOC: internals: clarify ambiguous wording in core-principles
    - DOC: internals: add a threat model definition
    - DOC: add security.txt describing how to report security issues
    - DOC: security: also add a note to exclude dev/ and admin/
    - BUG/MEDIUM: qmux: Close connection on invalid frame
    - CLEANUP: fix comment typo
    - BUG/MEDIUM: h3: fix MAX_PUSH_ID handling
    - BUG/MINOR: cache: Fix copy of value when parsing maxage
    - BUG/MEDIUM: mux-h1: Dup connection/upgrade value to parse it when making headers
    - BUG/MEDIUM: htx: Fix headers rollback on partial copy in htx_xfer()
    - MINOR: deinit: release the in-memory copy of shared libs
    - MINOR: debug: add -dA to dump an archive of all dependencies
    - BUG/MEDIUM: ssl: Make sure the alpn length is small enough
    - BUG/MINOR: applet: Commit changes into input buffer after sending HTX data
    - BUG/MINOR: mux-spop: Fix possible off-by-one OOB read in spop_get_varint()
    - BUG/MEDIUM: leastconn: Unlock the write lock on allocation failure
    - BUG/MINOR: tasks: Increase the right niced_task counter
    - BUILD: makefile: search for Lua 5.5 as well
    - DEV: dev/gdb: improve ebtree pointer handling
    - DEV: dev/gdb: add simple task dump
    - DEV: dev/gdb: add simple thread dump
    - DEV: dev/gdb: add fdtab dump
    - DOC: config: add a few more explanation in http-reusee regarding sni-auto
    - REGTESTS: add basic QMux tests
    - BUG/MINOR: http-act: Properly handle final evaluation in pause action
    - BUILD: makefile/lua: use the system's default library before all other variants
    - BUG/MINOR: startup: unbreak chroot with CAP_SYS_CHROOT
    - BUG/MINOR: haterm: do not try to bind QUIC when not supported
    - BUG/MINOR: haterm: also apply the tcp-bind-opts to clear TCP "bind" lines
    - CLEANUP: haterm: do not try to bind to SSL when not built in
    - MINOR: haterm: enable ktls on the SSL bind line when supported
    - CI: github: replace cirrus by a vmactions/freebsd-vm job
    - BUILD: makefile: fix build error with GNU make 4.2.1 and /bin/dash
    - BUG/MEDIUM: channel: Fix condition to know if a channel may send
    - BUG/MEDIUM: vars: Properly eval set-var-fmt action for emtpy log-format string
    - CI: github: run illumos job weekly on Mondays at 03:00 instead of monthly
    - BUG/MEDIUM: stream: Don't use small buffer on queuing with a request data filter
    - BUG/MINOR: jwe: don't write randoms past MAX_DECRYPTED_CEK_LEN in RSA_PKCS1_PADDING
    - BUG/MEDIUM: chunk: do not rely on small trash by default for expressions
    - CLEANUP: map: always test pat->ref in sample_conv_map_key()
    - DEV: patchbot: prepare for new version 3.5-dev
    - MINOR: version: mention that it's 3.4 LTS now.
2026-06-03 15:01:51 +02:00
Willy Tarreau
1cf7dc07e9 MINOR: version: mention that it's 3.4 LTS now.
The version will be maintained up to around Q2 2031. Let's
also update the INSTALL file to mention this.
2026-06-03 15:00:25 +02:00
Willy Tarreau
667645ed2b DEV: patchbot: prepare for new version 3.5-dev
The bot will now load the prompt for the upcoming 3.5 version so we have
to rename the files and update their contents to match the current version.
2026-06-03 14:56:22 +02:00
Willy Tarreau
a7c64a5b12 CLEANUP: map: always test pat->ref in sample_conv_map_key()
sample_conf_map_key() calls pattern_exec_match() which may return a
static pattern with ref=NULL when passed with fill=1 (which is the
case) and pat->match == NULL (which doesn't seem to be the case). It
doesn't seem it could happen with standard maps, as only "-m found"
drops has a NULL ->match function and there's no keyword associated
with it) but maybe this could happen with maps implemented in Lua,
though this remains unlikely.

Anyway better clarify the situation by always checking that the ref
is non-null before dereferencing it, it will at least avoid warnings
from code coverage tools.
2026-06-03 14:45:54 +02:00
Willy Tarreau
b794190262 BUG/MEDIUM: chunk: do not rely on small trash by default for expressions
There's a corner case with get_trash_chunk_sz() combined with the use
of small bufs: if some incoming data is going to be inflated by a
converter in a non-predictable way (say url_enc etc) then there are
two possibilities:
  - either we try to allocate a size that corresponds to the data, but
    we risk to allocate a small buf to convert a 900B chunk, that will
    now fail if it contains too many non-printable chars;
  - or we try to allocate 3x the size to be conservative, but without
    large bufs we'd fail to transcode any chunk larger than 5.3kB, even
    if it contains only printable chars.

The approach should definitely be refined and it is not 100% reliable
for now. Better temporarily ignore the small buffers for these particular
cases where the savings are not relevant, and see how to pass the knowledge
of the expected size ranges deeper down the API in 3.5. We may possibly rely
on the current trash size (instead of contents) or other mechanisms that
are yet to be specified. alloc_small_trash_chunk() gets the same change
BTW for the same reasons.

The comment for get_trash_chunk_sz() was updated to restate the importance
of being conservative when requesting a size.

No backport is needed.
2026-06-03 14:45:54 +02:00
Willy Tarreau
bf4878226e BUG/MINOR: jwe: don't write randoms past MAX_DECRYPTED_CEK_LEN in RSA_PKCS1_PADDING
The recent fix in commit 1a5a33396d ("BUG/MEDIUM: jwe: substitute random
CEK on RSA1_5 decryption failure per RFC 7516 #11.5") writes 8 bytes at
once but stops at the last one, so it can overflow the sample by 7 bytes.
This is totally harmless since the max size is 64 bytes, but better stop
at the boundary. A final loop completes one byte at a time by construction
so that we can adapt to any value of MAX_DECRYPTED_CEK_LEN, but the compiler
will not emit it since we stop at 64.

No backport is needed, it's only for 3.4.
2026-06-03 14:45:54 +02:00
Christopher Faulet
8b71e1f155 BUG/MEDIUM: stream: Don't use small buffer on queuing with a request data filter
When there is a filter registered on the request data forwarding, we must
disable usage of the small buffers. For now it is safer to do so because we
don't know if the filter will properly handle the small buffers. In
addition, there is a true issue because it is possible to never re-arm the
receives in that case because the buffer reserve must be respected. This
leads to think a small buffer is always full, even empty one.

No backport needed.
2026-06-03 14:29:51 +02:00
William Lallemand
91aa9b88c9 CI: github: run illumos job weekly on Mondays at 03:00 instead of monthly
The previous schedule (25th of each month) provided too little coverage
frequency. Switch to a weekly run every Monday at 03:00 UTC to catch
regressions sooner.
2026-06-03 13:22:04 +02:00
Christopher Faulet
d0ab99932a BUG/MEDIUM: vars: Properly eval set-var-fmt action for emtpy log-format string
When the log-format string was empty, in action_store() function, a fallback was
performed on the expression evaluation, thinking a set-var() was performed.
However, it is possible to have an empty log-format string. At least, on 3.2 and
3.0, it is allowed to parse an empty log-format string, quoted empty string are
not rejected.

So, on 3.2 and 3.0, it was possible to have a "set-var-fmt" action in the config
leading to parse an empty log-format string. Doing so, a crash could be
experienced when the action was executed because the fallback on the expression
evaluation led to dereference a NULL pointer.

To fix the issue, during parsing the action type is now set to a different value
for a "set-var" or a "set-var-fmt" action. And this action type is tested during
execution to perform the right action.

This patch should fix issue #3406. It must be backported as far as 3.0. Only 3.2
and 3.0 are affected by the issue.
2026-06-03 12:05:56 +02:00
Christopher Faulet
1b4255a885 BUG/MEDIUM: channel: Fix condition to know if a channel may send
Historically, we considered a channel cannot send before the connection was
established. This was useful to know if the reserve should still be
respected for the receives. This was because it was possible to rewrite the
request on connection retry (because of http-send-name-header option).

However noadays, it is a useless limitation. Once data forwarding is
started, there is no longer rewrites on the request at the stream layer
(http-send-name-header option is handled by the muxes). And, since it is
possible to use small buffers to queue requests, it could be an issue,
because the reserve and the small buffer size are the same by default. Once
a small request was finally dequeued, the receives on client side were not
re-armed because we should still respect the reserve on receives
(channel_recv_limit() was returning 0 in that case).

To fix the issue, we must consider a channel may send since the underlying
stconn has reached the SC_ST_REQ state, instead of SC_ST_EST. Doing so, we
are able to ignore the reserve earlier and the receives can be re-armed even
with small buffers.

There is no reason to backport this patch, except if an issue is reported,
because only the 3.4 is concerned. But it could theorically be backported to
all stable versions.
2026-06-03 12:05:56 +02:00
Willy Tarreau
326618b9a9 BUILD: makefile: fix build error with GNU make 4.2.1 and /bin/dash
The latest fix in the Makefile in commit 9993688954 ("BUILD: makefile/lua:
use the system's default library before all other variants") broke the
build on a machine with GNU make 4.2.1 and /bin/dash:

  Makefile:690: *** unterminated call to function 'shell': missing ')'.  Stop.

It's caused by the '#' in '#include'. Protecting it with a backslash
fixes the make issue but moves it to the shell where it's echoed in the
output. Printf '\043' works but not sure if it's everywhere yet. At this
point better just revert that tiny part which was made to refine the
presence check for lua.h by checking that it contains valid C code. If
the commit above is backported, this one will have to be as well.
2026-06-03 12:04:21 +02:00
William Lallemand
e1b5f3bbc3 CI: github: replace cirrus by a vmactions/freebsd-vm job
Cirrus FreeBSD jobs is not available anymore since June 1st , this job
uses github qemu-based images to run a FreeBSD job.

Remove Cirrus job.
2026-06-03 11:20:31 +02:00
Willy Tarreau
d17fb63ce7 MINOR: haterm: enable ktls on the SSL bind line when supported
When both USE_LINUX_SPLICE and USE_KTLS are enabled, it's worth
enabling kTLS on the bind line as it significantly increases the
local bit rate as well as through TLS accelerators (up to x2/x3).
The -dT option remains available to disable it. It was verified to
gracefully downgrade when not supported (e.g. OpenSSL 3.0.1 does
this).
2026-06-02 19:19:25 +02:00
Willy Tarreau
564b9d06c0 CLEANUP: haterm: do not try to bind to SSL when not built in
When built without USE_OPENSSL, the binding errors are dirty, speaking
about crt-store and stuff like this. Better just indicate that SSL
support was not built in and explain how to enable it.
2026-06-02 18:57:05 +02:00
Willy Tarreau
24ea0e013d BUG/MINOR: haterm: also apply the tcp-bind-opts to clear TCP "bind" lines
Commit 92581043fb ("MINOR: haterm: add long options for QUIC and TCP
"bind" settings") added --tcp-bind-opts. The doc (and commit) says that
it applies to TCP bind lines but it only applied to the TCP/SSL ones,
not the clear ones. Let's fix it. No backport needed, this is only 3.4.
2026-06-02 18:52:56 +02:00
Willy Tarreau
777ea8b185 BUG/MINOR: haterm: do not try to bind QUIC when not supported
When building without QUIC support (e.g. an SSL library not supporting
it), we'll get errors when trying to bind to the SSL port that QUIC is
not supported because the quic binding was unconditional. Let's only
place it when QUIC is supported. No backport needed, this is only 3.4.
2026-06-02 18:46:01 +02:00
Maxime Henrion
c24db7c76a BUG/MINOR: startup: unbreak chroot with CAP_SYS_CHROOT
The use of the unshare() mechanism to get the ability to chroot as an
unprivileged user produced a warning on some configurations where the
haproxy process has the CAP_SYS_CHROOT capability. We now only attempt
to use it when a previous chroot() call failed because of insufficient
privileges.

This should fix GitHub issue #3395. No backport needed.
2026-06-02 17:36:33 +02:00
Willy Tarreau
9993688954 BUILD: makefile/lua: use the system's default library before all other variants
The recent update to the makefile in commit bfbca23dc2 ("BUILD: makefile:
search for Lua 5.5 as well") to enable searching for Lua 5.5 revealed a
problem by which we were using the fallback versions before the main one
(e.g. /usr/include/lua-5.4/lua.h before /usr/include/lua.h). However, the
libs often contain the version in their name so that we can end up linking
with 5.5 while 5.4 was used in the include.

This was detected only when enabling lua 5.5 because in Lua 5.4
"luaL_openlibs()" was a symbol and became an inline in 5.5, preventing
from using a mix of the two versions.

The current change is minimal in that it skips all fallbacks when lua.h
is present in /usr/include, and includes it in the test to make sure that
the directory found contains valid C. LUA_LIB checks for lua before the
variants so as to remain consistent with the system provided version.

Thanks to @gene-git for reporting this problem in GH issue #3404.

This may have to be backported after a period of observation if users
face build issues for older releases on newer distros. In this case,
backporting 1c0f781994 ("MINOR: hlua: Add support for lua 5.5") would
equally be needed. However this will result in the system's version
being used first, which may or may not be desired.
2026-06-02 17:13:20 +02:00
Christopher Faulet
cb161bfeb7 BUG/MINOR: http-act: Properly handle final evaluation in pause action
The ACT_OPT_FINAL flag was not properly handled in the pause action. When
this flag is set, because of an abort or an unexpected error, an action must
no longer yield. However, in the pause action, this flag was never tested.
In case of client abort for instance, this could trigger an internal error
instead of a client error.

This patch should fix the issue #3403. It must be backported as far as 3.2.
2026-06-02 16:25:48 +02:00
Amaury Denoyelle
1c9e4b0d18 REGTESTS: add basic QMux tests
Write two simple QMux tests, for http/3 in SSL and clear.
2026-06-02 13:31:15 +02:00
Willy Tarreau
7ac4bcfbd4 DOC: config: add a few more explanation in http-reusee regarding sni-auto
The default sni-auto that aims at not upsetting certain servers doing
excessive checks of SNI vs host has some drawbacks (lower reuse ratio)
that are particularly hard to diagnose, so let's explain how connections
are reused/purged when dealing with many hosts, and how to cheat as well.

Let's also mention the expression used by "sni-auto" since it was only
mentioned in the code.
2026-06-02 09:14:11 +02:00
Willy Tarreau
83634a4c9a DEV: dev/gdb: add fdtab dump
Three functions are provided here:
  fd_dump: lists all FDs
  fd_dump_conn: lists all FDs holding a connection
  fd_dump_listener: lists all FDs holding a listener

They take no argument, and dump some of the known info. E.g. for
a connection, ctrl, xprt, flags, mux, sessions, frontend's name
and session's age are reported. Example:

  (gdb) fd_dump_conn
  fd    31: rm=0 tm=0x2 um=0 st=0x21 refc=0x1 tkov=0 gen=0 conn=0x7fffe803b600: flg=0x300 err=0 ctrl=0xdf51c0 xprt=0xdf5c80 mux=0xbaeee0 sess=0x7ffff003b570: fe=0x1e45b00 id=foo age=0ms

They are particularly slow because they iterate over all possible FDs,
so better limit them to the desired types.
2026-06-01 19:08:42 +02:00
Willy Tarreau
ca5f6cd053 DEV: dev/gdb: add simple thread dump
The thread_dump function dumps the list of known threads and a few info
on them (pointer, current run queue, flags etc). This should help more
easily spot a particular one and find stuck ones.

E.g:

  (gdb) thread_dump
  Tid    0: pth=0x7ffff7e797c0 mono=2222322327950732 now_ms=4294947291 fl=0x38 rq=-1 cq=0 current=(nil)
  Tid    1: pth=0x7ffff78d8640 mono=2222322327928085 now_ms=4294947291 fl=0x38 rq=-1 cq=0 current=(nil)
  Tid    2: pth=0x7ffff6b7e640 mono=2222322327927150 now_ms=4294947291 fl=0x38 rq=-1 cq=0 current=(nil)
  Tid    3: pth=0x7ffff637d640 mono=2222322327924878 now_ms=4294947291 fl=0x38 rq=-1 cq=0 current=(nil)
  Tid    4: pth=0x7ffff5b7c640 mono=2222322327925676 now_ms=4294947291 fl=0x38 rq=-1 cq=0 current=(nil)
  Tid    5: pth=0x7ffff537b640 mono=2222322327929524 now_ms=4294947291 fl=0x38 rq=-1 cq=0 current=(nil)
  Tid    6: pth=0x7ffff4b7a640 mono=2222322327926817 now_ms=4294947291 fl=0x38 rq=-1 cq=0 current=(nil)
  Tid    7: pth=0x7fffdffff640 mono=2222322327947960 now_ms=4294947291 fl=0x38 rq=-1 cq=0 current=(nil)
2026-06-01 19:08:42 +02:00
Willy Tarreau
c82ac139f4 DEV: dev/gdb: add simple task dump
New functions task_dump_wq and task_dump_rq can be used to dump tasks
in a wait queue or in a run queue respectively. For the wait queue (the
most common usage), one needs to pass either the thread-local's timers,
or the thread group ones for shared tasks:

  task_dump_wq &ha_tgroup_ctx[0].timers
  task_dump_wq &ha_thread_ctx[0].timers

For the run queue, task_dump_rq will take the thread's rqueue:

  task_dump_rq &ha_thread_ctx[0].rqueue

The output is  the task pointer and a dump of the task* struct per line,
then a total count at the end.
2026-06-01 19:08:42 +02:00
Willy Tarreau
837d69f8ef DEV: dev/gdb: improve ebtree pointer handling
The ebtree descent functions currently use $arg0 as is and it's up to
the user to manually type the required casts that are never obvious
(particularly when coming from a pointer). Let's put the eb_root* cast
in the function to be more user-friendly.
2026-06-01 19:08:42 +02:00
Willy Tarreau
bfbca23dc2 BUILD: makefile: search for Lua 5.5 as well
Support for Lua 5.5 was brought in 3.4-dev2 with commit 1c0f781994
("MINOR: hlua: Add support for lua 5.5") but the Makefile doesn't look
for it, which can be quite confusing on recent distros which start to
ship with it. Let's add it to the looked up names.
2026-06-01 19:08:42 +02:00
Olivier Houchard
24455aa4e0 BUG/MINOR: tasks: Increase the right niced_task counter
In __task_wakeup(), for a niced task, we don't always want to increase
the niced_task counter of the running thread's thread group, if we are
waking up the task of another thread, who belongs to a different thread
group, then we want to increment that thread group's counter instead, as
that's the one that will get decremented later.
So just increase the counter for the target thread'd thread group,
instead of using tg_ctx.
The impact is probably pretty minor, niced task shared amongst thread
are not very common, and the impact would mostly mean we'd run more/less
tasks in one run of process_runnable_tasks() than expected.
This should be backported as far as 2.8.
2026-06-01 17:52:13 +02:00
Olivier Houchard
c0aa9f01f1 BUG/MEDIUM: leastconn: Unlock the write lock on allocation failure
When we fail to allocate a new tree element, we're still holding the
write lock, so we should do an write unlock, not a read unlock, or the
lock will get corrupted and most likely this will end in a deadlock.

This should be backported up to 3.2.
2026-06-01 16:08:45 +02:00
Christopher Faulet
4a540a4fb7 BUG/MINOR: mux-spop: Fix possible off-by-one OOB read in spop_get_varint()
In spop_get_varint(), -1 is returned if there is not enough data in the
buffer to decode the variable integer. However a strict comparison agasint
b_data() was performed, which is wrong. A failure must be reported if the
index is greater or equal to b_data().

This patch must be backported as far as 3.2.
2026-06-01 15:39:43 +02:00
Christopher Faulet
b8543c54d4 BUG/MINOR: applet: Commit changes into input buffer after sending HTX data
After sending HTX data to an applet, htx_to_buf() must be called on the
applet buffer to commit changes (and possibly to reset the buffer if it is
empty). This was performed on the output buffer while it should in fact be
performed on the input buffer. So let's fix it.

This patch must be backported as far as 3.0.
2026-06-01 15:39:43 +02:00
Olivier Houchard
8497107132 BUG/MEDIUM: ssl: Make sure the alpn length is small enough
When the check for server hash was introduced to make sure we're using
the right alpn, the logic to store the new alpn was flawed. We should
always check that the new alpn length is small enough to fit in the
buffer, no matter if the server hash is not the same or not. So always
check the length first, and only check if the alpn or the server changed
after.
This should be backported whenever commit
de3f245df0 has been backported.
2026-06-01 14:47:45 +02:00
Willy Tarreau
030a2bfeeb MINOR: debug: add -dA to dump an archive of all dependencies
This adds "-dA[file]" on the command line, which dumps an archive of all
dependencies detected at runtime into the designated file in tar format.
This is equivalent to "set-dumpable libs", but instead of keeping the libs
in memory, it dumps them into a file. This may be used after a core dump,
in order to provide all necessary libraries to developers to permit them
to exploit the core. This may not be available on all operating systems.
2026-06-01 15:01:32 +02:00
Willy Tarreau
f8fd6d25d8 MINOR: deinit: release the in-memory copy of shared libs
When shared libs were loaded via "set-dumpable libs", better release
them upon deinit, it will make valgrind happier. For this we now have
a new function free_collected_libs() in tools.c and call it in deinit().
2026-06-01 15:01:32 +02:00
Christopher Faulet
2199053018 BUG/MEDIUM: htx: Fix headers rollback on partial copy in htx_xfer()
In htx_xfer() function, when headers are partially copied, depending on the
flags, a rollback may be performed to remove all copied headers from the
destination message. However, there was an issue in the loop performing the
rollback. Instead of decrementing the returned value using the size of the
HTX block from the destination message, the one from the source message was
used. So the wrong value was be returned and in worst case, it could
overflow.

In addition, the BUG_ON() in the loop was removed because test condition was
wrong.

It is a 3.4-specific issue. No backport needed.
2026-06-01 09:59:33 +02:00
Christopher Faulet
de25313cd8 BUG/MEDIUM: mux-h1: Dup connection/upgrade value to parse it when making headers
When message headers are formatted, the connection and upgrade header values
are parsed to be sanitized and to fill H1M flags. The values are modified in
place without changing the HTX message information accordingly (the block
info and the HTX info). It could be an issue if the output buffer is full
and the header cannot be formatted. Because the formatting can be stopped
with a HTX message in hazardous state.

It should be quite difficult to trigger this issue. But now, a copy of the
value is performed before parsing it. So only the copy will be altered,
leaving the HTX message in a safe state.

This patch must be backported to all stable versions.
2026-06-01 09:59:33 +02:00
Christopher Faulet
f1aac4a3b2 BUG/MINOR: cache: Fix copy of value when parsing maxage
During maxage parsing, the size of the value was not properly computed when
it was copied into the trash chunk. The name (max-age or s-maxage) must be
skipped with the '=' character. But instead of doing a subtraction, and
addition was performed, adding 2 extra bytes to the value used for the
convertion to integer.

In addition, the "chunk_memcat(chk, "", 1)" operation to add a trailing
NULL-byte was replaced by "*(b_tail(chk)) = '\0'". It a bit easier to
understand.

This patch should be backported to all stable versions.
2026-06-01 09:59:33 +02:00
Amaury Denoyelle
3a5189faba BUG/MEDIUM: h3: fix MAX_PUSH_ID handling
MAX_PUSH_ID frames are emitted by the client only on the control stream.
These conditions are checked via h3_check_frame_valid() since the
following patch.

  e4a5a64198
  BUG/MINOR: h3: reject server MAX_PUSH_ID frame

However control stream test was inverted by mistake. This patch fixes
it.

Due to this bug, H3 connections were improperly closed on error by
haproxy for clients which send MAX_PUSH_ID frames. This has been
detected on the QUIC interop with aioquic and neqo clients.

This must be backported up to 3.3.
2026-06-01 09:55:14 +02:00
Amaury Denoyelle
c989d9da6d CLEANUP: fix comment typo
Fix comment for H3_UNI_S_T_CTRL used for unidirectional streams.
2026-06-01 09:55:14 +02:00
Olivier Houchard
bda42604c0 BUG/MEDIUM: qmux: Close connection on invalid frame
In qcc_qmux_recv(), when calling qmux_parse_frm(), also treat negative
values as an error, and close the connexion. qmux_parse_frm() will
return -1 if the frame is of an invalid type, and we don't want to
process any further, or we will crash.
2026-06-01 08:59:02 +02:00
Willy Tarreau
41a20c1738 DOC: security: also add a note to exclude dev/ and admin/
These ones are not intended for production so they're out of scope.
This also fixes a paragraph formatting issue left after a fix.
2026-06-01 00:46:21 +02:00
Willy Tarreau
03b828b648 DOC: add security.txt describing how to report security issues
Move the security contact out of intro.txt into a dedicated, easily
searchable doc/security.txt that points reporters at the threat model
first, and reference it from intro.txt's contacts section and the
documentation index.
2026-05-31 22:44:15 +02:00
Willy Tarreau
8badf5d2fa DOC: internals: add a threat model definition
Add doc/internals/threat-model.txt describing what does and does not
qualify as a security vulnerability in HAProxy so that reporters and
developers have a common understanding of the threat model, and make it
clear that anything non-critical should be handled in the open and
not hidden behind embargoes.

The document lists assets to protect, what constitutes an attack, what
are the mitigations in place, and the severity ordering of various
risks. This may in the long term also help developers make better
choices of default settings and option names, and may also justify
changing default settings over time when modern operating systems
bring new possibilities.

A section also lists some invariants and defaults in an attempt to
limit the risk of reporting theoretical issues that are technically
impossible to happen in the field.

This is an initial version meant to be refined as cases arise. It
was incrementally designed and cross-checked with the help of three
independent LLMs (Qwen, Gemini and Claude) until each correctly
classified a set of sample reports against it. In the current state
they do not raise any residual ambiguities anymore.
2026-05-31 20:28:08 +02:00
Willy Tarreau
551e01e3e7 DOC: internals: clarify ambiguous wording in core-principles
After testing against a few LLMs, it appeared that several entries in
the core principles document were ambiguous or imprecise and could be
misread (size_t, pools, trash, dwcas, comparison, ncbuf). No more
complaint after this rewording so this will be sufficient for now.
2026-05-31 16:38:03 +02:00
Ilia Shipitsin
0bf22b86d0 CLEANUP: admin/halog: improve handling of memory allocation errors
Found via cppcheck  --force --enable=all --output-file=haproxy.log :

admin/halog/halog.c:1805:2: warning: If memory allocation fails, then there is a possible null pointer dereference: ustat [nullPointerOutOfMemory]
admin/halog/halog.c:1806:2: warning: If memory allocation fails, then there is a possible null pointer dereference: ustat [nullPointerOutOfMemory]
admin/halog/halog.c:1809:2: warning: If memory allocation fails, then there is a possible null pointer dereference: ustat [nullPointerOutOfMemory]
admin/halog/halog.c:1810:2: warning: If memory allocation fails, then there is a possible null pointer dereference: ustat [nullPointerOutOfMemory]
admin/halog/halog.c:1814:2: warning: If memory allocation fails, then there is a possible null pointer dereference: ustat [nullPointerOutOfMemory]
2026-05-31 10:30:00 +02:00
Ilia Shipitsin
c1d6973571 CLEANUP: ncbmbuf: improve handling of memory allocation errors in unit tests
Found via cppcheck  --force --enable=all --output-file=haproxy.log :

src/ncbmbuf.c:192:9: warning: If memory allocation fails, then there is a possible null pointer dereference: area [nullPointerOutOfMemory]
src/ncbmbuf.c:373:9: warning: If memory allocation fails, then there is a possible null pointer dereference: data [nullPointerOutOfMemory]
src/ncbmbuf.c:546:9: warning: If memory allocation fails, then there is a possible null pointer dereference: data [nullPointerOutOfMemory]
2026-05-31 10:29:49 +02:00
Ilia Shipitsin
a93b407811 MINOR: addons/51degrees: handle memory allocation failures
Found via cppcheck  --force --enable=all --output-file=haproxy.log :
addons/51degrees/51d.c:130:3: warning: If memory allocation fails, then
  there is a possible null pointer dereference: name [nullPointerOutOfMemory]
addons/51degrees/51d.c:922:4: warning: If memory allocation fails, then
   there is a possible null pointer dereference: _51d_property_list [nullPointerOutOfMemory]
2026-05-31 10:26:41 +02:00
Ilia Shipitsin
9393ff4f71 CLEANUP: addons/51degrees: initialize variables
Found via cppcheck  --force --enable=all --output-file=haproxy.log :
addons/51degrees/51d.c:1073:8: error: Uninitialized variable: _51d_prop_name.name [uninitvar]
2026-05-31 10:26:24 +02:00
Olivier Houchard
30811a3bac BUG/MEDIUM: resolvers: Wait a bit before calling the xprt prepare_srv
We can't call call the prepare_srv() method too early, because it needs
global.nbthreads to be properly set, which won't be true at post_parse
time. So instead, make it so that code runs later, as a post_check
function, when it will be safe to do so.

This should be backported up to 2.8.
This should fix github issue #3402
2026-05-29 19:20:23 +02:00
Maxime Henrion
839b87ac9f BUG/MINOR: cache: fix cache tree iteration
Ever since the introduction of multiple cache trees, the "show cache"
CLI command was not properly showing the contents of each tree, but was
only showing the first one.
Fix that by properly resetting next_key when we switch to the next tree.

Should be backported up to 3.0.
2026-05-29 17:16:03 +02:00
William Lallemand
2d91a846f9 BUILD: Makefile: put EXTRA_MAKE help at the right place
the EXTRA_MAKE help was in the USE_* list which is not the right place
for it, this patch move it to the list of variables in make help
2026-05-29 16:39:58 +02:00
Olivier Houchard
004ad29bb2 MINOR: quic: Copy sin6_flowinfo and sin6_scope_id too
In in46un_to_addr(), when copying a struct sockaddr_in6, copy the
sin6_flowinfo and sin6_scope_id, as they are part of the structure too.
They are unlikely to be of any use for us, but this is more correct
anyway.
2026-05-29 15:36:47 +02:00
Olivier Houchard
d796a31945 BUG/MINOR: quic: Fix another buffer overflow with sockaddr_in46
Very similarly to what was fixed with commit
63f853957a, we cast a sockaddr_in46 in
quic_dgram_parse() to sockaddr_storage while providing source and
destination addresses to qc_handle_conn_migration(), which will then
copy the whole sockaddr_storage, thus reading memory past what was
provided.
While this most likely won't have any impact, let's do the right thing,
and use in46un_to_addr() to generate a real sockaddr_storage.
This does not need to be backported.
2026-05-29 15:36:43 +02:00
Willy Tarreau
45f14ba836 BUILD: makefile: include EXTRA_MAKE in the .build_opts construction
EXTRA_MAKE allows to source an external makefile to bring new options
that will result in including add-ons etc. It must be part of the
construction of .build_opts that decides whether or not existing .o
are reusable or need to be rebuilt, otherwise we can end up with a mix
of .o built with some options and others with different options.

No backport is needed, as this appeared in 3.4.
2026-05-29 11:07:38 +02:00
Willy Tarreau
4185bc2cb8 MINOR: thread: report when thread-groups or nbthread results in less threads
Some setups where the number of threads is forced without any binding
(no cpu-map), are quite suspicious if they result in less threads than
available CPUs, and not even predictably bound, so we want to notify
the user that this might be an oversight.

Similarly, when thread-groups is forced and not nbthread (and no cpu-map),
and the final number of threads is lower than the hard-limit or the number
of CPUs we also indicate the impact and how to remedy it. This can happen
for example when starting on a machine with more than 64 CPUs and
thread-groups forced to 1, or on more than 128 CPUs and thread-groups
forced to 2 (e.g. when moving an older config to a new platform).

It is possible that some of these conditions might need to be readjusted
in the future to catch other traps or to relax certain commonly used,
valid cases, so for now it is preferable not to backport this patch.
2026-05-28 18:49:47 +02:00
Willy Tarreau
dad00a7442 MINOR: cpu-topo: notify when cpu-policy is ignored due to other settings
The cpu-policy directive is ignored when nbthreads, thread-groups, or
cpu-map are set. In addition, first-usable-node is ignored when the
process was externally restricted (e.g. taskset). This is difficult to
debug when it happens because multiple parameters come into the mix and
it's easy to forget to unset one. Let's emit a notice when this happens
and the policy was forced. This way, it remains silent with the default
policy, but if it was forced, the incompatibility is reported.

It's worth noting that ll the cpu-policy functions take a char **err
but none uses it. It could have been useful here instead of calling
ha_notice() all along, but one needs to determine who the consumers
are and who will be responsible for freeing the message, so let's go
with ha_notice() given that were were already some diag_warnings in
these functions.

It could be helpful to backport this to 3.2.
2026-05-28 18:49:47 +02:00
Willy Tarreau
f91b1ce9af CLEANUP: thread: indicate when max-threads-per-group is ignored
Since it's easy to get caught by some parameters being ignored, let's
detect when mtpg was explicitly set and report a notice if it is ignored
due to thread-groups being set. For this we need to avoid presetting
the value in the global section and only set it when entering function
thread_detect_count(), which is OK since the value cannot be used before.
2026-05-28 18:49:47 +02:00
Willy Tarreau
f5847d11f7 BUG/MEDIUM: threads: ignore max-threads-per-group when thread-groups is set
As documented, max-threads-per-group is the default number of threads
to arrange in a group before creating another group, and is only meant
to be used when thread-groups is not set.

However it was always enforced, so configs like:

   global
       thready-groups 2

which were sufficient in 3.2 and above to start with 64-128 threads
are now suddenly limited to 32 threads! Let's relax the limit when
thread-groups is set!

No backport is needed since this is only 3.4.
2026-05-28 18:49:47 +02:00
Willy Tarreau
617df441d6 BUG/MINOR: threads: set at least grp_max when mtpg is too small
When starting, say, 128 threads with max-threads-per-group set to 2
and MAX_TGROUPS set to the default 32, instead of setting the resulting
number of groups to 32 and threads to 64, they're set to 1 and 32
respectively because the condition to raise grp_min is not satisfied.

Let's cut the condition in two parts to also permit to raise it at
least to grp_max.

This should be backported to 3.2.
2026-05-28 18:49:47 +02:00
Willy Tarreau
40508247c6 REGTESTS: quic: disable quic/ocsp_auto_update for now
It was made from the split of the original one into the SSL and the QUIC
variant. However there's a catch: both use the same certificates which
includes the OCSP URL 127.0.0.1:12345, and both need to start a server
on that port. Depending on the number of parallel process and their
speed, they might very well work, or totally fail due to a binding
conflict and the fact that the test runs for a few seconds.

Let's disable the QUIC variant for now, since the whole point of the
test is to verify all the sequencing, the SSL one is greatly sufficient.
Maybe a better approach can be found later.
2026-05-28 18:49:47 +02:00
William Lallemand
5e5b1522cf BUG/MEDIUM: lua: register hlua_init() as a pre-check to fix crash without Lua config
Commit 1c59c39171 deferred hlua_init() to be called lazily from the
config keyword handlers (lua-load, lua-load-per-thread,
lua-prepend-path, tune.lua.openlibs), with a call inside
hlua_post_init() as a safety net for the case where no Lua directive
appears in the configuration at all.

The problem is hlua_init() is a function that allocates internal
servers (socket_proxy, socket_tcp, socket_ssl) that must exist before
haproxy initialize the configuration. But hlua_post_init() is done too
far after this initialization, so the safety net does not work
correctly.

This would results in a crash in the deinit() if no lua
configuration was loaded in haproxy.

   Core was generated by `./haproxy -W -f /dev/null'.
   Program terminated with signal SIGSEGV, Segmentation fault.
   #0  0x00005671c72b1047 in _ceb_first (root=0x30, kofs=16, key_type=CEB_KT_U64, key_len=0,
       is_dup_ptr=0x7ffc13197a14) at include/import/cebtree-prv.h:1160
   1160		if (!*root)
   (gdb) bt
   #0  0x00005671c72b1047 in _ceb_first (root=0x30, kofs=16, key_type=CEB_KT_U64, key_len=0,
       is_dup_ptr=0x7ffc13197a14) at include/import/cebtree-prv.h:1160
   #1  _ceb64_first (root=0x30, kofs=16) at src/_ceb_int.c:73
   #2  ceb64_ofs_first (root=0x30, kofs=16) at src/_ceb_int.c:66
   #3  0x00005671c6be5e6e in srv_close_idle_conns (srv=0x5671fd592a80) at src/server.c:7676
   #4  0x00005671c6d3be17 in deinit_proxy (p=0x5671fd5d7780) at src/proxy.c:393
   #5  0x00005671c6d3c536 in proxy_drop (p=0x5671fd5d7780) at src/proxy.c:479
   #6  0x00005671c6aed998 in hlua_deinit () at src/hlua.c:14934
   #7  0x00005671c6db2e41 in deinit () at src/haproxy.c:2846
   #8  0x00005671c6db3d98 in deinit_and_exit (status=0) at src/haproxy.c:2966
   #9  0x00005671c6db6111 in main (argc=4, argv=0x7ffc131983c8) at src/haproxy.c:3997

The fix is to do the initialization earlier, in a pre-check callback.

Thanks to Amaury for reporting this issue.

No backport needed.
2026-05-28 18:46:50 +02:00
Frederic Lecaille
54633f078c Revert "MEDIUM: quic: optimize HKDF operations by reusing per-thread contexts"
This reverts commit 4e0af590e8.
This patch does not work at all with AWSLC! This is incredible!

No need to backport.
2026-05-28 18:15:19 +02:00
Frederic Lecaille
6599dd7d41 BUG/MINOR: quic: update drs->lost before calling on_ack_recv
The QUIC congestion control algorithm impacted by this bug is BBR.

In qc_notify_cc_of_newly_acked_pkts(), drs->lost was updated after
quic_cc_drs_on_ack_recv(), causing the current sample's lost count to
miss the bytes_lost from the current loss detection round. This meant
that rs->lost = drs->lost - rs->prior_lost would always be 0 for the
current losses, since both drs->lost and rs->prior_lost (captured at
packet send time) excluded the current bytes_lost.

Moving drs->lost += bytes_lost before on_ack_recv ensures that the
rate sample correctly includes the newly detected lost bytes, matching
the BBR algorithm's intent where C.delta_lost = C.lost - C.prior_lost
should reflect all losses since the last sample.

Must be backported as far as 3.1 where delivery rate sampling was
implemented.
2026-05-28 17:47:31 +02:00
Frederic Lecaille
45ad1037d0 BUG/MEDIUM: quic: reset consecutive_losses on exit from recovery period (cubic)
When exiting the recovery period and re-entering congestion avoidance,
the consecutive_losses counter was not reset. This meant that if a loss
event arrived immediately after the ACK that ended recovery, the counter
would still hold the value that triggered recovery, causing an immediate
re-entry into recovery (recovery -> CA -> recovery loop).

Resetting consecutive_losses to 0 on recovery exit matches the behavior
of resetting it on ACK in CA, ensuring a clean slate for the new
congestion avoidance period.

Must be backported to all versions.
2026-05-28 17:47:31 +02:00
Frederic Lecaille
ab8603c6d5 BUG/MEDIUM: quic: reset cwnd in slow_start on persistent congestion (cubic)
The cubic slow_start callback was only resetting the internal cubic state
without reducing the congestion window, unlike newreno which calls
quic_cc_path_reset(). Per RFC 9002, persistent congestion should trigger
both entry into slow start and a reduction of the congestion window.

Must be backported to all versions.
2026-05-28 17:47:31 +02:00
Frederic Lecaille
4e0af590e8 MEDIUM: quic: optimize HKDF operations by reusing per-thread contexts
Allocating and freeing an OpenSSL EVP_PKEY_CTX context via
EVP_PKEY_CTX_new_id() and EVP_PKEY_CTX_free() on every HKDF cryptographic
operation (such as during stateless reset token generation) induces
unnecessary memory allocation overhead.

Optimize this by introducing a global per-thread context array
'quic_tls_hkdf_ctxs'. These contexts are allocated and initialized once
at startup via a POST_CHECK hook (quic_tls_alloc_hkdf_ctxs) and are
properly freed at exit via a POST_DEINIT hook (quic_tls_dealloc_hkdf_ctxs).

The functions quic_hkdf_extract(), quic_hkdf_expand(), and
quic_hkdf_extract_and_expand() now reuse the pre-allocated context
corresponding to the current thread ID ('tid'), removing dynamic
allocations from these frequent execution paths.

As a cleanup, quic_hkdf_expand() is now static and unexported from the
header file.

Should be easily backported to all versions for optimization purposes.
2026-05-28 17:47:31 +02:00
Frederic Lecaille
52ce316786 BUG/MINOR: quic: fix ack range node pool_free call passing wrong pointer type
In quic_insert_new_range(), the variable 'first' is a struct eb64_node*,
but pool_free expects a struct quic_arng_node*. While the addresses are identical
(since 'first' is the first member of quic_arng_node), this is technically
incorrect and should use eb64_entry() for proper type safety.

Must be backported to all versions.
2026-05-28 17:47:31 +02:00
Amaury Denoyelle
1cf1a0c8b1 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.
2026-05-28 17:36:05 +02:00
Amaury Denoyelle
c76e0f1bc4 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.

  00d668549e
  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.
2026-05-28 17:36:05 +02:00
Amaury Denoyelle
802a3b7288 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.
2026-05-28 17:36:05 +02:00
Amaury Denoyelle
fb828a4711 MINOR: mux_quic/flags: add missing flags
Add missing mux QUIC values for the dev flags utility, both for qcc and
qcs types.
2026-05-28 17:36:05 +02:00
William Lallemand
b21e130ea5 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.
2026-05-28 16:45:55 +02:00
William Lallemand
026a038bbd 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.
2026-05-28 16:45:27 +02:00
William Lallemand
6ebf0d4c95 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.
2026-05-28 16:44:59 +02:00
Christopher Faulet
fbd7148b15 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.
2026-05-28 14:52:06 +02:00
William Lallemand
2130c9ccfb 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
2026-05-28 11:36:02 +02:00
William Lallemand
1c59c39171 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.
2026-05-28 11:36:02 +02:00
Frederic Lecaille
9a39e55ded BUG/MINOR: quic: Fix memory leak in quic_deallocate_dghdlrs()
Some checks failed
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
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.
2026-05-28 07:30:29 +02:00
Frederic Lecaille
1974240520 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.
2026-05-28 07:28:41 +02:00
Frederic Lecaille
7ad81403d0 CLEANUP: qpack: move encoded macros to qpack-t.h to avoid duplication
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
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.
2026-05-27 18:40:53 +02:00
Frederic Lecaille
8874f06b9e 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.
2026-05-27 18:40:53 +02:00
Frederic Lecaille
629fbee3be 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.
2026-05-27 18:40:53 +02:00
Frederic Lecaille
e2d2f67666 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.
2026-05-27 18:40:53 +02:00
Frederic Lecaille
0e83b7cd08 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.
2026-05-27 18:40:53 +02:00
Frederic Lecaille
2f20eb5bd8 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.
2026-05-27 18:40:53 +02:00
Frederic Lecaille
40313cd0d5 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.
2026-05-27 18:40:53 +02:00
Christopher Faulet
091768ab3e Revert "BUG/MEDIUM: dns: fix long loops in additional records parse on name failure"
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
This reverts commit fefce297ab.

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.
2026-05-27 15:42:10 +02:00
Amaury Denoyelle
cd652efeca BUG/MINOR: qmux: reject too large initial record
Some checks failed
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
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.
2026-05-27 15:38:55 +02:00
Amaury Denoyelle
205312023a 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.
2026-05-27 15:38:51 +02:00
Amaury Denoyelle
8a8898aedd 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.
2026-05-27 15:38:49 +02:00
Olivier Houchard
1589621100 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".
2026-05-27 12:28:14 +02:00
Chad Lavoie
8d771110e0 BUG/MINOR: mux-h1: H2 preface rejection doesn't update stick-table glitches
commit 72fd357814 ("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]
2026-05-27 10:53:00 +02:00
William Lallemand
85a833feba 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.
2026-05-27 10:20:55 +02:00
Christopher Faulet
31cd3d13aa 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.
2026-05-27 09:30:00 +02:00
Willy Tarreau
88da61e218 [RELEASE] Released version 3.4-dev14
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
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
2026-05-26 21:56:40 +02:00
Christopher Faulet
16446de17c 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.
2026-05-26 18:28:07 +02:00
Christopher Faulet
010ab9798e 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.
2026-05-26 18:28:07 +02:00
Christopher Faulet
3ffbf5539e 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.
2026-05-26 18:28:07 +02:00
Christopher Faulet
3843f48faf 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.
2026-05-26 18:28:07 +02:00
Christopher Faulet
b238c08015 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.
2026-05-26 18:28:07 +02:00
Christopher Faulet
547c2e4e78 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.
2026-05-26 18:28:07 +02:00
Christopher Faulet
3ac082b2b2 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.
2026-05-26 18:28:07 +02:00
Remi Tricot-Le Breton
1a5a33396d 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.
2026-05-26 18:19:00 +02:00
Remi Tricot-Le Breton
4e7518ed21 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).
2026-05-26 18:14:21 +02:00
Amaury Denoyelle
ce9371a768 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.
2026-05-26 17:21:07 +02:00
Amaury Denoyelle
2c0e633f6b BUG/MINOR: qmux: do not crash on frame parsing issue
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
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.
2026-05-26 14:29:55 +02:00
Willy Tarreau
b463072032 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.
2026-05-26 14:18:20 +02:00
Amaury Denoyelle
f7130c0f36 BUG/MINOR: h3: add missing break on rcv_buf()
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
The following patch ensures server MAX_PUSH_ID are rejected as a client.
This has been implemented by extending h3_rcv_buf().

  e4a5a64198
  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.
2026-05-26 14:14:24 +02:00
William Lallemand
dfb6daca1f 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.
2026-05-26 14:13:23 +02:00
Amaury Denoyelle
e4a5a64198 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.
2026-05-26 13:52:04 +02:00
Amaury Denoyelle
4a8bb2fe5f 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.
2026-05-26 13:52:03 +02:00
Amaury Denoyelle
d8460a5339 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.
2026-05-26 13:52:03 +02:00
Amaury Denoyelle
8e77620616 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.
2026-05-26 13:52:03 +02:00
Amaury Denoyelle
433cce7af1 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.
2026-05-26 13:52:03 +02:00
Willy Tarreau
4a9ec66fd8 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
2026-05-26 13:13:24 +02:00
Willy Tarreau
73b5f0eed4 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.
2026-05-26 13:13:24 +02:00
Willy Tarreau
7ac4d7d69f 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.
2026-05-26 13:13:24 +02:00
Willy Tarreau
85003563c5 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).
2026-05-26 13:13:24 +02:00
Willy Tarreau
f932863484 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.
2026-05-26 13:13:24 +02:00
Willy Tarreau
26c3b3f41d 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.
2026-05-26 13:13:24 +02:00
Willy Tarreau
9b6389c8a0 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.
2026-05-26 13:13:24 +02:00
Willy Tarreau
93f9ecbfe6 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.
2026-05-26 13:13:24 +02:00
Willy Tarreau
2a47cab7f3 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.
2026-05-26 13:13:24 +02:00
Willy Tarreau
c41c731f5e 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").
2026-05-26 13:13:24 +02:00
Willy Tarreau
2653936510 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").
2026-05-26 13:13:24 +02:00
Willy Tarreau
997c99df9c 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.
2026-05-26 13:13:24 +02:00
Willy Tarreau
076655e18d 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").
2026-05-26 13:13:24 +02:00
Willy Tarreau
8cb0a0c53d 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.
2026-05-26 13:13:24 +02:00
Willy Tarreau
e583b38c63 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.
2026-05-26 13:13:24 +02:00
Willy Tarreau
ffdc91c4a1 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.
2026-05-26 13:13:24 +02:00
Willy Tarreau
4f58fef3d4 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.
2026-05-26 13:13:24 +02:00
Willy Tarreau
73472025f2 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.
2026-05-26 13:13:24 +02:00
Willy Tarreau
5cb932826d 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.
2026-05-26 13:13:24 +02:00
Willy Tarreau
8bdcc55163 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.
2026-05-26 13:13:24 +02:00
Willy Tarreau
b9aaf3c18a 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.
2026-05-26 13:13:18 +02:00
Willy Tarreau
635652c5aa 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.
2026-05-26 13:11:35 +02:00
Frederic Lecaille
04811943b5 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.
2026-05-26 10:56:18 +02:00
Willy Tarreau
e8c9aabd62 BUG/MINOR: haterm: fix the random suffix multiplication
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
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.
2026-05-25 20:49:22 +02:00
Willy Tarreau
32fc35ef09 CLEANUP: resolvers: fix comment typos and wrong filenames in file headers
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
A few asorted comment fixes for resolvers (incorrect file name etc).
2026-05-25 10:57:14 +02:00
Willy Tarreau
6bb8cb51e6 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.
2026-05-25 10:57:14 +02:00
Willy Tarreau
8fe8d5fbe3 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.
2026-05-25 10:57:13 +02:00
Willy Tarreau
b78b023d55 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.
2026-05-25 10:57:13 +02:00
Willy Tarreau
7d182a2ed5 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.
2026-05-25 10:52:42 +02:00
Willy Tarreau
c0e302fe79 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.
2026-05-25 10:52:42 +02:00
Willy Tarreau
478e7e52cb 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.
2026-05-25 10:52:42 +02:00
Willy Tarreau
8e1d33a648 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.
2026-05-25 10:52:42 +02:00
Willy Tarreau
49d6306de3 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.
2026-05-25 10:52:42 +02:00
Willy Tarreau
01ebb668a4 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.
2026-05-25 10:52:42 +02:00
Willy Tarreau
340cc86efb 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.
2026-05-25 10:52:42 +02:00
Willy Tarreau
f62d020140 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).
2026-05-25 10:52:42 +02:00
Willy Tarreau
bbef74fb21 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.
2026-05-25 10:16:06 +02:00
Willy Tarreau
608951844e 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.
2026-05-25 10:16:06 +02:00
Willy Tarreau
f9088a5d75 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.
2026-05-25 10:16:06 +02:00
Willy Tarreau
007d5946b4 BUILD: intops: mask the fail value in array_size_or_fail()
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
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.
2026-05-25 07:33:35 +02:00
CyberpsychoJacob
4db85fc53e 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.
2026-05-23 18:09:59 +02:00
Christopher Faulet
41bb1c24f6 BUG/MEDIUM: cli: Fix parsing of pattern finishing a command payload
Some checks failed
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
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.
2026-05-22 17:17:01 +02:00
Christopher Faulet
9091cfa617 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.
2026-05-22 17:17:01 +02:00
Christopher Faulet
57b526e022 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.
2026-05-22 17:17:01 +02:00
Christopher Faulet
2644f9ddf9 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.
2026-05-22 17:17:01 +02:00
Amaury Denoyelle
7cab3a3c3a BUG/MINOR: quic: fix ODCID lookup from derived value
Some checks failed
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
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.
2026-05-22 16:03:10 +02:00
Christopher Faulet
04b9215a2e BUG/MEDIUM: ssl-gencert: Unlock LRU cache if failing to generate certificate
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
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.
2026-05-22 11:37:00 +02:00
Christopher Faulet
75f72c2eb9 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.
2026-05-22 11:13:33 +02:00
Christopher Faulet
1ed4ef6659 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.
2026-05-22 08:45:57 +02:00
Amaury Denoyelle
3fab21ea42 MINOR: mux_quic: do not crash on unhandled QMux frame reception
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
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().
2026-05-21 15:57:20 +02:00
Amaury Denoyelle
f9d4d659a4 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.
2026-05-21 15:57:20 +02:00
Amaury Denoyelle
c0aa91a202 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.
2026-05-21 15:57:20 +02:00
Remi Tricot-Le Breton
e2c3cd9eb7 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.
2026-05-21 15:43:49 +02:00
Amaury Denoyelle
6717531053 MINOR: backend: support QMux in clear for BE side
Some checks failed
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
Use xprt_add_l6hs() at the end of connect_server() if selected MUX layer
relies on a temporary handshake prior to its initialization. This
functions is noop is SSL layer is active.

This change is necessary to support clear QMux on the backend side.
Recently defined <init_xprt> from mux_proto_list is used to render the
code as generic as possible.
2026-05-21 15:09:10 +02:00
Amaury Denoyelle
812962d110 MINOR: session: support QMux in clear on FE side
Activates xprt_qmux layer if necessary via session_accept_fd(). This is
necessary to be able to support QMux in clear. This operation is noop if
SSL is active, as in this case xprt_qmux will be activated after the SSL
handshake completion.

To ensure MUX init is delayed when running with clear QMux, mask
CO_FL_WAIT_XPRT_L6 is added to test if the embryonic task must be
started instead.
2026-05-21 15:09:10 +02:00
Amaury Denoyelle
8fe8f78473 MINOR: connection: define mask CO_FL_WAIT_XPRT_L6
Define a new connection flag mask CO_FL_WAIT_XPRT_L6. This will be used
to indicate that a XPRT layer is running on top of layer 6. For now,
only xprt_qmux implements this method of operation.
2026-05-21 15:09:10 +02:00
Amaury Denoyelle
cdeb2aa4ef MINOR: xprt_qmux: define default value for get_alpn
Extend get_alpn() for xprt_qmux layer. If lower layer does not implement
ALPN negotiation, return a statically default protocol value. Currently
this is set to "h3".

This change is required to support QMux in clear without SSL. In the
future, it could be useful to configure the default protocol, for
example by extending the syntax for the "proto" keyword.
2026-05-21 15:09:10 +02:00
Amaury Denoyelle
9e6e0fd149 MINOR: connection: define xprt_add_l6hs()
When QMux protocol is used, xprt_qmux layer is setup after SSL handshake
completion but prior to the MUX initialization. Once transport
parameters exchange is successful, the layer is removed and the MUX is
started.

The layer setup operation was performed directly on ssl_sock_io_cb().
Simplify the code by extracting it in a dedicated function
xprt_add_l6hs(). The function is generic so the requested XPRT layer
must be passed as argument.

The code is mostly identical. One difference is that a check is
performed to ensure no SSL handshake is pending. If this is the case,
the function is a noop. This will become useful to support QMux
transparently both in clear or on top of SSL.

Another minor addition is that CO_FL_XPRT_READY flag is automatically
resetted by xprt_add_l6hs(). This allows the code to use
conn_xprt_start() standard function after XPRT init.
2026-05-21 15:09:10 +02:00
Amaury Denoyelle
e98595e4e5 MINOR: ssl_sock: remove unneeded check on QMux flags
A recent patch has introduced <init_xprt> mux_proto_list member. This
allows to activate QMux on SSL handshake completion without explicit
"proto qmux" setting.

Thanks to this change, on SSL handshake completion it is not necessary
anymore to check for CO_FL_QMUX_* flags.
2026-05-21 15:09:10 +02:00
Willy Tarreau
413f6f9a1f BUG/MEDIUM: net_helper: fix a remaining possibly infinite loop in converters
The various tcp_option_* converters rely on tcp_fullhdr_find_opt() to
find the option. However, the same bug as fixed in commit dbf471f99a
("BUG/MAJOR: net_helper: ip.fp infinite loop on malformed tcp options")
was also present there, by which an option of length 0 could be looped
over indefinitely. In practice this does not happen since such options
are not valid, but if passed encoded in an HTTP header for example, it
could possibly be passed.

While fixing it, let's check for length >1 in all 3 locations insteead
of only non-zero, since there's no point processing a malformed option
that wouldn't even be properly skipped.

This fix doesn't need to be backported, unless the ip.fp series is.

Thanks to @Vincent55 for reporting this issue.
2026-05-21 15:05:39 +02:00
Willy Tarreau
3475a5bb9f BUILD: proxy: unstatify the proxies_del_lock to avoid a warning without threads
When threads are disabled, "static __decl_spinlock(foo);" ends up as
"static;", causing a build warning when threads are disabled. We don't
need it to be static so let's drop "static" here. No backport is needed,
this is 3.4-only.
2026-05-21 09:03:03 +02:00
Willy Tarreau
050e06dd66 MINOR: config: shm-stats-file is no longer experimental
As confirmed by Aurlien, there isn't any point in keeping this feature
in experimental status, it's now stable.
2026-05-21 08:50:20 +02:00
128 changed files with 2519 additions and 704 deletions

View file

@ -1,18 +0,0 @@
FreeBSD_task:
freebsd_instance:
matrix:
image_family: freebsd-14-3
only_if: $CIRRUS_BRANCH =~ 'master|next'
install_script:
- pkg update -f && pkg upgrade -y && pkg install -y openssl git gmake lua54 socat pcre2
script:
- sudo sysctl kern.corefile=/tmp/%N.%P.core
- sudo sysctl kern.sugid_coredump=1
- scripts/build-vtest.sh
- gmake CC=clang V=1 ERR=1 TARGET=freebsd USE_ZLIB=1 USE_PCRE2=1 USE_PCRE2_JIT=1 USE_OPENSSL=1 USE_LUA=1 LUA_INC=/usr/local/include/lua54 LUA_LIB=/usr/local/lib LUA_LIB_NAME=lua-5.4
- ./haproxy -vv
- ldd haproxy
test_script:
- env VTEST_PROGRAM=../vtest/vtest gmake reg-tests REGTESTS_TYPES=default,bug,devel
on_failure:
debug_script: (for folder in /tmp/*regtest*/vtc.*; do cat $folder/INFO $folder/LOG; done && ls /tmp/haproxy.*.core && gdb -ex 'thread apply all bt full' ./haproxy /tmp/haproxy.*.core)

38
.github/workflows/freebsd.yml vendored Normal file
View file

@ -0,0 +1,38 @@
name: FreeBSD
on:
push:
branches:
- master
- next
workflow_dispatch:
permissions:
contents: read
jobs:
clang:
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'haproxy' || github.event_name == 'workflow_dispatch' }}
steps:
- name: "Checkout repository"
uses: actions/checkout@v6
- name: "Build and test on FreeBSD"
uses: vmactions/freebsd-vm@v1
with:
release: "14.3"
prepare: |
pkg update -f && pkg upgrade -y && pkg install -y openssl git gmake lua54 socat pcre2 python3
run: |
sysctl kern.corefile=/tmp/%N.%P.core
sysctl kern.sugid_coredump=1
scripts/build-vtest.sh
gmake CC=clang V=1 ERR=1 TARGET=freebsd USE_ZLIB=1 USE_PCRE2=1 USE_PCRE2_JIT=1 USE_OPENSSL=1 USE_LUA=1 LUA_INC=/usr/local/include/lua54 LUA_LIB=/usr/local/lib LUA_LIB_NAME=lua-5.4
./haproxy -vv
ldd haproxy
if ! env VTEST_PROGRAM=../vtest/vtest gmake reg-tests REGTESTS_TYPES=default,bug,devel; then
for folder in /tmp/*regtest*/vtc.*; do cat $folder/INFO $folder/LOG; done
ls /tmp/haproxy.*.core 2>/dev/null && gdb -ex 'thread apply all bt full' ./haproxy /tmp/haproxy.*.core
exit 1
fi

View file

@ -2,7 +2,7 @@ name: Illumos
on:
schedule:
- cron: "0 0 25 * *"
- cron: "0 3 * * 1"
workflow_dispatch:
permissions:

180
CHANGELOG
View file

@ -1,6 +1,186 @@
ChangeLog :
===========
2026/06/03 : 3.5-dev0
- MINOR: version: mention that it's development again
2026/06/03 : 3.4.0
- BUG/MINOR: tcpcheck: Check LDAP response to not read more data than available
- BUG/MINOR: ssl-gencert: validate SNI characters to prevent SAN certificate injection
- BUG/MINOR: mux-h1: H2 preface rejection doesn't update stick-table glitches
- BUG/MEDIUM: cpu-topo: Enforce thread-hard-limit on policy
- BUG/MEDIUM: qmux: do not crash on too large record
- BUG/MEDIUM: qmux: do not crash on receiving an invalid first frame
- BUG/MINOR: qmux: reject too large initial record
- Revert "BUG/MEDIUM: dns: fix long loops in additional records parse on name failure"
- BUG/MINOR: qpack: Fix index calculation in debug functions
- BUG/MINOR: qpack: fix potential null-pointer dereference in qpack_dht_insert()
- CLEANUP: qpack: fix copy-paste typo in value Huffman debug string
- BUG/MINOR: qpack: fix sign bit mask in qpack_decode_fs_pfx()
- CLEANUP: qpack: fix copy-paste typo in value Huffman debug string for WLN
- BUG/MINOR: qpack: fix huff_dec() error handling in qpack_decode_fs()
- CLEANUP: qpack: move encoded macros to qpack-t.h to avoid duplication
- BUG/MEDIUM: quic: handle ECONNREFUSED on RX side
- BUG/MINOR: quic: Fix memory leak in quic_deallocate_dghdlrs()
- BUG/MEDIUM: lua: defer Lua VM initialisation to the first Lua config keyword
- REGTESTS: lua: fix tune.lua.openlibs in Lua reg-tests
- BUG/MINOR: mux-h2: Count padding for connection flow control on error path
- BUILD: addons: convert 51d addon to EXTRA_MAKE
- BUILD: addons: convert deviceatlas addon to EXTRA_MAKE
- BUILD: addons: convert WURFL addon to EXTRA_MAKE
- MINOR: mux_quic/flags: add missing flags
- BUG/MINOR: mux_quic: open an idle QCS on reset on BE side
- BUG/MINOR: mux_quic: fix BE conn removal on app shutdown
- BUG/MINOR: mux_quic: prevent BE reuse with an errored conn
- BUG/MINOR: quic: fix ack range node pool_free call passing wrong pointer type
- MEDIUM: quic: optimize HKDF operations by reusing per-thread contexts
- BUG/MEDIUM: quic: reset cwnd in slow_start on persistent congestion (cubic)
- BUG/MEDIUM: quic: reset consecutive_losses on exit from recovery period (cubic)
- BUG/MINOR: quic: update drs->lost before calling on_ack_recv
- Revert "MEDIUM: quic: optimize HKDF operations by reusing per-thread contexts"
- BUG/MEDIUM: lua: register hlua_init() as a pre-check to fix crash without Lua config
- REGTESTS: quic: disable quic/ocsp_auto_update for now
- BUG/MINOR: threads: set at least grp_max when mtpg is too small
- BUG/MEDIUM: threads: ignore max-threads-per-group when thread-groups is set
- CLEANUP: thread: indicate when max-threads-per-group is ignored
- MINOR: cpu-topo: notify when cpu-policy is ignored due to other settings
- MINOR: thread: report when thread-groups or nbthread results in less threads
- BUILD: makefile: include EXTRA_MAKE in the .build_opts construction
- BUG/MINOR: quic: Fix another buffer overflow with sockaddr_in46
- MINOR: quic: Copy sin6_flowinfo and sin6_scope_id too
- BUILD: Makefile: put EXTRA_MAKE help at the right place
- BUG/MINOR: cache: fix cache tree iteration
- BUG/MEDIUM: resolvers: Wait a bit before calling the xprt prepare_srv
- CLEANUP: addons/51degrees: initialize variables
- MINOR: addons/51degrees: handle memory allocation failures
- CLEANUP: ncbmbuf: improve handling of memory allocation errors in unit tests
- CLEANUP: admin/halog: improve handling of memory allocation errors
- DOC: internals: clarify ambiguous wording in core-principles
- DOC: internals: add a threat model definition
- DOC: add security.txt describing how to report security issues
- DOC: security: also add a note to exclude dev/ and admin/
- BUG/MEDIUM: qmux: Close connection on invalid frame
- CLEANUP: fix comment typo
- BUG/MEDIUM: h3: fix MAX_PUSH_ID handling
- BUG/MINOR: cache: Fix copy of value when parsing maxage
- BUG/MEDIUM: mux-h1: Dup connection/upgrade value to parse it when making headers
- BUG/MEDIUM: htx: Fix headers rollback on partial copy in htx_xfer()
- MINOR: deinit: release the in-memory copy of shared libs
- MINOR: debug: add -dA to dump an archive of all dependencies
- BUG/MEDIUM: ssl: Make sure the alpn length is small enough
- BUG/MINOR: applet: Commit changes into input buffer after sending HTX data
- BUG/MINOR: mux-spop: Fix possible off-by-one OOB read in spop_get_varint()
- BUG/MEDIUM: leastconn: Unlock the write lock on allocation failure
- BUG/MINOR: tasks: Increase the right niced_task counter
- BUILD: makefile: search for Lua 5.5 as well
- DEV: dev/gdb: improve ebtree pointer handling
- DEV: dev/gdb: add simple task dump
- DEV: dev/gdb: add simple thread dump
- DEV: dev/gdb: add fdtab dump
- DOC: config: add a few more explanation in http-reusee regarding sni-auto
- REGTESTS: add basic QMux tests
- BUG/MINOR: http-act: Properly handle final evaluation in pause action
- BUILD: makefile/lua: use the system's default library before all other variants
- BUG/MINOR: startup: unbreak chroot with CAP_SYS_CHROOT
- BUG/MINOR: haterm: do not try to bind QUIC when not supported
- BUG/MINOR: haterm: also apply the tcp-bind-opts to clear TCP "bind" lines
- CLEANUP: haterm: do not try to bind to SSL when not built in
- MINOR: haterm: enable ktls on the SSL bind line when supported
- CI: github: replace cirrus by a vmactions/freebsd-vm job
- BUILD: makefile: fix build error with GNU make 4.2.1 and /bin/dash
- BUG/MEDIUM: channel: Fix condition to know if a channel may send
- BUG/MEDIUM: vars: Properly eval set-var-fmt action for emtpy log-format string
- CI: github: run illumos job weekly on Mondays at 03:00 instead of monthly
- BUG/MEDIUM: stream: Don't use small buffer on queuing with a request data filter
- BUG/MINOR: jwe: don't write randoms past MAX_DECRYPTED_CEK_LEN in RSA_PKCS1_PADDING
- BUG/MEDIUM: chunk: do not rely on small trash by default for expressions
- CLEANUP: map: always test pat->ref in sample_conv_map_key()
- DEV: patchbot: prepare for new version 3.5-dev
- MINOR: version: mention that it's 3.4 LTS now.
2026/05/26 : 3.4-dev14
- 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
2026/05/20 : 3.4-dev13
- BUG/MINOR: backend: correct parameter value validation in get_server_ph_post()
- BUG/MINOR: config/dns: properly fail on duplicate nameserver name detection

View file

@ -11,7 +11,7 @@ this task seriously and are doing a good job at backporting important fixes.
If for any reason you would prefer a different version than the one packaged
for your system, you want to be certain to have all the fixes or to get some
commercial support, other choices are available at http://www.haproxy.com/.
commercial support, other choices are available at https://www.haproxy.com/.
Areas covered in this document
@ -426,9 +426,9 @@ Lua is an embedded programming language supported by HAProxy to provide more
advanced scripting capabilities. Only versions 5.3 and above are supported.
In order to enable Lua support, please specify "USE_LUA=1" on the command line.
Some systems provide this library under various names to avoid conflicts with
previous versions. By default, HAProxy looks for "lua5.4", "lua54", "lua5.3",
"lua53", "lua". If your system uses a different naming, you may need to set the
library name in the "LUA_LIB_NAME" variable.
previous versions. By default, HAProxy looks for "lua5.5", "lua55", "lua5.4",
"lua54", "lua5.3", "lua53", "lua". If your system uses a different naming, you
may need to set the library name in the "LUA_LIB_NAME" variable.
If Lua is not provided on your system, it can be very simply built locally. It
can be downloaded from https://www.lua.org/, extracted and built, for example :

View file

@ -61,7 +61,6 @@
# USE_OBSOLETE_LINKER : use when the linker fails to emit __start_init/__stop_init
# USE_THREAD_DUMP : use the more advanced thread state dump system. Automatic.
# USE_OT : enable the OpenTracing filter
# EXTRA_MAKE : space-separated list of external addons using a Makefile.inc
# USE_MEMORY_PROFILING : enable the memory profiler. Linux-glibc only.
# USE_LIBATOMIC : force to link with/without libatomic. Automatic.
# USE_PTHREAD_EMULATION : replace pthread's rwlocks with ours
@ -95,6 +94,7 @@
# SILENT_DEFINE may be used to specify other defines which will not be
# reported by "haproxy -vv".
# EXTRA is used to force building or not building some extra tools.
# EXTRA_MAKE space-separated list of external addons using a Makefile.inc
# DESTDIR is not set by default and is used for installation only.
# It might be useful to set DESTDIR if you want to install haproxy
# in a sandbox.
@ -124,7 +124,7 @@
# LUA_LIB : force the lib path to lua
# LUA_INC : force the include path to lua
# LUA_LIB_NAME : force the lib name (or automatically evaluated, by order of
# priority : lua5.4, lua54, lua5.3, lua53, lua).
# priority: lua5.5, lua55, lua5.4, lua54, lua5.3, lua53, lua).
# OT_DEBUG : compile the OpenTracing filter in debug mode
# OT_INC : force the include path to libopentracing-c-wrapper
# OT_LIB : force the lib path to libopentracing-c-wrapper
@ -684,15 +684,15 @@ OPTIONS_OBJS += src/quic_openssl_compat.o
endif
ifneq ($(USE_LUA:0=),)
check_lua_inc = $(shell if [ -d $(2)$(1) ]; then echo $(2)$(1); fi;)
LUA_INC := $(firstword $(foreach lib,lua5.4 lua54 lua5.3 lua53 lua,$(call check_lua_inc,$(lib),"/usr/include/")))
check_lua_inc = $(shell if [ ! -e /usr/include/lua.h -a -e $(2)$(1)/lua.h ]; then echo $(2)$(1); fi;)
LUA_INC := $(firstword $(foreach lib,lua5.5 lua55 lua5.4 lua54 lua5.3 lua53 lua,$(call check_lua_inc,$(lib),"/usr/include/")))
check_lua_lib = $(shell echo "int main(){}" | $(CC) -o /dev/null -x c - $(2) -l$(1) 2>/dev/null && echo $(1))
check_lua_lib = $(shell echo "int main(){}" | $(CC) $(if $(LUA_INC),-I$(LUA_INC)) -o /dev/null -x c - $(2) -l$(1) 2>/dev/null && echo $(1))
LUA_LD_FLAGS := -Wl,$(if $(EXPORT_SYMBOL),$(EXPORT_SYMBOL),--export-dynamic) $(if $(LUA_LIB),-L$(LUA_LIB))
# Try to automatically detect the Lua library if not set
ifeq ($(LUA_LIB_NAME),)
LUA_LIB_NAME := $(firstword $(foreach lib,lua5.4 lua54 lua5.3 lua53 lua,$(call check_lua_lib,$(lib),$(LUA_LD_FLAGS))))
LUA_LIB_NAME := $(firstword $(foreach lib,lua lua5.5 lua55 lua5.4 lua54 lua5.3 lua53,$(call check_lua_lib,$(lib),$(LUA_LD_FLAGS))))
endif
# Lua lib name must be set now (forced/detected above)
@ -722,70 +722,15 @@ ifneq ($(USE_PROMEX:0=),)
endif
ifneq ($(USE_DEVICEATLAS:0=),)
# Use DEVICEATLAS_SRC and possibly DEVICEATLAS_INC and DEVICEATLAS_LIB to force path
# to DeviceAtlas headers and libraries if needed. In this context, DEVICEATLAS_NOCACHE
# can be used to disable the cache support if needed (this also removes the necessity of having
# a C++ toolchain installed).
DEVICEATLAS_INC = $(DEVICEATLAS_SRC)
DEVICEATLAS_LIB = $(DEVICEATLAS_SRC)
include addons/deviceatlas/Makefile.inc
OPTIONS_OBJS += addons/deviceatlas/da.o
EXTRA_MAKE += addons/deviceatlas
endif
# Use 51DEGREES_SRC and possibly 51DEGREES_INC and 51DEGREES_LIB to force path
# to 51degrees v3/v4 headers and libraries if needed. Note that the SRC/INC/
# LIB/CFLAGS/LDFLAGS variables names all use 51DEGREES as the prefix,
# regardless of the version since they are mutually exclusive. The version
# (51DEGREES_VER) must be either 3 or 4, and defaults to 3 if not set.
51DEGREES_INC = $(51DEGREES_SRC)
51DEGREES_LIB = $(51DEGREES_SRC)
51DEGREES_VER = 3
ifneq ($(USE_51DEGREES:0=),)
ifeq ($(51DEGREES_VER),4) # v4 here
_51DEGREES_SRC = $(shell find $(51DEGREES_LIB) -maxdepth 2 -name '*.c')
OPTIONS_OBJS += $(_51DEGREES_SRC:%.c=%.o)
51DEGREES_CFLAGS += -DUSE_51DEGREES_V4
ifeq ($(USE_THREAD:0=),)
51DEGREES_CFLAGS += -DFIFTYONEDEGREES_NO_THREADING -DFIFTYONE_DEGREES_NO_THREADING
endif
USE_LIBATOMIC = implicit
endif # 51DEGREES_VER==4
ifeq ($(51DEGREES_VER),3) # v3 here
OPTIONS_OBJS += $(51DEGREES_LIB)/../cityhash/city.o
OPTIONS_OBJS += $(51DEGREES_LIB)/51Degrees.o
ifeq ($(USE_THREAD:0=),)
51DEGREES_CFLAGS += -DFIFTYONEDEGREES_NO_THREADING
else
OPTIONS_OBJS += $(51DEGREES_LIB)/../threading.o
endif
else
ifneq ($(51DEGREES_VER),4)
$(error 51Degrees version (51DEGREES_VER) must be either 3 or 4)
endif
endif # 51DEGREES_VER==3
OPTIONS_OBJS += addons/51degrees/51d.o
51DEGREES_CFLAGS += $(if $(51DEGREES_INC),-I$(51DEGREES_INC))
51DEGREES_LDFLAGS += $(if $(51DEGREES_LIB),-L$(51DEGREES_LIB))
USE_MATH = implicit
EXTRA_MAKE += addons/51degrees
endif # USE_51DEGREES
ifneq ($(USE_WURFL:0=),)
# Use WURFL_SRC and possibly WURFL_INC and WURFL_LIB to force path
# to WURFL headers and libraries if needed.
WURFL_INC = $(WURFL_SRC)
WURFL_LIB = $(WURFL_SRC)
OPTIONS_OBJS += addons/wurfl/wurfl.o
WURFL_CFLAGS = $(if $(WURFL_INC),-I$(WURFL_INC))
ifneq ($(WURFL_DEBUG),)
WURFL_CFLAGS += -DWURFL_DEBUG
endif
ifneq ($(WURFL_HEADER_WITH_DETAILS),)
WURFL_CFLAGS += -DWURFL_HEADER_WITH_DETAILS
endif
WURFL_LDFLAGS = $(if $(WURFL_LIB),-L$(WURFL_LIB)) -lwurfl
EXTRA_MAKE += addons/wurfl
endif
ifneq ($(USE_PCRE:0=)$(USE_STATIC_PCRE:0=)$(USE_PCRE_JIT:0=),)
@ -1058,7 +1003,7 @@ IGNORE_OPTS=help install install-man install-doc install-bin \
ifneq ($(TARGET),)
ifeq ($(filter $(firstword $(MAKECMDGOALS)),$(IGNORE_OPTS)),)
build_opts = $(shell rm -f .build_opts.new; echo \'$(TARGET) $(BUILD_OPTIONS) $(VERBOSE_CFLAGS) $(WARN_CFLAGS) $(NOWARN_CFLAGS) $(DEBUG)\' > .build_opts.new; if cmp -s .build_opts .build_opts.new; then rm -f .build_opts.new; else mv -f .build_opts.new .build_opts; fi)
build_opts = $(shell rm -f .build_opts.new; echo \'$(TARGET) $(BUILD_OPTIONS) $(EXTRA_MAKE) $(VERBOSE_CFLAGS) $(WARN_CFLAGS) $(NOWARN_CFLAGS) $(DEBUG)\' > .build_opts.new; if cmp -s .build_opts .build_opts.new; then rm -f .build_opts.new; else mv -f .build_opts.new .build_opts; fi)
.build_opts: $(build_opts)
else
.build_opts:

View file

@ -4,7 +4,7 @@
[![Illumos](https://github.com/haproxy/haproxy/actions/workflows/illumos.yml/badge.svg)](https://github.com/haproxy/haproxy/actions/workflows/illumos.yml)
[![NetBSD](https://github.com/haproxy/haproxy/actions/workflows/netbsd.yml/badge.svg)](https://github.com/haproxy/haproxy/actions/workflows/netbsd.yml)
[![CrossCompile](https://github.com/haproxy/haproxy/actions/workflows/cross-zoo.yml/badge.svg)](https://github.com/haproxy/haproxy/actions/workflows/cross-zoo.yml)
[![FreeBSD](https://api.cirrus-ci.com/github/haproxy/haproxy.svg?task=FreeBSD)](https://cirrus-ci.com/github/haproxy/haproxy/)
[![FreeBSD](https://github.com/haproxy/haproxy/actions/workflows/freebsd.yml/badge.svg)](https://github.com/haproxy/haproxy/actions/workflows/freebsd.yml)
[![VTest](https://github.com/haproxy/haproxy/actions/workflows/vtest.yml/badge.svg)](https://github.com/haproxy/haproxy/actions/workflows/vtest.yml)
![HAProxy logo](doc/HAProxyCommunityEdition_60px.png)

View file

@ -1,2 +1,2 @@
$Format:%ci$
2026/05/20
2026/06/03

View file

@ -1 +1 @@
3.4-dev13
3.5-dev0

View file

@ -127,7 +127,16 @@ static int _51d_property_name_list(char **args, int section_type, struct proxy *
while (*(args[cur_arg])) {
name = calloc(1, sizeof(*name));
if (!name) {
memprintf(err, "'%s' failed to allocate memory.", args[0]);
return -1;
}
name->name = strdup(args[cur_arg]);
if (!name->name) {
free(name);
memprintf(err, "'%s' failed to allocate memory.", args[0]);
return -1;
}
LIST_APPEND(&global_51degrees.property_names, &name->list);
++cur_arg;
}
@ -303,6 +312,7 @@ static void _51d_init_device_offsets(fiftyoneDegreesDeviceOffsets *offsets) {
static void _51d_set_device_offsets(struct sample *smp, fiftyoneDegreesDeviceOffsets *offsets)
{
struct buffer *temp = get_trash_chunk();
struct channel *chn;
struct htx *htx;
struct http_hdr_ctx ctx;
@ -324,7 +334,15 @@ static void _51d_set_device_offsets(struct sample *smp, fiftyoneDegreesDeviceOff
if (http_find_header(htx, name, &ctx, 1)) {
(offsets->firstOffset + offsets->size)->httpHeaderOffset = *(global_51degrees.header_offsets + i);
(offsets->firstOffset + offsets->size)->deviceOffset = fiftyoneDegreesGetDeviceOffset(&global_51degrees.data_set, ctx.value.ptr);
/* Copy value into trash and NUL-terminate before passing to the
* 51Degrees Trie API, which expects a C string.
*/
if (ctx.value.len >= temp->size)
continue;
memcpy(temp->area, ctx.value.ptr, ctx.value.len);
temp->area[ctx.value.len] = '\0';
temp->data = ctx.value.len + 1;
(offsets->firstOffset + offsets->size)->deviceOffset = fiftyoneDegreesGetDeviceOffset(&global_51degrees.data_set, temp->area);
offsets->size++;
}
}
@ -919,6 +937,10 @@ static int init_51degrees(void)
list_for_each_entry(name, &global_51degrees.property_names, list)
++i;
_51d_property_list = calloc(i, sizeof(*_51d_property_list));
if (!_51d_property_list) {
ha_alert("51Degrees: Failed to allocate property list.\n");
return (ERR_FATAL | ERR_ALERT);
}
i = 0;
list_for_each_entry(name, &global_51degrees.property_names, list)
@ -1053,7 +1075,7 @@ static int init_51degrees(void)
static void deinit_51degrees(void)
{
struct _51d_property_names *_51d_prop_name, *_51d_prop_nameb;
struct _51d_property_names *_51d_prop_name = NULL, *_51d_prop_nameb = NULL;
#if defined(FIFTYONEDEGREES_H_PATTERN_INCLUDED) || defined(FIFTYONEDEGREES_H_TRIE_INCLUDED)
free(global_51degrees.header_names);

View file

@ -0,0 +1,37 @@
# Use 51DEGREES_SRC and possibly 51DEGREES_INC and 51DEGREES_LIB to force path
# to 51degrees v3/v4 headers and libraries if needed. Note that the SRC/INC/
# LIB/CFLAGS/LDFLAGS variables names all use 51DEGREES as the prefix,
# regardless of the version since they are mutually exclusive. The version
# (51DEGREES_VER) must be either 3 or 4, and defaults to 3 if not set.
51DEGREES_INC = $(51DEGREES_SRC)
51DEGREES_LIB = $(51DEGREES_SRC)
51DEGREES_VER = 3
ifeq ($(51DEGREES_VER),4) # v4 here
_51DEGREES_SRC = $(shell find $(51DEGREES_LIB) -maxdepth 2 -name '*.c')
OPTIONS_OBJS += $(_51DEGREES_SRC:%.c=%.o)
51DEGREES_CFLAGS += -DUSE_51DEGREES_V4
ifeq ($(USE_THREAD:0=),)
51DEGREES_CFLAGS += -DFIFTYONEDEGREES_NO_THREADING -DFIFTYONE_DEGREES_NO_THREADING
endif
USE_LIBATOMIC = implicit
endif # 51DEGREES_VER==4
ifeq ($(51DEGREES_VER),3) # v3 here
OPTIONS_OBJS += $(51DEGREES_LIB)/../cityhash/city.o
OPTIONS_OBJS += $(51DEGREES_LIB)/51Degrees.o
ifeq ($(USE_THREAD:0=),)
51DEGREES_CFLAGS += -DFIFTYONEDEGREES_NO_THREADING
else
OPTIONS_OBJS += $(51DEGREES_LIB)/../threading.o
endif
else
ifneq ($(51DEGREES_VER),4)
$(error 51Degrees version (51DEGREES_VER) must be either 3 or 4)
endif
endif # 51DEGREES_VER==3
OPTIONS_OBJS += addons/51degrees/51d.o
51DEGREES_CFLAGS += $(if $(51DEGREES_INC),-I$(51DEGREES_INC))
51DEGREES_LDFLAGS += $(if $(51DEGREES_LIB),-L$(51DEGREES_LIB))
USE_MATH = implicit

View file

@ -1,6 +1,13 @@
# DEVICEATLAS_SRC : DeviceAtlas API source root path
# Use DEVICEATLAS_SRC and possibly DEVICEATLAS_INC and DEVICEATLAS_LIB to force path
# to DeviceAtlas headers and libraries if needed. In this context, DEVICEATLAS_NOCACHE
# can be used to disable the cache support if needed (this also removes the necessity of having
# a C++ toolchain installed).
DEVICEATLAS_INC = $(DEVICEATLAS_SRC)
DEVICEATLAS_LIB = $(DEVICEATLAS_SRC)
CXX := c++
CXXLIB := -lstdc++
@ -28,5 +35,7 @@ OPTIONS_OBJS += $(DEVICEATLAS_SRC)/dadwcurl.o
OPTIONS_OBJS += $(DEVICEATLAS_SRC)/Os/daunix.o
endif
OPTIONS_OBJS += addons/deviceatlas/da.o
addons/deviceatlas/dummy/%.o: addons/deviceatlas/dummy/%.cpp
$(cmd_CXX) $(CXXFLAGS) -c -o $@ $<

13
addons/wurfl/Makefile.mk Normal file
View file

@ -0,0 +1,13 @@
# Use WURFL_SRC and possibly WURFL_INC and WURFL_LIB to force path
# to WURFL headers and libraries if needed.
WURFL_INC = $(WURFL_SRC)
WURFL_LIB = $(WURFL_SRC)
OPTIONS_OBJS += addons/wurfl/wurfl.o
WURFL_CFLAGS = $(if $(WURFL_INC),-I$(WURFL_INC))
ifneq ($(WURFL_DEBUG),)
WURFL_CFLAGS += -DWURFL_DEBUG
endif
ifneq ($(WURFL_HEADER_WITH_DETAILS),)
WURFL_CFLAGS += -DWURFL_HEADER_WITH_DETAILS
endif
WURFL_LDFLAGS = $(if $(WURFL_LIB),-L$(WURFL_LIB)) -lwurfl

View file

@ -1801,6 +1801,8 @@ void filter_count_ip(const char *source_field, const char *accept_field, const c
*/
if (unlikely(!ustat))
ustat = calloc(1, sizeof(*ustat));
if (!ustat)
return;
ustat->nb_err = err;
ustat->nb_req = 1;

View file

@ -26,7 +26,7 @@ end
# returns $node filled with the first node of ebroot $arg0
define ebtree_first
# browse ebtree left until encountering leaf
set $node = (struct eb_node *)$arg0->b[0]
set $node = (struct eb_node *)((struct eb_root*)$arg0)->b[0]
while 1
_ebtree_set_tag_node $node
if $tag == 0
@ -41,7 +41,7 @@ end
# finds next ebtree node after $arg0, and returns it in $node
define ebtree_next
# get parent
set $node = (struct eb_root *)$arg0->leaf_p
set $node = (struct eb_root *)((struct eb_node *)$arg0)->leaf_p
# Walking up from right branch, so we cannot be below root
# while (eb_gettag(t) != EB_LEFT) // #define EB_LEFT 0
while 1

39
dev/gdb/fd.gdb Normal file
View file

@ -0,0 +1,39 @@
# list info about open FD
define fd_dump
set $f = 0
while $f < $g.maxsock
if fdtab[$f].owner != 0
printf "fd %5d: rm=%#lx tm=%#lx um=%#lx cb=%p ownr=%p st=%#x refc=%#x tkov=%u gen=%u\n", $f, fdtab[$f].running_mask, fdtab[$f].thread_mask, fdtab[$f].update_mask, fdtab[$f].iocb, fdtab[$f].owner, fdtab[$f].state, fdtab[$f].refc_tgid, fdtab[$f].nb_takeover, fdtab[$f].generation
end
set $f = $f + 1
end
end
# only those attached to a listener
define fd_dump_listener
set $f = 0
while $f < $g.maxsock
if fdtab[$f].owner != 0 && fdtab[$f].iocb == &sock_accept_iocb
set $c = (struct listener *)fdtab[$f].owner
printf "fd %5d: rm=%#lx tm=%#lx um=%#lx st=%#x refc=%#x tkov=%u gen=%u listener=%p(%s): flg=%#x state=%d fe=%p(%s) acc=%p\n", $f, fdtab[$f].running_mask, fdtab[$f].thread_mask, fdtab[$f].update_mask, fdtab[$f].state, fdtab[$f].refc_tgid, fdtab[$f].nb_takeover, fdtab[$f].generation, fdtab[$f].owner, $c->name, $c->flags, $c->state, $c->bind_conf.frontend, $c->bind_conf.frontend.id, $c->bind_conf.accept
end
set $f = $f + 1
end
end
# only those attached to a connection
define fd_dump_conn
set $f = 0
while $f < $g.maxsock
if fdtab[$f].owner != 0 && fdtab[$f].iocb == &sock_conn_iocb
set $c = (struct connection *)fdtab[$f].owner
printf "fd %5d: rm=%#lx tm=%#lx um=%#lx st=%#x refc=%#x tkov=%u gen=%u conn=%p: flg=%#x err=%#x ctrl=%p xprt=%p mux=%p", $f, fdtab[$f].running_mask, fdtab[$f].thread_mask, fdtab[$f].update_mask, fdtab[$f].state, fdtab[$f].refc_tgid, fdtab[$f].nb_takeover, fdtab[$f].generation, fdtab[$f].owner, $c->flags, $c->err_code, $c->ctrl, $c->xprt, $c->mux
if *$c->target == OBJ_TYPE_LISTENER
set $s = (struct session *)$c->owner
printf " sess=%p: fe=%p id=%s age=%dms", $s, $s->fe, $s->fe->id, (*global_now_ns - $s->accept_ts) / 1000000
end
printf "\n"
end
set $f = $f + 1
end
end

31
dev/gdb/task.gdb Normal file
View file

@ -0,0 +1,31 @@
# lists all tasks in the wait queue whose ebroot pointed to by $arg0
# e.g.
# task_dump_wq &ha_tgroup_ctx[0].timers
# task_dump_wq &ha_thread_ctx[0].timers
#
define task_dump_rq
set $tot=0
ebtree_first ($arg0)
while ($node != 0)
set $tot = $tot+1
set $p = (struct task *)((void*)$node-(long)&((struct task*)0).rq)
printf "task %p ",$p
p -pretty off -- /a *$p
ebtree_next $node
end
printf "Total: %d tasks.\n",$tot
end
define task_dump_wq
set $tot=0
ebtree_first ($arg0)
while ($node != 0)
set $tot = $tot+1
set $p = (struct task *)((void*)$node-(long)&((struct task*)0).wq)
printf "task %p ",$p
p -pretty off -- /a *$p
ebtree_next $node
end
printf "Total: %d tasks.\n",$tot
end

10
dev/gdb/thread.gdb Normal file
View file

@ -0,0 +1,10 @@
# list info about current threads (ptr, now_ms, queue, current)
define thread_dump
set $t = 0
while $t < $g.nbthread
set $i = $ti[$t].pth_id
set $h = $tc[$t].current
printf "Tid %4d: pth=%p mono=%llu now_ms=%u fl=0x%02x rq=%d cq=%d current=%p\n", $t, $i, $tc[$t].curr_mono_time, (unsigned)(($tc[$t].curr_mono_time + now_offset)/1000000), $tc[$t].flags, $tc[$t].current_queue, $tc[$t].rq_total, $h
set $t = $t + 1
end
end

View file

@ -0,0 +1,70 @@
BEGININPUT
BEGINCONTEXT
HAProxy's development cycle consists in one development branch, and multiple
maintenance branches.
All the development is made into the development branch exclusively. This
includes mostly new features, doc updates, cleanups and or course, fixes.
The maintenance branches, also called stable branches, never see any
development, and only receive ultra-safe fixes for bugs that affect them,
that are picked from the development branch.
Branches are numbered in 0.1 increments. Every 6 months, upon a new major
release, the development branch enters maintenance and a new development branch
is created with a new, higher version. The current development branch is
3.5-dev, and maintenance branches are 3.4 and below.
Fixes created in the development branch for issues that were introduced in an
earlier branch are applied in descending order to each and every version till
that branch that introduced the issue: 3.4 first, then 3.2, then 3.1, then 3.0
and so on. This operation is called "backporting". A fix for an issue is never
backported beyond the branch that introduced the issue. An important point is
that the project maintainers really aim at zero regression in maintenance
branches, so they're never willing to take any risk backporting patches that
are not deemed strictly necessary.
Fixes consist of patches managed using the Git version control tool and are
identified by a Git commit ID and a commit message. For this reason we
indistinctly talk about backporting fixes, commits, or patches; all mean the
same thing. When mentioning commit IDs, developers always use a short form
made of the first 8 characters only, and expect the AI assistant to do the
same.
It seldom happens that some fixes depend on changes that were brought by other
patches that were not in some branches and that will need to be backported as
well for the fix to work. In this case, such information is explicitly provided
in the commit message by the patch's author in natural language.
Developers are serious and always indicate if a patch needs to be backported.
Sometimes they omit the exact target branch, or they will say that the patch is
"needed" in some older branch, but it means the same. If a commit message
doesn't mention any backport instructions, it means that the commit does not
have to be backported. And patches that are not strictly bug fixes nor doc
improvements are normally not backported. For example, fixes for design
limitations, architectural improvements and performance optimizations are
considered too risky for a backport. Finally, all bug fixes are tagged as
"BUG" at the beginning of their subject line. Patches that are not tagged as
such are not bugs, and must never be backported unless their commit message
explicitly requests so.
ENDCONTEXT
A developer is reviewing the development branch, trying to spot which commits
need to be backported to maintenance branches. This person is already expert
on HAProxy and everything related to Git, patch management, and the risks
associated with backports, so he doesn't want to be told how to proceed nor to
review the contents of the patch.
The goal for this developer is to get some help from the AI assistant to save
some precious time on this tedious review work. In order to do a better job, he
needs an accurate summary of the information and instructions found in each
commit message. Specifically he needs to figure if the patch fixes a problem
affecting an older branch or not, if it needs to be backported, if so to which
branches, and if other patches need to be backported along with it.
The indented text block below after an "id" line and starting with a Subject line
is a commit message from the HAProxy development branch that describes a patch
applied to that branch, starting with its subject line, please read it carefully.

View file

@ -0,0 +1,29 @@
ENDINPUT
BEGININSTRUCTION
You are an AI assistant that follows instruction extremely well. Help as much
as you can, responding to a single question using a single response.
The developer wants to know if he needs to backport the patch above to fix
maintenance branches, for which branches, and what possible dependencies might
be mentioned in the commit message. Carefully study the commit message and its
backporting instructions if any (otherwise it should probably not be backported),
then provide a very concise and short summary that will help the developer decide
to backport it, or simply to skip it.
Start by explaining in one or two sentences what you recommend for this one and why.
Finally, based on your analysis, give your general conclusion as "Conclusion: X"
where X is a single word among:
- "yes", if you recommend to backport the patch right now either because
it explicitly states this or because it's a fix for a bug that affects
a maintenance branch (3.4 or lower);
- "wait", if this patch explicitly mentions that it must be backported, but
only after waiting some time.
- "no", if nothing clearly indicates a necessity to backport this patch (e.g.
lack of explicit backport instructions, or it's just an improvement);
- "uncertain" otherwise for cases not covered above
ENDINSTRUCTION
Explanation:

View file

@ -2,8 +2,8 @@
HAProxy
Configuration Manual
----------------------
version 3.4
2026/05/20
version 3.5
2026/06/03
This document covers the configuration language as implemented in the version
@ -3329,7 +3329,7 @@ setenv <name> <value>
the configuration file sees the new value. See also "presetenv", "resetenv",
and "unsetenv".
shm-stats-file <name> [ EXPERIMENTAL ]
shm-stats-file <name>
When this directive is set, it enables the use of shared memory for storing
stats counters. <name> is used as argument to shm_open() to open the shared
memory at a unique location. It also means that the directive is only
@ -3345,7 +3345,7 @@ shm-stats-file <name> [ EXPERIMENTAL ]
See also "guid", "guid-prefix" and "shm-stats-file-max-objects"
shm-stats-file-max-objects <number> [ EXPERIMENTAL ]
shm-stats-file-max-objects <number>
This setting defines the maximum number of objects the shared memory used
for shared counters will be able to store per thread group. It is directly
related to the maximum memory size of the shm and is used to "premap" the
@ -4940,8 +4940,8 @@ tune.lua.openlibs [all | none | <lib>[,<lib>...]]
tune.lua.openlibs string,math,table,utf8 # safe subset, no I/O or OS
tune.lua.openlibs all # default, load everything
This setting must be set before any "lua-load" or "lua-load-per-thread"
directive, otherwise a parse error is returned.
This setting must be set before any "lua-load", "lua-load-per-thread" or
"lua-prepend-path" directive, otherwise a parse error is returned.
tune.lua.service-timeout <timeout>
This is the execution timeout for the Lua services. This is useful for
@ -9105,7 +9105,9 @@ http-reuse { never | safe | aggressive | always }
- proxy protocol
- TOS and mark socket options
- connection name, determined either by the result of the evaluation of the
"pool-conn-name" expression if present, otherwise by the "sni" expression
"pool-conn-name" expression if present, otherwise by the "sni" expression,
which defaults to "req.hdr(host),field(1,:)", i.e. uses the incoming
request's "Host" header field without the colon nor the port number.
In some occasions, connection lookup or reuse is not performed due to extra
restrictions. This is determined by the reuse strategy specified via the
@ -9179,6 +9181,26 @@ http-reuse { never | safe | aggressive | always }
too few connections are kept open. It may be desirable in this case to adjust
such thresholds or simply to increase the global "maxconn" value.
In some rare cases, when the host name is used to distinguish outgoing TLS
connections (e.g. forward proxy), where most request target different hosts,
the reuse rate will be very low, and the automatic eviction of rarely used
connections will kick in before connections have a chance to be reused,
because the mechanism continuously measures the average number of connections
needed to deliver the service without exhausting resources. In such
situations, setting "pool-low-conn" to a value close to the average expected
number of idle connections may help preserve more connections by encouraging
threads to setup their own instead of trying to pick other threads' and
shrinking the pool of available connections.
If a locally hosted server uses a single certificate (with multiple host
names or wildcards) and operates multiple sites, it may be more effective to
just use "no-sni-auto" on the "server" line to avoid reserving a connection
to a single Host name. This will significantly increase the reuse rate. Some
servers might perform excessive checks between Host and SNI though, resulting
in rejecting subsequent requests, so this option requires preliminary
validation. The default behavior ("sni-auto") is to be safe even with such
servers.
When thread groups are explicitly enabled, it is important to understand that
idle connections are only usable between threads from a same group. As such
it may happen that unfair load between groups leads to more idle connections
@ -9962,7 +9984,7 @@ no option accept-unsafe-violations-in-http-request
When this option is set, the following rules are observed:
* In H1 only, invalid characters, including NULL character, in header name
will be accepted;
will not be rejected; however the header will be dropped.
* In H1 only, NULL character in header value will be accepted;
@ -10027,8 +10049,11 @@ no option accept-unsafe-violations-in-http-response
When this option is set, the following rules are observed:
* In H1 only, status codes longer than 3 digits but whose value fits in 16
bits are not rejected.
* In H1 only, invalid characters, including NULL character, in header name
will be accepted;
will not be rejected; however the header will be dropped.
* In H1 only, NULL character in header value will be accepted;
@ -19896,11 +19921,13 @@ sni-auto
May be used in the following contexts: tcp, http, log, peers, ring
The "sni-auto" parameter enables the automatic SNI selection, if no value was
already set. It is enabled by default but this parameter may be used as
"server" setting to reset any "no-sni-auto" setting which would have been
inherited from "default-server" directive as default value. It may also be
used as "default-server" setting to reset any previous "default-server"
"no-sni-auto" setting.
already set. It sets the "sni" expression to "req.hdr(host),field(1,:)",
which means that an SNI will be presented with the Host name of the request
that is being sent to the server, but dropping the port number. It is enabled
by default but this parameter may be used as "server" setting to reset any
"no-sni-auto" setting which would have been inherited from "default-server"
directive as default value. It may also be used as "default-server" setting
to reset any previous "default-server" "no-sni-auto" setting.
For HTTPS connections, the selected SNI is based on the request host header
value, if found. Otherwise it remains unset. For other protocols, the option

View file

@ -21,8 +21,11 @@ HAPROXY CORE PRINCIPLES
- Aliases: uchar (unsigned char), uint (unsigned int), ulong (unsigned long),
ushort (unsigned short), ullong (unsigned long long), llong (long long),
schar (signed char).
- size_t always same size as long but often declared as uint on 32-bit and
ulong on 64-bit. Do not use in printf() without a cast (ulong with "%lu").
- size_t is always the same size as long, but its underlying type is often
uint on 32-bit and ulong on 64-bit. This is a frequent source of build
errors on 32-bit platforms (e.g. passing a size_t where a long* is
expected, or printing one with "%lu"); always cast in printf() (ulong
with "%lu").
- Main platforms are x86_64 and aarch64 with high thread counts (>=64).
- Unaligned accesses are permitted for archs that support them; portable
wrappers in net_helper.h (read_u32(), write_u32() etc).
@ -36,7 +39,8 @@ HAPROXY CORE PRINCIPLES
3. MEMORY MANAGEMENT AND POOLS
- Pools are used for runtime allocation; malloc/free are for boot code only.
- pool_alloc() semantics match malloc(); the return must always be tested.
- pool_alloc() and malloc() are not interchangeable / compatible.
- pool_alloc() and malloc() are not interchangeable: memory obtained from one
must not be released using the other's free function.
- pool_free() semantics match free(); it is a no-op on NULL.
- pool_free() makes the pointer invalid immediately; it must not be touched
or passed to pool_free() again.
@ -57,8 +61,8 @@ HAPROXY CORE PRINCIPLES
- idempotent functions b_alloc() and b_free() use pools to manage the
storage area and check <size> to know if alloc/free still needed.
- a non-contiguous version exists (ncbuf, ncbmbuf), allowing holes anywhere
in data. The former mandates holes of at least 8 bytes. The second relies
on a bitmap of populated places.
in data. ncbuf mandates holes of at least 8 bytes, while ncbmbuf relies on
a bitmap of populated places.
- another string API exists, "ist", representing a pointer and a length in a
struct that is returned by inline functions and macros. It is described in
doc/internals/api/ist.txt
@ -73,8 +77,11 @@ HAPROXY CORE PRINCIPLES
- Chunks are used for linear operations like chunk_printf().
- Trash is a thread-local temporary buffer; scope stays within the caller.
- trash always the same size as a buffer (global.tune.bufsize).
- get_trash_chunk() provides up to 3 rotating thread-local trash chunks (with
a scope spanning from the call to the next function call).
- get_trash_chunk() provides rotating thread-local trash chunks. Since almost
any function may itself call get_trash_chunk(), a returned chunk is only
guaranteed valid until the next call into another function and must not be
held across such a call. The rotation lets a single function safely use up
to 3 distinct chunks at once for its own data manipulation.
- For longer lived trash chunks, alloc_trash_chunk() is available but must be
released using free_trash_chunk() on leaving.
- standard doubly-linked lists (struct list) are provided via macros LIST_*.
@ -103,8 +110,9 @@ HAPROXY CORE PRINCIPLES
"ti" current thread info.
- "tgid" always current tg number, "tg_ctx" current tg context.
- HA_ATOMIC_* for atomic operations on integers and pointers (includes load
and store). DWCAS available on some platforms but requires an equivalent
for other ones.
and store). DWCAS is available on some platforms but requires an equivalent
fallback on the others (possibly a more complex operation, e.g. emulation
using two or more CAS).
- The _HA_ATOMIC_* version (leading underscore) do not use barriers so these
must be explicit (__ha_barrier_*).
- Atomic loops must use CPU relaxation or exponential back-off.
@ -178,8 +186,9 @@ HAPROXY CORE PRINCIPLES
- Avoid static storage when persistence is not needed.
- Macros in uppercase unless they're used to wrap functions which then get a
leading underscore.
- Explicitly compare functions returning non-zero with 0 (e.g. strcmp) unless
they explicitly return a boolean (e.g. isalnum) or a pointer (e.g. strchr).
- Explicitly compare against 0 the return of functions that yield an integer
which is not a boolean (e.g. strcmp), unless they return a boolean (e.g.
isalnum) or a pointer (e.g. strchr).
- Unsigned int comparisons to zero never use >0 but !=0 to avoid signedness
mistakes.
- turn non-zero integer to boolean using "!" or "!!".

View file

@ -0,0 +1,233 @@
HAProxy Threat Model & Trust Boundaries
This document defines the security boundaries of HAProxy, explicitly outlining
what does and does not constitute a security vulnerability. Its purpose is to
give reporters, developers and reviewers a single, predictable basis for
judging an issue's real-world impact.
The project's strong preference is to fix issues quickly and in the open.
Public handling gets fixes to users sooner and spares the ecosystem
(distributions in particular) the heavy cost of embargo coordination, which in
practice has rarely served users. Private, coordinated disclosure is reserved
for the few cases whose real-world impact genuinely warrants it, judged from
the severity ordering (section 6) and the mitigations (section 5). An issue
that is technically in scope but contained in practice does not, by itself,
call for an embargo.
These boundaries apply strictly to officially supported, documented builds
running under a sane, production-ready configuration. Security guarantees are
explicitly voided when using opt-in unsafe knobs, undocumented behavior, or
experimental features. A configuration that merely lacks a recommended
hardening step (for instance, no chroot) does not by itself move a
client-triggered bug out of scope; the missing mitigation only widens the
blast radius (sections 5 and 6).
1. ASSETS TO PROTECT
HAProxy sits on the critical path of the services it fronts, so its
availability and the integrity and confidentiality of the configuration and
secrets it holds are all essential to protect. The assets below are not
ranked here; their relative severity is ranked in section 6.
- Integrity and confidentiality of the host and configuration: a compromise
of the network-facing worker must not extend to the filesystem, nor to the
configuration and its dependencies (private keys, Lua scripts, maps,
crt-lists, ACLs). On a properly configured system the default structural
mitigations prevent this, leaving only a compromise of the master process
as a residual path (see section 5).
- Confidentiality of long-lived secrets: TLS private keys and certificates
above all. Unlike transient client data, their disclosure is permanent and
systemic (impersonation and traffic decryption until every key is rotated
and revoked).
- Availability of the proxied service: being on the critical path, keeping
HAProxy serving is paramount. A small, cheap amount of attacker input
must neither consume a disproportionate amount of CPU or memory
(asymmetric DoS, see section 3) nor crash or stall the process.
- Confidentiality and isolation of client data: data belonging to one
connection, stream or client must never leak to another, and process
memory (including uninitialized memory) must never leak to a client.
- Process integrity (memory safety): no RCE, memory corruption or undefined
behaviour (UB) reachable from untrusted input.
- Correct enforcement of the configured policy: access controls, routing and
header manipulations decided by the configuration must not be bypassable
by crafted input.
2. ATTACKER AND ENTRY POINTS
- The reference attacker is an untrusted client able to send arbitrary
bytes to a frontend: raw TCP payloads, HTTP/1, HTTP/2 and HTTP/3 (QUIC)
traffic, and arbitrary TLS handshake records.
- Entry points in scope are therefore the listeners and everything that
parses or transforms client-supplied data: TLS, the HTTP muxes, HTX,
header/URL processing, sample fetches and converters acting on request
data, stick-tables fed by client data, the cache, and the QUIC/H3 stack.
- A secondary untrusted source is the DNS resolver path: even though
nameservers are configured, their answers arrive over UDP and can be
spoofed by an off-path attacker, so the response parser handles
attacker-influenced input.
3. WHAT QUALIFIES AS A SECURITY BUG (IN SCOPE)
- Memory-safety issues (overflow, out-of-bounds, use-after-free, type
confusion, UB) reachable from untrusted client input.
- Cross-client or cross-stream effects: HTTP request smuggling, response
splitting, cache poisoning, and any mixing of data between concurrent
streams or connections (notably in the H2/H3 multiplexers).
- Disclosure of process memory or of another client's data to a client.
- Bypass of a policy that the configuration is meant to enforce (e.g.
defeating an http-request deny/acl through request crafting).
- Asymmetric / algorithmic denial of service: a single or a few cheap
requests causing disproportionate CPU or memory usage (hash-collision
flooding, catastrophic regex backtracking, quadratic parsing, unbounded
allocation, etc). This is distinct from volumetric DoS (see 4).
- Misuse of a third-party library on untrusted input: feeding malformed
client data into OpenSSL, PCRE, Lua, zlib, etc. in a way that corrupts
memory or crashes the process is in scope. A vulnerability inside the
library itself is handled by that library's project, not here.
- Mishandling of spoofable DNS responses: memory corruption, crashes or
cache/state poisoning in the resolver caused by a crafted DNS answer are
in scope, despite nameservers being nominally trusted (see section 2).
4. WHAT DOES NOT QUALIFY (OUT OF SCOPE)
The following do not fall into the security-bug category.
Trusted peers, servers and protocols:
- attacks that require a non-compliant or malicious server: in a reverse
proxy, servers are trusted, or ejected. This covers server-to-client
attacks in general.
- attacks on protocols only used with trusted peers: peers, PROXY protocol,
CIP (NetScaler Client-IP insertion), SOCKS, a local server reached over
an ABNS or UNIX socket, an FCGI server, etc., as well as TLS servers
contacted by the internal httpclient.
- malfunction of a trusted auxiliary service (log server, ring output,
CLI API consumer, etc.).
Privileged or local access (the actor is already trusted):
- any problem triggered through admin access to the CLI.
- anything requiring access to the master CLI.
- anything requiring access to the command line.
- anything requiring write access to the configuration file or any of its
dependencies (Lua scripts, certificates, crt-list, acl, map, etc.).
- anything requiring a configuration running as root, or chrooted to "/"
(i.e. with no effective chroot).
Opt-in unsafe or experimental knobs (the operator disabled a safety):
- anything requiring "experimental-mode on" on the CLI.
- anything requiring "insecure-fork-wanted".
- anything requiring "accept-unsafe-violations-*".
- anything requiring "expose-experimental-directives".
Misconfiguration:
- anything requiring a configuration that emits warnings at boot.
- anything requiring a nonsensical configuration, e.g. a server looping back
to the frontend, non-standard header processing or URL rewriting, or an
excessively large number of headers or excessively large header/body
sizes.
Volumetric or otherwise detectable activity:
- anything requiring such a high and sustained level of activity that it
would be detected and blocked in production (e.g. billions of requests or
connections). This is volumetric DoS, as opposed to the asymmetric DoS of
section 3.
Inherent protocol limitations:
- anything that is a limitation of a standard protocol rather than an
implementation flaw. For example, HTTP/1 has no way to abort a single
transfer without closing the connection, so a client aborting a transfer
will necessarily cause the corresponding server-side connection to be
closed; this is by design of the protocol, not a vulnerability.
Features that are not security boundaries:
- the stats page, including its admin mode, relies on HTTP basic
authentication and was never meant to be a security boundary. Exposing a
public-facing, admin-enabled stats page is therefore not covered.
- configuring a listener to accept the PROXY protocol or CIP from senders
that are not restricted to trusted ones is a misconfiguration: these
headers are believed on trust, so the listener must be reachable only by
the trusted L4/L7 component that prepends them.
Side channels:
- cryptographic and micro-architectural side channels (timing, cache,
speculative execution, etc.) are out of scope. Constant-time handling of
secrets is pursued on a best-effort basis as ordinary hardening where it
clearly matters, but observable timing or resource variations are not
handled as security bugs.
Log integrity:
- escaping of data emitted to logs is a configuration responsibility.
Injection of control characters or forged fields through logged client
data (e.g. when default escaping is disabled, or when a downstream log
consumer mis-parses) is not covered.
5. DEFENSE IN DEPTH (DEFAULT HARDENING)
A correctly deployed HAProxy combines several built-in mitigations that
bound the impact of a successful compromise. These are deliberately taken
into account when assessing the real-world severity of an issue and the
handling it deserves: when one of them contains the practical impact of a
bug, that bug rarely warrants a coordinated embargo and is usually better
fixed quickly and in the open, where users get the fix sooner. They lower
severity, not the obligation to fix: an exploitable memory-safety bug
reachable from client input is still corrected as a bug.
- No fork()/exec() in the worker: the worker never forks nor runs external
programs, so an attacker who achieves code execution has little ability
to spawn a shell or launch persistent background code. ("insecure-fork-
wanted" deliberately disables this and is itself out of scope, see
section 4.)
- chroot and privilege drop: in the sane configuration this document
assumes, the worker drops to an unprivileged user/group and chroots into
an empty, unwritable directory. Injected code therefore has no filesystem
access and very limited means to act on the host.
- Activity watchdog: a thread that stops making progress, e.g. hijacked
into an attacker-controlled loop or otherwise stuck, no longer services
the event loop; the watchdog detects this lack of activity and kills the
process after a few seconds rather than letting it be silently held.
- Master/worker separation: only the worker is exposed to the network and
runs the parsers reachable by clients, and it is the unprivileged,
chrooted process. The master keeps privileges and filesystem access but
has no network exposure. The master must therefore be protected as the
trusted, more privileged component; an attacker is assumed to face only
the worker. The master must under no circumstances be reachable from the
worker (e.g. a master CLI bound to a TCP socket such as localhost is
trivially reachable from compromised worker code and defeats this critical
separation).
6. SEVERITY ORDERING
The worst-case outcomes below are ranked by their realistic impact on a
standard configuration, from most to least severe, and the effort spent
guarding against each is proportional to that severity. The ranking reflects
the master/worker privilege split and the containment provided by the
section-5 mitigations.
1. Remote code execution in the master process. The master is privileged
and has filesystem access, so compromising it defeats every
containment, leaks every secret, and can subvert or take down the
whole service.
2. Chosen disclosure of long-lived secrets, TLS private keys and
certificates above all. Unlike an outage the damage is permanent and
silent: stolen keys allow impersonation, interception and, absent
forward secrecy, decryption of captured traffic, until every affected
key is rotated and revoked across the ecosystem; a restart does not
undo it. "Chosen" sets this rank, not scope: any disclosure of process
memory or of another client's data to a client is in scope (section 3);
this top rank is reserved for a targeted exfiltration, where the
attacker steers the read to a known secret. A leak that cannot be
steered toward a specific secret is still an in-scope disclosure bug,
but ranks far lower - often no worse than the crash such a read tends
to cause first.
3. Crash of the master process. It brings the entire service down and
prevents workers from being respawned: a full but recoverable outage.
4. Crash of the worker process. A transient outage: in-flight connections
are lost and traffic is interrupted for the fraction of a second it
takes to respawn.
5. Remote code execution in the worker process. Contained by no-fork,
chroot, privilege drop and the watchdog, its availability impact is
usually below a worker crash, except in the unlikely case where it
unlocks the chosen disclosure of level 2, which is hard to reach
through the internals from injected code.
6. Policy bypass. Serious, but with no direct availability impact.
7. SECURITY-RELEVANT INVARIANTS AND DEFAULTS
The values below define the conditions HAProxy is designed to operate
within, and may be relied upon by parsing and processing code. A suspected
vulnerability that can only be triggered by conditions outside them
(typically values pushed beyond the stated limits) does not qualify as
security-relevant:
- trash buffers and struct buffer storage are always at least a few kB.
- default buffer size is 16 kB (15 kB max input, as 1 kB is reserved for
rewrites), tunable up to <256 MB.
- default log line is 1 kB, tunable up to <=64 kB.

View file

@ -1,7 +1,7 @@
-----------------------
HAProxy Starter Guide
-----------------------
version 3.4
version 3.5
This document is an introduction to HAProxy for all those who don't know it, as
@ -97,6 +97,9 @@ to the mailing list whose responses are present in these documents.
protocol which is implemented by HAProxy and a number of third party
products.
- security.txt : how to report a security issue, and what does and does not
qualify as a vulnerability.
- README : how to build HAProxy from sources
@ -1686,15 +1689,7 @@ information you might later regret. Since the issue tracker presents itself as
a very long thread, please avoid pasting very long dumps (a few hundreds lines
or more) and attach them instead.
If you've found what you're absolutely certain can be considered a critical
security issue that would put many users in serious trouble if discussed in a
public place, then you can send it with the reproducer to security@haproxy.org.
A small team of trusted developers will receive it and will be able to propose
a fix. We usually don't use embargoes and once a fix is available it gets
merged. In some rare circumstances it can happen that a release is coordinated
with software vendors. Please note that this process usually messes up with
everyone's work, and that rushed up releases can sometimes introduce new bugs,
so it's best avoided unless strictly necessary; as such, there is often little
consideration for reports that needlessly cause such extra burden, and the best
way to see your work credited usually is to provide a working fix, which will
appear in changelogs.
If you believe you may have found a security issue, please refer to the file
doc/security.txt. It explains what does and does not qualify as a vulnerability
in HAProxy, and how to report a genuine one privately. Most suspected issues
turn out to be ordinary bugs that are better reported as described above.

View file

@ -1,7 +1,7 @@
------------------------
HAProxy Management Guide
------------------------
version 3.4
version 3.5
This document describes how to start, stop, manage, and troubleshoot HAProxy,
@ -215,6 +215,18 @@ list of options is :
in foreground and to show incoming and outgoing events. It must never be
used in an init script.
-dA[file] : dump an archive of all dependencies detected at boot time in the
designated file in tar format, immediately after the configuration is done
loading. This is equivalent to "set-dumpable libs", but instead of keeping
the libs in memory, it dumps them into a file. This may be used after a
core dump, in order to provide all necessary libraries to developers to
permit them to exploit the core. This may not be available on all operating
systems. It is highly recommended to use this with the regular
configuration files, and optionally with "-c" when used manually, to make
haproxy immediately exit after the dump, without starting. Example:
$ haproxy -dA/tmp/libs.tar -c -f /etc/haproxy/haproxy.cfg
-dC[key] : dump the configuration file. It is performed after the lines are
tokenized, so comments are stripped and indenting is forced. If a non-zero
key is specified, lines are truncated before sensitive/confidential fields,

40
doc/security.txt Normal file
View file

@ -0,0 +1,40 @@
Reporting security issues in HAProxy
------------------------------------
Before reporting anything, please read doc/internals/threat-model.txt. It
defines precisely what is and is not considered a security vulnerability in
HAProxy. A fair number of suspected issues (and most automated or LLM-assisted
findings) fall outside that boundary: they are ordinary bugs, and are best
reported and fixed in public through the usual channels described in the
"Contacts" section of doc/intro.txt.
If, after reading the threat model, you are confident you have found a genuine
security issue that would put many users at risk if discussed in the open, the
security team can be reached at security@haproxy.org, a private list read by a
handful of security officers; anything shared there remains private. Please
include a reproducer, and ideally a proposed and tested patch, as well as a
valid name under which the report can be credited.
Auxiliary tools in dev/ and admin/ are not intended for production use and are
by nature out of the security scope. Please report bugs affecting them via the
regular channels.
We usually don't use embargoes: once a fix is available it simply gets merged.
In rare circumstances a release may be coordinated with software vendors, but
this disrupts everyone's work and rushed releases can introduce new bugs, so it
is avoided unless strictly necessary. As a result, reports that needlessly cause
such extra burden get little consideration, and the most effective and best
credited way to report an issue is to provide a working fix, which will appear
in the changelogs.
Findings produced with the help of AI MUST be accompanied by a working, tested
patch. Such tools routinely report issues that are out of scope (see the
threat model above) or simply not real, and reviewing them by hand wastes the
very time and trust this process depends on. A model-generated report that
arrives without a verified reproducer and a fix will generally not be
processed.
See also:
- doc/internals/threat-model.txt : what qualifies as a vulnerability
- doc/internals/core-principles.txt : the project's design principles
- doc/intro.txt : general contacts and bug reporting

View file

@ -422,7 +422,7 @@ static inline int channel_is_rewritable(const struct channel *chn)
*/
static inline int channel_may_send(const struct channel *chn)
{
return chn_cons(chn)->state == SC_ST_EST;
return chn_cons(chn)->state >= SC_ST_REQ;
}
/* HTX version of channel_may_recv(). Returns non-zero if the channel can still

View file

@ -165,9 +165,7 @@ static forceinline struct buffer *alloc_small_trash_chunk(void)
*/
static forceinline struct buffer *alloc_trash_chunk_sz(size_t size)
{
if (pool_head_small_trash && size <= pool_head_small_trash->size)
return alloc_small_trash_chunk();
else if (size <= pool_head_trash->size)
if (size <= pool_head_trash->size)
return alloc_trash_chunk();
else if (pool_head_large_trash && size <= pool_head_large_trash->size)
return alloc_large_trash_chunk();

View file

@ -179,6 +179,8 @@ enum {
/* below we have all handshake flags grouped into one */
CO_FL_HANDSHAKE = CO_FL_SEND_PROXY | CO_FL_ACCEPT_PROXY | CO_FL_ACCEPT_CIP | CO_FL_SOCKS4_SEND | CO_FL_SOCKS4_RECV,
CO_FL_WAIT_XPRT = CO_FL_WAIT_L4_CONN | CO_FL_HANDSHAKE | CO_FL_WAIT_L6_CONN,
/* handshake running on top of a layer6 */
CO_FL_WAIT_XPRT_L6 = CO_FL_QMUX_SEND | CO_FL_QMUX_RECV,
CO_FL_SSL_WAIT_HS = 0x08000000, /* wait for an SSL handshake to complete */

View file

@ -114,6 +114,7 @@ int conn_reverse(struct connection *conn);
const char *conn_err_code_name(struct connection *c);
const char *conn_err_code_str(struct connection *c);
int xprt_add_hs(struct connection *conn);
int xprt_add_l6hs(struct connection *conn, int xprt);
void register_mux_proto(struct mux_proto_list *list);
static inline void conn_report_term_evt(struct connection *conn, enum term_event_loc loc, unsigned char type);

View file

@ -61,6 +61,7 @@ extern struct cfgfile fileless_cfg;
/* storage for collected libs */
extern void *lib_storage;
extern size_t lib_size;
extern char *lib_output_file;
struct proxy;
struct server;

View file

@ -98,7 +98,7 @@ enum h1m_state {
#define H1_MF_UPG_WEBSOCKET 0x00008000 // Set for a Websocket upgrade handshake
#define H1_MF_TE_CHUNKED 0x00010000 // T-E "chunked"
#define H1_MF_TE_OTHER 0x00020000 // T-E other than supported ones found (only "chunked" is supported for now)
/* unused: 0x00040000 */
#define H1_MF_UPG_HDR 0x00040000 // non-empty Upgrapde header found
#define H1_MF_NOT_HTTP 0x00080000 // Not an HTTP message (e.g "RTSP", only possible if invalid message are accepted)
/* Mask to use to reset H1M flags when we restart headers parsing.
*

View file

@ -27,7 +27,7 @@
#include <haproxy/buf-t.h>
#include <haproxy/mux_quic-t.h>
/* H3 unidirecational stream types
/* H3 unidirectional stream types
* Emitted as the first byte on the stream to differentiate it.
*/
#define H3_UNI_S_T_CTRL 0x00

View file

@ -326,6 +326,50 @@ static inline int is_immutable_header(struct ist hdr)
}
}
/* This function parses comma-separated values from <hv> and rewrite it in place,
* skip all occurrences of <value>. It is the caller responsibility to deal with
* empty header value.
*/
static inline void http_remove_header_value(struct ist *hv, struct ist value)
{
char *e, *n, *p;
struct ist word;
word.ptr = hv->ptr - 1; // -1 for next loop's pre-increment
p = hv->ptr;
e = hv->ptr + hv->len;
hv->len = 0;
while (++word.ptr < e) {
/* skip leading delimiter and blanks */
if (HTTP_IS_LWS(*word.ptr))
continue;
n = http_find_hdr_value_end(word.ptr, e); // next comma or end of line
word.len = n - word.ptr;
/* trim trailing blanks */
while (word.len && HTTP_IS_LWS(word.ptr[word.len-1]))
word.len--;
if (isteqi(word, value))
goto skip_val;
if (hv->ptr + hv->len == p) {
/* no rewrite done till now */
hv->len = n - hv->ptr;
}
else {
if (hv->len)
hv->ptr[hv->len++] = ',';
istcat(hv, word, e - hv->ptr);
}
skip_val:
word.ptr = p = n;
}
}
#endif /* _HAPROXY_HTTP_H */
/*

View file

@ -141,6 +141,7 @@
#define HTX_SL_F_CONN_UPG 0x00001000 /* The message contains "connection: upgrade" header */
#define HTX_SL_F_BODYLESS_RESP 0x00002000 /* The response to this message is bodyless (only for request) */
#define HTX_SL_F_NOT_HTTP 0x00004000 /* Not an HTTP message (e.g "RTSP", only possible if invalid message are accepted) */
#define HTX_SL_F_UPG_HDR 0x00008000 /* non-empty Upgrapde header found */
/* This function is used to report flags in debugging tools. Please reflect
* below any single-bit flag addition above in the same order via the
@ -157,7 +158,8 @@ static forceinline char *hsl_show_flags(char *buf, size_t len, const char *delim
_(HTX_SL_F_CLEN, _(HTX_SL_F_CHNK, _(HTX_SL_F_VER_11,
_(HTX_SL_F_BODYLESS, _(HTX_SL_F_HAS_SCHM, _(HTX_SL_F_SCHM_HTTP,
_(HTX_SL_F_SCHM_HTTPS, _(HTX_SL_F_HAS_AUTHORITY,
_(HTX_SL_F_NORMALIZED_URI, _(HTX_SL_F_CONN_UPG)))))))))))));
_(HTX_SL_F_NORMALIZED_URI, _(HTX_SL_F_CONN_UPG, _(HTX_SL_F_BODYLESS_RESP,
_(HTX_SL_F_NOT_HTTP, _(HTX_SL_F_UPG_HDR))))))))))))))));
/* epilogue */
_(~0U);
return buf;

View file

@ -121,8 +121,8 @@ static inline size_t array_size_or_fail(size_t m, size_t n)
{
size_t size;
if (mulsz_overflow(m, n, &size))
return ~(size_t)0;
if (unlikely(mulsz_overflow(m, n, &size)))
return DISGUISE(~(size_t)0);
return size;
}

View file

@ -286,9 +286,11 @@ static forceinline char *qcc_show_flags(char *buf, size_t len, const char *delim
/* flags */
_(QC_CF_ERRL,
_(QC_CF_ERRL_DONE,
_(QC_CF_IS_BACK,
_(QC_CF_CONN_FULL,
_(QC_CF_CONN_SHUT,
_(QC_CF_ERR_CONN,
_(QC_CF_WAIT_HS)))));
_(QC_CF_WAIT_HS)))))));
/* epilogue */
_(~0U);
return buf;
@ -331,7 +333,8 @@ static forceinline char *qcs_show_flags(char *buf, size_t len, const char *delim
_(QC_SF_HREQ_RECV,
_(QC_SF_TO_STOP_SENDING,
_(QC_SF_UNKNOWN_PL_LENGTH,
_(QC_SF_RECV_RESET))))))))))));
_(QC_SF_RECV_RESET,
_(QC_SF_EOI_SUSPENDED)))))))))))));
/* epilogue */
_(~0U);
return buf;

View file

@ -43,6 +43,14 @@
#define QPACK_DEC_INST_SCCL 0x40 // Stream Cancellation
#define QPACK_DEC_INST_SACK 0x80 // Section Acknowledgment
/* Encoded field line bitmasks (shared between encoder and decoder) */
#define QPACK_EFL_BITMASK 0xf0
#define QPACK_LFL_WPBNM 0x00 // Literal field line with post-base name reference
#define QPACK_IFL_WPBI 0x10 // Indexed field line with post-based index
#define QPACK_LFL_WLN_BIT 0x20 // Literal field line with literal name
#define QPACK_LFL_WNR_BIT 0x40 // Literal field line with name reference
#define QPACK_IFL_BIT 0x80 // Indexed field line
/* RFC 9204 6. Error Handling */
enum qpack_err {
QPACK_ERR_DECOMPRESSION_FAILED = 0x200,

View file

@ -93,24 +93,30 @@ static inline struct ist qpack_get_value(const struct qpack_dht *dht, const stru
return ret;
}
/* takes an idx, returns the associated name */
/* takes an absolute idx (including static table offset), returns the associated name */
static inline struct ist qpack_idx_to_name(const struct qpack_dht *dht, uint32_t idx)
{
const struct qpack_dte *dte;
dte = qpack_get_dte(dht, idx);
if (idx < QPACK_SHT_SIZE)
return ist("### ERR ###"); /* static table entries not accessible via dht */
dte = qpack_get_dte(dht, idx - QPACK_SHT_SIZE);
if (!dte)
return ist("### ERR ###"); // error
return qpack_get_name(dht, dte);
}
/* takes an idx, returns the associated value */
/* takes an absolute idx (including static table offset), returns the associated value */
static inline struct ist qpack_idx_to_value(const struct qpack_dht *dht, uint32_t idx)
{
const struct qpack_dte *dte;
dte = qpack_get_dte(dht, idx);
if (idx < QPACK_SHT_SIZE)
return ist("### ERR ###"); /* static table entries not accessible via dht */
dte = qpack_get_dte(dht, idx - QPACK_SHT_SIZE);
if (!dte)
return ist("### ERR ###"); // error

View file

@ -135,6 +135,8 @@ static inline void in46un_to_addr(const union sockaddr_in46 *src,
in6->sin6_family = AF_INET6;
in6->sin6_addr = src->in6.sin6_addr;
in6->sin6_port = src->in6.sin6_port;
in6->sin6_flowinfo = src->in6.sin6_flowinfo;
in6->sin6_scope_id = src->in6.sin6_scope_id;
break;
default:

View file

@ -1,5 +1,5 @@
/*
* include/haproxy/dns-t.h
* include/haproxy/resolvers-t.h
* This file provides structures and types for DNS.
*
* Copyright (C) 2014 Baptiste Assmann <bedis9@gmail.com>
@ -114,7 +114,7 @@ struct resolv_answer_item {
char name[DNS_MAX_NAME_SIZE+1]; /* answer name */
int16_t type; /* question type */
int16_t class; /* query class */
int32_t ttl; /* response TTL */
uint32_t ttl; /* response TTL */
int16_t priority; /* SRV type priority */
uint16_t weight; /* SRV type weight */
uint16_t port; /* SRV type port */
@ -281,7 +281,7 @@ enum {
* matching preference was found.
*/
RSLV_UPD_SRVIP_NOT_FOUND, /* provided IP not found
* OR provided IP found and preference is not match and an IP
* OR provided IP found and preference is not matched and an IP
* matching preference was found.
*/
RSLV_UPD_NO_IP_FOUND, /* no IP could be found in the response */

View file

@ -1,5 +1,5 @@
/*
* include/haproxy/dns.h
* include/haproxy/resolvers.h
* This file provides functions related to DNS protocol
*
* Copyright (C) 2014 Baptiste Assmann <bedis9@gmail.com>

View file

@ -279,6 +279,8 @@ struct srv_per_thread {
struct ceb_root *idle_conns; /* Shareable idle connections */
struct ceb_root *safe_conns; /* Safe idle connections */
struct ceb_root *avail_conns; /* Connections in use, but with still new streams available */
struct server *srv; /* Back-pointer to the server */
struct eb32_node idle_node; /* When to next do cleanup in the idle connections */
#ifdef USE_QUIC
struct ist quic_retry_token;
#endif
@ -401,7 +403,6 @@ struct server {
* thread, and generally at the same time.
*/
THREAD_ALIGN();
struct eb32_node idle_node; /* When to next do cleanup in the idle connections */
unsigned int curr_idle_conns; /* Current number of orphan idling connections, both the idle and the safe lists */
unsigned int curr_idle_nb; /* Current number of connections in the idle list */
unsigned int curr_safe_nb; /* Current number of connections in the safe list */

View file

@ -41,9 +41,9 @@
#include <haproxy/tools.h>
__decl_thread(extern HA_SPINLOCK_T idle_conn_srv_lock);
extern struct idle_conns idle_conns[MAX_THREADS];
extern struct task *idle_conn_task;
extern struct task *idle_conn_task[MAX_THREADS];
extern struct eb_root idle_conn_srv[MAX_THREADS];
extern struct mt_list servers_list;
extern struct dict server_key_dict;

View file

@ -50,7 +50,7 @@ struct certificate_ocsp {
int refcount_store; /* Number of ckch_store that reference this certificate_ocsp */
int refcount; /* Number of actual references to this certificate_ocsp (SSL_CTXs mostly) */
struct buffer response;
long expire;
unsigned long expire;
X509 *issuer;
STACK_OF(X509) *chain;
struct eb64_node next_update; /* Key of items inserted in ocsp_update_tree (sorted by absolute date) */

View file

@ -188,4 +188,12 @@ struct file_name_node {
char name[VAR_ARRAY]; /* storage, used with cebus_*() */
};
/* a pair of uint64_t. It's purposely arranged in little endian to help
* being vectorized on modern processors.
*/
struct uint64_pair {
uint64_t l;
uint64_t h;
};
#endif /* _HAPROXY_TOOLS_T_H */

View file

@ -1154,6 +1154,8 @@ void *get_sym_curr_addr(const char *name);
void *get_sym_next_addr(const char *name);
int dump_libs(struct buffer *output, int with_addr);
void collect_libs(void);
void free_collected_libs(void);
int copy_libs_to_file(void);
/* Note that this may result in opening libgcc() on first call, so it may need
* to have been called once before chrooting.
@ -1288,11 +1290,27 @@ static inline void _ha_aligned_free(void *ptr)
int parse_dotted_uints(const char *s, unsigned int **nums, size_t *sz);
/* PRNG */
struct uint64_pair _ha_random64_pair_hashed(void);
void ha_generate_uuid_v4(struct buffer *output);
void ha_generate_uuid_v7(struct buffer *output);
void ha_random_seed(const unsigned char *seed, size_t len);
void ha_random_jump96(uint32_t dist);
void ha_random_seed_thread(void);
void ha_random_jump128(uint32_t dist);
void ha_random_jump192(uint32_t dist);
uint64_t ha_random64(void);
uint64_t ha_random64_internal(void);
/* Returns a pair of uint64_t randoms hashed so as not to disclose the internal
* PRNG state.
*/
static inline void ha_random64_pair_hashed(uint64_t *l, uint64_t *h)
{
struct uint64_pair ret = _ha_random64_pair_hashed();
*l = ret.l;
*h = ret.h;
}
static inline uint32_t ha_random32()
{

View file

@ -33,7 +33,7 @@
#ifdef CONFIG_PRODUCT_BRANCH
#define PRODUCT_BRANCH CONFIG_PRODUCT_BRANCH
#else
#define PRODUCT_BRANCH "3.4"
#define PRODUCT_BRANCH "3.5"
#endif
#ifdef CONFIG_PRODUCT_STATUS

View file

@ -0,0 +1,77 @@
varnishtest "Health-checks: some external check tests"
feature ignore_unknown_macro
#REGTEST_TYPE=slow
server s1 {
rxreq
expect req.method == GET
expect req.url == /health
expect req.proto == HTTP/1.1
txresp
} -start
syslog S1 -level notice {
recv
expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be[0-9]/srv succeeded, reason: External check passed, code: 0"
recv
expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be[0-9]/srv succeeded, reason: External check passed, code: 0"
} -start
syslog S2 -level notice {
recv
expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be[0-9]/srv succeeded.*code: 200"
} -start
haproxy h1 -conf {
global
.if feature(THREAD)
thread-groups 1
.endif
external-check
insecure-fork-wanted
healthcheck http-health
type httpchk
http-check send meth GET uri /health ver HTTP/1.1
defaults
mode http
timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
option log-health-checks
backend be1
log ${S1_addr}:${S1_port} len 2048 local0
option external-check
external-check command /bin/true
server srv ${h1_li1_addr}:${h1_li1_port} check inter 100ms rise 1 fall 1
defaults
mode http
timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
option external-check
external-check command /bin/true
option log-health-checks
backend be2
log ${S1_addr}:${S1_port} len 2048 local0
server srv ${h1_li1_addr}:${h1_li1_port} check inter 100ms rise 1 fall 1
backend be3
log ${S2_addr}:${S2_port} len 2048 local0
option external-check
external-check command /bin/true
server srv ${s1_addr}:${s1_port} check inter 100ms rise 1 fall 1 healthcheck http-health
listen li1
mode http
bind "fd@${li1}"
http-request return status 200
} -start
syslog S1 -wait
syslog S2 -wait

View file

@ -9,7 +9,7 @@ haproxy h1 -conf {
thread-groups 1
.endif
tune.lua.openlibs none
tune.lua.openlibs string
tune.lua.bool-sample-conversion normal
lua-load ${testdir}/h_txn_get_priv.lua

View file

@ -42,7 +42,7 @@ haproxy h1 -conf {
thread-groups 1
.endif
tune.lua.openlibs none
tune.lua.openlibs string
tune.lua.bool-sample-conversion normal
lua-load ${testdir}/lua_httpclient.lua

View file

@ -14,7 +14,7 @@ haproxy h1 -conf {
thread-groups 1
.endif
tune.lua.openlibs none
tune.lua.openlibs string
tune.lua.bool-sample-conversion normal
lua-load ${testdir}/lua_socket.lua

View file

@ -10,7 +10,7 @@ haproxy h1 -conf {
thread-groups 1
.endif
tune.lua.openlibs none
tune.lua.openlibs string,table
tune.lua.bool-sample-conversion normal
lua-load ${testdir}/txn_get_priv.lua
lua-load ${testdir}/txn_get_priv-print_r.lua

1
reg-tests/qmux/certs Symbolic link
View file

@ -0,0 +1 @@
../ssl/certs

39
reg-tests/qmux/h3.vtc Normal file
View file

@ -0,0 +1,39 @@
varnishtest "HTTP/3 over QMux"
feature ignore_unknown_macro
# TODO to adjust once QMux compilation is QUIC/SSL free
feature cmd "$HAPROXY_PROGRAM -cc 'feature(QUIC) && !feature(QUIC_OPENSSL_COMPAT) && !feature(OPENSSL_WOLFSSL) && ssllib_name_startswith(OpenSSL) && openssl_version_atleast(1.1.1)'"
haproxy h1 -conf {
global
.if feature(THREAD)
thread-groups 1
.endif
expose-experimental-directives
ssl-server-verify none
defaults
mode http
timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
frontend fterm
bind "fd@${fterm}" ssl crt ${testdir}/certs/common.pem alpn h3
http-request return status 200 hdr x-alpn %[ssl_fc_alpn] hdr x-ver %[req.ver]
frontend fpub
bind "fd@${fpub}" proto h1
use_backend be
backend be
server hap ${h1_fterm_addr}:${h1_fterm_port} ssl alpn h3
} -start
client c1 -connect ${h1_fpub_sock} {
txreq
rxresp
expect resp.status == 200
expect resp.http.x-alpn == "h3"
expect resp.http.x-ver == "3.0"
} -run

38
reg-tests/qmux/h3_clr.vtc Normal file
View file

@ -0,0 +1,38 @@
varnishtest "HTTP/3 over clear QMux"
feature ignore_unknown_macro
# TODO to adjust once QMux compilation is QUIC/SSL free
feature cmd "$HAPROXY_PROGRAM -cc 'feature(QUIC) && !feature(QUIC_OPENSSL_COMPAT) && !feature(OPENSSL_WOLFSSL) && ssllib_name_startswith(OpenSSL) && openssl_version_atleast(1.1.1)'"
feature cmd "$HAPROXY_PROGRAM -cc 'version_atleast(3.4-dev14)'"
haproxy h1 -conf {
global
.if feature(THREAD)
thread-groups 1
.endif
expose-experimental-directives
defaults
mode http
timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
frontend fterm
bind "fd@${fterm}" proto qmux
http-request return status 200 hdr x-ver %[req.ver]
frontend fpub
bind "fd@${fpub}" proto h1
use_backend be
backend be
server hap ${h1_fterm_addr}:${h1_fterm_port} proto qmux
} -start
client c1 -connect ${h1_fpub_sock} {
txreq
rxresp
expect resp.status == 200
expect resp.http.x-ver == "3.0"
} -run

View file

@ -1,5 +1,9 @@
#REGTEST_TYPE=slow
#REGTEST_TYPE=broken
# reg-test is around ~2.5s
# It is incompatible with ssl/ocsp_auto_update.vtc running in parallel because
# both start a server on the same port, whose URL is specified in the test
# certificates. Given that the test is essentially about testing OCSP update,
# let's just use the more generic SSL one.
# broken with BoringSSL.

View file

@ -1532,7 +1532,7 @@ int acme_res_certificate(struct task *task, struct acme_ctx *ctx, char **errmsg)
hdrs = hc->res.hdrs;
for (hdr = hdrs; isttest(hdr->v); hdr++) {
for (hdr = hdrs; hdrs && isttest(hdr->v); hdr++) {
if (isteqi(hdr->n, ist("Replay-Nonce"))) {
istfree(&ctx->nonce);
ctx->nonce = istdup(hdr->v);
@ -1562,6 +1562,16 @@ int acme_res_certificate(struct task *task, struct acme_ctx *ctx, char **errmsg)
key = ctx->store->data->key;
ctx->store->data->key = NULL;
/* OpenSSL's BIO_new_mem_buf() expects a NUL-terminated string when
* passed -1. The httpclient buffer lacks this, so manually terminate
* it here to prevent an out-of-bounds heap read during PEM parsing.
*/
if (b_room(&hc->res.buf) < 1) {
memprintf(errmsg, "ACME certificate response has no room for NUL terminator");
goto error;
}
hc->res.buf.area[hc->res.buf.data] = '\0';
/* XXX: might need a function dedicated to this, which does not read a private key */
if (ssl_sock_load_pem_into_ckch(ctx->store->path, hc->res.buf.area, ctx->store->data , errmsg) != 0)
goto error;
@ -1735,7 +1745,7 @@ int acme_res_finalize(struct task *task, struct acme_ctx *ctx, char **errmsg)
hdrs = hc->res.hdrs;
for (hdr = hdrs; isttest(hdr->v); hdr++) {
for (hdr = hdrs; hdrs && isttest(hdr->v); hdr++) {
if (isteqi(hdr->n, ist("Replay-Nonce"))) {
istfree(&ctx->nonce);
ctx->nonce = istdup(hdr->v);
@ -1836,7 +1846,7 @@ enum acme_ret acme_res_challenge(struct task *task, struct acme_ctx *ctx, struct
TRACE_DATA(__FUNCTION__, ACME_EV_RES, ctx, NULL, &hc->res.buf);
for (hdr = hdrs; isttest(hdr->v); hdr++) {
for (hdr = hdrs; hdrs && isttest(hdr->v); hdr++) {
if (isteqi(hdr->n, ist("Replay-Nonce"))) {
istfree(&ctx->nonce);
ctx->nonce = istdup(hdr->v);
@ -1963,7 +1973,7 @@ int acme_res_auth(struct task *task, struct acme_ctx *ctx, struct acme_auth *aut
hdrs = hc->res.hdrs;
for (hdr = hdrs; isttest(hdr->v); hdr++) {
for (hdr = hdrs; hdrs && isttest(hdr->v); hdr++) {
if (isteqi(hdr->n, ist("Replay-Nonce"))) {
istfree(&ctx->nonce);
ctx->nonce = istdup(hdr->v);
@ -2279,7 +2289,7 @@ int acme_res_neworder(struct task *task, struct acme_ctx *ctx, char **errmsg)
hdrs = hc->res.hdrs;
for (hdr = hdrs; isttest(hdr->v); hdr++) {
for (hdr = hdrs; hdrs && isttest(hdr->v); hdr++) {
if (isteqi(hdr->n, ist("Replay-Nonce"))) {
istfree(&ctx->nonce);
ctx->nonce = istdup(hdr->v);
@ -2458,7 +2468,7 @@ int acme_res_account(struct task *task, struct acme_ctx *ctx, int newaccount, ch
hdrs = hc->res.hdrs;
for (hdr = hdrs; isttest(hdr->v); hdr++) {
for (hdr = hdrs; hdrs && isttest(hdr->v); hdr++) {
if (isteqi(hdr->n, ist("Location"))) {
istfree(&ctx->kid);
ctx->kid = istdup(hdr->v);
@ -2525,7 +2535,7 @@ int acme_nonce(struct task *task, struct acme_ctx *ctx, char **errmsg)
hdrs = hc->res.hdrs;
for (hdr = hdrs; isttest(hdr->v); hdr++) {
for (hdr = hdrs; hdrs && isttest(hdr->v); hdr++) {
if (isteqi(hdr->n, ist("Replay-Nonce"))) {
istfree(&ctx->nonce);
ctx->nonce = istdup(hdr->v);

View file

@ -80,7 +80,10 @@ static const char *const memprof_methods[MEMPROF_METH_METHODS] = {
struct memprof_stats memprof_stats[MEMPROF_HASH_BUCKETS + 1] = { };
/* used to detect recursive calls */
static THREAD_LOCAL int in_memprof = 0;
#define MEMPROF_IN_INIT (1U << 0)
#define MEMPROF_IN_HANDLER (1U << 1)
static THREAD_LOCAL uint in_memprof = 0; // arithmetic OR of MEMPROF_IN_*
/* These ones are used by glibc and will be called early. They are in charge of
* initializing the handlers with the original functions.
@ -137,7 +140,7 @@ static __attribute__((noreturn)) void memprof_die(const char *msg)
*/
static void memprof_init()
{
in_memprof++;
in_memprof |= MEMPROF_IN_INIT;
memprof_malloc_handler = get_sym_next_addr("malloc");
if (!memprof_malloc_handler)
memprof_die("FATAL: malloc() function not found.\n");
@ -168,7 +171,7 @@ static void memprof_init()
memprof_aligned_alloc_handler = get_sym_next_addr("aligned_alloc");
memprof_posix_memalign_handler = get_sym_next_addr("posix_memalign");
in_memprof--;
in_memprof &= ~MEMPROF_IN_INIT;
}
/* the initial handlers will initialize all regular handlers and will call the
@ -177,7 +180,7 @@ static void memprof_init()
*/
static void *memprof_malloc_initial_handler(size_t size)
{
if (in_memprof) {
if (in_memprof & MEMPROF_IN_INIT) {
/* it's likely that dlsym() needs malloc(), let's fail */
return NULL;
}
@ -188,7 +191,7 @@ static void *memprof_malloc_initial_handler(size_t size)
static void *memprof_calloc_initial_handler(size_t nmemb, size_t size)
{
if (in_memprof) {
if (in_memprof & MEMPROF_IN_INIT) {
/* it's likely that dlsym() needs calloc(), let's fail */
return NULL;
}
@ -198,7 +201,7 @@ static void *memprof_calloc_initial_handler(size_t nmemb, size_t size)
static void *memprof_realloc_initial_handler(void *ptr, size_t size)
{
if (in_memprof) {
if (in_memprof & MEMPROF_IN_INIT) {
/* it's likely that dlsym() needs realloc(), let's fail */
return NULL;
}
@ -209,7 +212,7 @@ static void *memprof_realloc_initial_handler(void *ptr, size_t size)
static char *memprof_strdup_initial_handler(const char *s)
{
if (in_memprof) {
if (in_memprof & MEMPROF_IN_INIT) {
/* probably that dlsym() needs strdup(), let's fail */
return NULL;
}
@ -228,7 +231,7 @@ static void memprof_free_initial_handler(void *ptr)
static char *memprof_strndup_initial_handler(const char *s, size_t n)
{
if (in_memprof) {
if (in_memprof & MEMPROF_IN_INIT) {
/* probably that dlsym() needs strndup(), let's fail */
return NULL;
}
@ -239,7 +242,7 @@ static char *memprof_strndup_initial_handler(const char *s, size_t n)
static void *memprof_valloc_initial_handler(size_t sz)
{
if (in_memprof) {
if (in_memprof & MEMPROF_IN_INIT) {
/* probably that dlsym() needs valloc(), let's fail */
return NULL;
}
@ -250,7 +253,7 @@ static void *memprof_valloc_initial_handler(size_t sz)
static void *memprof_pvalloc_initial_handler(size_t sz)
{
if (in_memprof) {
if (in_memprof & MEMPROF_IN_INIT) {
/* probably that dlsym() needs pvalloc(), let's fail */
return NULL;
}
@ -261,7 +264,7 @@ static void *memprof_pvalloc_initial_handler(size_t sz)
static void *memprof_memalign_initial_handler(size_t al, size_t sz)
{
if (in_memprof) {
if (in_memprof & MEMPROF_IN_INIT) {
/* probably that dlsym() needs memalign(), let's fail */
return NULL;
}
@ -272,7 +275,7 @@ static void *memprof_memalign_initial_handler(size_t al, size_t sz)
static void *memprof_aligned_alloc_initial_handler(size_t al, size_t sz)
{
if (in_memprof) {
if (in_memprof & MEMPROF_IN_INIT) {
/* probably that dlsym() needs aligned_alloc(), let's fail */
return NULL;
}
@ -283,7 +286,7 @@ static void *memprof_aligned_alloc_initial_handler(size_t al, size_t sz)
static int memprof_posix_memalign_initial_handler(void **ptr, size_t al, size_t sz)
{
if (in_memprof) {
if (in_memprof & MEMPROF_IN_INIT) {
/* probably that dlsym() needs posix_memalign(), let's fail */
return ENOMEM;
}
@ -344,11 +347,13 @@ void *malloc(size_t size)
struct memprof_stats *bin;
void *ret;
if (likely(!(profiling & HA_PROF_MEMORY)))
if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
return memprof_malloc_handler(size);
in_memprof |= MEMPROF_IN_HANDLER;
ret = memprof_malloc_handler(size);
size = malloc_usable_size(ret) + sizeof(void *);
in_memprof &= ~MEMPROF_IN_HANDLER;
bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_MALLOC);
if (unlikely(th_ctx->lock_level & 0x7F))
@ -371,11 +376,13 @@ void *calloc(size_t nmemb, size_t size)
struct memprof_stats *bin;
void *ret;
if (likely(!(profiling & HA_PROF_MEMORY)))
if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
return memprof_calloc_handler(nmemb, size);
in_memprof |= MEMPROF_IN_HANDLER;
ret = memprof_calloc_handler(nmemb, size);
size = malloc_usable_size(ret) + sizeof(void *);
in_memprof &= ~MEMPROF_IN_HANDLER;
bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_CALLOC);
if (unlikely(th_ctx->lock_level & 0x7F))
@ -401,12 +408,14 @@ void *realloc(void *ptr, size_t size)
size_t size_before;
void *ret;
if (likely(!(profiling & HA_PROF_MEMORY)))
if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
return memprof_realloc_handler(ptr, size);
in_memprof |= MEMPROF_IN_HANDLER;
size_before = malloc_usable_size(ptr);
ret = memprof_realloc_handler(ptr, size);
size = malloc_usable_size(ret);
in_memprof &= ~MEMPROF_IN_HANDLER;
/* only count the extra link for new allocations */
if (!ptr)
@ -439,11 +448,13 @@ char *strdup(const char *s)
size_t size;
char *ret;
if (likely(!(profiling & HA_PROF_MEMORY)))
if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
return memprof_strdup_handler(s);
in_memprof |= MEMPROF_IN_HANDLER;
ret = memprof_strdup_handler(s);
size = malloc_usable_size(ret) + sizeof(void *);
in_memprof &= ~MEMPROF_IN_HANDLER;
bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_STRDUP);
if (unlikely(th_ctx->lock_level & 0x7F))
@ -469,13 +480,15 @@ void free(void *ptr)
struct memprof_stats *bin;
size_t size_before;
if (likely(!(profiling & HA_PROF_MEMORY) || !ptr)) {
if (likely(!(profiling & HA_PROF_MEMORY) || !ptr || (in_memprof & MEMPROF_IN_HANDLER))) {
memprof_free_handler(ptr);
return;
}
in_memprof |= MEMPROF_IN_HANDLER;
size_before = malloc_usable_size(ptr) + sizeof(void *);
memprof_free_handler(ptr);
in_memprof &= ~MEMPROF_IN_HANDLER;
bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_FREE);
if (unlikely(th_ctx->lock_level & 0x7F))
@ -495,10 +508,13 @@ char *strndup(const char *s, size_t size)
return NULL;
ret = memprof_strndup_handler(s, size);
if (likely(!(profiling & HA_PROF_MEMORY)))
if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
return ret;
in_memprof |= MEMPROF_IN_HANDLER;
size = malloc_usable_size(ret) + sizeof(void *);
in_memprof &= ~MEMPROF_IN_HANDLER;
bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_STRNDUP);
if (unlikely(th_ctx->lock_level & 0x7F))
_HA_ATOMIC_ADD(&bin->locked_calls, 1);
@ -516,10 +532,13 @@ void *valloc(size_t size)
return NULL;
ret = memprof_valloc_handler(size);
if (likely(!(profiling & HA_PROF_MEMORY)))
if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
return ret;
in_memprof |= MEMPROF_IN_HANDLER;
size = malloc_usable_size(ret) + sizeof(void *);
in_memprof &= ~MEMPROF_IN_HANDLER;
bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_VALLOC);
if (unlikely(th_ctx->lock_level & 0x7F))
_HA_ATOMIC_ADD(&bin->locked_calls, 1);
@ -537,10 +556,13 @@ void *pvalloc(size_t size)
return NULL;
ret = memprof_pvalloc_handler(size);
if (likely(!(profiling & HA_PROF_MEMORY)))
if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
return ret;
in_memprof |= MEMPROF_IN_HANDLER;
size = malloc_usable_size(ret) + sizeof(void *);
in_memprof &= ~MEMPROF_IN_HANDLER;
bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_PVALLOC);
if (unlikely(th_ctx->lock_level & 0x7F))
_HA_ATOMIC_ADD(&bin->locked_calls, 1);
@ -558,10 +580,13 @@ void *memalign(size_t align, size_t size)
return NULL;
ret = memprof_memalign_handler(align, size);
if (likely(!(profiling & HA_PROF_MEMORY)))
if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
return ret;
in_memprof |= MEMPROF_IN_HANDLER;
size = malloc_usable_size(ret) + sizeof(void *);
in_memprof &= ~MEMPROF_IN_HANDLER;
bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_MEMALIGN);
if (unlikely(th_ctx->lock_level & 0x7F))
_HA_ATOMIC_ADD(&bin->locked_calls, 1);
@ -579,10 +604,13 @@ void *aligned_alloc(size_t align, size_t size)
return NULL;
ret = memprof_aligned_alloc_handler(align, size);
if (likely(!(profiling & HA_PROF_MEMORY)))
if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
return ret;
in_memprof |= MEMPROF_IN_HANDLER;
size = malloc_usable_size(ret) + sizeof(void *);
in_memprof &= ~MEMPROF_IN_HANDLER;
bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_ALIGNED_ALLOC);
if (unlikely(th_ctx->lock_level & 0x7F))
_HA_ATOMIC_ADD(&bin->locked_calls, 1);
@ -600,13 +628,16 @@ int posix_memalign(void **ptr, size_t align, size_t size)
return ENOMEM;
ret = memprof_posix_memalign_handler(ptr, align, size);
if (likely(!(profiling & HA_PROF_MEMORY)))
if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
return ret;
if (ret != 0) // error
return ret;
in_memprof |= MEMPROF_IN_HANDLER;
size = malloc_usable_size(*ptr) + sizeof(void *);
in_memprof &= ~MEMPROF_IN_HANDLER;
bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_POSIX_MEMALIGN);
if (unlikely(th_ctx->lock_level & 0x7F))
_HA_ATOMIC_ADD(&bin->locked_calls, 1);

View file

@ -539,9 +539,6 @@ size_t appctx_rcv_buf(struct stconn *sc, struct buffer *buf, size_t count, unsig
if (applet_fl_test(appctx, APPCTX_FL_OUTBLK_ALLOC))
goto end;
if (!count)
goto end;
if (!appctx_get_buf(appctx, &appctx->outbuf)) {
TRACE_STATE("waiting for appctx outbuf allocation", APPLET_EV_RECV|APPLET_EV_BLK, appctx);
goto end;
@ -550,6 +547,7 @@ size_t appctx_rcv_buf(struct stconn *sc, struct buffer *buf, size_t count, unsig
if (flags & CO_RFL_BUF_FLUSH)
applet_fl_set(appctx, APPCTX_FL_FASTFWD);
if (count)
ret = CALL_APPLET_WITH_RET(appctx->applet, rcv_buf(appctx, buf, count, flags));
if (ret)
applet_fl_clr(appctx, APPCTX_FL_OUTBLK_FULL);
@ -613,7 +611,7 @@ size_t appctx_htx_snd_buf(struct appctx *appctx, struct buffer *buf, size_t coun
appctx_htx->flags |= (buf_htx->flags & HTX_FL_EOM);
}
htx_to_buf(appctx_htx, &appctx->outbuf);
htx_to_buf(appctx_htx, &appctx->inbuf);
htx_to_buf(buf_htx, buf);
ret -= buf_htx->data;
end:

View file

@ -297,7 +297,7 @@ check_user(struct userlist *ul, const char *user, const char *pass)
fprintf(stderr, ", crypt=%s\n", ((ep) ? ep : ""));
#endif
if (ep && strcmp(ep, u->pass) == 0)
if (ep && u->pass && strcmp(ep, u->pass) == 0)
return 1;
else
return 0;

View file

@ -1818,7 +1818,7 @@ int connect_server(struct stream *s)
{
struct connection *cli_conn = objt_conn(strm_orig(s));
struct connection *srv_conn = NULL;
const struct mux_proto_list *mux_proto;
const struct mux_proto_list *mux_proto = NULL;
struct server *srv;
struct ist name = IST_NULL;
struct sample *name_smp;
@ -2139,13 +2139,11 @@ int connect_server(struct stream *s)
}
if (may_start_mux_now) {
/* Delay QMux MUX init to let xprt_qmux handshake runs first. */
/* Delay MUX init if an XPRT handshake is required prior. */
mux_proto = conn_select_mux_be(srv_conn);
if (mux_proto && mux_proto->init_xprt == XPRT_QMUX) {
srv_conn->flags |= (CO_FL_QMUX_RECV|CO_FL_QMUX_SEND);
if (mux_proto && mux_proto->init_xprt)
may_start_mux_now = 0;
}
}
#if defined(USE_OPENSSL) && defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
/* if websocket stream, try to update connection ALPN. */
@ -2254,6 +2252,13 @@ int connect_server(struct stream *s)
}
}
}
else if (mux_proto && mux_proto->init_xprt) {
/* Add handshake layer prior to MUX init if required. Does nothing if SSL layer is active though. */
if (xprt_add_l6hs(srv_conn, mux_proto->init_xprt)) {
conn_full_close(srv_conn);
return SF_ERR_INTERNAL;
}
}
/*
* Now that the mux may have been created, we can start the xprt.

View file

@ -125,6 +125,9 @@ int base64dec(const char *in, size_t ilen, char *out, size_t olen) {
signed char b;
int convlen = 0, i = 0, pad = 0;
if (!ilen)
return 0;
if (ilen % 4)
return -1;

View file

@ -374,7 +374,7 @@ static int secondary_key_cmp(const char *ref_key, const char *new_key)
* delete_expired==0, write otherwise.
*/
struct cache_entry *get_secondary_entry(struct cache_tree *cache, struct cache_entry *entry,
const char *secondary_key, int delete_expired)
const char *primary_hash, const char *secondary_key, int delete_expired)
{
struct eb32_node *node = &entry->eb;
@ -395,6 +395,12 @@ struct cache_entry *get_secondary_entry(struct cache_tree *cache, struct cache_e
entry = node ? eb32_entry(node, struct cache_entry, eb) : NULL;
}
/* Now verify the full primary hash matches: eb32 only compares 32 bits so
* we could have ended up on a different, unrelated entry.
*/
if (entry && primary_hash && memcmp(entry->hash, primary_hash, sizeof(entry->hash)))
entry = NULL;
/* Expired entry */
if (entry && entry->expire <= date.tv_sec) {
if (delete_expired) {
@ -943,8 +949,8 @@ int http_calc_maxage(struct stream *s, struct cache *cache, int *true_maxage)
if (value) {
struct buffer *chk = get_trash_chunk();
chunk_memcat(chk, value, ctx.value.len - 8 + 1);
chunk_memcat(chk, "", 1);
chunk_memcat(chk, value, ctx.value.len - (8 + 1));
*(b_tail(chk)) = '\0';
offset = (*chk->area == '"') ? 1 : 0;
smaxage = strtol(chk->area + offset, &endptr, 10);
if (unlikely(smaxage < 0 || endptr == chk->area + offset))
@ -955,8 +961,8 @@ int http_calc_maxage(struct stream *s, struct cache *cache, int *true_maxage)
if (value) {
struct buffer *chk = get_trash_chunk();
chunk_memcat(chk, value, ctx.value.len - 7 + 1);
chunk_memcat(chk, "", 1);
chunk_memcat(chk, value, ctx.value.len - (7 + 1));
*(b_tail(chk)) = '\0';
offset = (*chk->area == '"') ? 1 : 0;
maxage = strtol(chk->area + offset, &endptr, 10);
if (unlikely(maxage < 0 || endptr == chk->area + offset))
@ -1303,7 +1309,7 @@ enum act_return http_action_store_cache(struct act_rule *rule, struct proxy *px,
if (old) {
if (vary_signature)
old = get_secondary_entry(cache_tree, old,
txn->cache_secondary_hash, 1);
txn->cache_hash, txn->cache_secondary_hash, 1);
if (old) {
if (!old->complete) {
/* An entry with the same primary key is already being
@ -2178,9 +2184,20 @@ enum act_return http_action_req_cache_use(struct act_rule *rule, struct proxy *p
if (!http_request_build_secondary_key(s, res->secondary_key_signature)) {
cache_rdlock(cache_tree);
sec_entry = get_secondary_entry(cache_tree, res,
s->txn.http->cache_hash,
s->txn.http->cache_secondary_hash,
0);
if (sec_entry && sec_entry != res) {
if (!sec_entry) {
/* Secondary key miss: release the retained primary entry
* and reattach the detached row before returning.
*/
release_entry(cache_tree, res, 0);
shctx_wrlock(shctx);
if (detached)
shctx_row_reattach(shctx, entry_block);
shctx_wrunlock(shctx);
}
else if (sec_entry != res) {
/* The wrong row was added to the hot list. */
release_entry(cache_tree, res, 0);
retain_entry(sec_entry);
@ -3030,6 +3047,7 @@ static int cli_io_handler_show_cache(struct appctx *appctx)
node = eb32_lookup_ge(&cache_tree->entries, next_key);
if (!node) {
ctx->next_key = 0;
next_key = 0;
break;
}

View file

@ -1629,11 +1629,6 @@ static int cfg_parse_global_shm_stats_file(char **args, int section_type,
struct proxy *curpx, const struct proxy *defpx,
const char *file, int line, char **err)
{
if (!experimental_directives_allowed) {
memprintf(err, "'%s' directive is experimental, must be allowed via a global 'expose-experimental-directives'", args[0]);
return -1;
}
if (global.shm_stats_file != NULL) {
memprintf(err, "'%s' already specified.\n", args[0]);
return -1;
@ -1644,7 +1639,6 @@ static int cfg_parse_global_shm_stats_file(char **args, int section_type,
return -1;
}
mark_tainted(TAINTED_CONFIG_EXP_KW_DECLARED);
global.shm_stats_file = strdup(args[1]);
return 0;
}
@ -1653,11 +1647,6 @@ static int cfg_parse_global_shm_stats_file_max_objects(char **args, int section_
struct proxy *curpx, const struct proxy *defpx,
const char *file, int line, char **err)
{
if (!experimental_directives_allowed) {
memprintf(err, "'%s' directive is experimental, must be allowed via a global 'expose-experimental-directives'", args[0]);
return -1;
}
if (shm_stats_file_max_objects != -1) {
memprintf(err, "'%s' already specified.\n", args[0]);
return -1;
@ -1668,7 +1657,6 @@ static int cfg_parse_global_shm_stats_file_max_objects(char **args, int section_
return -1;
}
mark_tainted(TAINTED_CONFIG_EXP_KW_DECLARED);
shm_stats_file_max_objects = atoi(args[1]);
return 0;
}

View file

@ -2486,16 +2486,17 @@ init_proxies_list_stage1:
/* At this point, target names have already been resolved. */
/***********************************************************/
idle_conn_task = task_new_anywhere();
if (!idle_conn_task) {
for (int i = 0; i < global.nbthread; i++) {
idle_conn_srv[i] = EB_ROOT;
idle_conn_task[i] = task_new_on(i);
if (!idle_conn_task[i]) {
ha_alert("parsing : failed to allocate global idle connection task.\n");
cfgerr++;
}
else {
idle_conn_task->process = srv_cleanup_idle_conns;
idle_conn_task->context = NULL;
idle_conn_task[i]->process = srv_cleanup_idle_conns;
idle_conn_task[i]->context = NULL;
for (i = 0; i < global.nbthread; i++) {
idle_conns[i].cleanup_task = task_new_on(i);
if (!idle_conns[i].cleanup_task) {
ha_alert("parsing : failed to allocate idle connection tasks for thread '%d'.\n", i);

View file

@ -232,6 +232,9 @@ static void check_trace(enum trace_level level, uint64_t mask,
chunk_appendf(&trace_buf, " sc=%p(0x%08x)", check->sc, check->sc->flags);
}
if (check->type != PR_O2_TCPCHK_CHK)
return;
if (mask & CHK_EV_TCPCHK) {
const char *type;
@ -1404,6 +1407,19 @@ struct task *process_chk_conn(struct task *t, void *context, unsigned int state)
check_release_buf(check, &check->bi);
check_release_buf(check, &check->bo);
if (unlikely(LIST_INLIST(&check->check_queue))) {
/*
* If that check is still queued, and we're about to
* purge it, then remove it from the queue, as it is
* about to be freed.
* This can happen if a server is deleted while the check
* is queued.
*/
if (check->state & CHK_ST_PURGE)
LIST_DEL_INIT(&check->check_queue);
}
else
_HA_ATOMIC_DEC(&th_ctx->running_checks);
_HA_ATOMIC_DEC(&th_ctx->active_checks);
check->state &= ~(CHK_ST_INPROGRESS|CHK_ST_IN_ALLOC|CHK_ST_OUT_ALLOC);
@ -1563,6 +1579,7 @@ void free_check(struct check *check)
ha_free(&check->tcpcheck);
}
LIST_DEL_INIT(&check->check_queue);
pool_free(pool_head_uniqueid, istptr(check->unique_id));
check->unique_id = IST_NULL;
ha_free(&check->pool_conn_name);
@ -1682,7 +1699,7 @@ static int start_checks()
for (px = proxies_list; px; px = px->next) {
for (s = px->srv; s; s = s->next) {
if ((px->options2 & PR_O2_USE_SBUF_CHECK) &&
(s->check.tcpcheck->rs->flags & TCPCHK_RULES_MAY_USE_SBUF))
(s->check.tcpcheck->rs && s->check.tcpcheck->rs->flags & TCPCHK_RULES_MAY_USE_SBUF))
s->check.state |= CHK_ST_USE_SMALL_BUFF;
if (s->check.state & CHK_ST_CONFIGURED) {
@ -1799,6 +1816,9 @@ int init_srv_check(struct server *srv)
if (!srv->do_check || !(srv->proxy->cap & PR_CAP_BE))
goto out;
if (!srv->check.type && (srv->proxy->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK)
goto init;
check_type = srv->check.tcpcheck->rs->flags & TCPCHK_RULES_PROTO_CHK;
if (!(srv->flags & SRV_F_DYNAMIC)) {
@ -1939,7 +1959,7 @@ int init_srv_check(struct server *srv)
}
init:
err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
err = init_check(&srv->check, srv->check.type ? srv->check.type : (srv->proxy->options2 & PR_O2_CHK_ANY));
if (err) {
ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);

View file

@ -145,14 +145,15 @@ struct buffer *get_small_trash_chunk(void)
/* Returns a trash chunk accordingly to the requested size. This function may
* fail if the requested size is too big or if the large chunks are not
* configured.
* configured. Note that requesting a size larger than the largest available
* buffer will result in NULL being returned, so better be conservative when
* requesting the size and plan to use get_larger_trash_chunk() later if not
* sufficient.
*/
struct buffer *get_trash_chunk_sz(size_t size)
{
if (likely(size > small_trash_size && size <= trash_size))
if (likely(size <= trash_size))
return get_trash_chunk();
else if (small_trash_size && size <= small_trash_size)
return get_small_trash_chunk();
else if (large_trash_size && size <= large_trash_size)
return get_large_trash_chunk();
else

View file

@ -1151,8 +1151,13 @@ int cli_parse_cmdline(struct appctx *appctx)
*/
if (len-1 == strlen(appctx->cli_ctx.payload_pat)) {
if (strncmp(str, appctx->cli_ctx.payload_pat, len-1) == 0) {
/* end of payload was reached, rewind before the previous \n and replace it by a \0 */
b_sub(buf, strlen(appctx->cli_ctx.payload_pat) + 2);
/* end of payload was reached, rewind before the previous \n, if any, and replace it by a \0
* Otherwise, the payload is empty, just
*/
if (b_data(buf) > len)
b_sub(buf, len+1);
else
b_sub(buf, len);
*b_tail(buf) = '\0';
appctx->st1 &= ~APPCTX_CLI_ST1_PAYLOAD;
}

View file

@ -196,7 +196,7 @@ int conn_notify_mux(struct connection *conn, int old_flags, int forced_wake)
* information to create one, typically from the ALPN. If we're
* done with the handshake, attempt to create one.
*/
if (unlikely(!conn->mux) && !(conn->flags & (CO_FL_WAIT_XPRT|CO_FL_QMUX_RECV|CO_FL_QMUX_SEND))) {
if (unlikely(!conn->mux) && !(conn->flags & (CO_FL_WAIT_XPRT|CO_FL_WAIT_XPRT_L6))) {
ret = conn_create_mux(conn, NULL);
if (ret < 0)
goto done;
@ -847,6 +847,43 @@ int xprt_add_hs(struct connection *conn)
return 0;
}
/* Activates an <xprt> layer on top of <conn> connection. This handshake layer
* should be designed to work on top of the layer 6. If SSL is active and its
* handshake still in progress, this function does nothing.
*
* Returns 0 on success else a negative error code.
*/
int xprt_add_l6hs(struct connection *conn, int xprt)
{
const struct xprt_ops *ops = xprt_get(xprt);
void *ops_ctx = NULL;
/* Only QMux is supported as handshake on top of layer6 for now. */
BUG_ON(xprt != XPRT_QMUX);
if (conn->flags & CO_FL_ERROR)
return -1;
/* Do nothing if SSL is in used but handshake still in progress. In
* this case, xprt layer will be added on handshake completion.
*/
if (conn->xprt == xprt_get(XPRT_SSL) &&
(conn->flags & CO_FL_WAIT_L6_CONN)) {
return 0;
}
if (ops->init(conn, &ops_ctx))
return -1;
ops->add_xprt(conn, ops_ctx, conn->xprt_ctx, conn->xprt, NULL, NULL);
conn->xprt = ops;
conn->xprt_ctx = ops_ctx;
/* Reset XPRT READY flag before the next conn_xprt_start(). */
conn->flags &= ~CO_FL_XPRT_READY;
return 0;
}
/* returns a short name for an error, typically the same as the enum name
* without the "CO_ER_" prefix, or an empty string for no error (better eye
* catching in logs). This is more compact for some debug cases.

View file

@ -19,6 +19,7 @@
/* cpu_policy_conf flags */
#define CPU_POLICY_ONE_THREAD_PER_CORE (1 << 0)
#define CPU_POLICY_SET_IN_CONFIG (1 << 1)
/* cpu_policy_conf affinities */
#define CPU_AFFINITY_PER_GROUP (1 << 0)
@ -1106,14 +1107,23 @@ static int cpu_policy_first_usable_node(int policy, int tmin, int tmax, int gmin
int grp, thr;
int thr_count = 0;
if (!global.numa_cpu_mapping)
if (!global.numa_cpu_mapping) {
if (cpu_policy_conf.flags & CPU_POLICY_SET_IN_CONFIG)
ha_notice("cpu-policy is ignored when numa-cpu-mapping is set.\n");
return 0;
}
if (global.nbthread)
if (global.nbthread) {
if (cpu_policy_conf.flags & CPU_POLICY_SET_IN_CONFIG)
ha_notice("cpu-policy is ignored when nbthreads is set.\n");
return 0;
}
if (cpu_mask_forced)
if (cpu_mask_forced) {
if (cpu_policy_conf.flags & CPU_POLICY_SET_IN_CONFIG)
ha_notice("cpu-policy first-numa-node is ignored when CPUs were externally restricted.\n");
return 0;
}
/* determine first and second nodes with usable CPUs */
for (cpu = 0; cpu <= cpu_topo_lastcpu; cpu++) {
@ -1505,11 +1515,17 @@ static int cpu_policy_group_by_cluster(int policy, int tmin, int tmax, int gmin,
int cid;
int div;
if (global.nbthread)
if (global.nbthread) {
if (cpu_policy_conf.flags & CPU_POLICY_SET_IN_CONFIG)
ha_notice("cpu-policy is ignored when nbthreads is set.\n");
return 0;
}
if (global.nbtgroups)
if (global.nbtgroups) {
if (cpu_policy_conf.flags & CPU_POLICY_SET_IN_CONFIG)
ha_notice("cpu-policy is ignored when thread-groups is set.\n");
return 0;
}
ha_cpuset_zero(&visited_cl_set);
@ -1520,7 +1536,8 @@ static int cpu_policy_group_by_cluster(int policy, int tmin, int tmax, int gmin,
div = ha_cpu_policy[policy].arg;
div = div ? div : 1;
while (global.nbtgroups < MAX_TGROUPS && global.nbthread < MAX_THREADS) {
while (global.nbtgroups < MAX_TGROUPS && (global.nbthread < MAX_THREADS) &&
(global.thread_limit == 0 || global.nbthread < global.thread_limit)) {
ha_cpuset_zero(&node_cpu_set);
ha_cpuset_zero(&touse_tsid);
ha_cpuset_zero(&touse_ccx);
@ -1550,6 +1567,10 @@ static int cpu_policy_group_by_cluster(int policy, int tmin, int tmax, int gmin,
ha_cpuset_set(&touse_tsid, ha_cpu_topo[cpu].ts_id);
} else if (!(cpu_policy_conf.flags & CPU_POLICY_ONE_THREAD_PER_CORE))
thr_count++;
if (global.thread_limit != 0 &&
thr_count + global.nbthread >= global.thread_limit)
break;
}
/* now cid = next cluster_id or -1 if none; cpu_count is the
@ -1593,11 +1614,17 @@ static int cpu_policy_group_by_ccx(int policy, int tmin, int tmax, int gmin, int
int l3id;
int div;
if (global.nbthread)
if (global.nbthread) {
if (cpu_policy_conf.flags & CPU_POLICY_SET_IN_CONFIG)
ha_notice("cpu-policy is ignored when nbthreads is set.\n");
return 0;
}
if (global.nbtgroups)
if (global.nbtgroups) {
if (cpu_policy_conf.flags & CPU_POLICY_SET_IN_CONFIG)
ha_notice("cpu-policy is ignored when thread-groups is set.\n");
return 0;
}
ha_cpuset_zero(&visited_ccx_set);
@ -1608,7 +1635,8 @@ static int cpu_policy_group_by_ccx(int policy, int tmin, int tmax, int gmin, int
div = ha_cpu_policy[policy].arg;
div = div ? div : 1;
while (global.nbtgroups < MAX_TGROUPS && global.nbthread < MAX_THREADS) {
while (global.nbtgroups < MAX_TGROUPS && global.nbthread < MAX_THREADS &&
(global.thread_limit == 0 || global.nbthread < global.thread_limit)) {
ha_cpuset_zero(&node_cpu_set);
ha_cpuset_zero(&touse_tsid);
ha_cpuset_zero(&touse_ccx);
@ -1638,6 +1666,9 @@ static int cpu_policy_group_by_ccx(int policy, int tmin, int tmax, int gmin, int
ha_cpuset_set(&touse_tsid, ha_cpu_topo[cpu].ts_id);
} else if (!(cpu_policy_conf.flags & CPU_POLICY_ONE_THREAD_PER_CORE))
thr_count++;
if (global.thread_limit != 0 &&
global.nbthread + thr_count >= global.thread_limit)
break;
}
/* now l3id = next L3 ID or -1 if none; cpu_count is the
@ -1672,8 +1703,17 @@ static int cpu_policy_performance(int policy, int tmin, int tmax, int gmin, int
int cpu, cluster;
int capa;
if (global.nbthread || global.nbtgroups)
if (global.nbthread) {
if (cpu_policy_conf.flags & CPU_POLICY_SET_IN_CONFIG)
ha_notice("cpu-policy is ignored when nbthreads is set.\n");
return 0;
}
if (global.nbtgroups) {
if (cpu_policy_conf.flags & CPU_POLICY_SET_IN_CONFIG)
ha_notice("cpu-policy is ignored when thread-groups is set.\n");
return 0;
}
/* sort clusters by average reverse capacity */
cpu_cluster_reorder_by_avg_capa(ha_cpu_clusters, cpu_topo_maxcpus);
@ -1717,8 +1757,17 @@ static int cpu_policy_efficiency(int policy, int tmin, int tmax, int gmin, int g
int cpu, cluster;
int capa;
if (global.nbthread || global.nbtgroups)
if (global.nbthread) {
if (cpu_policy_conf.flags & CPU_POLICY_SET_IN_CONFIG)
ha_notice("cpu-policy is ignored when nbthreads is set.\n");
return 0;
}
if (global.nbtgroups) {
if (cpu_policy_conf.flags & CPU_POLICY_SET_IN_CONFIG)
ha_notice("cpu-policy is ignored when thread-groups is set.\n");
return 0;
}
/* sort clusters by average reverse capacity */
cpu_cluster_reorder_by_avg_capa(ha_cpu_clusters, cpu_topo_maxcpus);
@ -1759,8 +1808,17 @@ static int cpu_policy_resource(int policy, int tmin, int tmax, int gmin, int gma
int cpu, cluster;
int capa;
if (global.nbthread || global.nbtgroups)
if (global.nbthread) {
if (cpu_policy_conf.flags & CPU_POLICY_SET_IN_CONFIG)
ha_notice("cpu-policy is ignored when nbthreads is set.\n");
return 0;
}
if (global.nbtgroups) {
if (cpu_policy_conf.flags & CPU_POLICY_SET_IN_CONFIG)
ha_notice("cpu-policy is ignored when thread-groups is set.\n");
return 0;
}
/* sort clusters by reverse capacity */
cpu_cluster_reorder_by_capa(ha_cpu_clusters, cpu_topo_maxcpus);
@ -1795,6 +1853,8 @@ int cpu_apply_policy(int tmin, int tmax, int gmin, int gmax, char **err)
if (cpu_map_configured()) {
/* nothing to do */
if (cpu_policy_conf.flags & CPU_POLICY_SET_IN_CONFIG)
ha_notice("cpu-policy is ignored when cpu-map is set.\n");
return 0;
}
@ -2350,6 +2410,7 @@ static int cfg_parse_cpu_policy(char **args, int section_type, struct proxy *cur
for (i = 0; ha_cpu_policy[i].name; i++) {
if (strcmp(args[1], ha_cpu_policy[i].name) == 0) {
cpu_policy_conf.cpu_policy = i;
cpu_policy_conf.flags |= CPU_POLICY_SET_IN_CONFIG;
return 0;
}
}

View file

@ -79,7 +79,7 @@ static struct dict_entry *__dict_lookup(struct dict *d, const char *s)
*/
struct dict_entry *dict_insert(struct dict *d, char *s)
{
struct dict_entry *de;
struct dict_entry *de, *tree_de;
struct ebpt_node *n;
HA_RWLOCK_RDLOCK(DICT_LOCK, &d->rwlock);
@ -97,13 +97,18 @@ struct dict_entry *dict_insert(struct dict *d, char *s)
HA_RWLOCK_WRLOCK(DICT_LOCK, &d->rwlock);
n = ebis_insert(&d->values, &de->value);
tree_de = container_of(n, struct dict_entry, value);
if (tree_de == de)
HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock);
else {
/* another entry was already there, we'll return it, kill
* ours and bump the other's refcount before returning it.
*/
HA_ATOMIC_INC(&tree_de->refcount);
HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock);
if (n != &de->value) {
free_dict_entry(de);
de = container_of(n, struct dict_entry, value);
}
return de;
return tree_de;
}
@ -117,10 +122,11 @@ void dict_entry_unref(struct dict *d, struct dict_entry *de)
if (!de)
return;
if (HA_ATOMIC_SUB_FETCH(&de->refcount, 1) != 0)
return;
HA_RWLOCK_WRLOCK(DICT_LOCK, &d->rwlock);
if (HA_ATOMIC_SUB_FETCH(&de->refcount, 1) != 0) {
HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock);
return;
}
ebpt_delete(&de->value);
HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock);

View file

@ -316,6 +316,9 @@ void h1_parse_upgrade_header(struct h1m *h1m, struct ist *value)
skip_val:
word.ptr = p = n;
}
if (istlen(*value))
h1m->flags |= H1_MF_UPG_HDR;
}
/* Macros used in the HTTP/1 parser, to check for the expected presence of
@ -710,6 +713,16 @@ int h1_headers_to_hdr_list(char *start, const char *stop,
case H1_MSG_RPCODE:
http_msg_rpcode:
if (likely(HTTP_IS_DIGIT(*ptr))) {
if (ptr - sl.st.c.ptr >= 3) {
/* more than 3 digits */
if (h1m->err_pos == -1) /* only capture the error pointer */
h1m->err_pos = ptr - start + skip;
else if (h1m->err_pos < -1 || sl.st.status >= ((uint16_t)~0 - 9) / 10) {
/* strict checks or risk of overflow */
state = H1_MSG_RPCODE;
goto http_msg_invalid;
}
}
sl.st.status = sl.st.status * 10 + *ptr - '0';
EAT_AND_JUMP_OR_RETURN(ptr, end, http_msg_rpcode, http_msg_ood, state, H1_MSG_RPCODE);
}
@ -952,6 +965,20 @@ int h1_headers_to_hdr_list(char *start, const char *stop,
goto http_output_full;
}
/* Skip headers whose names contain forbidden
* chars. When any is detected, h1m->err_pos >= 0,
* so we recheck the name only when an error was
* detected.
*/
if (unlikely(h1m->err_pos >= 0)) {
size_t i = 0;
while (i < n.len && HTTP_IS_TOKEN(n.ptr[i]))
i++;
if (i < n.len)
break;
}
if (isteqi(n, ist("transfer-encoding"))) {
ret = h1_parse_xfer_enc_header(h1m, v);
if (ret < 0) {
@ -1248,9 +1275,10 @@ int h1_headers_to_hdr_list(char *start, const char *stop,
void h1_generate_random_ws_input_key(char key_out[25])
{
/* generate a random websocket key */
const uint64_t rand1 = ha_random64(), rand2 = ha_random64();
uint64_t rand1, rand2;
char key[16];
ha_random64_pair_hashed(&rand1, &rand2);
memcpy(key, &rand1, 8);
memcpy(&key[8], &rand2, 8);
a2base64(key, 16, key_out, 25);

View file

@ -162,6 +162,8 @@ static unsigned int h1m_htx_sl_flags(struct h1m *h1m)
}
if (h1m->flags & H1_MF_CONN_UPG)
flags |= HTX_SL_F_CONN_UPG;
if (h1m->flags & H1_MF_UPG_HDR)
flags |= HTX_SL_F_UPG_HDR;
return flags;
}
@ -213,6 +215,31 @@ static int h1_postparse_req_hdrs(struct h1m *h1m, union h1_sl *h1sl, struct htx
}
}
/* Remove Upgrade header if no 'connection: upgrade' found */
if ((h1m->flags & (H1_MF_CONN_UPG|H1_MF_UPG_HDR)) == H1_MF_UPG_HDR) {
int i;
for (i = 0; hdrs[i].n.len; i++) {
if (isteqi(hdrs[i].n, ist("upgrade")))
hdrs[i].v = IST_NULL;
}
h1m->flags &=~ (H1_MF_CONN_UPG|H1_MF_UPG_HDR);
}
/* Remove 'Upgrade' value from connection header if not Upgrade header found */
if ((h1m->flags & (H1_MF_CONN_UPG|H1_MF_UPG_HDR)) == H1_MF_CONN_UPG) {
int i;
for (i = 0; hdrs[i].n.len; i++) {
if (isteqi(hdrs[i].n, ist("connection"))) {
http_remove_header_value(&hdrs[i].v, ist("upgrade"));
if (!istlen(hdrs[i].v))
hdrs[i].v = IST_NULL;
}
}
h1m->flags &=~ (H1_MF_CONN_UPG|H1_MF_UPG_HDR);
}
flags |= h1m_htx_sl_flags(h1m);
sl = htx_add_stline(htx, HTX_BLK_REQ_SL, flags, meth, uri, vsn);

104
src/h3.c
View file

@ -212,8 +212,33 @@ static ssize_t h3_init_uni_stream(struct h3c *h3c, struct qcs *qcs,
break;
case H3_UNI_S_T_PUSH:
/* TODO not supported for the moment */
h3s->type = H3S_T_PUSH;
if (!conn_is_back(qcs->qcc->conn)) {
/* RFC 9114 6.2.2. Push Streams
*
* Only servers can push; if a server receives a client-initiated push
* stream, this MUST be treated as a connection error of type
* H3_STREAM_CREATION_ERROR.
*/
TRACE_ERROR("reject push from client", H3_EV_H3S_NEW, qcs->qcc->conn, qcs);
qcc_set_error(qcs->qcc, H3_ERR_STREAM_CREATION_ERROR, 1,
muxc_tevt_type_proto_err);
qcc_report_glitch(qcs->qcc, 1);
goto err;
}
else {
/* RFC 9114 4.6. Server Push
*
* A client MUST treat receipt of a push stream as a connection
* error of type H3_ID_ERROR when no MAX_PUSH_ID frame has been sent or
* when the stream references a push ID that is greater than the maximum
* push ID.
*/
TRACE_ERROR("reject push from server outside of MAX_PUSH_ID", H3_EV_H3S_NEW, qcs->qcc->conn, qcs);
qcc_set_error(qcs->qcc, H3_ERR_ID_ERROR, 1,
muxc_tevt_type_proto_err);
qcc_report_glitch(qcs->qcc, 1);
goto err;
}
break;
case H3_UNI_S_T_QPACK_DEC:
@ -365,7 +390,6 @@ static int h3_check_frame_valid(struct h3c *h3c, struct qcs *qcs, uint64_t ftype
case H3_FT_CANCEL_PUSH:
case H3_FT_GOAWAY:
case H3_FT_MAX_PUSH_ID:
/* RFC 9114 7.2.3. CANCEL_PUSH
*
* A CANCEL_PUSH frame is sent on the control stream. Receiving a
@ -379,13 +403,6 @@ static int h3_check_frame_valid(struct h3c *h3c, struct qcs *qcs, uint64_t ftype
* control stream as a connection error of type H3_FRAME_UNEXPECTED.
*/
/* RFC 9114 7.2.7. MAX_PUSH_ID
*
* The MAX_PUSH_ID frame is always sent on the control stream. Receipt
* of a MAX_PUSH_ID frame on any other stream MUST be treated as a
* connection error of type H3_FRAME_UNEXPECTED.
*/
if (h3s->type != H3S_T_CTRL)
ret = H3_ERR_FRAME_UNEXPECTED;
else if (!(h3c->flags & H3_CF_SETTINGS_RECV))
@ -412,16 +429,35 @@ static int h3_check_frame_valid(struct h3c *h3c, struct qcs *qcs, uint64_t ftype
case H3_FT_PUSH_PROMISE:
/* RFC 9114 7.2.5. PUSH_PROMISE
*
* If a PUSH_PROMISE frame is received on the control stream, the client
* MUST respond with a connection error of type H3_FRAME_UNEXPECTED.
*
* A client MUST NOT send a PUSH_PROMISE frame. A server MUST treat the
* receipt of a PUSH_PROMISE frame as a connection error of type
* H3_FRAME_UNEXPECTED.
*/
/* TODO server-side only. */
if (h3s->type == H3S_T_CTRL || !conn_is_back(qcs->qcc->conn))
ret = H3_ERR_FRAME_UNEXPECTED;
break;
case H3_FT_MAX_PUSH_ID:
/* RFC 9114 7.2.7. MAX_PUSH_ID
*
* The MAX_PUSH_ID frame is always sent on the control stream. Receipt
* of a MAX_PUSH_ID frame on any other stream MUST be treated as a
* connection error of type H3_FRAME_UNEXPECTED.
*
* A server MUST NOT send a MAX_PUSH_ID frame. A client MUST treat the
* receipt of a MAX_PUSH_ID frame as a connection error of type
* H3_FRAME_UNEXPECTED.
*/
if (h3s->type != H3S_T_CTRL || conn_is_back(qcs->qcc->conn))
ret = H3_ERR_FRAME_UNEXPECTED;
else if (!(h3c->flags & H3_CF_SETTINGS_RECV))
ret = H3_ERR_MISSING_SETTINGS;
break;
default:
/* RFC 9114 9. Extensions to HTTP/3
*
@ -1930,6 +1966,25 @@ static ssize_t h3_rcv_buf(struct qcs *qcs, struct buffer *b, int fin)
h3s->st_req = H3S_ST_REQ_TRAILERS;
}
break;
case H3_FT_CANCEL_PUSH:
if (!conn_is_back(qcs->qcc->conn)) {
/* RFC 9114 7.2.3. CANCEL_PUSH
*
* If a server receives a CANCEL_PUSH frame for a push ID
* that has not yet been mentioned by a PUSH_PROMISE frame, this MUST be
* treated as a connection error of type H3_ID_ERROR.
*/
TRACE_ERROR("reject CANCEL_PUSH from client", H3_EV_RX_FRAME, qcs->qcc->conn, qcs);
qcc_set_error(qcs->qcc, H3_ERR_ID_ERROR, 1,
muxc_tevt_type_proto_err);
qcc_report_glitch(qcs->qcc, 1);
goto err;
}
else {
/* Not supported */
ret = flen;
}
break;
case H3_FT_GOAWAY:
ret = h3_parse_goaway_frm(qcs->qcc->ctx, b, flen);
if (ret < 0) {
@ -1938,12 +1993,6 @@ static ssize_t h3_rcv_buf(struct qcs *qcs, struct buffer *b, int fin)
goto err;
}
break;
case H3_FT_CANCEL_PUSH:
case H3_FT_PUSH_PROMISE:
case H3_FT_MAX_PUSH_ID:
/* Not supported */
ret = flen;
break;
case H3_FT_SETTINGS:
ret = h3_parse_settings_frm(qcs->qcc->ctx, b, flen);
if (ret < 0) {
@ -1953,6 +2002,25 @@ static ssize_t h3_rcv_buf(struct qcs *qcs, struct buffer *b, int fin)
}
h3c->flags |= H3_CF_SETTINGS_RECV;
break;
case H3_FT_PUSH_PROMISE:
/* h3_check_frame_valid() must reject on server side. */
BUG_ON(!conn_is_back(qcs->qcc->conn));
/* RFC 9114 7.2.5. PUSH_PROMISE
*
* A client MUST treat
* receipt of a PUSH_PROMISE frame that contains a larger push ID than
* the client has advertised as a connection error of H3_ID_ERROR.
*/
ret = H3_ERR_ID_ERROR;
break;
case H3_FT_MAX_PUSH_ID:
/* h3_check_frame_valid() must reject on client side. */
BUG_ON(conn_is_back(qcs->qcc->conn));
/* Not supported. */
ret = flen;
break;
default:
/* RFC 9114 Section 9. Extensions to HTTP/3
*

View file

@ -210,7 +210,6 @@ struct global global = {
#endif
/* by default allow clients which use a privileged port for TCP only */
.clt_privileged_ports = HA_PROTO_TCP,
.maxthrpertgroup = DEF_MAX_THREADS_PER_GROUP,
/* others NULL OK */
};
@ -275,6 +274,7 @@ unsigned int deprecated_directives_allowed = 0;
/* mapped storage for collected libs */
void *lib_storage = NULL;
size_t lib_size = 0;
char *lib_output_file = NULL;
int check_kw_experimental(struct cfg_keyword *kw, const char *file, int linenum,
char **errmsg)
@ -785,6 +785,9 @@ static void usage(char *name)
#if defined(HA_HAVE_DUMP_LIBS)
" -dL dumps loaded object files after config checks\n"
#endif
#if defined(HA_HAVE_DUMP_LIBS) && defined(HA_HAVE_DL_ITERATE_PHDR)
" -dA[file] collects libs into a tar file at <file>\n"
#endif
#if defined(USE_CPU_AFFINITY)
" -dc dumps the list of selected and evicted CPUs\n"
#endif
@ -1628,6 +1631,16 @@ void haproxy_init_args(int argc, char **argv)
#if defined(HA_HAVE_DUMP_LIBS)
else if (*flag == 'd' && flag[1] == 'L')
arg_mode |= MODE_DUMP_LIBS;
# if defined(HA_HAVE_DL_ITERATE_PHDR)
else if (*flag == 'd' && flag[1] == 'A') {
lib_output_file = flag + 2;
if (!*lib_output_file) {
ha_alert("-dA: missing output file name\n");
exit(1);
}
arg_mode |= MODE_DUMP_LIBS; // stop on libs dump
}
# endif /* HA_HAVE_DL_ITERATE_PHDR */
#endif
else if (*flag == 'd' && flag[1] == 'K') {
arg_mode |= MODE_DUMP_KWD;
@ -1926,20 +1939,30 @@ static void dump_registered_keywords(void)
/* Generate a random cluster-secret in case the setting is not provided in the
* configuration. This allows to use features which rely on it albeit with some
* limitations.
* limitations. The function prefers RAND_bytes() if available, otherwise falls
* back to ha_random64_pair_hashed().
*/
static void generate_random_cluster_secret()
{
/* used as a default random cluster-secret if none defined. */
uint64_t rand;
union {
uint64_t by64[2];
uchar by8[16];
} rand;
/* The caller must not overwrite an already defined secret. */
BUG_ON(cluster_secret_isset);
BUG_ON(sizeof(global.cluster_secret) != sizeof(rand));
#ifdef USE_OPENSSL
if (RAND_bytes(rand.by8, sizeof(rand.by8)) != 1)
#endif
{
/* no SSL or not working, fall back to other sources */
ha_random64_pair_hashed(&rand.by64[0], &rand.by64[1]);
}
rand = ha_random64();
memcpy(global.cluster_secret, &rand, sizeof(rand));
rand = ha_random64();
memcpy(global.cluster_secret + sizeof(rand), &rand, sizeof(rand));
cluster_secret_isset = 1;
}
@ -2129,9 +2152,6 @@ static void step_init_1()
if (init_acl() != 0)
exit(1);
/* Initialise lua. */
hlua_init();
/* set modes given from cmdline */
global.mode |= (arg_mode & (MODE_DAEMON | MODE_MWORKER | MODE_FOREGROUND | MODE_VERBOSE
| MODE_QUIET | MODE_CHECK | MODE_DEBUG | MODE_ZERO_WARNING
@ -2306,6 +2326,17 @@ static void step_init_2(int argc, char** argv)
#if defined(HA_HAVE_DUMP_LIBS)
if (global.mode & MODE_DUMP_LIBS && !master) {
# if defined(HA_HAVE_DL_ITERATE_PHDR)
if (lib_output_file) {
/* we'll dump everything to lib_output_file */
if (copy_libs_to_file() < 0)
deinit_and_exit(1);
/* release memory if no longer needed */
if ((global.tune.options & (GTUNE_SET_DUMPABLE | GTUNE_COLLECT_LIBS)) !=
(GTUNE_SET_DUMPABLE | GTUNE_COLLECT_LIBS))
free_collected_libs();
}
# endif
qfprintf(stdout, "List of loaded object files:\n");
chunk_reset(&trash);
if (dump_libs(&trash, ((arg_mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_VERBOSE)))
@ -2779,6 +2810,7 @@ void deinit(void)
struct cfg_postparser *pprs, *pprsb;
char **tmp = init_env;
int cur_fd;
int i;
/* the user may want to skip this phase */
if (global.tune.options & GTUNE_QUICK_EXIT)
@ -2855,8 +2887,10 @@ void deinit(void)
ha_free(&global.server_state_base);
ha_free(&global.server_state_file);
ha_free(&global.stats_file);
task_destroy(idle_conn_task);
idle_conn_task = NULL;
for (i = 0; i < global.nbthread; i++) {
task_destroy(idle_conn_task[i]);
idle_conn_task[i] = NULL;
}
list_for_each_entry_safe(log, logb, &global.loggers, list) {
LIST_DEL_INIT(&log->list);
@ -2950,7 +2984,7 @@ void deinit(void)
free(init_env);
}
free(progname);
free_collected_libs();
} /* end deinit() */
__attribute__((noreturn)) void deinit_and_exit(int status)
@ -3088,6 +3122,7 @@ void *run_thread_poll_loop(void *data)
ha_set_thread(data);
set_thread_cpu_affinity();
clock_set_local_source();
ha_random_seed_thread();
#ifdef USE_THREAD
ha_thread_info[tid].pth_id = ha_get_pthread_id(tid);
@ -3329,16 +3364,18 @@ static void setup_user_ns(uid_t euid, gid_t egid)
static int do_chroot(const char *prog, const char *path)
{
const char *chroot_dir = path;
int error = 0;
const char *dir, *chroot_dir;
int error, chroot_error;
error = chroot_error = 0;
dir = chroot_dir = path;
if (strcmp(path, "auto") == 0) {
/* When "chroot auto" is used, we attempt to chroot to an
* anonymous and read-only directory.
*/
char tmpdir[] = "/tmp/haproxy.XXXXXX";
chroot_dir = mkdtemp(tmpdir);
if (chroot_dir == NULL) {
dir = mkdtemp(tmpdir);
if (dir == NULL) {
ha_alert("[%s.main()] Cannot create(%s) for chroot auto.\n",
prog, tmpdir);
return -1;
@ -3349,16 +3386,32 @@ static int do_chroot(const char *prog, const char *path)
* want to remove the directory).
*/
DISGUISE(rmdir(tmpdir));
chroot_dir = ".";
if (!error)
error = chroot(".");
chroot_error = chroot(".");
} else if (strcmp(path, "/") != 0) {
error = chroot(path);
chroot_error = chroot(path);
}
if (!error)
#ifdef CLONE_NEWUSER
/* If the chroot failed because of insufficient privileges and
* unshare(CLONE_NEWUSER) is available, we attempt it to gain the
* abilty to chroot as an unprivileged user. If that worked, we
* try the chroot again.
*/
if (chroot_error && errno == EPERM) {
uid_t euid = geteuid();
gid_t egid = getegid();
if (unshare(CLONE_NEWUSER) == 0) {
setup_user_ns(euid, egid);
chroot_error = chroot(chroot_dir);
}
}
#endif
if (!error && !chroot_error)
error = chdir("/");
if (error) {
ha_alert("[%s.main()] Cannot chroot(%s).\n", prog, chroot_dir);
if (error || chroot_error) {
ha_alert("[%s.main()] Cannot chroot(%s).\n", prog, dir);
return -1;
}
@ -3702,20 +3755,6 @@ int main(int argc, char **argv)
}
}
#ifdef CLONE_NEWUSER
/* When we aren't root and intend to chroot, we try the Linux-only
* unshare(CLONE_NEWUSER) mechanism if available to allow chroot as an
* unprivileged user. If that doesn't work, we just let the subsequent
* chroot() fail as it would have previously.
*/
if (geteuid() != 0 && global.chroot != NULL) {
uid_t euid = geteuid();
gid_t egid = getegid();
if (unshare(CLONE_NEWUSER) == 0)
setup_user_ns(euid, egid);
}
#endif
/* Must chroot and setgid/setuid in the children */
/* chroot if needed */
if (global.chroot != NULL && do_chroot(argv[0], global.chroot) != 0) {

View file

@ -788,7 +788,7 @@ static void hstream_parse_uri(struct ist uri, struct hstream *hs)
} while (*next);
if (use_rand)
result = ((long long)ha_random64() * result) / ((long long)RAND_MAX + 1);
result = ((long long)statistical_prng() * result) / 0xFFFFFFFFU;
switch (*arg) {
case 's':

View file

@ -3,6 +3,7 @@
#include <haproxy/chunk.h>
#include <haproxy/errors.h>
#include <haproxy/global.h>
#include <haproxy/openssl-compat.h>
#include <haproxy/version.h>
static int haterm_debug;
@ -28,7 +29,9 @@ static void haterm_usage(char *name)
" -d : enable the traces for all http protocols\n"
" -dS : disables splice() usage even when available\n"
" -dZ : disable zero-copy forwarding\n"
#if defined(USE_QUIC)
" --" QUIC_BIND_LONG_OPT " <opts> : append options to QUIC \"bind\" lines\n"
#endif
" --" TCP_BIND_LONG_OPT " <opts> : append options to TCP \"bind\" lines\n"
, name);
exit(1);
@ -171,7 +174,7 @@ void haproxy_init_args(int argc, char **argv)
struct hbuf fbuf = HBUF_NULL; // "frontend" section
struct hbuf tbuf = HBUF_NULL; // "traces" section
char *bits = NULL, *curves = NULL;
char *quic_bind_opt = NULL, *tcp_bind_opt = NULL;
char *quic_bind_opt __maybe_unused = NULL, *tcp_bind_opt = NULL;
int sargc; /* saved argc */
char **sargv; /* saved argv */
@ -203,6 +206,7 @@ void haproxy_init_args(int argc, char **argv)
if (*opt == '-') {
/* long options */
opt++;
#if defined(USE_QUIC)
if (strcmp(opt, QUIC_BIND_LONG_OPT) == 0) {
argv++; argc--;
if (argc <= 0 || **argv == '-')
@ -210,7 +214,9 @@ void haproxy_init_args(int argc, char **argv)
quic_bind_opt = *argv;
}
else if (strcmp(opt, TCP_BIND_LONG_OPT) == 0) {
else
#endif
if (strcmp(opt, TCP_BIND_LONG_OPT) == 0) {
argv++; argc--;
if (argc <= 0 || **argv == '-')
haterm_usage(progname);
@ -254,6 +260,11 @@ void haproxy_init_args(int argc, char **argv)
else if (*opt == 'd' && *(opt+1) == 'S') {
global.tune.options &= ~GTUNE_USE_SPLICE;
}
# if defined(HA_USE_KTLS)
else if (*opt == 'd' && *(opt+1) == 'T') {
global.tune.options |= GTUNE_NO_KTLS;
}
# endif
#endif
else if (*opt == 'd' && *(opt+1) == 'Z') {
global.tune.no_zero_copy_fwd |= NO_ZERO_COPY_FWD;
@ -394,20 +405,30 @@ void haproxy_init_args(int argc, char **argv)
}
/* clear HTTP */
hbuf_appendf(&fbuf, "\tbind %s:%s shards by-thread\n", ip, port1);
hbuf_appendf(&fbuf, "\tbind %s:%s shards by-thread%s%s\n", ip, port1,
tcp_bind_opt ? " " : "",
tcp_bind_opt ? tcp_bind_opt : "");
has_bind = 1;
if (port2) {
#if defined(USE_OPENSSL)
has_ssl = 1;
/* SSL/TCP binding */
hbuf_appendf(&fbuf, "\tbind %s:%s shards by-thread ssl "
"alpn h2,http1.1,http1.0"
"alpn h3,h2,http1.1,http1.0"
" crt " HATERM_RSA_CERT_NAME
" crt " HATERM_ECDSA_CERT_NAME "%s%s\n",
" crt " HATERM_ECDSA_CERT_NAME "%s%s%s\n",
ip, port2,
tcp_bind_opt ? " " : "",
tcp_bind_opt ? tcp_bind_opt : "");
tcp_bind_opt ? tcp_bind_opt : "",
# if defined(USE_LINUX_SPLICE) && defined(HA_USE_KTLS)
" ktls on"
# else
"" /* no ktls */
# endif
);
# if defined(USE_QUIC)
/* QUIC binding */
hbuf_appendf(&fbuf, "\tbind %s@%s:%s shards by-thread ssl"
" crt " HATERM_RSA_CERT_NAME
@ -415,6 +436,11 @@ void haproxy_init_args(int argc, char **argv)
ipv6 ? "quic6" : "quic4", ip, port2,
quic_bind_opt ? " " : "",
quic_bind_opt ? quic_bind_opt : "");
# endif /* USE_QUIC */
#else /* !USE_OPENSSL */
ha_alert("SSL support not compiled in. Rebuild with USE_OPENSSL=1.\n");
goto leave;
#endif /* USE_OPENSSL */
}
}
else
@ -438,6 +464,12 @@ void haproxy_init_args(int argc, char **argv)
}
hbuf_appendf(&gbuf, "global\n");
hbuf_appendf(&gbuf, "\ttune.memory.hot-size 3145728\n");
if (has_ssl)
hbuf_appendf(&gbuf, "\texpose-experimental-directives\n");
#if defined(USE_LINUX_SPLICE) && defined(HA_USE_KTLS)
if (has_ssl)
hbuf_appendf(&gbuf, "\ttune.pipesize 262144\n");
#endif
}
/* "global" section */

View file

@ -2949,20 +2949,20 @@ __LJMP static int hlua_socket_receive_yield(struct lua_State *L, int status, lua
/* remove final \r\n. */
if (nblk == 1) {
if (blk1[len1-1] == '\n') {
if (len1 && blk1[len1-1] == '\n') {
len1--;
skip_at_end++;
if (blk1[len1-1] == '\r') {
if (len1 && blk1[len1-1] == '\r') {
len1--;
skip_at_end++;
}
}
}
else {
if (blk2[len2-1] == '\n') {
if (len2 && blk2[len2-1] == '\n') {
len2--;
skip_at_end++;
if (blk2[len2-1] == '\r') {
if (len2 && blk2[len2-1] == '\r') {
len2--;
skip_at_end++;
}
@ -6709,6 +6709,20 @@ __LJMP static inline int hlua_http_add_hdr(lua_State *L, struct http_msg *msg)
size_t value_len;
const char *value = MAY_LJMP(luaL_checklstring(L, 3, &value_len));
struct htx *htx = htxbuf(&msg->chn->buf);
size_t i;
/* Reject header values containing CR/LF/NUL to prevent HTTP header
* injection on HTTP/1 output.
*/
for (i = 0; i < name_len; i++) {
if (name[i] == 0 || name[i] == '\r' || name[i] == '\n')
WILL_LJMP(lua_error(L));
}
for (i = 0; i < value_len; i++) {
if (value[i] == 0 || value[i] == '\r' || value[i] == '\n')
WILL_LJMP(lua_error(L));
}
lua_pushboolean(L, http_add_header(htx, ist2(name, name_len),
ist2(value, value_len), 1));
@ -13378,6 +13392,16 @@ static int hlua_cfg_parse_openlibs(char **args, int section_type, struct proxy *
return -1;
}
/* Reject a non-default restriction if the Lua VM is already initialised,
* which happens when lua-load, lua-load-per-thread or lua-prepend-path
* appeared before this directive.
*/
if (flags != HLUA_OPENLIBS_ALL && hlua_states[0]) {
memprintf(err, "'%s' must appear before any 'lua-load', 'lua-load-per-thread' or 'lua-prepend-path' directive",
args[0]);
return -1;
}
hlua_openlibs_flags = flags;
return 0;
}
@ -13478,6 +13502,8 @@ static int hlua_load(char **args, int section_type, struct proxy *curpx,
return -1;
}
hlua_init();
/* loading for global state */
hlua_state_id = 0;
ha_set_thread(NULL);
@ -13496,6 +13522,8 @@ static int hlua_load_per_thread(char **args, int section_type, struct proxy *cur
return -1;
}
hlua_init();
if (per_thread_load == NULL) {
/* allocate the first entry large enough to store the final NULL */
per_thread_load = calloc(1, sizeof(*per_thread_load));
@ -13584,6 +13612,8 @@ static int hlua_config_prepend_path(char **args, int section_type, struct proxy
struct prepend_path *p = NULL;
size_t i;
hlua_init();
if (too_many_args(2, args, err, NULL)) {
goto err;
}
@ -13992,6 +14022,7 @@ int hlua_post_init()
hlua_body = 0;
#if defined(USE_OPENSSL)
/* Initialize SSL server. */
if (socket_ssl->xprt->prepare_srv) {
@ -14819,6 +14850,9 @@ void hlua_init(void) {
};
#endif
if (hlua_states[0])
return; /* already initialised */
/* Init post init function list head */
for (i = 0; i < MAX_THREADS + 1; i++)
LIST_INIT(&hlua_init_functions[i]);
@ -14907,3 +14941,14 @@ static void hlua_register_build_options(void)
}
INITCALL0(STG_REGISTER, hlua_register_build_options);
/* Ensure the Lua VM is initialised even if no Lua directive appeared
* in the configuration (e.g. no global section at all).
*/
static int hlua_pre_check(void)
{
hlua_init();
return ERR_NONE;
}
REGISTER_PRE_CHECK(hlua_pre_check);

View file

@ -2082,6 +2082,9 @@ static enum act_return http_action_pause(struct act_rule *rule, struct proxy *px
struct channel *chn = ((rule->from == ACT_F_HTTP_REQ) ? &s->req : &s->res);
struct sample *key;
if (flags & ACT_OPT_FINAL)
goto end;
if (!tick_isset(chn->analyse_exp)) {
int time;
@ -2099,6 +2102,7 @@ static enum act_return http_action_pause(struct act_rule *rule, struct proxy *px
if (tick_isset(chn->analyse_exp) && !tick_is_expired(chn->analyse_exp, now_ms))
return ACT_RET_YIELD;
end:
chn->analyse_exp = TICK_ETERNITY;
return ACT_RET_CONT;
}

View file

@ -3996,19 +3996,19 @@ void http_check_response_for_cacheability(struct stream *s, struct channel *res)
continue;
}
if (isteqi(ctx.value, ist("private")) ||
isteqi(ctx.value, ist("no-cache")) ||
isteqi(ctx.value, ist("no-store")) ||
isteqi(ctx.value, ist("s-maxage=0"))) {
txn->flags &= ~TX_CACHEABLE & ~TX_CACHE_COOK;
continue;
}
/* We might have a no-cache="set-cookie" form. */
if (istmatchi(ctx.value, ist("no-cache=\"set-cookie"))) {
if (isteqi(ctx.value, ist("no-cache=\"set-cookie\""))) {
txn->flags &= ~TX_CACHE_COOK;
continue;
}
if (isteqi(ctx.value, ist("private")) || istmatchi(ctx.value, ist("private=")) ||
isteqi(ctx.value, ist("no-cache")) || istmatchi(ctx.value, ist("no-cache=")) ||
isteqi(ctx.value, ist("no-store")) || istmatchi(ctx.value, ist("no-store=")) ||
isteqi(ctx.value, ist("s-maxage=0"))) {
txn->flags &= ~TX_CACHEABLE & ~TX_CACHE_COOK;
continue;
}
if (istmatchi(ctx.value, ist("s-maxage"))) {
has_freshness_info = 1;
has_null_maxage = 0; /* The null max-age is overridden, ignore it */

View file

@ -263,7 +263,7 @@ static int sample_conv_url_dec(const struct arg *args, struct sample *smp, void
* before decoding.
*/
if (smp->flags & SMP_F_CONST || smp->data.u.str.size <= smp->data.u.str.data) {
struct buffer *str = get_trash_chunk_sz(smp->data.u.str.data);
struct buffer *str = get_trash_chunk_sz(smp->data.u.str.data + 1);
if (!str)
return 0;

View file

@ -356,7 +356,7 @@ static inline int http_7239_extract_node(struct ist *input, struct forwarded_hea
if (!quoted)
return 0; /* not supported */
*input = istnext(*input);
if (!http_7239_extract_nodeport(input, nodeport))
if (!istlen(*input) || !http_7239_extract_nodeport(input, nodeport))
return 0; /* invalid nodeport */
out:
/* ok */

View file

@ -135,7 +135,7 @@ static int get_http_auth(struct sample *smp, struct htx *htx)
chunk_initlen(&txn->auth.method_data, p, 0, istend(ctx.value) - p);
if (!strncasecmp("Basic", auth_method.area, auth_method.data)) {
if (isteqi(ist2(auth_method.area, auth_method.data), ist("Basic"))) {
struct buffer *http_auth = get_trash_chunk();
len = base64dec(txn->auth.method_data.area,
@ -159,7 +159,7 @@ static int get_http_auth(struct sample *smp, struct htx *htx)
txn->auth.method = HTTP_AUTH_BASIC;
return 1;
} else if (!strncasecmp("Bearer", auth_method.area, auth_method.data)) {
} else if (isteqi(ist2(auth_method.area, auth_method.data), ist("Bearer"))) {
txn->auth.method = HTTP_AUTH_BEARER;
return 1;
}

View file

@ -831,8 +831,7 @@ size_t htx_xfer(struct htx *dst, struct htx *src, size_t count, unsigned int fla
/* Remove partial headers/trailers from <dst> and rollback on <src> to not remove them later */
while (type == HTX_BLK_REQ_SL || type == HTX_BLK_RES_SL || type == HTX_BLK_HDR || type == HTX_BLK_TLR) {
BUG_ON(type != htx_get_blk_type(blk));
ret -= meta_sz + htx_get_blksz(blk);
ret -= meta_sz + htx_get_blksz(dstblk);
htx_remove_blk(dst, dstblk);
dstblk = htx_get_tail_blk(dst);
blk = htx_get_prev_blk(src, blk);

View file

@ -266,8 +266,10 @@ static int parse_jose(struct buffer *decoded_jose, int *alg, int *enc, struct jo
if (gcm) {
/* Look for "tag" field (used by aes gcm encryption) */
if (decode_jose_field(decoded_jose, "$.tag", &jose_fields->tag, 1))
/* Look for "tag" field (used by aes gcm encryption).
* GCMKW tag must be exactly 16 bytes per RFC 7518 */
if (decode_jose_field(decoded_jose, "$.tag", &jose_fields->tag, 1) ||
b_data(jose_fields->tag) != 16)
goto end;
/* Look for "iv" field (used by aes gcm encryption) */
@ -556,6 +558,12 @@ static int decrypt_ciphertext(jwe_enc enc, struct jwt_item items[JWE_ELT_MAX],
(*aead_tag)->data = size;
if (gcm) {
/* RFC 7518 mandates a 128-bit (16 byte) authentication tag for A*GCM.
* OpenSSL accepts 1-16 bytes but only verifies that many bytes, so a
* truncated tag reduces forgery work factor to ~256 per byte short. */
if ((*aead_tag)->data != 16)
goto end;
aad = alloc_trash_chunk();
if (!aad)
goto end;
@ -576,8 +584,13 @@ static int decrypt_ciphertext(jwe_enc enc, struct jwt_item items[JWE_ELT_MAX],
goto end;
/* Only use the second part of the decrypted key for actual
* content decryption. */
if (b_data(decrypted_cek) != key_size * 2)
* content decryption.
* Because of the RSAES-PKCS1-V1_5 algorithm, we might have a
* bigger than expected decrypted_cek (if it was filled with
* random bytes in do_decrypt_cek_rsa) and still want to call
* aes_process on the ciphertext in order to avoid timing
* attacks. */
if (b_data(decrypted_cek) < key_size * 2)
goto end;
chunk_memcpy(aes_key, decrypted_cek->area + key_size, key_size);
}
@ -811,8 +824,36 @@ static int do_decrypt_cek_rsa(struct buffer *cek, struct buffer *decrypted_cek,
}
if (EVP_PKEY_decrypt(ctx, (unsigned char*)b_orig(decrypted_cek), &outl,
(unsigned char*)b_orig(cek), b_data(cek)) <= 0)
(unsigned char*)b_orig(cek), b_data(cek)) <= 0) {
/* Per RFC 7516 #11.5, on RSAES-PKCS1-V1_5 decryption failure,
* substitute a random CEK and continue into content decryption.
* This prevents the Bleichenbacher timing oracle: without this
* guard, "padding invalid" (fast exit) is distinguishable from
* "padding valid + AEAD tag fail" (full decrypt path).
* We will build the biggest decrypted_cek necessary rather than
* filling the entire buffer, which would be a key for the
* A256CBC_HS512 encrypting algorithm for which the decrypted
* cek contains the actual key as well as the tag.
*/
if (pad == RSA_PKCS1_PADDING) {
#define MAX_DECRYPTED_CEK_LEN (32 * 2) /* See https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.2.1 */
int i;
unsigned char *p = (unsigned char *)b_orig(decrypted_cek);
/* fill 8 bytes at a time */
for (i = 0; i <= MAX_DECRYPTED_CEK_LEN - 8; i++) {
uint64_t r = ha_random64();
memcpy(p, &r, 8);
p += 8;
}
/* complete if not multiple of 8 (normally not the case) */
for (; i < MAX_DECRYPTED_CEK_LEN; i++)
*(p++) = ha_random64();
outl = MAX_DECRYPTED_CEK_LEN;
} else
goto end;
}
decrypted_cek->data = outl;

View file

@ -437,7 +437,7 @@ static void fwlc_srv_reposition(struct server *s)
tree_elt = fwlc_alloc_tree_elt(s->proxy, allocated_elt);
if (tree_elt == NULL) {
/* We failed to allocate memory, just try again later */
HA_RWLOCK_RDUNLOCK(LBPRM_LOCK, &s->proxy->lbprm.lock);
HA_RWLOCK_WRUNLOCK(LBPRM_LOCK, &s->proxy->lbprm.lock);
_HA_ATOMIC_STORE(&s->lb_lock, 0);
if (s->requeue_tasklet)
tasklet_wakeup(s->requeue_tasklet);

View file

@ -3319,7 +3319,7 @@ struct ist *build_log_header(struct log_header hdr, size_t *nbelem)
break;
}
else if (metadata && metadata[LOG_META_TIME].len >= LOG_ISOTIME_MINLEN) {
int month;
uint month;
char *timestamp = metadata[LOG_META_TIME].ptr;
/* iso time always begins like this: '1970-01-01T00:00:00' */
@ -5499,7 +5499,7 @@ void parse_log_message(char *buf, size_t buflen, int *level, int *facility,
return;
fac_level = 10*fac_level + (*p - '0');
p++;
if ((p - buf) > buflen)
if ((p - buf) >= buflen)
return;
}
@ -6743,6 +6743,7 @@ int cfg_parse_log_profile(const char *file, int linenum, char **args, int kwm)
SMP_VAL_FE_LOG_END, &errmsg)) {
ha_alert("Parsing [%s:%d]: failed to parse logformat: %s.\n",
file, linenum, errmsg);
lf_expr_deinit(target_lf);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}

View file

@ -186,7 +186,7 @@ static int sample_conv_map_key(const struct arg *arg_p, struct sample *smp, void
pat = pattern_exec_match(&desc->pat, smp, 1);
/* Match case. */
if (pat) {
if (pat && pat->ref) {
smp->data.type = SMP_T_STR;
smp->flags |= SMP_F_CONST;
smp->data.u.str.area = (char *)pat->ref->pattern;
@ -998,8 +998,12 @@ static int cli_parse_del_map(char **args, char *payload, struct appctx *appctx,
/* Lookup the reference in the maps. */
ctx->ref = pat_ref_lookup_ref(args[2]);
if (!ctx->ref ||
!(ctx->ref->flags & ctx->display_flags))
!(ctx->ref->flags & ctx->display_flags)) {
if (ctx->display_flags == PAT_REF_MAP)
return cli_err(appctx, "Unknown map identifier. Please use #<id> or <file>.\n");
else
return cli_err(appctx, "Unknown ACL identifier. Please use #<id> or <file>.\n");
}
/* If the entry identifier start with a '#', it is considered as
* pointer id

View file

@ -1595,7 +1595,7 @@ static int fcgi_conn_handle_values_result(struct fcgi_conn *fconn)
return 0;
}
if (unlikely(b_contig_data(dbuf, b_head_ofs(dbuf)) < fconn->drl)) {
if (unlikely(b_contig_data(dbuf, 0) < fconn->drl)) {
/* Realign the dmux buffer if the record wraps. It is unexpected
* at this stage because it should be the first record received
* from the FCGI application.
@ -2516,13 +2516,8 @@ static int fcgi_strm_handle_end_request(struct fcgi_conn *fconn, struct fcgi_str
return 0;
}
if (unlikely(b_contig_data(dbuf, b_head_ofs(dbuf)) < fconn->drl)) {
/* Realign the dmux buffer if the record wraps. It is unexpected
* at this stage because it should be the first record received
* from the FCGI application.
*/
if (unlikely(b_contig_data(dbuf, 0) < fconn->drl))
b_slow_realign_ofs(dbuf, trash.area, 0);
}
inbuf = b_make(b_head(dbuf), b_data(dbuf), 0, fconn->drl);
@ -2644,6 +2639,16 @@ static void fcgi_process_demux(struct fcgi_conn *fconn)
}
fstrm = tmp_fstrm;
if (fconn->dsi == 0 && fconn->drt != FCGI_GET_VALUES_RESULT && fconn->drt != FCGI_UNKNOWN_TYPE) {
/* Stream ID 0 is reserved for management records and
* must not used for application record type.
*/
fconn->state = FCGI_CS_CLOSED;
TRACE_ERROR("Application record with SID 0", FCGI_EV_RX_RECORD|FCGI_EV_RX_FHDR|FCGI_EV_RX_GETVAL|FCGI_EV_FCONN_ERR, fconn->conn);
TRACE_STATE("switching to CLOSED", FCGI_EV_RX_RECORD|FCGI_EV_RX_FHDR|FCGI_EV_RX_GETVAL|FCGI_EV_FCONN_ERR, fconn->conn);
goto fail;
}
if (fstrm->state == FCGI_SS_CLOSED && fconn->dsi != 0) {
/* ignore all record for closed streams */
goto ignore_record;

View file

@ -2708,11 +2708,17 @@ static size_t h1_make_headers(struct h1s *h1s, struct h1m *h1m, struct htx *htx,
h1s->flags |= H1S_F_HAVE_CLEN;
}
else if (isteq(n, ist("connection"))) {
/* copy the value because it can be modified, but the HTX blocks will not */
memcpy(trash.area, v.ptr, v.len);
v.ptr = trash.area;
h1_parse_connection_header(h1m, &v);
if (!v.len)
goto nextblk;
}
else if (isteq(n, ist("upgrade"))) {
/* copy the value because it can be modified, but the HTX blocks will not */
memcpy(trash.area, v.ptr, v.len);
v.ptr = trash.area;
h1_parse_upgrade_header(h1m, &v);
if (!v.len)
goto nextblk;
@ -4257,11 +4263,11 @@ static int h1_process(struct h1c * h1c)
h1c->conn->xprt->subscribe(h1c->conn, h1c->conn->xprt_ctx, SUB_RETRY_RECV, &h1c->wait_event);
}
}
no_parsing:
if (h1c->glitches != prev_glitches && !(h1c->flags & H1C_F_IS_BACK))
session_add_glitch_ctr(sess, h1c->glitches - prev_glitches);
}
no_parsing:
h1_send(h1c);
/* H1 connection must be released ASAP if:

View file

@ -4007,7 +4007,7 @@ static int h2c_handle_data(struct h2c *h2c, struct h2s *h2s)
* going to kill the stream but must still update the connection's
* window.
*/
h2c->rcvd_c += h2c->dfl - h2c->dpl;
h2c->rcvd_c += h2c->dfl;
strm_err:
h2s_error(h2s, error);
h2c->st0 = H2_CS_FRAME_E;
@ -4128,7 +4128,7 @@ static int h2_frame_check_vs_state(struct h2c *h2c, struct h2s *h2s)
/* even if we reject out-of-stream DATA, it must
* still count against the connection's flow control.
*/
h2c->rcvd_c += h2c->dfl - h2c->dpl;
h2c->rcvd_c += h2c->dfl;
}
h2c_report_glitch(h2c, 1, "invalid frame type after receiving RST_STREAM");
@ -6236,6 +6236,13 @@ next_frame:
/* Skip StreamDep and weight for now (we don't support PRIORITY) */
if (h2c->dff & H2_F_HEADERS_PRIORITY) {
if (flen < 5) {
h2c_report_glitch(h2c, 1, "too short PRIORITY frame");
TRACE_STATE("too short PRIORITY frame", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR);
goto fail;
}
if (read_n32(hdrs) == h2c->dsi) {
/* RFC7540#5.3.1 : stream dep may not depend on itself */
h2c_report_glitch(h2c, 1, "PRIORITY frame referencing itself");
@ -6245,13 +6252,6 @@ next_frame:
goto fail;
}
if (flen < 5) {
h2c_report_glitch(h2c, 1, "too short PRIORITY frame");
TRACE_STATE("too short PRIORITY frame", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR);
goto fail;
}
hdrs += 5; // stream dep = 4, weight = 1
flen -= 5;
}

View file

@ -278,10 +278,10 @@ static forceinline void qcc_rm_sc(struct qcc *qcc)
}
/* Checks if <qcc> connection can be used to attach new streams on it or if
* reuse is definitely blocked. This is based on constant parameters such as
* the server max-reuse limit or if the peer has requested a graceful shutdown.
* Flow control is not taken into account here as it can be adjusted
* dynamically over the connection lifetime.
* reuse is definitely blocked. This is based on constant parameters such as a
* connection error or timeout, the server max-reuse limit or if the peer has
* requested a graceful shutdown. Flow control is not taken into account here
* as it can be adjusted dynamically over the connection lifetime.
*
* Returns a boolean value indicating if reuse is possible.
*/
@ -289,6 +289,10 @@ static int qcc_be_is_reusable(const struct qcc *qcc)
{
const struct server *srv = __objt_server(qcc->conn->target);
/* Connection on error or already on timeout. */
if (qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL) || !qcc->task)
return 0;
/* Shutdown initiated by the peer - in HTTP/3 this corresponds to a GOAWAY frame received. */
if (qcc->flags & QC_CF_CONN_SHUT)
return 0;
@ -303,27 +307,30 @@ static int qcc_be_is_reusable(const struct qcc *qcc)
return 1;
}
/* Indicates if a connection is idle and cannot be used anymore. If true the
* connection should be released as soon as possible.
*/
static inline int qcc_is_dead(const struct qcc *qcc)
{
/* Maintain connection if there is still request streams active. */
if (qcc->nb_hreq)
return 0;
/* Connection considered dead if either :
* - remote error detected at transport level
* - error detected locally
if (!conn_is_back(qcc->conn)) {
/* FE conn considered dead if either :
* - transport or local error reported
* - MUX timeout expired
* - app layer shut (FE side only - used for stream.max-total)
* - stream attach definitely blocked (BE side only - max-reuse reached or H3 GOAWAY reception)
* - app layer shut
*/
if (qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL_DONE) ||
!qcc->task ||
(!conn_is_back(qcc->conn) && qcc->app_st == QCC_APP_ST_SHUT) ||
(conn_is_back(qcc->conn) && !qcc_be_is_reusable(qcc))) {
return 1;
return qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL_DONE) ||
!qcc->task || qcc->app_st == QCC_APP_ST_SHUT;
}
else {
/* BE conn considered dead if reuse is definitively blocked.
* Checks similar conditions as FE side and more.
*/
return !qcc_be_is_reusable(qcc);
}
return 0;
}
/* Refresh the timeout on <qcc> if needed depending on its state. */
@ -1737,6 +1744,10 @@ void qcc_reset_stream(struct qcs *qcs, int err, int tevt)
qcs->flags |= QC_SF_TO_RESET;
qcs->err = err;
/* On BE side, a QCS may be resetted before any data emission. */
if (conn_is_back(qcs->qcc->conn))
qcs_idle_open(qcs);
if (diff) {
const int soft_blocked = qfctl_sblocked(&qcc->tx.fc);
@ -2322,6 +2333,9 @@ int qcc_recv_reset_stream(struct qcc *qcc, uint64_t id, uint64_t err, uint64_t f
goto err;
}
qcs->flags |= QC_SF_SIZE_KNOWN|QC_SF_RECV_RESET;
qcs_close_remote(qcs);
/* RFC 9000 3.2. Receiving Stream States
*
* An
@ -2329,14 +2343,15 @@ int qcc_recv_reset_stream(struct qcc *qcc, uint64_t id, uint64_t err, uint64_t f
* data that was not consumed, and signal the receipt of the
* RESET_STREAM.
*/
qcs->flags |= QC_SF_SIZE_KNOWN|QC_SF_RECV_RESET;
qcs_close_remote(qcs);
while (!eb_is_empty(&qcs->rx.bufs)) {
b = container_of(eb64_first(&qcs->rx.bufs),
struct qc_stream_rxbuf, off_node);
qcs_free_rxbuf(qcs, b);
}
/* Remove stream from recv_list if present. */
LIST_DEL_INIT(&qcs->el_recv);
out:
if (qcc->glitches != prev_glitches && !(qcc->flags & QC_CF_IS_BACK))
session_add_glitch_ctr(qcc->conn->owner, qcc->glitches - prev_glitches);
@ -3304,6 +3319,7 @@ static int qcc_io_recv(struct qcc *qcc)
qcc_qmux_recv(qcc);
}
next_recv:
while (!LIST_ISEMPTY(&qcc->recv_list)) {
qcs = LIST_ELEM(qcc->recv_list.n, struct qcs *, el_recv);
/* No need to add an uni local stream in recv_list. */
@ -3311,12 +3327,26 @@ static int qcc_io_recv(struct qcc *qcc)
while (qcs_rx_avail_data(qcs) && !(qcs->flags & QC_SF_DEM_FULL)) {
ret = qcc_decode_qcs(qcc, qcs);
if (ret <= 0) {
LIST_DEL_INIT(&qcs->el_recv);
if (ret <= 0)
/* Interrupt all receive if connection on error. */
if (qcc->flags & QC_CF_ERRL)
goto done;
/* Decode next entry if stream on error. */
goto next_recv;
}
total += ret;
}
/* Always remove QCS from recv_list to prevent infinite loop.
* This is performed even if inner loop was not executed : QCS
* has nothing to do in recv_list if no avail Rx data or demux
* is blocked. Next decoding will be performed on new data read
* unless demux is blocked. In this case QCS will be reinserted
* in recv_list on unblocking to execute decode here again.
*/
LIST_DEL_INIT(&qcs->el_recv);
}
done:
@ -3551,8 +3581,12 @@ static void qcc_app_shutdown(struct qcc *qcc)
}
/* A connection is not reusable if app layer is closed. */
if (qcc->flags & QC_CF_IS_BACK)
if (qcc->flags & QC_CF_IS_BACK) {
if (qcc->conn->flags & CO_FL_LIST_MASK)
conn_delete_from_tree(qcc->conn, tid);
else if (qcc->conn->flags & CO_FL_SESS_IDLE)
session_unown_conn(qcc->conn->owner, qcc->conn);
}
out:
qcc->app_st = QCC_APP_ST_SHUT;

View file

@ -1033,7 +1033,7 @@ static __maybe_unused int spop_get_varint(const struct buffer *b, int o, uint64_
size_t idx = o;
int r;
if (idx > b_data(b))
if (idx >= b_data(b))
return -1;
p = (unsigned char *)b_peek(b, idx++);
@ -1043,7 +1043,7 @@ static __maybe_unused int spop_get_varint(const struct buffer *b, int o, uint64_
r = 4;
do {
if (idx > b_data(b))
if (idx >= b_data(b))
return -1;
p = (unsigned char *)b_peek(b, idx++);
*i += (uint64_t)*p << r;
@ -1652,10 +1652,10 @@ static int spop_conn_handle_hello(struct spop_conn *spop_conn)
return 0;
}
if (unlikely(b_contig_data(dbuf, b_head_ofs(dbuf)) < spop_conn->dfl)) {
if (unlikely(b_contig_data(dbuf, 0) < spop_conn->dfl)) {
/* Realign the dmux buffer if the frame wraps. It is unexpected
* at this stage because it should be the first record received
* from the FCGI application.
* from the SPOA.
*/
b_slow_realign_ofs(dbuf, trash.area, 0);
}
@ -1824,13 +1824,8 @@ static int spop_conn_handle_disconnect(struct spop_conn *spop_conn)
return 0;
}
if (unlikely(b_contig_data(dbuf, b_head_ofs(dbuf)) < spop_conn->dfl)) {
/* Realign the dmux buffer if the frame wraps. It is unexpected
* at this stage because it should be the first record received
* from the FCGI application.
*/
if (unlikely(b_contig_data(dbuf, 0) < spop_conn->dfl))
b_slow_realign_ofs(dbuf, trash.area, 0);
}
p = b_head(dbuf);
end = p + spop_conn->dfl;
@ -1936,13 +1931,8 @@ static int spop_conn_handle_ack(struct spop_conn *spop_conn, struct spop_strm *s
return 0;
}
if (unlikely(b_contig_data(dbuf, b_head_ofs(dbuf)) < spop_conn->dfl)) {
/* Realign the dmux buffer if the frame wraps. It is unexpected
* at this stage because it should be the first record received
* from the FCGI application.
*/
if (unlikely(b_contig_data(dbuf, 0) < spop_conn->dfl))
b_slow_realign_ofs(dbuf, trash.area, 0);
}
spop_conn->flags &= ~SPOP_CF_DEM_SFULL;
rxbuf = spop_get_buf(spop_conn, &spop_strm->rxbuf);

View file

@ -1298,7 +1298,7 @@ void mworker_apply_master_worker_mode(void)
/* This one must not be exported, it's internal! */
unsetenv("HAPROXY_MWORKER_REEXEC");
ha_random_jump96(1);
ha_random_jump128(1);
list_for_each_entry(child, &proc_list, list) {
if ((child->options & PROC_O_TYPE_WORKER) && (child->options & PROC_O_INIT)) {

View file

@ -370,6 +370,12 @@ void test_ncbmb(void)
char *data = calloc(16384, 1);
struct ncbmbuf buf;
if (!area || !data) {
free(area);
free(data);
return;
}
memset(data, 0x11, 16384);
/* 7 bytes data // 1 byte bitmap (0xfe) */
@ -543,6 +549,12 @@ void test_ngtcp2_crypto(void)
char *data = calloc(16384, 1);
struct ncbmbuf buf;
if (!area || !data) {
free(area);
free(data);
return;
}
memset(data, 0x11, 16384);
buf = ncbmb_make(area, 16384, 0);

View file

@ -447,8 +447,8 @@ static size_t tcp_fullhdr_find_opt(const struct sample *smp, uint8_t opt)
/* kind1 = NOP and is a single byte, others have a length field */
if (smp->data.u.str.area[next] == 1)
next++;
else if (next + 1 < len)
next += smp->data.u.str.area[next + 1];
else if (next + 1 < len && smp->data.u.str.area[next + 1] > 1)
next += (uchar)smp->data.u.str.area[next + 1];
else
break;
if (smp->data.u.str.area[curr] == opt && next <= len)
@ -605,7 +605,7 @@ static int sample_conv_tcp_options_list(const struct arg *arg_p, struct sample *
/* kind1 = NOP and is a single byte, others have a length field */
if (smp->data.u.str.area[ofs] == 1)
ofs++;
else if (ofs + 1 < len && smp->data.u.str.area[ofs + 1])
else if (ofs + 1 < len && smp->data.u.str.area[ofs + 1] > 1)
ofs += (uchar)smp->data.u.str.area[ofs + 1];
else
break;
@ -780,7 +780,7 @@ static int sample_conv_ip_fp(const struct arg *arg_p, struct sample *smp, void *
/* kind1 = NOP and is a single byte, others have a length field */
if (smp->data.u.str.area[ofs] == 1)
next = ofs + 1;
else if ((ofs + 1 < tcplen) && smp->data.u.str.area[ofs + 1]) /* optlen 0 will cause an infinite loop */
else if ((ofs + 1 < tcplen) && smp->data.u.str.area[ofs + 1] > 1)
next = ofs + (uchar)smp->data.u.str.area[ofs + 1];
else
break;

View file

@ -116,6 +116,9 @@ smp_client_hello_parse( struct sample *smp, enum client_hello_type type, unsigne
data += 5; /* enter TLS handshake */
bleft -= 5;
if (bleft < hs_len)
goto too_short;
/* Check for a complete client hello starting at <data> */
if (bleft < 1)
goto too_short;
@ -129,15 +132,18 @@ smp_client_hello_parse( struct sample *smp, enum client_hello_type type, unsigne
if (hs_len < 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2)
goto not_ssl_hello; /* too short to have an extension */
data += 4;
bleft -= 4;
/* We want the full handshake here */
if (bleft < hs_len)
goto too_short;
data += 4;
/* Start of the ClientHello message */
if (data[0] < 0x03 || data[1] < 0x01) /* TLSv1 minimum */
goto not_ssl_hello;
/* Note: covered by the hs_len test 30 lines above */
ext_len = data[34]; /* session_id_len */
if (ext_len > 32 || ext_len > (hs_len - 35)) /* check for correct session_id len */
goto not_ssl_hello;

View file

@ -370,6 +370,13 @@ struct pool_head *create_pool_from_reg(const char *name, struct pool_registratio
return NULL;
}
if (invalid_char(name)) {
ha_alert("Pool '%s' declared at %s:%u contains invalid chars in its name and "
"cannot be registered. Please report to developers. Aborting.\n",
name, reg->file, reg->line);
return NULL;
}
extra_mark = (pool_debugging & POOL_DBG_TAG) ? POOL_EXTRA_MARK : 0;
extra_caller = (pool_debugging & POOL_DBG_CALLER) ? POOL_EXTRA_CALLER : 0;
extra = extra_mark + extra_caller;

View file

@ -625,8 +625,10 @@ static int quic_deallocate_dghdlrs(void)
int i;
if (quic_dghdlrs) {
for (i = 0; i < global.nbthread; ++i)
for (i = 0; i < global.nbthread; ++i) {
free(quic_dghdlrs[i].buf.buffer);
tasklet_free(quic_dghdlrs[i].task);
}
free(quic_dghdlrs);
}

Some files were not shown because too many files have changed in this diff Show more