21042 Commits

Author SHA1 Message Date
Christopher Faulet
d899f23017 BUG/MEDIUM: htx: Properly handle block modification during defragmentation
A regression was introcuded by the commit 0c6f2207f ("MEDIUM: htx: Refactor
htx defragmentation to merge data blocks").

When a defragmentation is performed, it is possible to alter a block
size. The main usage is to prepare a block value replacement. However, since
the commit above, the change is no longer handled. The block info are
changed but the size of the message is not modified accordingly.

This patch depends on the commit "MINOR: htx: Add helper function to get
type and size from the block info field"

No backport needed.
2026-04-14 14:07:21 +02:00
William Lallemand
3415abe56d MINOR: mjson: reintroduce mjson_next()
The lack of mjson_next() prevents to iterate easily and need to hack by
iterating on a loop of snprintf + $.field[XXX] combined with
mjson_find().

This reintroduce mjson_next() so we could iterate without having to
build the string.

The patch does not reintroduce MJSON_ENABLE_NEXT so it could be used
without having to define it.
2026-04-14 10:57:21 +02:00
William Lallemand
cf72132f22 MINOR: acme: display the type of challenge in ACME_INITIAL_DELAY
The ACME_INITIAL_DELAY state displays a message about 'dns-01', but this
state is also used for 'dns-persist-01'.

This patch displays the challenge that was configured instead of dns-01
2026-04-14 10:16:11 +02:00
Tim Duesterhus
ed0c51d2c0 MINOR: http_fetch: Add support for checks to unique-id fetch
This allows to use the `unique-id` fetch within `tcp-check` or `http-check`
ruleset. The format is taken from the checked server's backend (which is
naturally inherited from the corresponding `defaults` section).

This is particularly useful with

    http-check send ... hdr request-id %[unique-id]

to ensure all requests sent by HAProxy have a unique ID header attached.

This resolves GitHub Issue #3307.

Reviewed-by: Volker Dusch <github@wallbash.com>
2026-04-13 20:02:21 +02:00
Tim Duesterhus
2c748125f5 MINOR: check: Support generating a unique_id for checks
This implementation is directly modeled after `stream_generate_unique_id()` and
the corresponding `unique_id` field on `struct stream`.

It will be used in a future commit to enable the use of the `%[unique-id]`
fetch in check rules.
2026-04-13 20:01:42 +02:00
Tim Duesterhus
7ff2627112 CLEANUP: log: Stop touching struct stream internals for %ID
Use the return value of `stream_generate_unique_id()` instead of relying on the
`unique_id` field of `struct stream` when handling the `%ID` log placeholder.
This also allowed to unify the "stream available" and "stream not available"
paths.

