Compare commits

..

71 commits

Author SHA1 Message Date
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
50 changed files with 581 additions and 269 deletions

View file

@ -303,6 +303,7 @@ static void _51d_init_device_offsets(fiftyoneDegreesDeviceOffsets *offsets) {
static void _51d_set_device_offsets(struct sample *smp, fiftyoneDegreesDeviceOffsets *offsets) static void _51d_set_device_offsets(struct sample *smp, fiftyoneDegreesDeviceOffsets *offsets)
{ {
struct buffer *temp = get_trash_chunk();
struct channel *chn; struct channel *chn;
struct htx *htx; struct htx *htx;
struct http_hdr_ctx ctx; struct http_hdr_ctx ctx;
@ -324,7 +325,15 @@ static void _51d_set_device_offsets(struct sample *smp, fiftyoneDegreesDeviceOff
if (http_find_header(htx, name, &ctx, 1)) { if (http_find_header(htx, name, &ctx, 1)) {
(offsets->firstOffset + offsets->size)->httpHeaderOffset = *(global_51degrees.header_offsets + i); (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++; offsets->size++;
} }
} }

View file

@ -3329,7 +3329,7 @@ setenv <name> <value>
the configuration file sees the new value. See also "presetenv", "resetenv", the configuration file sees the new value. See also "presetenv", "resetenv",
and "unsetenv". 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 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 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 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" 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 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 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 related to the maximum memory size of the shm and is used to "premap" the
@ -9962,7 +9962,7 @@ no option accept-unsafe-violations-in-http-request
When this option is set, the following rules are observed: When this option is set, the following rules are observed:
* In H1 only, invalid characters, including NULL character, in header name * 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; * In H1 only, NULL character in header value will be accepted;
@ -10027,8 +10027,11 @@ no option accept-unsafe-violations-in-http-response
When this option is set, the following rules are observed: 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 * 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; * In H1 only, NULL character in header value will be accepted;

View file

@ -179,6 +179,8 @@ enum {
/* below we have all handshake flags grouped into one */ /* 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_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, 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 */ 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_name(struct connection *c);
const char *conn_err_code_str(struct connection *c); const char *conn_err_code_str(struct connection *c);
int xprt_add_hs(struct connection *conn); 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); 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); static inline void conn_report_term_evt(struct connection *conn, enum term_event_loc loc, unsigned char type);

View file

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

View file

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

View file

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

View file

@ -1288,11 +1288,27 @@ static inline void _ha_aligned_free(void *ptr)
int parse_dotted_uints(const char *s, unsigned int **nums, size_t *sz); int parse_dotted_uints(const char *s, unsigned int **nums, size_t *sz);
/* PRNG */ /* PRNG */
struct uint64_pair _ha_random64_pair_hashed(void);
void ha_generate_uuid_v4(struct buffer *output); void ha_generate_uuid_v4(struct buffer *output);
void ha_generate_uuid_v7(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_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(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() static inline uint32_t ha_random32()
{ {

View file

@ -1532,7 +1532,7 @@ int acme_res_certificate(struct task *task, struct acme_ctx *ctx, char **errmsg)
hdrs = hc->res.hdrs; 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"))) { if (isteqi(hdr->n, ist("Replay-Nonce"))) {
istfree(&ctx->nonce); istfree(&ctx->nonce);
ctx->nonce = istdup(hdr->v); 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; key = ctx->store->data->key;
ctx->store->data->key = NULL; 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 */ /* 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) if (ssl_sock_load_pem_into_ckch(ctx->store->path, hc->res.buf.area, ctx->store->data , errmsg) != 0)
goto error; goto error;
@ -1735,7 +1745,7 @@ int acme_res_finalize(struct task *task, struct acme_ctx *ctx, char **errmsg)
hdrs = hc->res.hdrs; 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"))) { if (isteqi(hdr->n, ist("Replay-Nonce"))) {
istfree(&ctx->nonce); istfree(&ctx->nonce);
ctx->nonce = istdup(hdr->v); 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); 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"))) { if (isteqi(hdr->n, ist("Replay-Nonce"))) {
istfree(&ctx->nonce); istfree(&ctx->nonce);
ctx->nonce = istdup(hdr->v); 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; 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"))) { if (isteqi(hdr->n, ist("Replay-Nonce"))) {
istfree(&ctx->nonce); istfree(&ctx->nonce);
ctx->nonce = istdup(hdr->v); 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; 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"))) { if (isteqi(hdr->n, ist("Replay-Nonce"))) {
istfree(&ctx->nonce); istfree(&ctx->nonce);
ctx->nonce = istdup(hdr->v); 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; 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"))) { if (isteqi(hdr->n, ist("Location"))) {
istfree(&ctx->kid); istfree(&ctx->kid);
ctx->kid = istdup(hdr->v); 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; 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"))) { if (isteqi(hdr->n, ist("Replay-Nonce"))) {
istfree(&ctx->nonce); istfree(&ctx->nonce);
ctx->nonce = istdup(hdr->v); ctx->nonce = istdup(hdr->v);

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)) if (applet_fl_test(appctx, APPCTX_FL_OUTBLK_ALLOC))
goto end; goto end;
if (!count)
goto end;
if (!appctx_get_buf(appctx, &appctx->outbuf)) { if (!appctx_get_buf(appctx, &appctx->outbuf)) {
TRACE_STATE("waiting for appctx outbuf allocation", APPLET_EV_RECV|APPLET_EV_BLK, appctx); TRACE_STATE("waiting for appctx outbuf allocation", APPLET_EV_RECV|APPLET_EV_BLK, appctx);
goto end; 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) if (flags & CO_RFL_BUF_FLUSH)
applet_fl_set(appctx, APPCTX_FL_FASTFWD); applet_fl_set(appctx, APPCTX_FL_FASTFWD);
if (count)
ret = CALL_APPLET_WITH_RET(appctx->applet, rcv_buf(appctx, buf, count, flags)); ret = CALL_APPLET_WITH_RET(appctx->applet, rcv_buf(appctx, buf, count, flags));
if (ret) if (ret)
applet_fl_clr(appctx, APPCTX_FL_OUTBLK_FULL); applet_fl_clr(appctx, APPCTX_FL_OUTBLK_FULL);

View file

@ -297,7 +297,7 @@ check_user(struct userlist *ul, const char *user, const char *pass)
fprintf(stderr, ", crypt=%s\n", ((ep) ? ep : "")); fprintf(stderr, ", crypt=%s\n", ((ep) ? ep : ""));
#endif #endif
if (ep && strcmp(ep, u->pass) == 0) if (ep && u->pass && strcmp(ep, u->pass) == 0)
return 1; return 1;
else else
return 0; 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 *cli_conn = objt_conn(strm_orig(s));
struct connection *srv_conn = NULL; struct connection *srv_conn = NULL;
const struct mux_proto_list *mux_proto; const struct mux_proto_list *mux_proto = NULL;
struct server *srv; struct server *srv;
struct ist name = IST_NULL; struct ist name = IST_NULL;
struct sample *name_smp; struct sample *name_smp;
@ -2139,13 +2139,11 @@ int connect_server(struct stream *s)
} }
if (may_start_mux_now) { 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); mux_proto = conn_select_mux_be(srv_conn);
if (mux_proto && mux_proto->init_xprt == XPRT_QMUX) { if (mux_proto && mux_proto->init_xprt)
srv_conn->flags |= (CO_FL_QMUX_RECV|CO_FL_QMUX_SEND);
may_start_mux_now = 0; may_start_mux_now = 0;
} }
}
#if defined(USE_OPENSSL) && defined(TLSEXT_TYPE_application_layer_protocol_negotiation) #if defined(USE_OPENSSL) && defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
/* if websocket stream, try to update connection ALPN. */ /* 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. * 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; signed char b;
int convlen = 0, i = 0, pad = 0; int convlen = 0, i = 0, pad = 0;
if (!ilen)
return 0;
if (ilen % 4) if (ilen % 4)
return -1; 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. * delete_expired==0, write otherwise.
*/ */
struct cache_entry *get_secondary_entry(struct cache_tree *cache, struct cache_entry *entry, 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; 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; 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 */ /* Expired entry */
if (entry && entry->expire <= date.tv_sec) { if (entry && entry->expire <= date.tv_sec) {
if (delete_expired) { if (delete_expired) {
@ -1303,7 +1309,7 @@ enum act_return http_action_store_cache(struct act_rule *rule, struct proxy *px,
if (old) { if (old) {
if (vary_signature) if (vary_signature)
old = get_secondary_entry(cache_tree, old, old = get_secondary_entry(cache_tree, old,
txn->cache_secondary_hash, 1); txn->cache_hash, txn->cache_secondary_hash, 1);
if (old) { if (old) {
if (!old->complete) { if (!old->complete) {
/* An entry with the same primary key is already being /* 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)) { if (!http_request_build_secondary_key(s, res->secondary_key_signature)) {
cache_rdlock(cache_tree); cache_rdlock(cache_tree);
sec_entry = get_secondary_entry(cache_tree, res, sec_entry = get_secondary_entry(cache_tree, res,
s->txn.http->cache_hash,
s->txn.http->cache_secondary_hash, s->txn.http->cache_secondary_hash,
0); 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. */ /* The wrong row was added to the hot list. */
release_entry(cache_tree, res, 0); release_entry(cache_tree, res, 0);
retain_entry(sec_entry); retain_entry(sec_entry);

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, struct proxy *curpx, const struct proxy *defpx,
const char *file, int line, char **err) 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) { if (global.shm_stats_file != NULL) {
memprintf(err, "'%s' already specified.\n", args[0]); memprintf(err, "'%s' already specified.\n", args[0]);
return -1; return -1;
@ -1644,7 +1639,6 @@ static int cfg_parse_global_shm_stats_file(char **args, int section_type,
return -1; return -1;
} }
mark_tainted(TAINTED_CONFIG_EXP_KW_DECLARED);
global.shm_stats_file = strdup(args[1]); global.shm_stats_file = strdup(args[1]);
return 0; 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, struct proxy *curpx, const struct proxy *defpx,
const char *file, int line, char **err) 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) { if (shm_stats_file_max_objects != -1) {
memprintf(err, "'%s' already specified.\n", args[0]); memprintf(err, "'%s' already specified.\n", args[0]);
return -1; return -1;
@ -1668,7 +1657,6 @@ static int cfg_parse_global_shm_stats_file_max_objects(char **args, int section_
return -1; return -1;
} }
mark_tainted(TAINTED_CONFIG_EXP_KW_DECLARED);
shm_stats_file_max_objects = atoi(args[1]); shm_stats_file_max_objects = atoi(args[1]);
return 0; return 0;
} }

View file

@ -1151,8 +1151,13 @@ int cli_parse_cmdline(struct appctx *appctx)
*/ */
if (len-1 == strlen(appctx->cli_ctx.payload_pat)) { if (len-1 == strlen(appctx->cli_ctx.payload_pat)) {
if (strncmp(str, appctx->cli_ctx.payload_pat, len-1) == 0) { 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 */ /* end of payload was reached, rewind before the previous \n, if any, and replace it by a \0
b_sub(buf, strlen(appctx->cli_ctx.payload_pat) + 2); * 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'; *b_tail(buf) = '\0';
appctx->st1 &= ~APPCTX_CLI_ST1_PAYLOAD; 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 * information to create one, typically from the ALPN. If we're
* done with the handshake, attempt to create one. * 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); ret = conn_create_mux(conn, NULL);
if (ret < 0) if (ret < 0)
goto done; goto done;
@ -847,6 +847,43 @@ int xprt_add_hs(struct connection *conn)
return 0; 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 /* 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 * 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. * catching in logs). This is more compact for some debug cases.

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 *dict_insert(struct dict *d, char *s)
{ {
struct dict_entry *de; struct dict_entry *de, *tree_de;
struct ebpt_node *n; struct ebpt_node *n;
HA_RWLOCK_RDLOCK(DICT_LOCK, &d->rwlock); 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); HA_RWLOCK_WRLOCK(DICT_LOCK, &d->rwlock);
n = ebis_insert(&d->values, &de->value); 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); HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock);
if (n != &de->value) {
free_dict_entry(de); free_dict_entry(de);
de = container_of(n, struct dict_entry, value);
} }
return tree_de;
return de;
} }
@ -117,10 +122,11 @@ void dict_entry_unref(struct dict *d, struct dict_entry *de)
if (!de) if (!de)
return; return;
if (HA_ATOMIC_SUB_FETCH(&de->refcount, 1) != 0)
return;
HA_RWLOCK_WRLOCK(DICT_LOCK, &d->rwlock); 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); ebpt_delete(&de->value);
HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock); HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock);

View file

@ -710,6 +710,16 @@ int h1_headers_to_hdr_list(char *start, const char *stop,
case H1_MSG_RPCODE: case H1_MSG_RPCODE:
http_msg_rpcode: http_msg_rpcode:
if (likely(HTTP_IS_DIGIT(*ptr))) { 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'; 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); EAT_AND_JUMP_OR_RETURN(ptr, end, http_msg_rpcode, http_msg_ood, state, H1_MSG_RPCODE);
} }
@ -952,6 +962,20 @@ int h1_headers_to_hdr_list(char *start, const char *stop,
goto http_output_full; 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"))) { if (isteqi(n, ist("transfer-encoding"))) {
ret = h1_parse_xfer_enc_header(h1m, v); ret = h1_parse_xfer_enc_header(h1m, v);
if (ret < 0) { if (ret < 0) {
@ -1248,9 +1272,10 @@ int h1_headers_to_hdr_list(char *start, const char *stop,
void h1_generate_random_ws_input_key(char key_out[25]) void h1_generate_random_ws_input_key(char key_out[25])
{ {
/* generate a random websocket key */ /* generate a random websocket key */
const uint64_t rand1 = ha_random64(), rand2 = ha_random64(); uint64_t rand1, rand2;
char key[16]; char key[16];
ha_random64_pair_hashed(&rand1, &rand2);
memcpy(key, &rand1, 8); memcpy(key, &rand1, 8);
memcpy(&key[8], &rand2, 8); memcpy(&key[8], &rand2, 8);
a2base64(key, 16, key_out, 25); a2base64(key, 16, key_out, 25);

View file

@ -212,8 +212,33 @@ static ssize_t h3_init_uni_stream(struct h3c *h3c, struct qcs *qcs,
break; break;
case H3_UNI_S_T_PUSH: case H3_UNI_S_T_PUSH:
/* TODO not supported for the moment */ if (!conn_is_back(qcs->qcc->conn)) {
h3s->type = H3S_T_PUSH; /* 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; break;
case H3_UNI_S_T_QPACK_DEC: 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_CANCEL_PUSH:
case H3_FT_GOAWAY: case H3_FT_GOAWAY:
case H3_FT_MAX_PUSH_ID:
/* RFC 9114 7.2.3. CANCEL_PUSH /* RFC 9114 7.2.3. CANCEL_PUSH
* *
* A CANCEL_PUSH frame is sent on the control stream. Receiving a * A CANCEL_PUSH frame is sent on the control stream. Receiving a
@ -412,16 +436,35 @@ static int h3_check_frame_valid(struct h3c *h3c, struct qcs *qcs, uint64_t ftype
case H3_FT_PUSH_PROMISE: case H3_FT_PUSH_PROMISE:
/* RFC 9114 7.2.5. 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 * 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 * receipt of a PUSH_PROMISE frame as a connection error of type
* H3_FRAME_UNEXPECTED. * H3_FRAME_UNEXPECTED.
*/ */
if (h3s->type == H3S_T_CTRL || !conn_is_back(qcs->qcc->conn))
/* TODO server-side only. */
ret = H3_ERR_FRAME_UNEXPECTED; ret = H3_ERR_FRAME_UNEXPECTED;
break; 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: default:
/* RFC 9114 9. Extensions to HTTP/3 /* RFC 9114 9. Extensions to HTTP/3
* *
@ -1930,6 +1973,25 @@ static ssize_t h3_rcv_buf(struct qcs *qcs, struct buffer *b, int fin)
h3s->st_req = H3S_ST_REQ_TRAILERS; h3s->st_req = H3S_ST_REQ_TRAILERS;
} }
break; 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: case H3_FT_GOAWAY:
ret = h3_parse_goaway_frm(qcs->qcc->ctx, b, flen); ret = h3_parse_goaway_frm(qcs->qcc->ctx, b, flen);
if (ret < 0) { if (ret < 0) {
@ -1938,12 +2000,6 @@ static ssize_t h3_rcv_buf(struct qcs *qcs, struct buffer *b, int fin)
goto err; goto err;
} }
break; 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: case H3_FT_SETTINGS:
ret = h3_parse_settings_frm(qcs->qcc->ctx, b, flen); ret = h3_parse_settings_frm(qcs->qcc->ctx, b, flen);
if (ret < 0) { if (ret < 0) {
@ -1953,6 +2009,25 @@ static ssize_t h3_rcv_buf(struct qcs *qcs, struct buffer *b, int fin)
} }
h3c->flags |= H3_CF_SETTINGS_RECV; h3c->flags |= H3_CF_SETTINGS_RECV;
break; 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: default:
/* RFC 9114 Section 9. Extensions to HTTP/3 /* RFC 9114 Section 9. Extensions to HTTP/3
* *

View file

@ -1926,20 +1926,30 @@ static void dump_registered_keywords(void)
/* Generate a random cluster-secret in case the setting is not provided in the /* 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 * 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() static void generate_random_cluster_secret()
{ {
/* used as a default random cluster-secret if none defined. */ /* 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. */ /* The caller must not overwrite an already defined secret. */
BUG_ON(cluster_secret_isset); 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)); memcpy(global.cluster_secret, &rand, sizeof(rand));
rand = ha_random64();
memcpy(global.cluster_secret + sizeof(rand), &rand, sizeof(rand));
cluster_secret_isset = 1; cluster_secret_isset = 1;
} }
@ -3088,6 +3098,7 @@ void *run_thread_poll_loop(void *data)
ha_set_thread(data); ha_set_thread(data);
set_thread_cpu_affinity(); set_thread_cpu_affinity();
clock_set_local_source(); clock_set_local_source();
ha_random_seed_thread();
#ifdef USE_THREAD #ifdef USE_THREAD
ha_thread_info[tid].pth_id = ha_get_pthread_id(tid); ha_thread_info[tid].pth_id = ha_get_pthread_id(tid);

View file

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

View file

@ -401,7 +401,7 @@ void haproxy_init_args(int argc, char **argv)
/* SSL/TCP binding */ /* SSL/TCP binding */
hbuf_appendf(&fbuf, "\tbind %s:%s shards by-thread ssl " 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_RSA_CERT_NAME
" crt " HATERM_ECDSA_CERT_NAME "%s%s\n", " crt " HATERM_ECDSA_CERT_NAME "%s%s\n",
ip, port2, ip, port2,
@ -438,6 +438,8 @@ void haproxy_init_args(int argc, char **argv)
} }
hbuf_appendf(&gbuf, "global\n"); hbuf_appendf(&gbuf, "global\n");
hbuf_appendf(&gbuf, "\ttune.memory.hot-size 3145728\n"); hbuf_appendf(&gbuf, "\ttune.memory.hot-size 3145728\n");
if (has_ssl)
hbuf_appendf(&gbuf, "\texpose-experimental-directives\n");
} }
/* "global" section */ /* "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. */ /* remove final \r\n. */
if (nblk == 1) { if (nblk == 1) {
if (blk1[len1-1] == '\n') { if (len1 && blk1[len1-1] == '\n') {
len1--; len1--;
skip_at_end++; skip_at_end++;
if (blk1[len1-1] == '\r') { if (len1 && blk1[len1-1] == '\r') {
len1--; len1--;
skip_at_end++; skip_at_end++;
} }
} }
} }
else { else {
if (blk2[len2-1] == '\n') { if (len2 && blk2[len2-1] == '\n') {
len2--; len2--;
skip_at_end++; skip_at_end++;
if (blk2[len2-1] == '\r') { if (len2 && blk2[len2-1] == '\r') {
len2--; len2--;
skip_at_end++; 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; size_t value_len;
const char *value = MAY_LJMP(luaL_checklstring(L, 3, &value_len)); const char *value = MAY_LJMP(luaL_checklstring(L, 3, &value_len));
struct htx *htx = htxbuf(&msg->chn->buf); 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), lua_pushboolean(L, http_add_header(htx, ist2(name, name_len),
ist2(value, value_len), 1)); ist2(value, value_len), 1));

View file

@ -3996,19 +3996,19 @@ void http_check_response_for_cacheability(struct stream *s, struct channel *res)
continue; 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. */ /* 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; txn->flags &= ~TX_CACHE_COOK;
continue; 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"))) { if (istmatchi(ctx.value, ist("s-maxage"))) {
has_freshness_info = 1; has_freshness_info = 1;
has_null_maxage = 0; /* The null max-age is overridden, ignore it */ 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. * before decoding.
*/ */
if (smp->flags & SMP_F_CONST || smp->data.u.str.size <= smp->data.u.str.data) { 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) if (!str)
return 0; return 0;

View file

@ -356,7 +356,7 @@ static inline int http_7239_extract_node(struct ist *input, struct forwarded_hea
if (!quoted) if (!quoted)
return 0; /* not supported */ return 0; /* not supported */
*input = istnext(*input); *input = istnext(*input);
if (!http_7239_extract_nodeport(input, nodeport)) if (!istlen(*input) || !http_7239_extract_nodeport(input, nodeport))
return 0; /* invalid nodeport */ return 0; /* invalid nodeport */
out: out:
/* ok */ /* 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); 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(); struct buffer *http_auth = get_trash_chunk();
len = base64dec(txn->auth.method_data.area, 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; txn->auth.method = HTTP_AUTH_BASIC;
return 1; 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; txn->auth.method = HTTP_AUTH_BEARER;
return 1; return 1;
} }

View file

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

View file

@ -6236,6 +6236,13 @@ next_frame:
/* Skip StreamDep and weight for now (we don't support PRIORITY) */ /* Skip StreamDep and weight for now (we don't support PRIORITY) */
if (h2c->dff & H2_F_HEADERS_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) { if (read_n32(hdrs) == h2c->dsi) {
/* RFC7540#5.3.1 : stream dep may not depend on itself */ /* RFC7540#5.3.1 : stream dep may not depend on itself */
h2c_report_glitch(h2c, 1, "PRIORITY frame referencing itself"); h2c_report_glitch(h2c, 1, "PRIORITY frame referencing itself");
@ -6245,13 +6252,6 @@ next_frame:
goto fail; 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 hdrs += 5; // stream dep = 4, weight = 1
flen -= 5; flen -= 5;
} }

View file

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

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 */ /* kind1 = NOP and is a single byte, others have a length field */
if (smp->data.u.str.area[next] == 1) if (smp->data.u.str.area[next] == 1)
next++; next++;
else if (next + 1 < len) else if (next + 1 < len && smp->data.u.str.area[next + 1] > 1)
next += smp->data.u.str.area[next + 1]; next += (uchar)smp->data.u.str.area[next + 1];
else else
break; break;
if (smp->data.u.str.area[curr] == opt && next <= len) 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 */ /* kind1 = NOP and is a single byte, others have a length field */
if (smp->data.u.str.area[ofs] == 1) if (smp->data.u.str.area[ofs] == 1)
ofs++; 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]; ofs += (uchar)smp->data.u.str.area[ofs + 1];
else else
break; 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 */ /* kind1 = NOP and is a single byte, others have a length field */
if (smp->data.u.str.area[ofs] == 1) if (smp->data.u.str.area[ofs] == 1)
next = 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]; next = ofs + (uchar)smp->data.u.str.area[ofs + 1];
else else
break; 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 */ data += 5; /* enter TLS handshake */
bleft -= 5; bleft -= 5;
if (bleft < hs_len)
goto too_short;
/* Check for a complete client hello starting at <data> */ /* Check for a complete client hello starting at <data> */
if (bleft < 1) if (bleft < 1)
goto too_short; 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) if (hs_len < 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + 2)
goto not_ssl_hello; /* too short to have an extension */ goto not_ssl_hello; /* too short to have an extension */
data += 4;
bleft -= 4;
/* We want the full handshake here */ /* We want the full handshake here */
if (bleft < hs_len) if (bleft < hs_len)
goto too_short; goto too_short;
data += 4;
/* Start of the ClientHello message */ /* Start of the ClientHello message */
if (data[0] < 0x03 || data[1] < 0x01) /* TLSv1 minimum */ if (data[0] < 0x03 || data[1] < 0x01) /* TLSv1 minimum */
goto not_ssl_hello; goto not_ssl_hello;
/* Note: covered by the hs_len test 30 lines above */
ext_len = data[34]; /* session_id_len */ ext_len = data[34]; /* session_id_len */
if (ext_len > 32 || ext_len > (hs_len - 35)) /* check for correct session_id len */ if (ext_len > 32 || ext_len > (hs_len - 35)) /* check for correct session_id len */
goto not_ssl_hello; goto not_ssl_hello;

View file

@ -69,7 +69,7 @@
#include <haproxy/uri_auth.h> #include <haproxy/uri_auth.h>
/* Lock to ensure multiple backends deletion concurrently is safe */ /* Lock to ensure multiple backends deletion concurrently is safe */
static __decl_spinlock(proxies_del_lock); __decl_spinlock(proxies_del_lock);
int listeners; /* # of proxy listeners, set by cfgparse */ int listeners; /* # of proxy listeners, set by cfgparse */
struct proxy *proxies_list = NULL; /* list of main proxies */ struct proxy *proxies_list = NULL; /* list of main proxies */

View file

@ -70,6 +70,10 @@ static int qmux_parse_frm(struct qcc *qcc, struct buffer *buf)
struct qf_reset_stream *rst_frm = &frm.reset_stream; struct qf_reset_stream *rst_frm = &frm.reset_stream;
qcc_recv_reset_stream(qcc, rst_frm->id, rst_frm->app_error_code, rst_frm->final_size); qcc_recv_reset_stream(qcc, rst_frm->id, rst_frm->app_error_code, rst_frm->final_size);
} }
else if (frm.type == QUIC_FT_STOP_SENDING) {
struct qf_stop_sending *ss_frm = &frm.stop_sending;
qcc_recv_stop_sending(qcc, ss_frm->id, ss_frm->app_error_code);
}
else if (frm.type == QUIC_FT_MAX_DATA) { else if (frm.type == QUIC_FT_MAX_DATA) {
struct qf_max_data *md_frm = &frm.max_data; struct qf_max_data *md_frm = &frm.max_data;
qcc_recv_max_data(qcc, md_frm->max_data); qcc_recv_max_data(qcc, md_frm->max_data);
@ -82,13 +86,26 @@ static int qmux_parse_frm(struct qcc *qcc, struct buffer *buf)
struct qf_max_streams *ms_frm = &frm.max_streams_bidi; struct qf_max_streams *ms_frm = &frm.max_streams_bidi;
qcc_recv_max_streams(qcc, ms_frm->max_streams, 1); qcc_recv_max_streams(qcc, ms_frm->max_streams, 1);
} }
else if (frm.type == QUIC_FT_MAX_STREAMS_UNI) {
struct qf_max_streams *ms_frm = &frm.max_streams_uni;
qcc_recv_max_streams(qcc, ms_frm->max_streams, 0);
}
else if (frm.type == QUIC_FT_DATA_BLOCKED || else if (frm.type == QUIC_FT_DATA_BLOCKED ||
frm.type == QUIC_FT_STREAM_DATA_BLOCKED || frm.type == QUIC_FT_STREAM_DATA_BLOCKED ||
frm.type == QUIC_FT_STREAMS_BLOCKED_BIDI || frm.type == QUIC_FT_STREAMS_BLOCKED_BIDI ||
frm.type == QUIC_FT_STREAMS_BLOCKED_UNI) { frm.type == QUIC_FT_STREAMS_BLOCKED_UNI) {
/* TODO */ /* TODO */
CHECK_IF("received flow control blocked frame not yet handled in QMux");
}
else if (frm.type == QUIC_FT_PADDING) {
CHECK_IF("received padding frame not yet handled in QMux");
}
else if (frm.type == QUIC_FT_CONNECTION_CLOSE ||
frm.type == QUIC_FT_CONNECTION_CLOSE_APP) {
CHECK_IF("received connection_close frame not yet handled in QMux");
} }
else { else {
/* qmux_is_frm_valid() must prevent this */
ABORT_NOW(); ABORT_NOW();
} }
@ -171,11 +188,10 @@ int qcc_qmux_recv(struct qcc *qcc)
buf_rec = b_make(b_orig(buf), b_size(buf), buf_rec = b_make(b_orig(buf), b_size(buf),
b_head_ofs(buf), qcc->rx.rlen); b_head_ofs(buf), qcc->rx.rlen);
frm_ret = qmux_parse_frm(qcc, &buf_rec); frm_ret = qmux_parse_frm(qcc, &buf_rec);
BUG_ON(frm_ret < 0); /* TODO handle fatal errors */
if (!frm_ret) { if (!frm_ret) {
/* emit FRAME_ENCODING_ERROR */ /* TODO implement proper connection closure */
ABORT_NOW(); conn->flags |= CO_FL_ERROR;
goto out;
} }
/* A frame cannot be bigger than a record thanks to <buf_rec> delimitation. */ /* A frame cannot be bigger than a record thanks to <buf_rec> delimitation. */

View file

@ -378,7 +378,7 @@ int quic_get_cid_tid(const unsigned char *cid, size_t cid_len,
tree = &quic_fe_cid_trees[quic_cid_tree_idx(&derive_cid)]; tree = &quic_fe_cid_trees[quic_cid_tree_idx(&derive_cid)];
HA_RWLOCK_RDLOCK(QC_CID_LOCK, &tree->lock); HA_RWLOCK_RDLOCK(QC_CID_LOCK, &tree->lock);
node = ebmb_lookup(&tree->root, cid, cid_len); node = ebmb_lookup(&tree->root, derive_cid.data, derive_cid.len);
if (node) { if (node) {
conn_id = ebmb_entry(node, struct quic_connection_id, node); conn_id = ebmb_entry(node, struct quic_connection_id, node);
cid_tid = HA_ATOMIC_LOAD(&conn_id->tid); cid_tid = HA_ATOMIC_LOAD(&conn_id->tid);

View file

@ -24,8 +24,10 @@ int quic_generate_token(unsigned char *token, size_t len,
unsigned char aad[sizeof(struct in6_addr)]; unsigned char aad[sizeof(struct in6_addr)];
size_t aadlen; size_t aadlen;
uint32_t ts = (uint32_t)date.tv_sec; uint32_t ts = (uint32_t)date.tv_sec;
uint64_t rand_u64; union {
unsigned char rand[QUIC_TOKEN_RAND_DLEN]; uint64_t u64[2];
uchar u8[QUIC_TOKEN_RAND_DLEN];
} rand;
unsigned char key[16]; unsigned char key[16];
unsigned char iv[QUIC_TLS_IV_LEN]; unsigned char iv[QUIC_TLS_IV_LEN];
const unsigned char *sec = global.cluster_secret; const unsigned char *sec = global.cluster_secret;
@ -35,10 +37,7 @@ int quic_generate_token(unsigned char *token, size_t len,
TRACE_ENTER(QUIC_EV_CONN_TXPKT); TRACE_ENTER(QUIC_EV_CONN_TXPKT);
/* Generate random data to be used as salt to derive the token secret. */ /* Generate random data to be used as salt to derive the token secret. */
rand_u64 = ha_random64(); ha_random64_pair_hashed(&rand.u64[0], &rand.u64[1]);
write_u64(rand, rand_u64);
rand_u64 = ha_random64();
write_u64(rand + sizeof(rand_u64), rand_u64);
if (len < QUIC_TOKEN_LEN) { if (len < QUIC_TOKEN_LEN) {
TRACE_ERROR("too small buffer", QUIC_EV_CONN_TXPKT); TRACE_ERROR("too small buffer", QUIC_EV_CONN_TXPKT);
@ -48,7 +47,7 @@ int quic_generate_token(unsigned char *token, size_t len,
/* Generate the AAD. */ /* Generate the AAD. */
aadlen = ipaddrcpy(aad, addr); aadlen = ipaddrcpy(aad, addr);
if (!quic_tls_derive_token_secret(EVP_sha256(), key, sizeof key, if (!quic_tls_derive_token_secret(EVP_sha256(), key, sizeof key,
iv, sizeof iv, rand, sizeof(rand), iv, sizeof iv, rand.u8, sizeof(rand.u8),
sec, seclen)) { sec, seclen)) {
TRACE_ERROR("quic_tls_derive_token_secret() failed", QUIC_EV_CONN_TXPKT); TRACE_ERROR("quic_tls_derive_token_secret() failed", QUIC_EV_CONN_TXPKT);
goto err; goto err;
@ -71,8 +70,8 @@ int quic_generate_token(unsigned char *token, size_t len,
} }
p += QUIC_TLS_TAG_LEN; p += QUIC_TLS_TAG_LEN;
memcpy(p, rand, sizeof(rand)); memcpy(p, rand.u8, sizeof(rand.u8));
p += sizeof(rand); p += sizeof(rand.u8);
ret = p - token; ret = p - token;
leave: leave:

View file

@ -444,7 +444,7 @@ INITCALL0(STG_REGISTER, regex_register_build_options);
#ifdef USE_PCRE2 #ifdef USE_PCRE2
static int init_pcre2_per_thread(void) static int init_pcre2_per_thread(void)
{ {
local_pcre2_match = pcre2_match_data_create(MAX_MATCH - 1, NULL); local_pcre2_match = pcre2_match_data_create(MAX_MATCH, NULL);
if (!local_pcre2_match) { if (!local_pcre2_match) {
ha_alert("Failed to allocate PCRE2 match data context for thread %u.\n", tid); ha_alert("Failed to allocate PCRE2 match data context for thread %u.\n", tid);
return 0; return 0;

View file

@ -63,7 +63,6 @@ struct list resolv_srvrq_list = LIST_HEAD_INIT(resolv_srvrq_list);
static THREAD_LOCAL struct list death_row; /* list of deferred resolutions to kill, local validity only */ static THREAD_LOCAL struct list death_row; /* list of deferred resolutions to kill, local validity only */
static THREAD_LOCAL unsigned int recurse = 0; /* counter to track calls to public functions */ static THREAD_LOCAL unsigned int recurse = 0; /* counter to track calls to public functions */
static THREAD_LOCAL uint64_t resolv_query_id_seed = 0; /* random seed */
struct resolvers *curr_resolvers = NULL; struct resolvers *curr_resolvers = NULL;
DECLARE_STATIC_TYPED_POOL(resolv_answer_item_pool, "resolv_answer_item", struct resolv_answer_item); DECLARE_STATIC_TYPED_POOL(resolv_answer_item_pool, "resolv_answer_item", struct resolv_answer_item);
@ -226,7 +225,7 @@ struct show_resolvers_ctx {
}; };
/* returns the currently accepted address families as a combination of /* returns the currently accepted address families as a combination of
* RSLV_ACCEPT_IPV4 and RSLV_ACCEPT_IPV6 only. It will dynamically adapt adapt * RSLV_ACCEPT_IPV4 and RSLV_ACCEPT_IPV6 only. It will dynamically adapt
* the IPv6 status to sock_inet6_seems_reachable if RSLV_AUTO_FAMILY is set, * the IPv6 status to sock_inet6_seems_reachable if RSLV_AUTO_FAMILY is set,
* otherwise returns the relevant bits of resolv_accept_families. * otherwise returns the relevant bits of resolv_accept_families.
*/ */
@ -365,18 +364,6 @@ struct resolv_answer_item *find_srvrq_answer_record(const struct resolv_requeste
return NULL; return NULL;
} }
/* 2 bytes random generator to generate DNS query ID */
static inline uint16_t resolv_rnd16(void)
{
if (!resolv_query_id_seed)
resolv_query_id_seed = now_ms;
resolv_query_id_seed ^= resolv_query_id_seed << 13;
resolv_query_id_seed ^= resolv_query_id_seed >> 7;
resolv_query_id_seed ^= resolv_query_id_seed << 17;
return resolv_query_id_seed;
}
static inline int resolv_resolution_timeout(struct resolv_resolution *res) static inline int resolv_resolution_timeout(struct resolv_resolution *res)
{ {
return (!LIST_ISEMPTY(&res->requesters) ? res->resolvers->timeout.resolve : res->resolvers->hold.valid); return (!LIST_ISEMPTY(&res->requesters) ? res->resolvers->timeout.resolve : res->resolvers->hold.valid);
@ -509,14 +496,14 @@ resolv_run_resolution(struct resolv_resolution *resolution)
return 0; return 0;
/* Check if a resolution has already been started for this server return /* Check if a resolution has already been started for this server return
* directly to avoid resolution pill up. */ * directly to avoid resolution pile up. */
if (resolution->step != RSLV_STEP_NONE) if (resolution->step != RSLV_STEP_NONE)
return 0; return 0;
/* Generates a new query id. We try at most 100 times to find a free /* Generates a new query id. We try at most 100 times to find a free
* query id */ * query id */
for (i = 0; i < 100; ++i) { for (i = 0; i < 100; ++i) {
query_id = resolv_rnd16(); query_id = (uint16_t)ha_random32();
if (!eb32_lookup(&resolvers->query_ids, query_id)) if (!eb32_lookup(&resolvers->query_ids, query_id))
break; break;
query_id = -1; query_id = -1;
@ -1236,8 +1223,7 @@ static int resolv_validate_dns_response(unsigned char *resp, unsigned char *bufe
if (reader + 4 > bufend) if (reader + 4 > bufend)
goto invalid_resp; goto invalid_resp;
answer_record->ttl = reader[0] * 16777216 + reader[1] * 65536 answer_record->ttl = read_n32(reader);
+ reader[2] * 256 + reader[3];
reader += 4; reader += 4;
/* Now reading data len */ /* Now reading data len */
@ -1436,7 +1422,7 @@ static int resolv_validate_dns_response(unsigned char *resp, unsigned char *bufe
if (len == 0) if (len == 0)
goto invalid_resp; goto invalid_resp;
if (reader + offset + 10 >= bufend) if (reader + offset + 10 > bufend)
goto invalid_resp; goto invalid_resp;
reader += offset; reader += offset;
@ -1450,7 +1436,7 @@ static int resolv_validate_dns_response(unsigned char *resp, unsigned char *bufe
len = reader[0] * 256 + reader[1]; len = reader[0] * 256 + reader[1];
reader += 2; reader += 2;
if (reader + len >= bufend) if (reader + len > bufend)
goto invalid_resp; goto invalid_resp;
reader += len; reader += len;
@ -1498,8 +1484,7 @@ static int resolv_validate_dns_response(unsigned char *resp, unsigned char *bufe
if (reader + 4 > bufend) if (reader + 4 > bufend)
goto invalid_resp; goto invalid_resp;
answer_record->ttl = reader[0] * 16777216 + reader[1] * 65536 answer_record->ttl = read_n32(reader);
+ reader[2] * 256 + reader[3];
reader += 4; reader += 4;
/* Now reading data len */ /* Now reading data len */
@ -1599,7 +1584,6 @@ static int resolv_validate_dns_response(unsigned char *resp, unsigned char *bufe
tmp_record->ar_item == NULL && tmp_record->ar_item == NULL &&
memcmp(tmp_record->data.target, answer_record->name, tmp_record->data_len) == 0) { memcmp(tmp_record->data.target, answer_record->name, tmp_record->data_len) == 0) {
/* Always use the received additional record to refresh info */ /* Always use the received additional record to refresh info */
pool_free(resolv_answer_item_pool, tmp_record->ar_item);
tmp_record->ar_item = answer_record; tmp_record->ar_item = answer_record;
answer_record = NULL; answer_record = NULL;
break; break;
@ -1855,7 +1839,15 @@ int resolv_dn_label_to_str(const char *dn, int dn_len, char *str, int str_len)
ptr = str; ptr = str;
for (i = 0; i < dn_len; ++i) { for (i = 0; i < dn_len; ++i) {
sz = dn[i]; sz = (unsigned char)dn[i];
if (!sz)
break;
/* Check str_len adding 1 for the dot if (i!=0) and 1 for null terminator */
if (str_len < sz+i+(!!i)+1)
return -1;
if (i) if (i)
*ptr++ = '.'; *ptr++ = '.';
/* copy the string at i+1 to lower case */ /* copy the string at i+1 to lower case */

View file

@ -2150,11 +2150,11 @@ static int sample_conv_be2hex_check(struct arg *args, struct sample_conv *conv,
*/ */
static int sample_conv_be2hex(const struct arg *args, struct sample *smp, void *private) static int sample_conv_be2hex(const struct arg *args, struct sample *smp, void *private)
{ {
struct buffer *trash = get_trash_chunk_sz(smp->data.u.str.data); struct buffer *trash = get_trash_chunk_sz(smp->data.u.str.data * 2);
int chunk_size = args[1].data.sint; int chunk_size = args[1].data.sint;
const int last = args[2].data.sint ? smp->data.u.str.data - chunk_size + 1 : smp->data.u.str.data; const int last = args[2].data.sint ? smp->data.u.str.data - chunk_size + 1 : smp->data.u.str.data;
int i; int i;
int max_size; size_t max_size;
int ptr = 0; int ptr = 0;
unsigned char c; unsigned char c;
@ -2163,7 +2163,9 @@ static int sample_conv_be2hex(const struct arg *args, struct sample *smp, void *
trash->data = 0; trash->data = 0;
if (args[0].data.str.data == 0 && args[2].data.sint == 0) if (args[0].data.str.data == 0 && args[2].data.sint == 0)
chunk_size = smp->data.u.str.data; chunk_size = smp->data.u.str.data;
max_size = trash->size - 2 * chunk_size; if (2 * (size_t)chunk_size > trash->size)
return 0;
max_size = trash->size - 2 * (size_t)chunk_size;
while (ptr < last && trash->data <= max_size) { while (ptr < last && trash->data <= max_size) {
if (ptr) { if (ptr) {

View file

@ -241,14 +241,17 @@ int session_accept_fd(struct connection *cli_conn)
if (l->bind_conf->options & BC_O_ACC_CIP) if (l->bind_conf->options & BC_O_ACC_CIP)
cli_conn->flags |= CO_FL_ACCEPT_CIP; cli_conn->flags |= CO_FL_ACCEPT_CIP;
if (l->bind_conf->mux_proto && l->bind_conf->mux_proto->init_xprt == XPRT_QMUX)
cli_conn->flags |= (CO_FL_QMUX_RECV|CO_FL_QMUX_SEND);
/* Add the handshake pseudo-XPRT */ /* Add the handshake pseudo-XPRT */
if (cli_conn->flags & (CO_FL_ACCEPT_PROXY | CO_FL_ACCEPT_CIP)) { if (cli_conn->flags & (CO_FL_ACCEPT_PROXY | CO_FL_ACCEPT_CIP)) {
if (xprt_add_hs(cli_conn) != 0) if (xprt_add_hs(cli_conn) != 0)
goto out_free_conn; goto out_free_conn;
} }
/* Add handshake layer prior to MUX init if required. Does nothing if SSL layer is active though. */
if (l->bind_conf->mux_proto && l->bind_conf->mux_proto->init_xprt) {
if (xprt_add_l6hs(cli_conn, l->bind_conf->mux_proto->init_xprt))
goto out_free_conn;
}
} }
/* Reversed conns already have an assigned session, do not recreate it. */ /* Reversed conns already have an assigned session, do not recreate it. */
@ -351,7 +354,7 @@ int session_accept_fd(struct connection *cli_conn)
* v | | | * v | | |
* conn -- owner ---> task <-----+ * conn -- owner ---> task <-----+
*/ */
if (cli_conn->flags & (CO_FL_WAIT_XPRT | CO_FL_EARLY_SSL_HS)) { if (cli_conn->flags & (CO_FL_WAIT_XPRT | CO_FL_EARLY_SSL_HS | CO_FL_WAIT_XPRT_L6)) {
int timeout; int timeout;
int clt_tmt = p->timeout.client; int clt_tmt = p->timeout.client;
int hs_tmt = p->timeout.client_hs; int hs_tmt = p->timeout.client_hs;

View file

@ -448,6 +448,7 @@ sni_lookup:
for (i = 0; i < trash.size && i < servername_len; i++) for (i = 0; i < trash.size && i < servername_len; i++)
trash.area[i] = tolower((unsigned char)servername[i]); trash.area[i] = tolower((unsigned char)servername[i]);
trash.area[i] = 0; trash.area[i] = 0;
servername = trash.area;
HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock); HA_RWLOCK_RDLOCK(SNI_LOCK, &s->sni_lock);
sni_ctx = ssl_sock_choose_sni_ctx(s, conn, trash.area, has_rsa_sig, has_ecdsa_sig); sni_ctx = ssl_sock_choose_sni_ctx(s, conn, trash.area, has_rsa_sig, has_ecdsa_sig);

View file

@ -356,8 +356,10 @@ int ssl_sock_generate_certificate(const char *servername, struct bind_conf *bind
ssl_ctx = (SSL_CTX *)lru->data; ssl_ctx = (SSL_CTX *)lru->data;
if (!ssl_ctx && lru) { if (!ssl_ctx && lru) {
ssl_ctx = ssl_sock_do_create_cert(servername, bind_conf, ssl); ssl_ctx = ssl_sock_do_create_cert(servername, bind_conf, ssl);
if (!ssl_ctx) if (!ssl_ctx) {
HA_RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
goto error; goto error;
}
lru64_commit(lru, ssl_ctx, cacert, 0, (void (*)(void *))SSL_CTX_free); lru64_commit(lru, ssl_ctx, cacert, 0, (void (*)(void *))SSL_CTX_free);
} }
SSL_set_SSL_CTX(ssl, ssl_ctx); SSL_set_SSL_CTX(ssl, ssl_ctx);

View file

@ -290,6 +290,8 @@ int ssl_sock_load_ocsp_response(struct buffer *ocsp_response,
int ret = 1; int ret = 1;
#ifdef HAVE_ASN1_TIME_TO_TM #ifdef HAVE_ASN1_TIME_TO_TM
struct tm nextupd_tm = {0}; struct tm nextupd_tm = {0};
#else
long expire = 0;
#endif #endif
resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char **)&p, resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char **)&p,
@ -391,11 +393,12 @@ int ssl_sock_load_ocsp_response(struct buffer *ocsp_response,
} }
ocsp->expire = my_timegm(&nextupd_tm) - OCSP_MAX_RESPONSE_TIME_SKEW; ocsp->expire = my_timegm(&nextupd_tm) - OCSP_MAX_RESPONSE_TIME_SKEW;
#else #else
ocsp->expire = asn1_generalizedtime_to_epoch(nextupd) - OCSP_MAX_RESPONSE_TIME_SKEW; expire = asn1_generalizedtime_to_epoch(nextupd) - OCSP_MAX_RESPONSE_TIME_SKEW;
if (ocsp->expire < 0) { if (expire < 0) {
memprintf(err, "OCSP single response: Invalid \"Next Update\" time"); memprintf(err, "OCSP single response: Invalid \"Next Update\" time");
goto out; goto out;
} }
ocsp->expire = expire;
#endif #endif
if (ocsp->expire < date.tv_sec) { if (ocsp->expire < date.tv_sec) {

View file

@ -6973,12 +6973,8 @@ struct task *ssl_sock_io_cb(struct task *t, void *context, unsigned int state)
mux = !conn_is_back(conn) ? mux = !conn_is_back(conn) ?
conn_select_mux_fe(conn) : conn_select_mux_be(conn); conn_select_mux_fe(conn) : conn_select_mux_be(conn);
if (ctx->conn->flags & (CO_FL_QMUX_RECV|CO_FL_QMUX_SEND) || if (mux->init_xprt) {
mux->init_xprt == XPRT_QMUX) { ret = xprt_add_l6hs(conn, mux->init_xprt);
const struct xprt_ops *ops = xprt_get(XPRT_QMUX);
void *xprt_ctx_hs = NULL;
ret = ops->init(conn, &xprt_ctx_hs);
/* Frontend conn must be freed in case of XPRT init failure. */ /* Frontend conn must be freed in case of XPRT init failure. */
if (ret) { if (ret) {
if (!conn_is_back(conn)) { if (!conn_is_back(conn)) {
@ -6990,15 +6986,7 @@ struct task *ssl_sock_io_cb(struct task *t, void *context, unsigned int state)
goto leave; goto leave;
} }
ret = ops->add_xprt(conn, xprt_ctx_hs, ret = conn_xprt_start(conn);
conn->xprt_ctx, conn->xprt, NULL, NULL);
BUG_ON(ret); /* xprt_qmux add_xprt always succeeds */
conn->xprt = ops;
conn->xprt_ctx = xprt_ctx_hs;
ret = conn->xprt->start(conn, xprt_ctx_hs);
BUG_ON(ret);
} }
else { else {
/* TODO MUX selection already performs by conn_select_mux_fe/be(). /* TODO MUX selection already performs by conn_select_mux_fe/be().

View file

@ -834,6 +834,8 @@ enum tcpcheck_eval_ret tcpcheck_spop_expect_hello(struct check *check, struct tc
goto invalid_frame; goto invalid_frame;
if (decode_varint(&ptr, end, &sz) == -1) if (decode_varint(&ptr, end, &sz) == -1)
goto invalid_frame; goto invalid_frame;
if (sz >= SPOP_ERR_ENTRIES)
sz = SPOP_ERR_UNKNOWN;
check->code = sz; check->code = sz;
} }
@ -989,7 +991,7 @@ enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct t
const char *sc = NULL; /* maxconn */ const char *sc = NULL; /* maxconn */
const char *err = NULL; /* first error to report */ const char *err = NULL; /* first error to report */
const char *wrn = NULL; /* first warning to report */ const char *wrn = NULL; /* first warning to report */
char *cmd, *p; char *cmd, *p, *end;
TRACE_ENTER(CHK_EV_TCPCHK_EXP, check); TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
@ -1018,10 +1020,11 @@ enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct t
*/ */
p = b_head(&check->bi); p = b_head(&check->bi);
while (*p && *p != '\n' && *p != '\r') end = b_tail(&check->bi);
while (p < end && *p && *p != '\n' && *p != '\r')
p++; p++;
if (!*p) { if (!*p || p == end) {
if (!last_read) if (!last_read)
goto wait_more_data; goto wait_more_data;

View file

@ -6235,48 +6235,106 @@ int varint_bytes(uint64_t v)
return len; return len;
} }
/* secret used for XXH hash involved in PRNG */
static char ha_random_xxh_secret[XXH3_SECRET_DEFAULT_SIZE] ALIGNED(64);
/* Random number generator state, see below */ /* 2^256 sequnce thread-local PRNG state known as "XOSHIRO256**".
static uint64_t ha_random_state[2] ALIGNED(2*sizeof(uint64_t)); * See details here:
* https://prng.di.unimi.it/
/* This is a thread-safe implementation of xoroshiro128** described below: * https://prng.di.unimi.it/xoshiro256starstar.c
* http://prng.di.unimi.it/ * It features a 2^256 long sequence, returns 64 high-quality bits on each call,
* It features a 2^128 long sequence, returns 64 high-quality bits on each call, * supports fast jumps and passes all common quality tests. Supporting 128-bit
* supports fast jumps and passes all common quality tests. It is thread-safe, * jumps, it allows to run thread-local with non-overlapping sequences. It must
* uses a double-cas on 64-bit architectures supporting it, and falls back to a * be seeded otherwise the ratio of zeroes is a bit high initially.
* local lock on other ones.
*/ */
uint64_t ha_random64() static THREAD_LOCAL uint64_t ha_random_state[4];
/* Returns the next 64-bit PRNG number from the thread-local 256-bit state and
* makes the internal state progress by one step. This is meant to be used by
* other local functions. Since its discloses the PRNG's internal state, it
* must not be called to produce externally visible randoms.
*/
static inline uint64_t _ha_random64_internal(void)
{ {
uint64_t old[2] ALIGNED(2*sizeof(uint64_t)); const uint64_t result = rotl64(ha_random_state[1] * 5, 7) * 9;
uint64_t new[2] ALIGNED(2*sizeof(uint64_t)); const uint64_t t = ha_random_state[1] << 17;
#if defined(USE_THREAD) && (!defined(HA_CAS_IS_8B) || !defined(HA_HAVE_CAS_DW)) ha_random_state[2] ^= ha_random_state[0];
static HA_SPINLOCK_T rand_lock; ha_random_state[3] ^= ha_random_state[1];
ha_random_state[1] ^= ha_random_state[2];
ha_random_state[0] ^= ha_random_state[3];
ha_random_state[2] ^= t;
ha_random_state[3] = rotl64(ha_random_state[3], 45);
return result;
}
HA_SPIN_LOCK(OTHER_LOCK, &rand_lock); /* Returns the next 64-bit PRNG number from the thread-local 256-bit state and
#endif * makes the internal state progress by one step. Since its discloses the PRNG's
* internal state, it must not be called to produce externally visible randoms.
*/
uint64_t ha_random64_internal(void)
{
return _ha_random64_internal();
}
old[0] = ha_random_state[0]; /* This function uses a pre-calculated jump table to of 4 uint64_t to perform a
old[1] = ha_random_state[1]; * jump equivalent to multiple calls to ha_random_next(). It shouldn't be
* used directly but only from the next functions.
*/
static void _ha_random_jump(const uint64_t *table)
{
uint64_t s0, s1, s2, s3;
uint i, j;
#if defined(USE_THREAD) && defined(HA_CAS_IS_8B) && defined(HA_HAVE_CAS_DW) s0 = s1 = s2 = s3 = 0;
do { for (i = 0; i < 4; i++) {
#endif for (j = 0; j < 64; j++) {
new[1] = old[0] ^ old[1]; if (table[i] & (1ULL << j)) {
new[0] = rotl64(old[0], 24) ^ new[1] ^ (new[1] << 16); // a, b s0 ^= ha_random_state[0];
new[1] = rotl64(new[1], 37); // c s1 ^= ha_random_state[1];
s2 ^= ha_random_state[2];
s3 ^= ha_random_state[3];
}
ha_random64_internal();
}
}
#if defined(USE_THREAD) && defined(HA_CAS_IS_8B) && defined(HA_HAVE_CAS_DW) ha_random_state[0] = s0;
} while (unlikely(!_HA_ATOMIC_DWCAS(ha_random_state, old, new))); ha_random_state[1] = s1;
#else ha_random_state[2] = s2;
ha_random_state[0] = new[0]; ha_random_state[3] = s3;
ha_random_state[1] = new[1]; }
#if defined(USE_THREAD)
HA_SPIN_UNLOCK(OTHER_LOCK, &rand_lock); /* This function is equivalent to calling <dist> times 2^128 calls to
#endif * ha_random_next(). It can be used to generate 2^128 non-overlapping
#endif * sequences. The <dist> argument is the distance to jump to and is used
return rotl64(old[0] * 5, 7) * 9; * in a loop so it rather not be too large if the processing time is a
* concern. It only applies to the current thread. Note that <dist> may
* not be zero.
*/
void ha_random_jump128(uint32_t dist)
{
static const uint64_t table[] = { 0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c };
BUG_ON(!dist);
while (dist--)
_ha_random_jump(table);
}
/* This function is equivalent to calling <dist> times 2^192 calls to
* ha_random_next(). It can be used to generate 2^64 non-overlapping
* sequences. The <dist> argument is the distance to jump to and is used
* in a loop so it rather not be too large if the processing time is a
* concern. It only applies to the current thread. Note that <dist> may
* not be zero.
*/
void ha_random_jump192(uint32_t dist)
{
static const uint64_t table[] = { 0x76e15d3efefdcbbf, 0xc5004e441c522fb3, 0x77710069854ee241, 0x39109bb02acbe635 };
BUG_ON(!dist);
while (dist--)
_ha_random_jump(table);
} }
/* seeds the random state using up to <len> bytes from <seed>, starting with /* seeds the random state using up to <len> bytes from <seed>, starting with
@ -6306,43 +6364,49 @@ void ha_random_seed(const unsigned char *seed, size_t len)
len = sizeof(ha_random_state); len = sizeof(ha_random_state);
memcpy(ha_random_state, seed, len); memcpy(ha_random_state, seed, len);
/* also initialize the secret table used by XXH3 */
XXH3_generateSecret(ha_random_xxh_secret, sizeof(ha_random_xxh_secret), seed, len);
} }
/* This causes a jump to (dist * 2^96) places in the pseudo-random sequence, /* Seed the PRNG for the current thread */
* and is equivalent to calling ha_random64() as many times. It is used to void ha_random_seed_thread(void)
* provide non-overlapping sequences of 2^96 numbers (~7*10^28) to up to 2^32
* different generators (i.e. different processes after a fork). The <dist>
* argument is the distance to jump to and is used in a loop so it rather not
* be too large if the processing time is a concern.
*
* BEWARE: this function is NOT thread-safe and must not be called during
* concurrent accesses to ha_random64().
*/
void ha_random_jump96(uint32_t dist)
{ {
while (dist--) { /* seed already done for first thread, but jump still necessary */
uint64_t s0 = 0; if (tid > 0)
uint64_t s1 = 0; ha_random_seed(boot_seed, sizeof(boot_seed));
int b; ha_random_jump192(tid + 1);
}
for (b = 0; b < 64; b++) { /* Returns a uint64_t random hashed so as not to disclose the internal PRNG
if ((0xd2a98b26625eee7bULL >> b) & 1) { * state. The function uses a local XXH secret that is created at boot, and
s0 ^= ha_random_state[0]; * now_ns as the seed to limit remote analysis.
s1 ^= ha_random_state[1]; */
} uint64_t ha_random64(void)
ha_random64(); {
} uint64_t ret;
for (b = 0; b < 64; b++) { ret = _ha_random64_internal();
if ((0xdddf9b1090aa7ac1ULL >> b) & 1) { return XXH3_64bits_withSecretandSeed(&ret, sizeof(ret),
s0 ^= ha_random_state[0]; ha_random_xxh_secret, sizeof(ha_random_xxh_secret),
s1 ^= ha_random_state[1]; now_ns);
} }
ha_random64();
} /* Returns a pair of uint64_t randoms hashed so as not to disclose the internal
ha_random_state[0] = s0; * PRNG state. This function shouldn't be used directly, better use the public
ha_random_state[1] = s1; * ha_random64_pair_hashed() which calls it. The function uses a local XXH
} * secret that is created at boot, and now_ns as the seed to limit remote
* analysis.
*/
struct uint64_pair _ha_random64_pair_hashed(void)
{
XXH128_hash_t ret;
ret = XXH3_128bits_withSecretandSeed(ha_random_state, 2*sizeof(uint64_t),
ha_random_xxh_secret, sizeof(ha_random_xxh_secret),
now_ns);
/* update the internal state */
_ha_random64_internal();
return (struct uint64_pair){ .l = ret.low64, .h = ret.high64 };
} }
/* Generates an RFC 9562 version 4 UUID into chunk /* Generates an RFC 9562 version 4 UUID into chunk
@ -6350,23 +6414,15 @@ void ha_random_jump96(uint32_t dist)
*/ */
void ha_generate_uuid_v4(struct buffer *output) void ha_generate_uuid_v4(struct buffer *output)
{ {
uint32_t rnd[4]; uint64_t l, h;
uint64_t last;
last = ha_random64();
rnd[0] = last;
rnd[1] = last >> 32;
last = ha_random64();
rnd[2] = last;
rnd[3] = last >> 32;
ha_random64_pair_hashed(&l, &h);
chunk_printf(output, "%8.8x-%4.4x-%4.4x-%4.4x-%12.12llx", chunk_printf(output, "%8.8x-%4.4x-%4.4x-%4.4x-%12.12llx",
rnd[0], (uint)l,
rnd[1] & 0xFFFF, (uint)(l >> 32) & 0xFFFF,
((rnd[1] >> 16u) & 0xFFF) | 0x4000, // highest 4 bits indicate the uuid version (uint)((l >> 48) & 0xFFF) | 0x4000, // highest 4 bits indicate the uuid version
(rnd[2] & 0x3FFF) | 0x8000, // the highest 2 bits indicate the UUID variant (10), (uint)(h & 0x3FFF) | 0x8000, // the highest 2 bits indicate the UUID variant (10),
(long long)((rnd[2] >> 14u) | ((uint64_t) rnd[3] << 18u)) & 0xFFFFFFFFFFFFull); (long long)(rotl64(h, 50) & 0xFFFFFFFFFFFFull));
} }
/* Generates an RFC 9562 version 7 UUID into chunk /* Generates an RFC 9562 version 7 UUID into chunk
@ -6374,24 +6430,18 @@ void ha_generate_uuid_v4(struct buffer *output)
*/ */
void ha_generate_uuid_v7(struct buffer *output) void ha_generate_uuid_v7(struct buffer *output)
{ {
uint32_t rnd[3]; uint64_t l, h;
uint64_t last;
uint64_t time; uint64_t time;
time = (date.tv_sec * 1000) + (date.tv_usec / 1000); time = (date.tv_sec * 1000) + (date.tv_usec / 1000);
last = ha_random64();
rnd[0] = last;
rnd[1] = last >> 32;
last = ha_random64();
rnd[2] = last;
ha_random64_pair_hashed(&l, &h);
chunk_printf(output, "%8.8x-%4.4x-%4.4x-%4.4x-%12.12llx", chunk_printf(output, "%8.8x-%4.4x-%4.4x-%4.4x-%12.12llx",
(uint)(time >> 16u), (uint)(time >> 16u),
(uint)(time & 0xFFFF), (uint)(time & 0xFFFF),
((rnd[0] >> 16u) & 0xFFF) | 0x7000, // highest 4 bits indicate the uuid version (uint)((l >> 16) & 0xFFF) | 0x7000, // highest 4 bits indicate the uuid version
(rnd[1] & 0x3FFF) | 0x8000, // the highest 2 bits indicate the UUID variant (10), (uint)(h & 0x3FFF) | 0x8000, // the highest 2 bits indicate the UUID variant (10),
(long long)((rnd[1] >> 14u) | ((uint64_t) rnd[2] << 18u)) & 0xFFFFFFFFFFFFull); (long long)(rotl64(h, 50) & 0xFFFFFFFFFFFFull));
} }

View file

@ -12,6 +12,9 @@
#include <haproxy/quic_frame.h> #include <haproxy/quic_frame.h>
#include <haproxy/quic_tp-t.h> #include <haproxy/quic_tp-t.h>
/* Default protocol when not running over SSL layer. */
#define XPRT_QMUX_DEFAULT_ALPN "h3"
struct xprt_qmux_ctx { struct xprt_qmux_ctx {
struct connection *conn; struct connection *conn;
struct wait_event wait_event; struct wait_event wait_event;
@ -207,7 +210,7 @@ struct task *xprt_qmux_io_cb(struct task *t, void *context, unsigned int state)
out: out:
if ((conn->flags & CO_FL_ERROR) || if ((conn->flags & CO_FL_ERROR) ||
!(conn->flags & (CO_FL_QMUX_RECV|CO_FL_QMUX_SEND))) { !(conn->flags & CO_FL_WAIT_XPRT_L6)) {
/* XPRT should be unsubscribed when transfer done or on error. */ /* XPRT should be unsubscribed when transfer done or on error. */
BUG_ON(ctx->wait_event.events); BUG_ON(ctx->wait_event.events);
@ -332,7 +335,7 @@ static void xprt_qmux_close(struct connection *conn, void *xprt_ctx)
if (ctx->ops_lower && ctx->ops_lower->close) if (ctx->ops_lower && ctx->ops_lower->close)
ctx->ops_lower->close(conn, ctx->ctx_lower); ctx->ops_lower->close(conn, ctx->ctx_lower);
conn->flags &= ~(CO_FL_QMUX_RECV|CO_FL_QMUX_SEND); conn->flags &= ~CO_FL_WAIT_XPRT_L6;
BUG_ON(conn->xprt_ctx != ctx); BUG_ON(conn->xprt_ctx != ctx);
conn->xprt_ctx = ctx->ctx_lower; conn->xprt_ctx = ctx->ctx_lower;
@ -346,6 +349,14 @@ static int xprt_qmux_get_alpn(const struct connection *conn, void *xprt_ctx,
const char **str, int *len) const char **str, int *len)
{ {
struct xprt_qmux_ctx *ctx = xprt_ctx; struct xprt_qmux_ctx *ctx = xprt_ctx;
/* Return a the default ALPN if lower layer is not able to negotiate it. */
if (!ctx->ops_lower || !ctx->ops_lower->get_alpn) {
*str = XPRT_QMUX_DEFAULT_ALPN;
*len = strlen(XPRT_QMUX_DEFAULT_ALPN);
return 1;
}
return ctx->ops_lower->get_alpn(conn, ctx->ctx_lower, str, len); return ctx->ops_lower->get_alpn(conn, ctx->ctx_lower, str, len);
} }