Roman Arutyunyan [Fri, 10 Jul 2026 16:10:12 +0000 (20:10 +0400)]
Avoid post-rewrite URI to path mapping with alias
Previously, after a "rewrite" had changed the URI, and before a new
location was chosen, the new URI might not match the current location
prefix. This becomes a problem when the location uses "alias", which
is why normally such inconsistency is detected by checking the
r->valid_location flag. Meanwhile this flag only handles the
inconsistency when using the "break" directive or parameter. However
in absence of those, there's still an inconsistency window from URI
change up until the end of the current set of rewrite directives.
URI to path mapping within this window might result in a buffer
overflow if location's prefix was longer than the rewritten URI.
The following example resulted in a buffer overflow with the request
"/foo/1":
Roman Arutyunyan [Wed, 29 Jul 2026 12:57:59 +0000 (16:57 +0400)]
Script: reserved memory overrun protection in ngx_http_script_run()
In b76754049 buffer overrun protection in script was introduced.
However in ngx_http_script_run() data could still spill over to the
reserved area. This had no visible effects except the return value
which could extend beyond the maximum formatted string size. This
value is however currently unused in the nginx code.
Later in a8289aa69 string length recalculation was implemented to
protect against buffer underwrite. Considering a possible spillover to
the reserved area, the final string size could become larger than the
allocated buffer size. This could later result in writing beyond
the buffer boundaries. Maximum buffer overflow is the size of the
reserved area.
This could affect calculating document root from variables in HTTP
or using access logging in HTTP or Stream. In the case of access
logging a possible impact is limited by an off-by-one write of a null
byte.
SSL: synced all session ticket keys to the worker process
The per-worker ticket key mirror is allocated with three slots
(ngx_array_push_n(keys, 3)), matching the shared memory
ticket_keys[3] array and the decrypt callback, which scans all
keys->nelts slots by key name. Key rotation, however, synced
only two slots from shared memory, leaving the third slot's
name, hmac_key, and aes_key uninitialized.
A ticket whose key name matched the uninitialized slot (which
holds unspecified pool memory, all zero in the best case) could
therefore be HMAC-verified and decrypted with uninitialized key
material, allowing a forged session.
Sync all keys->nelts slots, as ngx_ssl_ticket_keys_cleanup()
already assumes.
HTTP/3: kept the skipped frame length separate from data length
ngx_http_v3_parse_data() stored the remaining length of a skipped
(unknown) frame in st->length -- the same field the request body
filter uses for the number of DATA payload bytes to pass on as
request body. When an unknown frame was split across parser
invocations, the skip was interrupted with st->length holding the
skip remainder; on the next buffer the body filter, which calls
the parser only while st->length == 0, treated that remainder as
request body.
When proxying, this could result in sending unframed bytes, potentially
allowing for bypass of client_max_body_size, which is enforced only on
the DATA path, or an injection.
The fix is to keep unknown frame payload in a separate st->skip counter.
Vadim Zhestikov [Mon, 17 Aug 2026 16:19:46 +0000 (09:19 -0700)]
Limited the number of pending operations per request
Each pending asynchronous operation holds a reference in
r->main->blocked, an 8-bit field, and the increments were not checked.
With 256 operations pending on a single request the counter wrapped to
zero, after which the request could be freed while those operations
still referenced its pool. This is the same overflow that 4ec67cf86
addressed for r->main->count, and left unaddressed for r->main->blocked.
Operations beyond the limit are now performed synchronously where a
synchronous path exists, and refused otherwise, so that the counter
cannot overflow.
Maxim Dounin [Mon, 19 Feb 2024 22:23:43 +0000 (01:23 +0300)]
Upstream: fixed X-Accel-Redirect handling from cache files.
The X-Accel-Redirect header might appear in cache files if its handling
is ignored with the "proxy_ignore_headers" directive. If the cache file
is later served with different settings, ngx_http_upstream_process_headers()
used to call ngx_http_upstream_finalize_request(NGX_DECLINED), which
is not expected to happen before the cleanup handler is installed and
resulted in ngx_http_finalize_request(NGX_DONE) (after 2b032e8df,
nginx 1.7.11), leading to unexpected request counter decrement, "request
count is zero" alerts, and segmentation faults.
Similarly, errors in ngx_http_upstream_process_headers() resulted in
ngx_http_upstream_finalize_request(NGX_HTTP_INTERNAL_SERVER_ERROR) being
called. This is also not expected to happen before the cleanup handler is
installed, and resulted in ngx_http_finalize_request(NGX_DONE) without
proper request finalization.
Fix is to avoid calling ngx_http_upstream_finalize_request() from
ngx_http_upstream_process_headers(), notably when the cleanup handler
is not yet installed. Errors are now simply return NGX_ERROR, so the
caller is responsible for proper finalization by calling either
ngx_http_finalize_request() or ngx_http_upstream_finalize_request().
And X-Accel-Redirect handling now does not call
ngx_http_upstream_finalize_request(NGX_DECLINED) if no cleanup handler
is installed.
Reported by Jiří Setnička
(https://mailman.nginx.org/pipermail/nginx-devel/2024-February/HWLYHOO3DDB3XTFT6X3GRMXIEJ3SJRUA.html).
Dav: destination validation for COPY and MOVE with "alias".
If COPY or MOVE methods are enabled, the DAV module assumes that it is
configured for the whole server, and does not try validate that the
destination URI is within the particular location. This, however, does
not work well if it is in fact configured in a non-root location, and
this location uses the "alias" directive, such as in the following
configuration:
location /prefix/ {
dav_methods COPY;
alias /foo/;
}
In particular, if the destination URI is shorter than the aliased
location prefix, calling ngx_http_map_uri_to_path() with such
destination URI used to result in segmentation faults (9739e755b). And
if the destination URI is longer than the aliased location prefix, but
does not match it, calling ngx_http_map_uri_to_path() resulted in
unexpected path values, including ones above the "alias" directory
specified.
The fix is to check destination URI prefix if "alias" is used, similarly
to what we do with "try_files". Additionally, the
ngx_http_map_uri_to_path() function was updated with additional sanity
checking to prevent similar issues.
This change is a modified version of a patch by Maxim Dounin. The
modifications include skipping the prefix check if the URI was
rewritten, which preserves the "alias" diagnostics of
ngx_http_map_uri_to_path().
Ivan Ovchinnikov [Fri, 21 Aug 2026 20:48:15 +0000 (20:48 +0000)]
Event pipe: do not free buffers still used by the downstream
When a body filter returned an error, ngx_event_pipe_drain_chains()
returned all buffers to p->free_raw_bufs, including those in p->busy,
which are still owned by the downstream. If upstream EOF was detected
in the same ngx_event_pipe() pass, ngx_event_pipe_read_upstream() then
freed them while still referenced. With HTTP/2 only the stream's fake
connection is marked on error, so ngx_http_v2_send_output_queue() kept
sending DATA frames pointing at the freed buffers, a use-after-free.
Fixed by keeping the p->busy chains intact, the buffers are freed with
the request pool. This also stops the pipe from reusing them for new
upstream reads, which previously could overwrite response body data
still queued for the client. Additionally, the pipe no longer treats
a downstream it has given up on as merely blocked, so the loop is
still guaranteed to terminate.
Vadim Zhestikov [Tue, 11 Aug 2026 21:06:19 +0000 (14:06 -0700)]
Slice filter: overflow protection in slice range requests
When the byte range is passed in the client Range header, the range
start is accepted up to NGX_MAX_OFF_T_VALUE, while
ngx_http_slice_range_variable() adds the slice size to it when
formatting the slice end. With start values close to the maximum
this overflowed.
Now such range starts are ignored, similarly to other ranges not
handled by ngx_http_slice_get_start().
Vadim Zhestikov [Wed, 12 Aug 2026 19:41:54 +0000 (12:41 -0700)]
QUIC: rejected CRYPTO frames received in 1-RTT packets
If a CRYPTO frame was received after the handshake, ngx_quic_handshake()
ran the handshake completion for the second time. With the check added
in 6f8c520f4, this is limited to short header packets.
Since a client has nothing to send in a CRYPTO frame once the handshake
is complete, such frames are now rejected with the "unexpected_message"
connection error, as does TLS 1.3 for handshake messages received out of
order, and QUIC requires for KeyUpdate.
Vadim Zhestikov [Tue, 11 Aug 2026 18:06:28 +0000 (11:06 -0700)]
Memcached: respect the end marker in the maximum supported length
The length in the "VALUE" line was accepted up to NGX_MAX_OFF_T_VALUE,
while ngx_http_memcached_filter_init() adds the size of the end marker
to it. With lengths close to the maximum this overflowed u->length.
Now such lengths are rejected, similarly to other invalid lengths.
Roman Arutyunyan [Thu, 20 Aug 2026 12:53:42 +0000 (16:53 +0400)]
Core: delete accept timer on worker shutdown
Previously, when the worker file descriptor table was full, a listen
connection might be disabled and a timeout might be set for re-enabling
it. If during this period the worker received the NGX_CMD_QUIT signal,
the accept connection would be recycled, but the timer would remain set.
On timer expiration, an attempt to accept a new connection would result
in "accept4() failed (9: Bad file descriptor)" alert because c->fd was
reset to -1.
Fix is to delete accept timer when closing listen connections.
Roman Arutyunyan [Tue, 18 Aug 2026 16:58:42 +0000 (20:58 +0400)]
Core: make channel file descriptor errors non-fatal
If a worker process' file descriptor table is full, recvmsg() is not
able to read out the channel fd sent from the master. This results in
different behavior on different platforms.
On Linux, recvmsg() succeeds, returns the payload and drops the fd.
On BSD systems, the first recvmsg() call fails with EMSGSIZE/EMFILE,
but the next call succeeds and returns the payload without the fd.
On Solaris, recvmsg() fails with EMFILE and drops both the payload
and the fd.
Previously, all of these paths resulted in fatal errors. The worker
channel was closed and the worker was no longer capable of receiving
messages from the master. In particular, the NGX_CMD_QUIT command
could not be delivered anymore. As a result, the worker never exited
after nginx reload and kept running and accepting new connections
forever until killed by a signal.
Fix is to consider those errors non-fatal. An additional check is
added to the NGX_CMD_CLOSE_CHANNEL handler to make sure the worker
has received a file descriptor for this entry before. Also, the
MSG_CTRUNC check is removed since a proper ancillary data size
validation is now implemented.
Aleksei Bavshin [Thu, 16 Jul 2026 15:53:21 +0000 (08:53 -0700)]
Script: improved compatibility of complex value codes
In a8289aa69c74, we introduced a new code to finalize the evaluation of
a complex value, deferring the stack update to this new code. However,
the change inadvertently broke compatibility with several third-party
modules that were reusing ngx_http_script_complex_value_code.
This change relegates omitted ngx_http_script_complex_value_end_code
from crash to a potential read of uninitialized bytes at the end of the
allocated buffer.
David Carlier [Tue, 21 Jul 2026 03:35:57 +0000 (04:35 +0100)]
Perl: fixed $r->print() zero copy with mutable scalars
Since d9b7669666c8, the single-SV zero copy path in $r->print() no
longer required the SV to be read-only. Reference counting introduced
by that change keeps the SV alive, but not its string buffer: modifying
a mutable scalar after $r->print() overwrites the buffer in place or
reallocates it, while nginx might still have it queued in r->out, since
small responses are postponed by ngx_http_write_filter(). This resulted
in corrupted responses, and in freed memory being sent to the client
when the scalar was grown.
The SV is now required to be read-only again. Reference counting is
still needed here, as a read-only SV in an eval() might be freed with
the eval's op tree.
The check in ngx_select_init_conf() can be insufficient because it
doesn't cover descriptors for local files, which are not accounted
for in cycle->connection_n. For instance, to reach this condition
with new connections, no reusable connections must be available for
recycling in ngx_get_connection().
The fix adopts a similar code from the win32 select module.
Image filter: fixed reading past the received data
The size parser and the image decoders used ctx->length, which is the
allocation size (set to image_filter_buffer when the upstream response
omits Content-Length), as the amount of valid data, so a truncated
response without Content-Length could be parsed or decoded past the
received bytes into uninitialized buffer memory.
The length is now adjusted to the actually-read size once the response
body has been read.
With this change, active request object is recorded in the
ngx_http_perl_call_handler() function, and checked by
ngx_http_perl_set_request() to prevent use of unexpected request
objects.
Reported by Axel Mierczuk, Keith Hoodlet, 1Password’s Off-by-1 Labs.
Maxim Dounin [Fri, 26 Jun 2026 19:23:28 +0000 (22:23 +0300)]
Perl: introduced reference counting for perl scalars
Perl scalars might have a limited lifetime, and using them without
appropriate reference counting is incorrect. In particular, heap
use-after-free was observed in the following configuration (note the
"eval", which limits lifetime of the string being printed), which
demonstrates that the previously used SvREADONLY() optimizations are
incorrect:
Similarly, errors were observed with handlers in $r->sleep() and
$r->has_request_body() when a handler comes from an eval, such as in
the following configuration:
Accordingly, the SvREADONLY() optimization was removed in
ngx_http_perl_sv2str(), since it is expected to be used for small
strings, and using proper reference counting likely will be more costly
than just copying the string. In $r->print(), $r->sleep(), and
$r->has_request_body() proper reference counting was implemented, with
decrement operations being performed by pool cleanup handlers.
As a positive side effect, $r->print() can now avoid copying any single
scalar, not just read-only scalars.
Reported by Evan Hellman,
https://github.com/freenginx/nginx/issues/26
Zhidao HONG [Tue, 19 May 2026 07:59:56 +0000 (07:59 +0000)]
Proxy: fixed HTTP/2 upstream with revalidated cache send
Previously, a revalidated cached response could fail on a cached
keepalive connection with "upstream sent frame for unknown stream"
error followed by "cache file contains invalid header".
This happened because after a 304 response, nginx parsed the cached
response while the upstream peer connection was still attached to the
request. The HTTP/2 proxy code treated cached frames as frames from
that live connection, and could assign a real stream id instead of
treating the cached response as having no real stream.
The fix is to treat cached response parsing as cache-only regardless
of the current upstream peer connection: set the stream id to 0 and
skip live upstream control-frame handling while r->cached is set.
Zhidao HONG [Mon, 1 Jun 2026 08:41:33 +0000 (08:41 +0000)]
HTTP/2: fixed INITIAL_WINDOW_SIZE with saved state
The window size change was previously kept in a local variable while
processing SETTINGS parameters. If parsing was suspended after
SETTINGS_INITIAL_WINDOW_SIZE, the value was lost on resume and was
acknowledged without being applied.
Store the initial window size change in the HTTP/2 state until the
SETTINGS frame is fully processed.
The fix includes the socket option level (IPPROTO_IPV6) in the feature
test and the macro (NGX_HAVE_IPV6_DONTFRAG) in the
ngx_configure_listening_sockets() function.
Feng Wu [Tue, 23 Jun 2026 23:22:43 +0000 (07:22 +0800)]
Add missing bounds check in ngx_{http,stream}_compile_complex_value()
Complex value compilation scans strings for $1..$9 capture references.
Check that a byte after '$' is present before testing it, matching
ngx_str_t length semantics and avoiding reliance on NUL termination.
Apply the same check to both HTTP and stream implementations.
Roman Arutyunyan [Thu, 14 May 2026 13:47:42 +0000 (17:47 +0400)]
Rewrite: harden escape flags control
Following 2046b45aa0c6, this change introduces better control of memory
allocation flags for escaped values. Notably:
- The e->is_args flag is now explicitly reset on rewrite start.
If the flag was set prior to rewrite start, then buffer overflow
could happen before 2046b45aa0c6.
- The le->is_args flag value is now copied from e->is_args when
calculating complex value length for "if" and "set" directives.
If e->is_args was set, but le->is_args was not, then buffer overflow
could happen before 2046b45aa0c6.
Roman Arutyunyan [Sat, 25 Apr 2026 12:41:36 +0000 (16:41 +0400)]
Mail: fix session cleanup on error path
Previously, when ngx_handle_read_event() or ngx_handle_write_event()
returned an error while handling an SMTP, POP3 or IMAP session, the
released session memory could be accessed after handling the error.
Andrew Clayton [Wed, 29 Apr 2026 18:18:03 +0000 (19:18 +0100)]
Configure: fix gcc version detection in some corner cases
If the "gcc version ... " string appeared within "Configured with:", it
was picked up rather than the real gcc version string. This might then
break the configure scripts due to a malformed NGX_COMPILER macro.
The simple fix is to look for "gcc version ... " at the start of the
line, rather than anywhere within.
Suggested-by: Aleksei Bavshin <a.bavshin@nginx.com> Closes: https://github.com/nginx/nginx/issues/1278
SSL: logging level of "record layer failure" errors
The SSL_R_RECORD_LAYER_FAILURE ("record layer failure") errors are
reported by OpenSSL 3.2 or newer as the last record layer error for
various low level read errors. Further, a976e6b9e (1.23.4) caused
to always log them at the "crit" level. For example, the following
errors are observed on OpenSSL 3.2.0 - 4.0:
SSL_read() failed (SSL: error:0A000119:SSL routines::decryption failed
or bad record mac error:0A000139:SSL routines::record layer failure)
SSL_read() failed (SSL: error:1C800066:Provider routines::cipher operation
failed error:0A000119:SSL routines::decryption failed or bad record mac
error:0A000139:SSL routines::record layer failure)
SSL_read() failed (SSL: error:0A00010B:SSL routines::wrong version number
error:0A000139:SSL routines::record layer failure)
Maxim Dounin [Sun, 5 May 2024 21:07:18 +0000 (00:07 +0300)]
SSL: logging level of "invalid alert" errors.
The SSL_R_INVALID_ALERT ("invalid alert") errors are reported by OpenSSL
1.1.1 or newer if the client sends a malformed alert. These errors are
now logged at the "info" level.
Signed-off-by: Aleksei Bavshin <a.bavshin@nginx.com>
Origin: <https://freenginx.org/hg/nginx/rev/d89e0386b695>
Maxim Dounin [Sun, 5 May 2024 21:06:15 +0000 (00:06 +0300)]
SSL: logging level of all "SSL alert number N" errors.
Errors about alerts received from peers are generated by OpenSSL by adding
peer-provided alert description (from 0 to 255) to SSL_AD_REASON_OFFSET.
All such errors, including ones for unknown alerts, are now logged at the
"info" level, as these can be caused by a misbehaving client.
Signed-off-by: Aleksei Bavshin <a.bavshin@nginx.com>
Origin: <https://freenginx.org/hg/nginx/rev/f5423ee155fe>
QUIC: ignore the QUIC extension received in TLS handshake
Instead of failing the handshake, it is now accepted and ignored like
any other unknown extension. Unlike stated in RFC 9001, Section 8.2,
this represents less strict behaviour following RFC 8446, Section 4.2.
QUIC: registering the compat extension in all SSL contexts
With the OpenSSL compatibility layer, the transport parameters custom
TLS extension used to be registered only in SSL contexts of virtual
servers reachable over QUIC.
The ClientHello callback introduced in 0373fe5d9 (1.29.2) is called
by OpenSSL between collecting and parsing extensions, so the custom
extensions may not match the expected number if the SSL context was
replaced with SSL_set_SSL_CTX().
The fix is to register the extension in all SSL contexts, including
those that cannot be reached over QUIC, so that the array bounds are
preserved; the compatibility layer as a whole is initialized only
if the configuration has QUIC listeners. The keylog callback,
meaningful for QUIC connections, is kept on QUIC addresses.
Roman Arutyunyan [Mon, 29 Jun 2026 17:49:27 +0000 (21:49 +0400)]
Avoid duplicate subrequest finalization
Previously, if a subrequest was posted twice, it could be finalized in
both calls, excessively reducing r->main->count and potentially leading
to a use-after-free.
The fix is to avoid posting a request if it's already posted. Also,
as a hardening measure, r->write_event_handler is now reset to a no-op
handler during active subrequest finalization.
The problem manifests itself in ngx_http_ssi_filter_module during
unbuffered proxying. If a subrequest is created for an SSI include
statement while the main request has some data postponed by another
include, this subrequest becomes double-posted when the main request
data is flushed. The first post comes from ngx_http_subrequest() and
the second one comes from ngx_http_postpone_filter(). In case of a
quick subrequest finalization, the above mentioned problem happens.
Pavel Pautov [Fri, 15 May 2026 07:48:50 +0000 (00:48 -0700)]
Fixed uninitialized memory read caused by stale regex captures.
When ngx_http_regex_exec() reallocates r->captures array, it doesn't update
r->ncaptures value, if regex didn't match. So the next use of unnamed regex
capture triggers uninitialized read and potential buffer overrun.
This config demonstrates the issue:
map test $my_map {
volatile;
~mismatch(.*) 1; # reallocates r->captures in subrequests
default "";
}
server {
location ~(.*) { # sets r->ncaptures
slice 50;
# $1 will read from uninitialized memory in slice subrequests
proxy_set_header Test $my_map$1;
proxy_set_header Range $slice_range;
proxy_pass http://backend;
}
}
Script: avoid garbage at the end of the result string
If the script result turned out to be shorter than its predicted
length, the result string contained uninitialized bytes at the end.
The fix is to cut the result string by its actual size.
The following locations returned trailing garbage to the client with
URI "/1234abcd".
Maxim Dounin [Fri, 19 Jun 2026 19:54:46 +0000 (22:54 +0300)]
Script: buffer overrun protection in direct script usage.
Following the previous change, this change adds script overrun
protection to direct script evaluation in the proxy, fastcgi, scgi,
uwsgi, grpc proxy, index, and try_files modules.
This change is a modified version of a patch by Maxim Dounin. The
modifications include ngx_http_proxy_v2_module and hardened size checks.
Signed-off-by: Roman Arutyunyan <arut@nginx.com>
Origin: <https://freenginx.org/hg/nginx/rev/7e4d7feb4c77>
Maxim Dounin [Fri, 19 Jun 2026 19:54:27 +0000 (22:54 +0300)]
Script: buffer overrun protection.
With this change, all script copy operations now check if there is
enough room in the buffer. To do so, the script engine now provides the
e->end pointer, which specifies expected buffer end, and each copy
operation is checked against it with the ngx_http_script_check_length()
function.
The e->end pointer is optional and only checked when set, thus
introducing no incompatible API changes. All standard functions were
updated to use it, notably ngx_http_complex_value(), ngx_http_script_run(),
ngx_http_script_regex_start_code(), ngx_http_script_complex_value_code().
Direct script evaluation in the proxy, fastcgi, scgi, uwsgi, grpc proxy,
index, and try_files modules will be updated by a separate patch.
In particular, this catches issues as observed when evaluating variables
with side effects, such as in the following configuration:
map $uri $map {
~(?<capture>.*) $capture;
}
set $capture "";
set $temp "$capture $map";
As well as when evaluating non-cacheable variables, where length of a
variable might change between length and copy codes, such as in the
following configuration:
Charset: fixed another rare buffer overread in recode_from_utf8()
With prerequisites similar to 696a7f1b9, it was possible to gain 1-byte
overread on invalid UTF-8 sequences. The reason is ngx_utf8_decode()
stops advancing the pointer position on the first encountered invalid
byte. The fix is to adjust the advanced pointer up to the whole saved
sequence in this case. Note that this may result in different output
compared to complete invalid UTF-8 sequences, which we can disregard
at this point.
Reported by Han Yan of Xiaomi and p4p3r of CYBERONE.
Roman Arutyunyan [Thu, 14 May 2026 14:42:18 +0000 (18:42 +0400)]
Rewrite: fix buffer overflow with overlapping captures
When the rewrite replacement string had no variables, but had
overlapping captures, the length of the allocated buffer could be
smaller than the replacement string. This could happen either
when the "redirect" parameter is specified, or when arguments are
present in the replacement string.
The following configurations resulted in heap buffer overflow when
using URI "/++++++++++++++++++++++++++++++":
Proxy: fix large body with proxy_set_body and HTTP/2
Previously, if proxy_set_body was used with HTTP/2, and body size exceeded
16M, then an overflow happened in the 24-bit DATA frame size, which resulted
in sending unframed bytes, potentially allowing for an injection.
Also, DATA frame size could exceed NGX_HTTP_V2_DEFAULT_FRAME_SIZE (16K) and
available send window.
If there were arguments in a rewrite's replacement string, the is_args flag
was set and incorrectly never cleared. This resulted in escaping applied
to any captures evaluated afterwards in set or if. Additionally buffer was
allocated by ngx_http_script_complex_value_code() without escaping expected,
thus this also resulted in buffer overrun and possible segfault.
If the first response line was split across reads and it didn't appear
a status line, the portion already processed was lost. To preserve ABI,
the change reuses r->header_name_start for proper backtracking on status
line fallback.
Upstream: reset parsing state after invalid status line
Previously, it was possible to start parsing headers with a wrong
parsing state after status line was not recognized, as a fallback
used in the scgi and uwsgi modules.
David Carlier [Sun, 12 Apr 2026 06:13:23 +0000 (07:13 +0100)]
Charset: fix buffer over-read in recode_from_utf8().
When a multi-byte UTF-8 character was split across 3+ single-byte
buffers, the saved bytes continuation path had two related bugs:
ngx_utf8_decode() was called with the last saved-array index instead
of the byte count, causing it to report "incomplete" even when the
sequence was already complete.
The subsequent ngx_memcpy() used that same index as the copy length,
reading past the input buffer boundary.
Roman Arutyunyan [Thu, 30 Apr 2026 13:15:53 +0000 (17:15 +0400)]
QUIC: avoid assigning unvalidated address to new streams
Previously, when a client migrated to a new address, new QUIC streams
received this address before validation. This allowed an attacker to
create QUIC streams with a spoofed address.
Roman Arutyunyan [Tue, 21 Apr 2026 10:51:41 +0000 (14:51 +0400)]
OCSP: resolve cleanup on connection close
Previously, when a client SSL connection was terminated (typically due to a
timeout) while resolving an OCSP responder, the OCSP context was freed, but
the resolve context was not. This resulted in use-after-free on resolve
completion.
Proxy: fix keepalive for HTTP/2 with explicit or no body
Previously, when an HTTP/2 request had no body or an explicit body
was set by proxy_set_body, the request consisted of only one buffer,
which had no b->last_buf flag set. This prevented ctx->output_closed
from being set after processing this buffer. Consequently,
u->keepalive might not be set to store the connection in the
keepalive cache.
David Carlier [Sun, 15 Mar 2026 15:56:01 +0000 (15:56 +0000)]
Upstream: reset early_hints_length on upstream reinit.
When a request was retried to a new upstream after receiving 103
Early Hints from the previous one, the accumulated early_hints_length
was not reset, causing valid early hints from the next upstream to be
incorrectly rejected as "too big".
Maxim Dounin [Thu, 23 May 2024 21:20:01 +0000 (00:20 +0300)]
Added max_headers directive.
The directive limits the number of request headers accepted from clients.
While the total amount of headers is believed to be sufficiently limited
by the existing buffer size limits (client_header_buffer_size and
large_client_header_buffers), the additional limit on the number of headers
might be beneficial to better protect backend servers.
When parsing the `charset` parameter in the `Content-Type` header within
`ngx_http_upstream_copy_content_type`, an input such as `charset="`
resulted in an integer underflow.
In this scenario, both `p` and `last` point to the position immediately
following the opening quote. The logic to strip a trailing quote checked
`*(last - 1)` without verifying that `last > p`. This caused `last` to
be decremented to point to the opening quote itself, making `last < p`.
The subsequent length calculation `r->headers_out.charset.len = last - p`
resulted in -1, which wrapped to `SIZE_MAX` as `len` is a `size_t`. This
invalid length was later passed to `ngx_cpymem` in `ngx_http_header_filter`,
leading to an out-of-bounds memory access (detected as
`negative-size-param` by AddressSanitizer).
The fix ensures `last > p` before attempting to strip a trailing quote,
correctly resulting in a zero-length charset for malformed input.
The oss-fuzz payload that triggers this issue holds multiple 103 status
lines, and it's a sequence of 2 of those Content-Type headers that
trigger the ASAN report.
Co-authored-by: CodeMender <codemender-patching@google.com> Fixes: https://issues.oss-fuzz.com/issues/486561029 Signed-off-by: David Korczynski <david@adalogics.com>
CLOCK_MONOTONIC_FAST, like CLOCK_MONOTONIC_COARSE, has low accuracy. It
shows noticeable timing variation for short intervals, which is visible
in metrics like $upstream_response_time for fast upstream responses.
This change complements the work started in commit f29d7ade5.
In addition to the reasons described in f29d7ade5, the performance of
CLOCK_MONOTONIC is good enough on modern hardware when using a TSC
timecounter. This is especially true when it is accessed through a
shared page, as implemented in FreeBSD 10.0 (see git commits 869fd80fd449 and aea810386d8e for details).
Sergey Kandaurov [Tue, 10 Mar 2026 12:28:04 +0000 (16:28 +0400)]
SSL: compatibility with OpenSSL 4.0.
X509_get_issuer_name() and X509_get_subject_name() were changed to return
a const value. Since it is passed to functions with a non const argument
in older versions, the const modifier is conditionally compiled as needed.
ASN1_INTEGER was made opaque. ASN1_STRING accessors are used to preserve
the behaviour. ASN1_STRING_get0_data() compat shim is provided for OpenSSL
< 1.1.0 where it does not exist.
Sergey Kandaurov [Wed, 18 Mar 2026 12:39:37 +0000 (16:39 +0400)]
Mail: fixed clearing s->passwd in auth http requests.
Previously, it was not properly cleared retaining length as part of
authenticating with CRAM-MD5 and APOP methods that expect to receive
password in auth response. This resulted in null pointer dereference
and worker process crash in subsequent auth attempts with CRAM-MD5.
Roman Arutyunyan [Thu, 26 Feb 2026 07:52:53 +0000 (11:52 +0400)]
Mail: host validation.
Now host name resolved from client address is validated to only contain
the characters specified in RFC 1034, Section 3.5. The validation allows
to avoid injections when using the resolved host name in auth_http and
smtp proxy.
Reported by Asim Viladi Oglu Manizada, Colin Warren,
Xiao Liu (Yunnan University), Yuan Tan (UC Riverside), and
Bird Liu (Lanzhou University).
Roman Arutyunyan [Mon, 16 Mar 2026 16:13:03 +0000 (20:13 +0400)]
Dav: destination length validation for COPY and MOVE.
Previously, when alias was used in a location with Dav COPY or MOVE
enabled, and the destination URI was shorter than the alias, integer
underflow could happen in ngx_http_map_uri_to_path(), which could
result in heap buffer overwrite, followed by a possible segfault.
With some implementations of memcpy(), the segfault could be avoided
and the overwrite could result in a change of the source or destination
file names to be outside of the location root.
Reported by Calif.io in collaboration with Claude and Anthropic Research.
Mp4: fixed possible integer overflow on 32-bit platforms.
Previously, a 32-bit overflow could happen while validating atom entries
count. This allowed processing of an invalid atom with entrires beyond
its boundaries with reads and writes outside of the allocated mp4 buffer.
Roman Arutyunyan [Sat, 21 Feb 2026 08:04:36 +0000 (12:04 +0400)]
Mp4: avoid zero size buffers in output.
Previously, data validation checks did not cover the cases when the output
contained empty buffers. Such buffers are considered illegal and produce
"zero size buf in output" alerts. The change rejects the mp4 files which
produce such alerts.
Also, the change fixes possible buffer overread and overwrite that could
happen while processing empty stco and co64 atoms, as reported by
Pavel Kohout (Aisle Research) and Tim Becker.