Reviewed-by: Volker Dusch <github@wallbash.com>
2026-04-13 20:01:42 +02:00
Tim Duesterhus
38796d4c06 MINOR: Allow inlining of stream_generate_unique_id()
With the introduction of the `generate_unique_id()` helper, the actual
complicated logic is sitting in a different file. Allow inlining of
`stream_generate_unique_id()`, so that callers can benefit from an abstraction
without hiding away the access of `strm->unique_id` behind a function call.
2026-04-13 20:01:42 +02:00
Tim Duesterhus
73040e3a8e MINOR: Add generate_unique_id() helper
This new function will handle the actual generation of the unique ID according
to a format. The caller is responsible to check that no unique ID is stored
yet.
2026-04-13 20:01:02 +02:00
Tim Duesterhus
4cf06a7d23 CLEANUP: Make lf_expr parameter of sess_build_logline_orig() const
Since this is safely possible without making any changes, we can provide this
hint to the compiler.
2026-04-13 19:59:12 +02:00
Willy Tarreau
9a5db56a36 BUG/MINOR: haterm: don't apply the default pipe size margin twice
Commit 6d16b11022 ("BUG/MINOR: haterm: preserve the pipe size margin
for splicing") solved the issue of pipe size being sufficient for the
vmsplice() call, but as Christopher pointed out, the ratio was applied
to the default size of 64k, so now it's applied twice, giving 100k
instead of 80k. Let's drop it from there.

No backport needed.
2026-04-13 19:38:48 +02:00
Egor Shestakov
79c54d28b0 BUG/MINOR: acme: don't pass NULL into format string
Printing a "(null)" when NULL passed with the %s format specifier is a
GNU extension, so it must be avoided for portability reasons.

Must be backported as far as 3.2
2026-04-13 18:56:13 +02:00
William Lallemand
53679fe5f6 BUG/MINOR: acme: read the wildcard flag from the authorization response
The wildcard field was declared and used when building the dns-persist-01
TXT record value (policy=wildcard suffix), but was never populated from
the server's authorization response. Add the missing mjson_get_bool() call
to read $.wildcard before saving auth->dns.
2026-04-13 18:49:53 +02:00
William Lallemand
39476040ec MINOR: acme: extend resolver-based DNS pre-check to dns-persist-01
Add challenge_type parameter to acme_rslv_start() to select the correct
DNS lookup prefix: _validation-persist.<domain> for dns-persist-01 and
_acme-challenge.<domain> for dns-01.

Default cond_ready to ACME_RDY_DNS|ACME_RDY_DELAY for dns-persist-01.
Extend ACME_CLI_WAIT to cover dns-persist-01 alongside dns-01.

In ACME_RSLV_READY, check only TXT record existence for dns-persist-01
since the resolver cannot parse multiple strings within a single TXT entry.
2026-04-13 18:45:08 +02:00
Mia Kanashi
0d3689959d MEDIUM: acme: implement dns-persist-01 challenge
Implements draft DNS-PERSIST-01 challenge based on
https://datatracker.ietf.org/doc/html/draft-ietf-acme-dns-persist

Blog post: https://letsencrypt.org/2026/02/18/dns-persist-01

This challenge is designed to use preprovisioned DNS records,
unlike DNS-01 challenge it doesn't need per provider API integration.

In short instead of validating order by crafting a custom response
based on input recieved from ACME server, like other challenges do
in particular DNS-01, HTTP-01, TLS-ALPN-01, in this challenge you
authorize domain statically, ACME account key functions similar to
a private key and accounturi in the record functions like a public key,
ACME server verifies that account uri matches account key and authorizes
based on that. You only need to write DNS record one time,
accounturi binds to an account key, and will only change if new account
key is created, although it is possible to rotate account key without
changing account uri.

Main benefits of this challenge in contrast to DNS-01:
1. Security, no need to give reverse proxy write access to the DNS.
2. Simplicity, no complex per provider integrations like Lego needed.
3. Robustness, no worrying about DNS record cache each renewal.

It would be used like this:
1. generate an account key ahead of time
2. add required DNS record manually or automatically using IaC tools
3. start HAProxy with the same account key used

Intended way to use this challenge is with a code that will print
and maybe sets DNS records ahead of time. For example that could
be integrated into the IaC provisioning step. This challenge type
is extremely recent though, so those integrations are yet to be written.

It is possible to do this challenge without extra tools too,
with pebble / challtestsrv steps would be as following:

After starting HAProxy it will print required records in the logs.

With challtestsrv you can then set those records like this:

curl -d '{
  "host":"_validation-persist.localhost.",
  "value": "pebble.letsencrypt.org; accounturi=...; policy=wildcard"}
' http://localhost:8055/set-txt

After setting the records run renew with the name of the certificate:

echo "acme renew @cert/localhost.pem" \
  | socat stdio tcp4-connect:127.0.0.1:9999

Or just restart HAProxy.

Unlike with DNS-01 you don't have to worry about DNS records changing,
if there is any problem with DNS records you can just retry.
2026-04-13 18:45:08 +02:00
Willy Tarreau
6d16b11022 BUG/MINOR: haterm: preserve the pipe size margin for splicing
Originally in httpterm we used to allocate 5/4 of the size of a pipe to
permit to use vmsplice because there's some fragmentation or overhead
internally that requires to use a bit of margin. While this was initially
applied to haterm as well, it was accidentally lost with commit fb82dece47
("BUG/MEDIUM: haterm: Properly initialize the splicing support for haterm"),
resulting in errors about vmsplice() whenever tune.pipesize is set. Let's
enforce the ratio again.

No backport is needed.
2026-04-13 18:37:04 +02:00
Egor Shestakov
61f04d1951 MINOR: errors: remove excessive errmsg checks
I noticed some strange checks for presence of errmsg. Called functions
generate non-empty error message in case of failure, so a non-NULL address
of the error message is enough.

No backport needed.
2026-04-13 15:39:05 +02:00
Christopher Faulet
ebb801d7c8 BUG/MEDIUM: cli: Properly handle too big payload on a command line
When command line is parsed, when the payload was too big the error was not
properly handled. Instead of leaving the parsing function to print the
error, we looped infinitly trying to parse remaining data.

When the command line is too big, we must exit the parsing function in
CLI_ST_PRINT_ERR state. Instead of exiting the function, we only left the
while loop, setting this way the cli applet in CLI_ST_PROMPT state.

This patch must be backported as far as 3.2.
2026-04-13 15:18:47 +02:00
Egor Shestakov
c82b10b8d2 MINOR: tools: memvprintf(): remove <out> check that always true
memvprintf() exits early if the <out> is NULL, so the further NULL check is
redundant.

No backport needed.
2026-04-13 14:36:52 +02:00
Tim Duesterhus
801d028790 CLEANUP: http_fetch: Use local unique_id variable in smp_fetch_uniqueid()
Instead of relying on the implementation detail that
`stream_generate_unique_id()` will store the unique ID in `strm->unique_id` we
should use the returned value, especially since that one is already checked in
the `isttest()`.

Reviewed-by: Volker Dusch <github@wallbash.com>
2026-04-13 14:02:29 +02:00
Tim Duesterhus
44a461a19f CLEANUP: stream: Reduce duplication in stream_generate_unique_id()
The return value of the `if()` and `else` branch is identical. We can just move
it out of conditional paths.

Reviewed-by: Volker Dusch <github@wallbash.com>
2026-04-13 14:02:29 +02:00
Tim Duesterhus
f778e6feb0 CLEANUP: stream: Explain the two-step initialization in stream_generate_unique_id()
This two-step initialization of `strm->unique_id` looks like a refactoring
target. Add a comment to prevent regressions of the fix in
fb7b5c8a53cb4f19a223abd20660d47162aa8708.
2026-04-13 14:02:29 +02:00
Tim Duesterhus
756ad19f04 CLEANUP: log: Return size_t from sess_build_logline_orig()
`sess_build_logline_orig()` takes a `size_t maxsize` as input and accordingly
should also return `size_t` instead of `int` as the resulting length. In
practice most of the callers already stored the result in a `size_t` anyways.
The few places that used an `int` were adjusted.

This Coccinelle patch was used to check for completeness:

    @@
    type T != size_t;
    T var;
    @@

    (
    * var = build_logline(...)
    |
    * var = build_logline_orig(...)
    |
    * var = sess_build_logline(...)
    |
    * var = sess_build_logline_orig(...)
    )

Reviewed-by: Volker Dusch <github@wallbash.com>
2026-04-13 14:02:29 +02:00
Tim Duesterhus
34c17608e7 BUG/MINOR: log: Fix error message when using unavailable fetch in logfmt
The following configuration:

    defaults
    	unique-id-format TEST-%[srv_name]

    frontend fe_http
    	mode http

    	bind :::8080 v4v6

Emitted the following error:

    [ALERT]    (219835) : Parsing [./patch.cfg:2]: failed to parse unique-id : sample fetch <srv_name]> may not be reliably used here because it needs 'server' which is not available here.

The `]` in the name of the sample fetch should not be there.

This bug exists since at least HAProxy 2.4, which is the oldest supported
version. The fix should be backported there.

Reviewed-by: Volker Dusch <github@wallbash.com>
2026-04-13 14:02:29 +02:00
Amaury Denoyelle
34c9ded340 BUG/MINOR: quic: do not use hardcoded values in QMux TP frame builder
Reuse QUIC transport parameters value set in xprt_qstrm layer in frame
builder function. Prior to this patch, mux_quic would use different
values from the advertised ones.

No need to backport.
2026-04-13 13:38:11 +02:00
Amaury Denoyelle
175717f5be MINOR: mux_quic: remove duplicate QMux local transport params
When QMux was first implemented, values used for emitted transport
parameters in xprt_qstrm and local flow control in mux_quic were
initialized separately. This is error prone in particular if a value is
change in one layer but not the other.

This patch fixes this by using xprt_qstrm_lparams() in QMux init
function. Mux flow control is then loaded with these values. Thus all
values are now initialized in a single place which is xprt_qstrm_init().
2026-04-13 09:38:46 +02:00
Miroslav Zagorac
cd14abf9f3 MEDIUM: otel: added OpenTelemetry filter skeleton
The OpenTelemetry (OTel) filter enables distributed tracing of requests
across service boundaries, export of metrics such as request rates,
latencies and error counts, and structured logging tied to trace context,
giving operators a unified view of HAProxy traffic through any
OpenTelemetry-compatible backend.

The OTel filter is implemented using the standard HAProxy stream filter
API.  Stream filters attach to proxies and intercept traffic at each stage
of processing: they receive callbacks on stream creation and destruction,
channel analyzer events, HTTP header and payload processing, and TCP data
forwarding.  This allows the filter to collect telemetry data at every
stage of the request/response lifecycle without modifying the core proxy
logic.

This commit added the minimum set of files required for the filter to
compile: the addon Makefile with pkg-config-based detection of the
opentelemetry-c-wrapper library, header files with configuration
constants, utility macros and type definitions, and the source files
containing stub filter operation callbacks registered through
flt_otel_ops and the "opentelemetry" keyword parser entry point.

The filter uses the opentelemetry-c-wrapper library from HAProxy
Technologies, which provides a C interface to the OpenTelemetry C++ SDK.
This wrapper allows HAProxy, a C codebase, to leverage the full
OpenTelemetry observability pipeline without direct C++ dependencies
in the HAProxy source tree.

  https://github.com/haproxytech/opentelemetry-c-wrapper
  https://github.com/open-telemetry/opentelemetry-cpp

Build options:

  USE_OTEL     - enable the OpenTelemetry filter
  OTEL_DEBUG   - compile the filter in debug mode
  OTEL_INC     - force the include path to the C wrapper
  OTEL_LIB     - force the library path to the C wrapper
  OTEL_RUNPATH - add the C wrapper RUNPATH to the executable

Example build with OTel and debug enabled:

  make -j8 USE_OTEL=1 OTEL_DEBUG=1 TARGET=linux-glibc
2026-04-13 09:23:26 +02:00
Amaury Denoyelle
b8145fa5d4 BUG/MINOR: xprt_qstrm: do not parse record length on read again
conn_recv_qstrm() may be called several times per connection if the read
data is too short and a truncated record is received.

Previously, record length was parsed every time the function is invoked.
However, this must only be performed if record length varint is
incomplete. Once read and parsed, data are removed from the buffer via
b_quic_dec_int(). Thus, next conn_recv_qstrm() run will reread an
invalid record length this time.

This patch fixes this by only parsing record length if <rxrlen> member
is null. Prior to it, parsing of QMux transport parameters would fail in
case of a first truncated read, which would prevent the connection
initialization.

No need to backport.
2026-04-13 09:11:08 +02:00
Amaury Denoyelle
b5624a6365 BUG/MINOR: mux_quic: prevent QMux crash on qcc_io_send() error path
A QCC connection may be flagged with QC_CF_ERRL to trigger a
CONNECTION_CLOSE emission. However, for now error reporting is not
functional with QMux, as it relies on quic_conn layer access.

To prevent a crash in qcc_io_send() when using QMux, add a
conn_is_quic() check when QC_CF_ERRL is set to ensure no access will be
performed on quic_conn layer. In the future, this should be extended so
that QMux is also able to emit CONNECTION_CLOSE for connection closure.

No need to backport.
2026-04-13 09:11:08 +02:00
Christopher Faulet
fb82dece47 BUG/MEDIUM: haterm: Properly initialize the splicing support for haterm
First, we must not emit any warning if splicing is not configured and the
global maxpipes value is 0. Then we must not remove GTUNE_USE_SPLICE flag
when we fail to allocate the haterm master pipe. Instead, we test it when we
negociate with the opposite side, to properly exclude the splicing if it is
not usable.

No backport needed.
2026-04-10 16:32:29 +02:00
Christopher Faulet
313121639e Revert "BUG/MEDIUM: haterm: Move all init functions of haterm in haterm_init.c"
This reverts commit 8056117e988a3fde05d46ecc71b2d1a3d802977d.

Moving haterm init from haproxy is not the right way to fix the issue
because it should be possible to use a haterm configuration in haproxy.

So let's revert the commit above.
2026-04-10 16:32:29 +02:00
Amaury Denoyelle
63febbace7 BUG/MINOR: do not crash on QMux reception of BLOCKED frames
Add QUIC BLOCKED frames in the list of supported types in
qstrm_parse_frm(). Nothing is really implemented for them as for QUIC,
but this prevents a crash when receiving one of them via QMux.

No need to backport.
2026-04-10 10:30:49 +02:00
Amaury Denoyelle
a993f0c503 MEDIUM: mux-quic/xprt_qstrm: implement QMux record emission
This patch implements emission of the new Record layer for QMux frames.
This handles mux-quic and xprt_qstrm layers as this is performed
similarly in both cases.

Currently, the simplest approach has been prefered : each frame is
encoded in its own record. This is not the most efficient in size but it
is extremely simple to implement for a first interop testing.
2026-04-10 10:20:52 +02:00
Amaury Denoyelle
792e055c7c MEDIUM: xprt_qstrm: implement QMux record parsing
This patch implements the new QMux record layer parsing for xprt_qstrm.
This is mostly similar to the MUX code from the previous patch.

Along with this change, a new xprt_qstrm layer accessor exposes the
possible remaining record length after Transport parameters parsing.
This can only occur when xprt_qstrm Rx buffer is not completely emptied
due to other following frames. If stored in the same record, MUX layer
has to know the remaining record length.

Thus, xprt_qstrm_rxrlen() is now used in qmux_init() to preinitialize
<rx.rlen> QCC field.
2026-04-10 10:20:52 +02:00
Amaury Denoyelle
5271cdaca3 MEDIUM: mux-quic: implement QMux record parsing
This is the first patch of a serie which aims to support the new Record
layer defined by the draft 01 of QMux protocol.

  https://www.ietf.org/archive/id/draft-ietf-quic-qmux-01.html#name-qmux-records

This patch deals with QMux reception at the MUX layer. The function
qcc_qstrm_recv() is adapted to read record headers before frame parsing.
This requires to keep the last record length read in a new QCC field
named <rx.rlen>.

Frames are only parsed once a full record is received. One of the
advantage of the record layer is that it can only contains whole frame
without truncation.
2026-04-10 10:20:52 +02:00
Amaury Denoyelle
10f2867dc2 MINOR: xprt_qstrm: handle connection errors
This patch implements proper connection error handling for xprt_qstrm
layer. Basically, processing is interrupted if CO_FL_ERROR is
encountered after either rcv_buf or snd_buf operations. Connectionn
error is set to the newly defined value CO_ER_QSTRM.
2026-04-10 10:20:52 +02:00
Amaury Denoyelle
47199ce895 MINOR: xprt_qstrm: implement Tx buffering
This commit adds buffering on transmission for xprt_qstrm layer. This is
necessary in the rare case where send syscall only emits partial data.

A new <txbuf> member is defined in xprt_qstrm context. On first send
invokation, buffer is allocated and then the QMux transport parameters
frame is encoded. Then emission is performed via snd_buf and each time
the send function is invoked.
2026-04-10 10:20:52 +02:00
Amaury Denoyelle
fb3b268747 MINOR: xprt_qstrm/mux-quic: handle extra QMux frames after params
Layer xprt_qstrm is responsible to read the initial QMux transport
parameters frame. However, it could receive more data if some other
frames follow it. This extra content can only be handled by the MUX
layer once initialized.

Theorically, it could have been implemented via MSG_PEEK. However, this
flag is currently ignored by SSL layer. Besides, it is tedious to
implement safely. A new approach has been prefered where the MUX layer
is responsible to retrieve remaining data via xprt_qstrm_rxbuf()
accessor function during its initialization.

Thus, qmux_init() now may retrieve the buffer from xprt_qstrm layer.
This is performed via b_xfer() which will result in a zero copy
transfer. If this happens, tasklet is immediately scheduled to start
demuxing.
2026-04-10 10:20:52 +02:00
Amaury Denoyelle
890831f292 MINOR: xprt_qstrm: implement Rx buffering
Implement buffering for reception on xprt_qstrm layer. This is necessary
to handle reception of a truncated QMux transport parameters frame.

This is performed via a new dedicated <rxbuf> member in xprt_qstrm
context. Read is performed by reusing the buffer until a whole frame can
be read.
2026-04-10 10:20:52 +02:00
Amaury Denoyelle
c63e6ecd4b BUG/MINOR: quic: increment pos pointer on QMux transport params parsing
QUIC frame parsers functions take a <pos> pointer as input argument for
the data to be parsed. If parsing is successful, <pos> must be
incremented to point to the next data.

Increment was not performed when parsing QMux transport parameters
frame. This commit fixes this. Note that for now there is no real issue
as xprt_qstrm does not check the QMux frame length.

No need to backport.
2026-04-10 10:20:52 +02:00
Amaury Denoyelle
90d0e8a948 BUG/MINOR: mux-quic: fix potential NULL deref on qcc_release()
In qcc_release(), <conn> may be NULL. Thus every access on it must be
tested.

With recent QMux introduction, a call to conn_is_quic() has been added
prior to registration of the stream rejection callback. It could lead to
NULL deref as <conn> is not tested there. Fix this by adding an extra
check on the pointer validity.

No need to backport.
2026-04-10 10:20:52 +02:00
Greg Kroah-Hartman
4ad200f276 BUG/MINOR: hlua: fix use-after-free of HTTP reason string
hlua_applet_http_status() stored the result of luaL_optlstring()
directly in http_ctx->reason. The pointer references Lua-managed
string storage which is only guaranteed valid until the C function
returns to Lua. If the GC runs between applet:set_status(200, str)
and applet:start_response(), the pointer dangles.

hlua_applet_http_send_response() then calls ist(http_ctx->reason)
which does strlen() on freed memory, followed by memcpy into the
HTX status line. The freed-and-reallocated chunk contents are sent
verbatim to the HTTP client.

Trigger:

    applet:set_status(200, table.concat({"Reason ", str:rep(50)}))
    collectgarbage("collect"); collectgarbage("collect")
    applet:start_response()

With heap grooming, adjacent allocation contents (session data, TLS
material from the same thread) leak into the response status line.

Anchor the Lua string in the registry keyed by the http_ctx field
address so it survives until the applet is done with it. The
registry entry is overwritten on each call (handles repeated
set_status) and naturally cleaned up when the lua_State is closed.

This patch should be backported to all stable versions.
2026-04-10 10:18:27 +02:00
Greg Kroah-Hartman
0aeae23056 BUG/MEDIUM: mux-fcgi: prevent record-length truncation with large bufsize
FCGI content_length is a 16-bit field but fcgi_set_record_size()
is called with size_t/uint32_t arguments. With tune.bufsize >= 65544
(legal; cfgparse-global.c only enforces <= INT_MAX-16), a single
HTX DATA block or accumulated outbuf can exceed 65535 bytes. The
implicit conversion to uint16_t silently truncates the length field
while b_add(mbuf, outbuf.data) writes the full body.

A client posting ~99000 bytes can craft the body so that bytes
after the truncated length are parsed by PHP-FPM as fresh FCGI
records on the connection: a smuggled BEGIN_REQUEST + PARAMS with
arbitrary SCRIPT_FILENAME / PHP_VALUE bypasses all haproxy ACLs.

Fix the zero-copy path by refusing it when the block exceeds 65535
bytes (falls through to copy). Fix the copy path by capping
outbuf.size to 65535 + header so the data-fill loop naturally
stops at the FCGI maximum and emits the rest in a subsequent record.

The PARAMS path at line 2084 is similarly affected but harder to
trigger (requires combined header+param size > 65535) and is
covered by the same outbuf.size cap pattern if applied there.

This patch must be backported to all stable versions.
2026-04-10 09:40:16 +02:00
Greg Kroah-Hartman
e6c3660327 BUG/MINOR: sample: fix info leak in regsub when exp_replace fails
exp_replace() returns int and returns -1 when the back-reference
expansion overflows the output buffer (regex.c:51). output->data is
size_t, so -1 becomes SIZE_MAX. There was no error check.

The subsequent comparisons interpret SIZE_MAX as a huge length:
"output->data > b_room(trash)" tries to grow trash, then
"max > output->data" is false so max stays at trash->size, and
memcpy(trash, output->area, trash->size) copies the full chunk.
output->area is a pool_alloc()'d chunk that is NOT zeroed; the
bytes after the partial exp_replace output are stale data from a
prior pool user (request headers, response bodies from the same
worker thread).

Trigger with a backreference whose expansion exceeds bufsize:

    http-request set-header X %[req.hdr(In),regsub('(.+)','\1\1')]

and a request with In: of ~9000 bytes. The X header sent to the
backend then contains ~9KB of stale heap data.

With tune.bufsize.large set, get_larger_trash_chunk() upgrades trash
and the memcpy reads up to ~50KB past the (smaller) output->area
allocation.

http_ana.c:2728 and http_act.c:551 already check exp_replace() for
-1; this call site was missed when backreferences were added.

This patch must be backported to all stable versions.
2026-04-10 09:33:37 +02:00
Christopher Faulet
b0a9216ca5 BUG/MEDIUM: samples: Fix handling of SMP_T_METH samples
Samples of type SMP_T_METH were not properly handled in smp_dup(),
smp_is_safe() and smp_is_rw(). For "other" methods, for instance PATCH, a
fallback was performed on the SMP_T_STR type. Only the buffer considered
changed. "smp->data.u.meth.str" should be used for the SMP_T_METH samples
while smp->data.u.str should be used for SMP_T_STR samples. However, in
smp_dup(), the result was stored in wrong buffer, the string one instead of
the method one. In smp_is_safe() and smp_is_rw(), the method buffer was not
used at all.

We now take care to use the right buffer.

This patch must be backported to all stable versions.
2026-04-09 22:05:12 +02:00
Christopher Faulet
265be7e8cb BUG/MINOR: haterm: Return the good start-line for 100-continue interim message
When "Expect" header was found in request headers, "HTTP/1.1 100-continue"
was returned instead of "HTTP/1.1 100 continue". Let's fix it.

No backport needed.
2026-04-09 22:04:42 +02:00
Greg Kroah-Hartman
0cde3cd4df BUG/MINOR: http-act: validate decoded lengths in *-headers-bin
http_action_set_headers_bin() decodes varint name and value lengths
from a binary sample but never validates that the decoded length
fits in the remaining sample data before constructing the ist.

If the value's varint decodes to a large number with only a few
bytes following, v.len exceeds the buffer and http_add_header()
memcpys past the sample, copying adjacent heap data into a header
sent to the backend (or client, with http-response).

The intended source for this action is the hdrs_bin sample fetch
which produces well-formed output, but nothing prevents an admin
from feeding it req.body or another untrusted source. With:

    http-request set-var(txn.h) req.body
    http-request add-headers-bin var(txn.h)

a POST body of [05]"X-Foo"[c8]"AB" produces v = {ptr="AB", len=200}
and 198 bytes of adjacent heap data go into X-Foo.

http_action_del_headers_bin() was fixed too.

Compare spoe_decode_buffer() which has the equivalent check.
Validate both name and value lengths against remaining data.

No backport needed.
2026-04-09 17:10:56 +02:00
Greg Kroah-Hartman
b63cae7f9b BUG/MINOR: resolvers: fix memory leak on AAAA additional records
Commit c84c15d3938a ("BUG/MINOR: resolvers: Apply dns-accept-family
setting on additional records") converted a switch statement to an
if/else chain but left the break; in the AAAA branch. In the new
form, break exits the surrounding for loop instead of a switch case.

For every AAAA additional record in an SRV response:
  - answer_record allocated at line 1460 is never freed and never
    inserted into answer_tree -> ~580 bytes leaked per response
  - all subsequent additional records in the response are silently
    discarded

A DNS server controlling SRV responses for haproxy service discovery
can leak memory at MB/min rates given default resolution intervals.
Also breaks IPv6 SRV target resolution outright since the AAAA record
is leaked rather than attached to its SRV entry.
2026-04-09 16:31:05 +02:00
William Lallemand
591a85e29e MINOR: lua: add tune.lua.openlibs to restrict loaded Lua standard libraries
HAProxy has always called luaL_openlibs() unconditionally, which opens
all standard Lua libraries including io, os, package and debug. This
makes it impossible to prevent Lua scripts from executing binaries
(os.execute, io.popen), loading native C modules (package/require), or
bypassing any Lua-level sandbox via the debug library.

Add a new global directive tune.lua.openlibs that accepts a comma-separated
list of library names to load:

  tune.lua.openlibs none                   # only base + coroutine
  tune.lua.openlibs string,math,table,utf8 # safe libs only
  tune.lua.openlibs all                    # default, same as before

The base and coroutine libraries are always loaded regardless: base provides
core Lua functions that HAProxy relies on, and coroutine is required because
HAProxy overrides coroutine.create() with its own safe implementation.

When all libraries are enabled (the default), the fast path still calls
luaL_openlibs() directly with no overhead. A parse error is returned if
the directive appears after lua-load or lua-load-per-thread (the Lua state
is already initialised at that point), or if 'none' is combined with other
library names. Note that fork() and new thread creation are already blocked
by default regardless of this setting (see "insecure-fork-wanted").
2026-04-09 14:31:10 +02:00
Willy Tarreau
3020fde525 BUG/MAJOR: slz: always make sure to limit fixed output to less than worst case literals
Literals are sent in two ways:
  - in EOB state, unencoded and prefixed with their length
  - in FIXED state, huffman-encoded

And references are only sent in FIXED state.

The API promises that the amount of data will not grow by more than
5 bytes every 65535 input bytes (the comment was adjusted to remind
this last point). This is guaranteed by the literal encoding in EOB
state (BT, LEN, NLEN + bytes), which is supposed to be the worst
case by design.

However, as reported by Greg KH, this is currently not true: the test
that decides whether or not to switch to FIXED state to send references
doesn't properly account for the number of bytes needed to roll back
to the *exact* same state in EOB, which means sending EOB, BT,
alignment, LEN and NLEN in addition to the referenced bytes, versus
sending the encoding for the reference. By not taking into account the
cost of returning to the initial state (BT+LEN+NLEN), it was possible
to stay too long in the FIXED state and to consume the extra bytes that
are needed to return to the EOB state, resulting in producing much more
data in case of multiple switchovers (up to 6.25% increase was measured
in tests, or 1/16, which matches worst case estimates based on the code).

And this check is only valid when starting from EOB (in order to restore
the same state that offers this guarantee). When already in FIXED state,
the encoded reference is always smaller than or same size as the data.
The smallest match length we support is 4 bytes, and when encoded this
is no more than 28 bits, so it is safe to stay in FIXED state as long
as needed while checking the possibility of switching back to EOB.

This very slightly reduces the compression ratio (-0.17% on a linux
kernel source) but makes sure we respect the API promise of no more
than 5 extra bytes per 65535 of input. A side effect of the slightly
simpler check is an ~7.5% performance increase in compression speed.

Many thanks to Greg for the detailed report allowing to reproduce
the issue.

This is libslz upstream commit 002e838935bf298d967f670036efa95822b6c84e.

Note: in haproxy's default configuration (tune.bufsize 16384,
tune.maxrewrite 1024), this problem cannot be triggered, because the
reserve limits input to 15360 bytes, and the overflow is maximum
960 bytes resulting in 16320 bytes total, which still fits into the
buffer. However, reducing tune.maxrewrite below 964, or tune.bufsize
above 17408 can result in overflows for specially crafted patterns.

A workaround for larger buffers consists in always setting tune.bufsize
to at least 1/16 of tune.bufsize.

Reported-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Link: https://www.mail-archive.com/haproxy@formilux.org/msg46837.html
2026-04-08 19:14:25 +02:00
Olivier Houchard
d759e60a32 MEDIUM: check: Revamp the way the protocol and xprt are determined
Storing the protocol directly into the check was not a good idea,
because the protocol may not be determined until after a DNS resolution
on the server, and may even change at runtime, if the DNS changes.
What we can, however, figure out at start up, is the net_addr_type,
which will contain all that we need to find out which protocol to use
later.
Also revert the changes made by commit 07edaed1918a6433126b4d4d61b7f7b0e9324b30
that would not reuse the server xprt if a different alpn is set for
checks. The alpn is just a string, and should not influence the choice
of the xprt.
We'll now make sure to use the server xprt, unless an address is
provided, in which case we'll use whatever xprt matches that address, or
a port, in which case we'll assume we want TCP, and use check_ssl to
know whetver we want the SSL xprt or not.
Now that the check contains all that is needed to know which protocol to
look up, always just use that when creating a new check connection if it
is the default check connection, and for now, always use TCP when a
tcp-check or http-check connect rule is used (which means those can't be
used for QUIC so far).

This should hopefully fix github issue #3324.
2026-04-08 18:41:48 +02:00