From mdounin at mdounin.ru Fri Mar 6 04:08:55 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Fri, 06 Mar 2026 07:08:55 +0300 Subject: [nginx] Version bump. Message-ID: details: http://freenginx.org/hg/nginx/rev/01b69364693c branches: changeset: 9468:01b69364693c user: Maxim Dounin date: Fri Mar 06 06:54:11 2026 +0300 description: Version bump. diffstat: src/core/nginx.h | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diffs (14 lines): diff --git a/src/core/nginx.h b/src/core/nginx.h --- a/src/core/nginx.h +++ b/src/core/nginx.h @@ -9,8 +9,8 @@ #define _NGINX_H_INCLUDED_ -#define nginx_version 1029005 -#define NGINX_VERSION "1.29.5" +#define nginx_version 1029006 +#define NGINX_VERSION "1.29.6" #define freenginx 1 From mdounin at mdounin.ru Fri Mar 6 04:08:56 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Fri, 06 Mar 2026 07:08:56 +0300 Subject: [nginx] Upstream: silenced "temporary disabled" warnings with va... Message-ID: details: http://freenginx.org/hg/nginx/rev/52a4d08af7a5 branches: changeset: 9469:52a4d08af7a5 user: Maxim Dounin date: Fri Mar 06 06:54:13 2026 +0300 description: Upstream: silenced "temporary disabled" warnings with variables. Previously, if a server within a runtime-created upstream, as used for proxy_pass with variables, failed, the "upstream server temporarily disabled" warning was logged. For runtime-created upstreams the warning is useless and confusing, since such upstreams are never used for other requests. To disable the warning, max_fails is now set to 0 for runtime-created upstreams. Additionally, fail_timeout is also set to 0, for consistency. diffstat: src/http/ngx_http_upstream_round_robin.c | 8 ++++---- src/stream/ngx_stream_upstream_round_robin.c | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diffs (50 lines): diff --git a/src/http/ngx_http_upstream_round_robin.c b/src/http/ngx_http_upstream_round_robin.c --- a/src/http/ngx_http_upstream_round_robin.c +++ b/src/http/ngx_http_upstream_round_robin.c @@ -356,8 +356,8 @@ ngx_http_upstream_create_round_robin_pee peer[0].effective_weight = 1; peer[0].current_weight = 0; peer[0].max_conns = 0; - peer[0].max_fails = 1; - peer[0].fail_timeout = 10; + peer[0].max_fails = 0; + peer[0].fail_timeout = 0; peers->peer = peer; } else { @@ -390,8 +390,8 @@ ngx_http_upstream_create_round_robin_pee peer[i].effective_weight = 1; peer[i].current_weight = 0; peer[i].max_conns = 0; - peer[i].max_fails = 1; - peer[i].fail_timeout = 10; + peer[i].max_fails = 0; + peer[i].fail_timeout = 0; *peerp = &peer[i]; peerp = &peer[i].next; } diff --git a/src/stream/ngx_stream_upstream_round_robin.c b/src/stream/ngx_stream_upstream_round_robin.c --- a/src/stream/ngx_stream_upstream_round_robin.c +++ b/src/stream/ngx_stream_upstream_round_robin.c @@ -366,8 +366,8 @@ ngx_stream_upstream_create_round_robin_p peer[0].effective_weight = 1; peer[0].current_weight = 0; peer[0].max_conns = 0; - peer[0].max_fails = 1; - peer[0].fail_timeout = 10; + peer[0].max_fails = 0; + peer[0].fail_timeout = 0; peers->peer = peer; } else { @@ -400,8 +400,8 @@ ngx_stream_upstream_create_round_robin_p peer[i].effective_weight = 1; peer[i].current_weight = 0; peer[i].max_conns = 0; - peer[i].max_fails = 1; - peer[i].fail_timeout = 10; + peer[i].max_fails = 0; + peer[i].fail_timeout = 0; *peerp = &peer[i]; peerp = &peer[i].next; } From mdounin at mdounin.ru Fri Mar 6 04:10:24 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Fri, 06 Mar 2026 07:10:24 +0300 Subject: [nginx] gRPC: reinitialization of in, out, and busy chains. Message-ID: details: http://freenginx.org/hg/nginx/rev/3de452bb4b91 branches: changeset: 9470:3de452bb4b91 user: Maxim Dounin date: Fri Mar 06 07:09:06 2026 +0300 description: gRPC: reinitialization of in, out, and busy chains. If an error happens with data buffered in ctx->in (or control frames queued in ctx->out), keeping these while switching to the next upstream server will result in incorrect data sent to the new connection, likely causing a failure. Similarly, if there are buffers in ctx->busy, switching to the next upstream server will reinitialize ngx_chain_writer() context, u->writer, and these buffers will be forgotten. Still, since they are in ctx->busy and not marked as fully sent, ngx_chain_update_chains() won't look any further, thus breaking buffers reuse and causing excessive memory usage on long-running requests. The fix is to clear ctx->in, ctx->out, and ctx->busy chains on request reinitialization. Prodded by David Carlier, https://freenginx.org/pipermail/nginx-devel/2026-February/000934.html diffstat: src/http/modules/ngx_http_grpc_module.c | 3 +++ 1 files changed, 3 insertions(+), 0 deletions(-) diffs (13 lines): diff --git a/src/http/modules/ngx_http_grpc_module.c b/src/http/modules/ngx_http_grpc_module.c --- a/src/http/modules/ngx_http_grpc_module.c +++ b/src/http/modules/ngx_http_grpc_module.c @@ -1217,6 +1217,9 @@ ngx_http_grpc_reinit_request(ngx_http_re ctx->rst = 0; ctx->goaway = 0; ctx->connection = NULL; + ctx->in = NULL; + ctx->out = NULL; + ctx->busy = NULL; ctx->pings = 0; ctx->settings = 0; ctx->headers = 0; From mdounin at mdounin.ru Fri Mar 6 04:23:33 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Fri, 06 Mar 2026 07:23:33 +0300 Subject: [nginx] Xslt: fixed vsnprintf() usage. Message-ID: details: http://freenginx.org/hg/nginx/rev/2dbab7b19453 branches: changeset: 9471:2dbab7b19453 user: Maxim Dounin date: Fri Mar 06 07:11:33 2026 +0300 description: Xslt: fixed vsnprintf() usage. Previously, vsnprintf(), as used to handle error messages from the libxml2 library in ngx_http_xslt_sax_error(), was used incorrectly: the code assumed that vsnprintf() returns the number of bytes actually printed, and never fails. But vsnprintf(), as originally introduced in 4.4BSD and later standardized in C99, returns the number of characters that would have been written with a sufficiently large buffer, and "if the return value is greater than or equal to the size argument, the string was too short and some of the printed characters were discarded". Additionally, vsnprintf() might theoretically result in a zero-length error message, or might fail (for example, due to an incorrect format specification in libxml2). Most notably, this can result in out-of-bounds reads and segmentation faults in the worker process on very long error messages. The fix is to check the value returned by vsnprintf() and properly handle too long error messages, as well as zero-length error messages and vsnprintf() failures. diffstat: src/http/modules/ngx_http_xslt_filter_module.c | 21 +++++++++++++++++---- 1 files changed, 17 insertions(+), 4 deletions(-) diffs (44 lines): diff --git a/src/http/modules/ngx_http_xslt_filter_module.c b/src/http/modules/ngx_http_xslt_filter_module.c --- a/src/http/modules/ngx_http_xslt_filter_module.c +++ b/src/http/modules/ngx_http_xslt_filter_module.c @@ -540,23 +540,36 @@ ngx_http_xslt_sax_error(void *data, cons { xmlParserCtxtPtr ctxt = data; - size_t n; + int n; va_list args; + ngx_uint_t truncated; ngx_http_xslt_filter_ctx_t *ctx; u_char buf[NGX_MAX_ERROR_STR]; ctx = ctxt->sax->_private; buf[0] = '\0'; + truncated = 0; va_start(args, msg); - n = (size_t) vsnprintf((char *) buf, NGX_MAX_ERROR_STR, msg, args); + n = vsnprintf((char *) buf, NGX_MAX_ERROR_STR, msg, args); va_end(args); - while (--n && (buf[n] == CR || buf[n] == LF)) { /* void */ } + if (n < 0) { + n = 0; + truncated = 1; + } + + if (n >= NGX_MAX_ERROR_STR) { + n = NGX_MAX_ERROR_STR - 1; + truncated = 1; + } + + while (n && (buf[n - 1] == CR || buf[n - 1] == LF)) { n--; } ngx_log_error(NGX_LOG_ERR, ctx->request->connection->log, 0, - "libxml2 error: \"%*s\"", n + 1, buf); + "libxml2 error: \"%*s%s\"", + (size_t) n, buf, truncated ? "..." : ""); } From mdounin at mdounin.ru Fri Mar 6 04:23:33 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Fri, 06 Mar 2026 07:23:33 +0300 Subject: [nginx] Xslt: proper logging of long errors. Message-ID: details: http://freenginx.org/hg/nginx/rev/d7e6f90ec255 branches: changeset: 9472:d7e6f90ec255 user: Maxim Dounin date: Fri Mar 06 07:11:37 2026 +0300 description: Xslt: proper logging of long errors. To ensure that long error messages from the libxml2 library can be properly logged by ngx_http_xslt_sax_error(), the buffer size was changed to NGX_MAX_CONF_ERRSTR. It is smaller than NGX_MAX_ERROR_STR, the size of the low-level buffer used during logging, and therefore reserves some space for other information. Similar approach is used in ngx_conf_log_error(), where the macro comes from, as well as various other places, such as ngx_ssl_error(). diffstat: src/http/modules/ngx_http_xslt_filter_module.c | 8 ++++---- 1 files changed, 4 insertions(+), 4 deletions(-) diffs (32 lines): diff --git a/src/http/modules/ngx_http_xslt_filter_module.c b/src/http/modules/ngx_http_xslt_filter_module.c --- a/src/http/modules/ngx_http_xslt_filter_module.c +++ b/src/http/modules/ngx_http_xslt_filter_module.c @@ -544,7 +544,7 @@ ngx_http_xslt_sax_error(void *data, cons va_list args; ngx_uint_t truncated; ngx_http_xslt_filter_ctx_t *ctx; - u_char buf[NGX_MAX_ERROR_STR]; + u_char buf[NGX_MAX_CONF_ERRSTR]; ctx = ctxt->sax->_private; @@ -552,7 +552,7 @@ ngx_http_xslt_sax_error(void *data, cons truncated = 0; va_start(args, msg); - n = vsnprintf((char *) buf, NGX_MAX_ERROR_STR, msg, args); + n = vsnprintf((char *) buf, NGX_MAX_CONF_ERRSTR, msg, args); va_end(args); if (n < 0) { @@ -560,8 +560,8 @@ ngx_http_xslt_sax_error(void *data, cons truncated = 1; } - if (n >= NGX_MAX_ERROR_STR) { - n = NGX_MAX_ERROR_STR - 1; + if (n >= NGX_MAX_CONF_ERRSTR) { + n = NGX_MAX_CONF_ERRSTR - 1; truncated = 1; } From mdounin at mdounin.ru Fri Mar 6 04:29:52 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Fri, 06 Mar 2026 07:29:52 +0300 Subject: [nginx] SSL: added missing "goto failed" in ECH handling with Bo... Message-ID: details: http://freenginx.org/hg/nginx/rev/87c2bbf0e078 branches: changeset: 9473:87c2bbf0e078 user: Maxim Dounin date: Fri Mar 06 07:29:10 2026 +0300 description: SSL: added missing "goto failed" in ECH handling with BoringSSL. The "goto failed" statement was missed in the EVP_HPKE_KEY_new() error handling, as used during configuration of Encrypted Client Hello (ECH) with BoringSSL, resulting in segmentation fault in EVP_HPKE_KEY_init() if memory allocation in EVP_HPKE_KEY_new() failed. Missed in 9422:deb1ec630f7c (1.29.2). Reported by Yu Zhu, https://github.com/freenginx/nginx/issues/16 diffstat: src/event/ngx_event_openssl.c | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diffs (11 lines): diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -2023,6 +2023,7 @@ failed: if (hpkey == NULL) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "EVP_HPKE_KEY_new() failed"); + goto failed; } if (EVP_HPKE_KEY_init(hpkey, EVP_hpke_x25519_hkdf_sha256(), From mdounin at mdounin.ru Fri Mar 6 04:33:30 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Fri, 06 Mar 2026 07:33:30 +0300 Subject: [PATCH] Fixed clcf->auto_redirect in uwsgi_pass/scgi_pass with variables Message-ID: <02261aeedb0857c562fa.1772771610@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1772771457 -10800 # Fri Mar 06 07:30:57 2026 +0300 # Node ID 02261aeedb0857c562fac989bc7d3233fbdd2beb # Parent 87c2bbf0e078ab8e52100a1324cf7ebb64b6b96d Fixed clcf->auto_redirect in uwsgi_pass/scgi_pass with variables. Previously, clcf->auto_redirect was only set (for locations ending with "/") when uwsgi_pass and scgi_pass were used without variables, but not with variables. For proxy_pass and fastcgi_pass this was fixed in 2989:dff9764eaca2 (0.8.7). It was not, however, fixed in ngx_http_uwsgi_module (as imported later, in 3541:21452748d165, 0.8.40) and ngx_http_scgi_module (introduced shortly after, in 3637:d656caa72ec9, 0.8.42). The fix is mostly identical to the one in 2989:dff9764eaca2 (0.8.7) and ensures that clcf->auto_redirect is set both with and without variables. diff --git a/src/http/modules/ngx_http_scgi_module.c b/src/http/modules/ngx_http_scgi_module.c --- a/src/http/modules/ngx_http_scgi_module.c +++ b/src/http/modules/ngx_http_scgi_module.c @@ -1921,8 +1921,13 @@ ngx_http_scgi_pass(ngx_conf_t *cf, ngx_c } clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_http_scgi_handler; + if (clcf->name.len && clcf->name.data[clcf->name.len - 1] == '/') { + clcf->auto_redirect = 1; + } + value = cf->args->elts; url = &value[1]; @@ -1958,10 +1963,6 @@ ngx_http_scgi_pass(ngx_conf_t *cf, ngx_c return NGX_CONF_ERROR; } - if (clcf->name.len && clcf->name.data[clcf->name.len - 1] == '/') { - clcf->auto_redirect = 1; - } - return NGX_CONF_OK; } diff --git a/src/http/modules/ngx_http_uwsgi_module.c b/src/http/modules/ngx_http_uwsgi_module.c --- a/src/http/modules/ngx_http_uwsgi_module.c +++ b/src/http/modules/ngx_http_uwsgi_module.c @@ -2221,8 +2221,13 @@ ngx_http_uwsgi_pass(ngx_conf_t *cf, ngx_ } clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_http_uwsgi_handler; + if (clcf->name.len && clcf->name.data[clcf->name.len - 1] == '/') { + clcf->auto_redirect = 1; + } + value = cf->args->elts; url = &value[1]; @@ -2281,10 +2286,6 @@ ngx_http_uwsgi_pass(ngx_conf_t *cf, ngx_ return NGX_CONF_ERROR; } - if (clcf->name.len && clcf->name.data[clcf->name.len - 1] == '/') { - clcf->auto_redirect = 1; - } - return NGX_CONF_OK; } From mdounin at mdounin.ru Sun Mar 8 02:43:38 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Sun, 08 Mar 2026 05:43:38 +0300 Subject: [PATCH 1 of 3] Mp4: fixed ngx_http_mp4_seek_key_frame() Message-ID: # HG changeset patch # User Maxim Dounin # Date 1772936214 -10800 # Sun Mar 08 05:16:54 2026 +0300 # Node ID a98f57c9c8fb0e760d43fe9b507cd51e7da427cc # Parent 87c2bbf0e078ab8e52100a1324cf7ebb64b6b96d Mp4: fixed ngx_http_mp4_seek_key_frame(). Previously, ngx_http_mp4_seek_key_frame() might return key frame prefix larger than start_sample provided in function arguments, notably on an invalid stss entry with zero sample_number. This resulted in backwards loop in ngx_http_mp4_crop_stts_data() to incorrectly access data before the stts atom entries (and stop at the stts atom header mostly by chance). The fix is to make sure ngx_http_mp4_seek_key_frame() always returns a value equal or smaller than start_sample provided, regardless of the stss entries. Invalid entries with zero sample_number now will be interpreted as if there very large sample_number, so the previous one will be returned. diff --git a/src/http/modules/ngx_http_mp4_module.c b/src/http/modules/ngx_http_mp4_module.c --- a/src/http/modules/ngx_http_mp4_module.c +++ b/src/http/modules/ngx_http_mp4_module.c @@ -2528,13 +2528,14 @@ ngx_http_mp4_seek_key_frame(ngx_http_mp4 entry = (uint32_t *) data->pos; end = (uint32_t *) data->last; - /* sync samples starts from 1 */ - start_sample++; - key_prefix = 0; while (entry < end) { sample = ngx_mp4_get_32value(entry); + + /* sync samples starts from 1 */ + sample--; + if (sample > start_sample) { break; } From mdounin at mdounin.ru Sun Mar 8 02:43:39 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Sun, 08 Mar 2026 05:43:39 +0300 Subject: [PATCH 2 of 3] Mp4: fixed entries tests on 32-bit platforms In-Reply-To: References: Message-ID: <76e10dcd0806af147216.1772937819@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1772936216 -10800 # Sun Mar 08 05:16:56 2026 +0300 # Node ID 76e10dcd0806af14721671ced78b13dfbe4cd437 # Parent a98f57c9c8fb0e760d43fe9b507cd51e7da427cc Mp4: fixed entries tests on 32-bit platforms. Previously, tests for the number of entries in various atom read functions used the "entries * sizeof(uint32_t)" construct, with entries being uint32_t. On platforms with 32-bit sizeof() this will overflow, leading to a smaller result. This isn't a real issue in most cases, since the same construct is used to calculate atom_end pointer. Still, in some cases the number of entries is used directly, such as in stco/co64 atom updating, and a large value might result in segmentation faults. The fix is to cast "entries" to uint64_t before the multiplication to avoid the overflow. diff --git a/src/http/modules/ngx_http_mp4_module.c b/src/http/modules/ngx_http_mp4_module.c --- a/src/http/modules/ngx_http_mp4_module.c +++ b/src/http/modules/ngx_http_mp4_module.c @@ -2302,7 +2302,7 @@ ngx_http_mp4_read_stts_atom(ngx_http_mp4 "mp4 time-to-sample entries:%uD", entries); if (ngx_mp4_atom_data_size(ngx_mp4_stts_atom_t) - + entries * sizeof(ngx_mp4_stts_entry_t) > atom_data_size) + + (uint64_t) entries * sizeof(ngx_mp4_stts_entry_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stts atom too small", mp4->file.name.data); @@ -2606,7 +2606,7 @@ ngx_http_mp4_read_stss_atom(ngx_http_mp4 atom->last = atom_table; if (ngx_mp4_atom_data_size(ngx_http_mp4_stss_atom_t) - + entries * sizeof(uint32_t) > atom_data_size) + + (uint64_t) entries * sizeof(uint32_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stss atom too small", mp4->file.name.data); @@ -2811,7 +2811,7 @@ ngx_http_mp4_read_ctts_atom(ngx_http_mp4 atom->last = atom_table; if (ngx_mp4_atom_data_size(ngx_mp4_ctts_atom_t) - + entries * sizeof(ngx_mp4_ctts_entry_t) > atom_data_size) + + (uint64_t) entries * sizeof(ngx_mp4_ctts_entry_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 ctts atom too small", mp4->file.name.data); @@ -2993,7 +2993,7 @@ ngx_http_mp4_read_stsc_atom(ngx_http_mp4 "sample-to-chunk entries:%uD", entries); if (ngx_mp4_atom_data_size(ngx_mp4_stsc_atom_t) - + entries * sizeof(ngx_mp4_stsc_entry_t) > atom_data_size) + + (uint64_t) entries * sizeof(ngx_mp4_stsc_entry_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stsc atom too small", mp4->file.name.data); @@ -3363,7 +3363,7 @@ ngx_http_mp4_read_stsz_atom(ngx_http_mp4 if (size == 0) { if (ngx_mp4_atom_data_size(ngx_mp4_stsz_atom_t) - + entries * sizeof(uint32_t) > atom_data_size) + + (uint64_t) entries * sizeof(uint32_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stsz atom too small", @@ -3536,7 +3536,7 @@ ngx_http_mp4_read_stco_atom(ngx_http_mp4 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "chunks:%uD", entries); if (ngx_mp4_atom_data_size(ngx_mp4_stco_atom_t) - + entries * sizeof(uint32_t) > atom_data_size) + + (uint64_t) entries * sizeof(uint32_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stco atom too small", mp4->file.name.data); @@ -3754,7 +3754,7 @@ ngx_http_mp4_read_co64_atom(ngx_http_mp4 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "chunks:%uD", entries); if (ngx_mp4_atom_data_size(ngx_mp4_co64_atom_t) - + entries * sizeof(uint64_t) > atom_data_size) + + (uint64_t) entries * sizeof(uint64_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 co64 atom too small", mp4->file.name.data); From mdounin at mdounin.ru Sun Mar 8 02:43:40 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Sun, 08 Mar 2026 05:43:40 +0300 Subject: [PATCH 3 of 3] Mp4: fixed off-by-one in stco/co64 chunk number tests In-Reply-To: References: Message-ID: <2ba8f363ec1d6529dedc.1772937820@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1772936218 -10800 # Sun Mar 08 05:16:58 2026 +0300 # Node ID 2ba8f363ec1d6529dedc278100abc9e07609f3ee # Parent 76e10dcd0806af14721671ced78b13dfbe4cd437 Mp4: fixed off-by-one in stco/co64 chunk number tests. Previously, if trak->start_chunk was exactly equal to the number of entries in the stco/co64 atom (trak->chunks), it wasn't rejected by the tests in ngx_http_mp4_update_stco_atom() (and ngx_http_mp4_update_co64_atom()), and the following code accessed a value past the stco/co64 atom, potentially resulting in segmentation faults. diff --git a/src/http/modules/ngx_http_mp4_module.c b/src/http/modules/ngx_http_mp4_module.c --- a/src/http/modules/ngx_http_mp4_module.c +++ b/src/http/modules/ngx_http_mp4_module.c @@ -3606,7 +3606,7 @@ ngx_http_mp4_update_stco_atom(ngx_http_m return NGX_ERROR; } - if (trak->start_chunk > trak->chunks) { + if (trak->start_chunk >= trak->chunks) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "start time is out mp4 stco chunks in \"%s\"", mp4->file.name.data); @@ -3823,7 +3823,7 @@ ngx_http_mp4_update_co64_atom(ngx_http_m return NGX_ERROR; } - if (trak->start_chunk > trak->chunks) { + if (trak->start_chunk >= trak->chunks) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "start time is out mp4 co64 chunks in \"%s\"", mp4->file.name.data); From mdounin at mdounin.ru Sun Mar 8 02:46:23 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Sun, 08 Mar 2026 05:46:23 +0300 Subject: [PATCH 1 of 2] Tests: moved bad mp4 test to a separate file In-Reply-To: References: Message-ID: # HG changeset patch # User Maxim Dounin # Date 1772937917 -10800 # Sun Mar 08 05:45:17 2026 +0300 # Node ID c92474d354a0248fd1d5712d91bfc28d8583ffa9 # Parent cccd744b91590b480f4e06ce27eaa69b038d26b3 Tests: moved bad mp4 test to a separate file. diff --git a/mp4.t b/mp4.t --- a/mp4.t +++ b/mp4.t @@ -65,23 +65,7 @@ system('ffmpeg -nostdin -loglevel quiet . "${\($t->testdir())}/no_mdat.mp4") == 0 or die "Can't create mp4 file: $!"; -my $sbad = <<'EOF'; -00000000: 00 00 00 1c 66 74 79 70 69 73 6f 6d 00 00 02 00 |....ftypisom....| -00000010: 69 73 6f 6d 69 73 6f 32 6d 70 34 31 00 00 00 09 |isomiso2mp41....| -00000020: 6d 64 61 74 00 00 00 00 94 6d 6f 6f 76 00 00 00 |mdat.....moov...| -00000030: 8c 74 72 61 6b 00 00 00 84 6d 64 69 61 00 00 00 |.trak....mdia...| -00000040: 7c 6d 69 6e 66 00 00 00 74 73 74 62 6c 00 00 00 ||minf...tstbl...| -00000050: 18 73 74 74 73 00 00 00 00 00 00 00 01 00 00 03 |.stts...........| -00000060: 3a 00 00 04 00 00 00 00 28 73 74 73 63 00 00 00 |:.......(stsc...| -00000070: 00 00 00 00 02 00 00 00 01 00 00 03 0f 00 00 00 |................| -00000080: 01 00 00 00 02 00 00 00 2b 00 00 00 01 00 00 00 |........+.......| -00000090: 14 73 74 73 7a 00 00 00 00 00 00 05 a9 00 00 03 |.stsz...........| -000000a0: 3b 00 00 00 18 63 6f 36 34 00 00 00 00 00 00 00 |;....co64.......| -000000b0: 01 ff ff ff ff f0 0f fb e7 |.........| -EOF - -$t->write_file('bad.mp4', unhex($sbad)); -$t->run()->plan(27); +$t->run()->plan(26); ############################################################################### @@ -115,10 +99,6 @@ like(http_head("$test_uri?start=21"), qr $test_uri = '/no_mdat.mp4', goto again unless $test_uri eq '/no_mdat.mp4'; -# corrupted formats - -like(http_get("/bad.mp4?start=0.5"), qr/500 Internal/, 'co64 chunk beyond EOF'); - ############################################################################### sub durations { @@ -143,17 +123,4 @@ sub durations { sprintf "%.1f %.1f", $r =~ /duration=(\d+\.\d+)/g; } -sub unhex { - my ($input) = @_; - my $buffer = ''; - - for my $l ($input =~ m/: +((?:[0-9a-f]{2,4} +)+) /gms) { - for my $v ($l =~ m/[0-9a-f]{2}/g) { - $buffer .= chr(hex($v)); - } - } - - return $buffer; -} - ############################################################################### diff --git a/mp4.t b/mp4_bad.t copy from mp4.t copy to mp4_bad.t --- a/mp4.t +++ b/mp4_bad.t @@ -1,10 +1,10 @@ #!/usr/bin/perl +# (C) Maxim Dounin # (C) Sergey Kandaurov # (C) Nginx, Inc. -# Tests for mp4 module. -# Ensures that requested stream duration is given with sane accuracy. +# Tests for mp4 module, various bad mp4 files. ############################################################################### @@ -16,15 +16,14 @@ use Test::More; BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; -use Test::Nginx qw/ :DEFAULT http_content /; +use Test::Nginx; ############################################################################### select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http mp4/)->has_daemon('ffprobe') - ->has_daemon('ffmpeg') +my $t = Test::Nginx->new()->has(qw/http mp4/)->plan(1) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -49,100 +48,39 @@ http { EOF -plan(skip_all => 'no lavfi') - unless grep /lavfi/, `ffmpeg -loglevel quiet -formats`; -system('ffmpeg -nostdin -loglevel quiet -y ' - . '-f lavfi -i testsrc=duration=10:size=320x200:rate=15 ' - . '-f lavfi -i testsrc=duration=20:size=320x200:rate=15 ' - . '-map 0:0 -map 1:0 -pix_fmt yuv420p -g 15 -c:v mpeg4 ' - . "${\($t->testdir())}/test.mp4") == 0 - or die "Can't create mp4 file: $!"; -system('ffmpeg -nostdin -loglevel quiet -y ' - . '-f lavfi -i testsrc=duration=10:size=320x200:rate=15 ' - . '-f lavfi -i testsrc=duration=20:size=320x200:rate=15 ' - . '-map 0:0 -map 1:0 -pix_fmt yuv420p -g 15 -c:v mpeg4 ' - . '-movflags +faststart ' - . "${\($t->testdir())}/no_mdat.mp4") == 0 - or die "Can't create mp4 file: $!"; +# chunk offset in stco/co64 atom beyond the end of file -my $sbad = <<'EOF'; +my $bad_co64 = <<'EOF'; 00000000: 00 00 00 1c 66 74 79 70 69 73 6f 6d 00 00 02 00 |....ftypisom....| -00000010: 69 73 6f 6d 69 73 6f 32 6d 70 34 31 00 00 00 09 |isomiso2mp41....| -00000020: 6d 64 61 74 00 00 00 00 94 6d 6f 6f 76 00 00 00 |mdat.....moov...| -00000030: 8c 74 72 61 6b 00 00 00 84 6d 64 69 61 00 00 00 |.trak....mdia...| -00000040: 7c 6d 69 6e 66 00 00 00 74 73 74 62 6c 00 00 00 ||minf...tstbl...| -00000050: 18 73 74 74 73 00 00 00 00 00 00 00 01 00 00 03 |.stts...........| -00000060: 3a 00 00 04 00 00 00 00 28 73 74 73 63 00 00 00 |:.......(stsc...| -00000070: 00 00 00 00 02 00 00 00 01 00 00 03 0f 00 00 00 |................| -00000080: 01 00 00 00 02 00 00 00 2b 00 00 00 01 00 00 00 |........+.......| -00000090: 14 73 74 73 7a 00 00 00 00 00 00 05 a9 00 00 03 |.stsz...........| -000000a0: 3b 00 00 00 18 63 6f 36 34 00 00 00 00 00 00 00 |;....co64.......| -000000b0: 01 ff ff ff ff f0 0f fb e7 |.........| +00000000: 69 73 6f 6d 69 73 6f 32 6d 70 34 31 |isomiso2mp41| +00000000: 00 00 00 08 6d 64 61 74 |....mdat| +00000000: 00 00 00 94 6d 6f 6f 76 |....moov| +00000000: 00 00 00 8c 74 72 61 6b |....trak| +00000000: 00 00 00 84 6d 64 69 61 |....mdia| +00000000: 00 00 00 7c 6d 69 6e 66 |....minf| +00000000: 00 00 00 74 73 74 62 6c |....stbl| +00000000: 00 00 00 18 73 74 74 73 00 00 00 00 00 00 00 01 |....stts........| +00000000: 00 00 03 3a 00 00 04 00 |........| +00000000: 00 00 00 28 73 74 73 63 00 00 00 00 00 00 00 01 |....stsc........| +00000000: 00 00 00 01 ff ff ff ff 00 00 00 00 |............| +00000000: 00 00 00 02 ff ff ff ff 00 00 00 00 |............| +00000000: 00 00 00 14 73 74 73 7a 00 00 00 00 00 00 05 a9 |....stsz........| +00000000: 00 00 03 3b |....| +00000000: 00 00 00 18 63 6f 36 34 00 00 00 00 00 00 00 01 |....co64........| +00000000: ff ff ff ff f0 0f fb e7 |........| EOF -$t->write_file('bad.mp4', unhex($sbad)); -$t->run()->plan(27); +$t->write_file('bad_co64.mp4', unhex($bad_co64)); + +$t->run(); ############################################################################### -my $test_uri = '/test.mp4'; - -again: - -is(durations($t, 0.0), '10.0 20.0', 'start zero'); -is(durations($t, 2), '8.0 18.0', 'start integer'); -is(durations($t, 7.1), '2.9 12.9', 'start float'); - -is(durations($t, 6, 9), '3.0 3.0', 'start end integer'); -is(durations($t, 2.7, 5.6), '2.9 2.9', 'start end float'); - -is(durations($t, undef, 9), '9.0 9.0', 'end integer'); -is(durations($t, undef, 5.6), '5.6 5.6', 'end float'); - -# invalid range results in ignoring end argument - -like(http_head("$test_uri?start=1&end=1"), qr/200 OK/, 'zero range'); -like(http_head("$test_uri?start=1&end=0"), qr/200 OK/, 'negative range'); - -# start/end values exceeding track/file duration - -unlike(http_head("$test_uri?end=11"), qr!HTTP/1.1 500!, - 'end beyond short track'); -unlike(http_head("$test_uri?end=21"), qr!HTTP/1.1 500!, 'end beyond EOF'); -unlike(http_head("$test_uri?start=11"), qr!HTTP/1.1 500!, - 'start beyond short track'); -like(http_head("$test_uri?start=21"), qr!HTTP/1.1 500!, 'start beyond EOF'); - -$test_uri = '/no_mdat.mp4', goto again unless $test_uri eq '/no_mdat.mp4'; - -# corrupted formats - -like(http_get("/bad.mp4?start=0.5"), qr/500 Internal/, 'co64 chunk beyond EOF'); +like(http_get("/bad_co64.mp4?start=0.5"), qr/500 Internal/, + 'co64 chunk after eof'); ############################################################################### -sub durations { - my ($t, $start, $end) = @_; - my $path = $t->{_testdir} . '/frag.mp4'; - - my $uri = $test_uri; - if (defined $start) { - $uri .= "?start=$start"; - if (defined $end) { - $uri .= "&end=$end"; - } - - } elsif (defined $end) { - $uri .= "?end=$end"; - } - - $t->write_file('frag.mp4', http_content(http_get($uri))); - - my $r = `ffprobe -show_streams $path 2>/dev/null`; - Test::Nginx::log_core('||', $r); - sprintf "%.1f %.1f", $r =~ /duration=(\d+\.\d+)/g; -} - sub unhex { my ($input) = @_; my $buffer = ''; From mdounin at mdounin.ru Sun Mar 8 02:46:24 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Sun, 08 Mar 2026 05:46:24 +0300 Subject: [PATCH 2 of 2] Tests: additional tests for various mp4 issues In-Reply-To: References: Message-ID: # HG changeset patch # User Maxim Dounin # Date 1772937920 -10800 # Sun Mar 08 05:45:20 2026 +0300 # Node ID e9c5bc1ae12bb62cce7289339b9638fa277ae5ab # Parent c92474d354a0248fd1d5712d91bfc28d8583ffa9 Tests: additional tests for various mp4 issues. diff --git a/mp4_bad.t b/mp4_bad.t --- a/mp4_bad.t +++ b/mp4_bad.t @@ -23,7 +23,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http mp4/)->plan(1) +my $t = Test::Nginx->new()->has(qw/http mp4/)->plan(4) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -42,6 +42,8 @@ http { location / { mp4; + mp4_start_key_frame on; + postpone_output 0; } } } @@ -70,7 +72,106 @@ 00000000: 00 00 00 18 63 6f 36 34 00 0 00000000: ff ff ff ff f0 0f fb e7 |........| EOF +# zero entry in stss causes incorrect access during stts processing + +my $bad_key_frame = <<'EOF'; +00000000: 00 00 00 1c 66 74 79 70 69 73 6f 6d 00 00 02 00 |....ftypisom....| +00000000: 69 73 6f 6d 69 73 6f 32 6d 70 34 31 |isomiso2mp41| +00000000: 00 00 00 08 6d 64 61 74 |....mdat| +00000000: 00 00 00 b4 6d 6f 6f 76 |....moov| +00000000: 00 00 00 ac 74 72 61 6b |....trak| +00000000: 00 00 00 a4 6d 64 69 61 |....mdia| +00000000: 00 00 00 9c 6d 69 6e 66 |....minf| +00000000: 00 00 00 94 73 74 62 6c |....stbl| +00000000: 00 00 00 18 73 74 74 73 00 00 00 00 00 00 00 01 |....stts........| +00000000: 00 00 03 3a 00 00 04 00 |........| +00000000: 00 00 00 28 73 74 73 63 00 00 00 00 00 00 00 02 |....stsc........| +00000000: 00 00 00 01 ff ff ff ff 00 00 00 00 |............| +00000000: 00 00 00 02 ff ff ff ff 00 00 00 00 |............| +00000000: 00 00 00 14 73 74 73 7a 00 00 00 00 00 00 05 a9 |....stsz........| +00000000: 00 00 03 3b |....| +00000000: 00 00 00 18 73 74 63 6f 00 00 00 00 00 00 00 02 |....stco........| +00000000: 00 00 00 00 00 00 ff ff |........| +00000000: 00 00 00 20 73 74 73 73 00 00 00 00 00 00 00 01 |....stss........| +00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +EOF + +# entries check overflow on 32bit platforms + +my $bad_entries_overflow = <<'EOF'; +00000000: 00 00 00 1c 66 74 79 70 69 73 6f 6d 00 00 02 00 |....ftypisom....| +00000000: 69 73 6f 6d 69 73 6f 32 6d 70 34 31 |isomiso2mp41| +00000000: 00 00 00 08 6d 64 61 74 |....mdat| +00000000: 00 00 00 c4 6d 6f 6f 76 |....moov| +00000000: 00 00 00 bc 74 72 61 6b |....trak| +00000000: 00 00 00 b4 6d 64 69 61 |....mdia| +00000000: 00 00 00 20 6d 64 68 64 00 00 00 00 |....mdhd....| +00000000: 00 00 00 00 00 00 00 00 00 00 03 e8 ff ff ff ff |................| +00000000: 00 00 00 00 |....| +00000000: 00 00 00 8c 6d 69 6e 66 |....minf| +00000000: 00 00 00 84 73 74 62 6c |....stbl| +00000000: 00 00 00 18 73 74 74 73 00 00 00 00 00 00 00 01 |....stts........| +00000000: ff ff ff ff 00 00 00 01 |........| +00000000: 00 00 00 28 73 74 73 63 00 00 00 00 00 00 00 01 |....stsc........| +00000000: 00 00 00 01 00 00 00 01 00 00 00 00 |............| +00000000: 00 ff ff ff 00 00 00 ff 00 00 00 00 |............| +00000000: 00 00 00 14 73 74 73 7a 00 00 00 00 00 00 05 a9 |....stsz........| +00000000: 00 00 03 3b |....| +00000000: 00 00 00 28 73 74 63 6f 00 00 00 00 40 00 00 01 |....stco........| +00000000: 00 00 00 00 00 00 00 00 |........| +00000000: 00 00 00 00 00 00 00 00 |........| +00000000: 00 00 00 00 00 00 00 00 |........| +EOF + +# stco/co64 entries check off-by-one + +my $bad_entries_offbyone = <<'EOF'; +00000000: 00 00 00 1c 66 74 79 70 69 73 6f 6d 00 00 02 00 |....ftypisom....| +00000000: 69 73 6f 6d 69 73 6f 32 6d 70 34 31 |isomiso2mp41| +00000000: 00 00 00 08 6d 64 61 74 |....mdat| +00000000: 00 00 01 80 6d 6f 6f 76 |....moov| +00000000: 00 00 00 bc 74 72 61 6b |....trak| +00000000: 00 00 00 b4 6d 64 69 61 |....mdia| +00000000: 00 00 00 20 6d 64 68 64 00 00 00 00 |....mdhd....| +00000000: 00 00 00 00 00 00 00 00 00 00 03 e8 ff ff ff ff |................| +00000000: 00 00 00 00 |....| +00000000: 00 00 00 8c 6d 69 6e 66 |....minf| +00000000: 00 00 00 84 73 74 62 6c |....stbl| +00000000: 00 00 00 18 73 74 74 73 00 00 00 00 00 00 00 01 |....stts........| +00000000: ff ff ff ff 00 00 00 01 |........| +00000000: 00 00 00 28 73 74 73 63 00 00 00 00 00 00 00 02 |....stsc........| +00000000: 00 00 00 01 00 00 00 01 00 00 00 00 |............| +00000000: 00 ff ff ff 00 00 00 ff 00 00 00 00 |............| +00000000: 00 00 00 14 73 74 73 7a 00 00 00 00 00 00 05 a9 |....stsz........| +00000000: 00 00 03 3b |....| +00000000: 00 00 00 28 73 74 63 6f 00 00 00 00 00 00 00 06 |....stco........| +00000000: 00 00 00 00 00 00 00 00 |........| +00000000: 00 00 00 00 00 00 00 00 |........| +00000000: 00 00 00 00 00 00 00 00 |........| +00000000: 00 00 00 bc 74 72 61 6b |....trak| +00000000: 00 00 00 b4 6d 64 69 61 |....mdia| +00000000: 00 00 00 20 6d 64 68 64 00 00 00 00 |....mdhd....| +00000000: 00 00 00 00 00 00 00 00 00 00 03 e8 ff ff ff ff |................| +00000000: 00 00 00 00 |....| +00000000: 00 00 00 8c 6d 69 6e 66 |....minf| +00000000: 00 00 00 84 73 74 62 6c |....stbl| +00000000: 00 00 00 18 73 74 74 73 00 00 00 00 00 00 00 01 |....stts........| +00000000: ff ff ff ff 00 00 00 01 |........| +00000000: 00 00 00 28 73 74 73 63 00 00 00 00 00 00 00 02 |....stsc........| +00000000: 00 00 00 01 00 00 00 01 00 00 00 00 |............| +00000000: 00 ff ff ff 00 00 00 ff 00 00 00 00 |............| +00000000: 00 00 00 14 73 74 73 7a 00 00 00 00 00 00 05 a9 |....stsz........| +00000000: 00 00 03 3b |....| +00000000: 00 00 00 28 73 74 63 6f 00 00 00 00 00 00 00 01 |....stco........| +00000000: 00 00 00 00 00 00 00 00 |........| +00000000: 00 00 00 00 00 00 00 00 |........| +00000000: 00 00 00 00 00 00 00 00 |........| +EOF + $t->write_file('bad_co64.mp4', unhex($bad_co64)); +$t->write_file('bad_key_frame.mp4', unhex($bad_key_frame)); +$t->write_file('bad_entries_overflow.mp4', unhex($bad_entries_overflow)); +$t->write_file('bad_entries_offbyone.mp4', unhex($bad_entries_offbyone)); $t->run(); @@ -79,6 +180,21 @@ EOF like(http_get("/bad_co64.mp4?start=0.5"), qr/500 Internal/, 'co64 chunk after eof'); +TODO: { +local $TODO = 'not yet', $t->todo_alerts() + unless $t->has_version('1.29.6'); + +like(http_get("/bad_key_frame.mp4?start=0.5"), qr/200 OK/, + 'stss with zero entry'); + +like(http_get("/bad_entries_overflow.mp4?start=0.005"), qr/500 Internal/, + 'stco entries 32bit overflow'); + +like(http_get("/bad_entries_offbyone.mp4?start=0.001"), qr/500 Internal/, + 'stco entries off-by-one'); + +} + ############################################################################### sub unhex { From mdounin at mdounin.ru Tue Mar 10 01:07:24 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Tue, 10 Mar 2026 04:07:24 +0300 Subject: [nginx] Fixed clcf->auto_redirect in uwsgi_pass/scgi_pass with v... Message-ID: details: http://freenginx.org/hg/nginx/rev/6ea8697c0740 branches: changeset: 9474:6ea8697c0740 user: Maxim Dounin date: Tue Mar 10 03:50:52 2026 +0300 description: Fixed clcf->auto_redirect in uwsgi_pass/scgi_pass with variables. Previously, clcf->auto_redirect was only set (for locations ending with "/") when uwsgi_pass and scgi_pass were used without variables, but not with variables. For proxy_pass and fastcgi_pass this was fixed in 2989:dff9764eaca2 (0.8.7). It was not, however, fixed in ngx_http_uwsgi_module (as imported later, in 3541:21452748d165, 0.8.40) and ngx_http_scgi_module (introduced shortly after, in 3637:d656caa72ec9, 0.8.42). The fix is mostly identical to the one in 2989:dff9764eaca2 (0.8.7) and ensures that clcf->auto_redirect is set both with and without variables. diffstat: src/http/modules/ngx_http_scgi_module.c | 9 +++++---- src/http/modules/ngx_http_uwsgi_module.c | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diffs (56 lines): diff --git a/src/http/modules/ngx_http_scgi_module.c b/src/http/modules/ngx_http_scgi_module.c --- a/src/http/modules/ngx_http_scgi_module.c +++ b/src/http/modules/ngx_http_scgi_module.c @@ -1921,8 +1921,13 @@ ngx_http_scgi_pass(ngx_conf_t *cf, ngx_c } clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_http_scgi_handler; + if (clcf->name.len && clcf->name.data[clcf->name.len - 1] == '/') { + clcf->auto_redirect = 1; + } + value = cf->args->elts; url = &value[1]; @@ -1958,10 +1963,6 @@ ngx_http_scgi_pass(ngx_conf_t *cf, ngx_c return NGX_CONF_ERROR; } - if (clcf->name.len && clcf->name.data[clcf->name.len - 1] == '/') { - clcf->auto_redirect = 1; - } - return NGX_CONF_OK; } diff --git a/src/http/modules/ngx_http_uwsgi_module.c b/src/http/modules/ngx_http_uwsgi_module.c --- a/src/http/modules/ngx_http_uwsgi_module.c +++ b/src/http/modules/ngx_http_uwsgi_module.c @@ -2221,8 +2221,13 @@ ngx_http_uwsgi_pass(ngx_conf_t *cf, ngx_ } clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_http_uwsgi_handler; + if (clcf->name.len && clcf->name.data[clcf->name.len - 1] == '/') { + clcf->auto_redirect = 1; + } + value = cf->args->elts; url = &value[1]; @@ -2281,10 +2286,6 @@ ngx_http_uwsgi_pass(ngx_conf_t *cf, ngx_ return NGX_CONF_ERROR; } - if (clcf->name.len && clcf->name.data[clcf->name.len - 1] == '/') { - clcf->auto_redirect = 1; - } - return NGX_CONF_OK; } From mdounin at mdounin.ru Tue Mar 10 01:39:22 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Tue, 10 Mar 2026 04:39:22 +0300 Subject: [nginx] Mp4: fixed ngx_http_mp4_seek_key_frame(). Message-ID: details: http://freenginx.org/hg/nginx/rev/a181973aa820 branches: changeset: 9475:a181973aa820 user: Maxim Dounin date: Tue Mar 10 04:31:25 2026 +0300 description: Mp4: fixed ngx_http_mp4_seek_key_frame(). Previously, ngx_http_mp4_seek_key_frame() might return key frame prefix larger than start_sample provided in function arguments, notably on an invalid stss entry with zero sample_number. This resulted in backwards loop in ngx_http_mp4_crop_stts_data() to incorrectly access data before the stts atom entries (and stop at the stts atom header mostly by chance). The fix is to make sure ngx_http_mp4_seek_key_frame() always returns a value equal or smaller than start_sample provided, regardless of the stss entries. Invalid entries with zero sample_number now will be interpreted as if there very large sample_number, so the previous one will be returned. diffstat: src/http/modules/ngx_http_mp4_module.c | 7 ++++--- 1 files changed, 4 insertions(+), 3 deletions(-) diffs (21 lines): diff --git a/src/http/modules/ngx_http_mp4_module.c b/src/http/modules/ngx_http_mp4_module.c --- a/src/http/modules/ngx_http_mp4_module.c +++ b/src/http/modules/ngx_http_mp4_module.c @@ -2528,13 +2528,14 @@ ngx_http_mp4_seek_key_frame(ngx_http_mp4 entry = (uint32_t *) data->pos; end = (uint32_t *) data->last; - /* sync samples starts from 1 */ - start_sample++; - key_prefix = 0; while (entry < end) { sample = ngx_mp4_get_32value(entry); + + /* sync samples starts from 1 */ + sample--; + if (sample > start_sample) { break; } From mdounin at mdounin.ru Tue Mar 10 01:39:22 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Tue, 10 Mar 2026 04:39:22 +0300 Subject: [nginx] Mp4: fixed entries tests on 32-bit platforms. Message-ID: details: http://freenginx.org/hg/nginx/rev/efe2b1c11265 branches: changeset: 9476:efe2b1c11265 user: Maxim Dounin date: Tue Mar 10 04:31:28 2026 +0300 description: Mp4: fixed entries tests on 32-bit platforms. Previously, tests for the number of entries in various atom read functions used the "entries * sizeof(uint32_t)" construct, with entries being uint32_t. On platforms with 32-bit sizeof() this will overflow, leading to a smaller result. This isn't a real issue in most cases, since the same construct is used to calculate atom_end pointer. Still, in some cases the number of entries is used directly, such as in stco/co64 atom updating, and a large value might result in segmentation faults. The fix is to cast "entries" to uint64_t before the multiplication to avoid the overflow. diffstat: src/http/modules/ngx_http_mp4_module.c | 14 +++++++------- 1 files changed, 7 insertions(+), 7 deletions(-) diffs (66 lines): diff --git a/src/http/modules/ngx_http_mp4_module.c b/src/http/modules/ngx_http_mp4_module.c --- a/src/http/modules/ngx_http_mp4_module.c +++ b/src/http/modules/ngx_http_mp4_module.c @@ -2302,7 +2302,7 @@ ngx_http_mp4_read_stts_atom(ngx_http_mp4 "mp4 time-to-sample entries:%uD", entries); if (ngx_mp4_atom_data_size(ngx_mp4_stts_atom_t) - + entries * sizeof(ngx_mp4_stts_entry_t) > atom_data_size) + + (uint64_t) entries * sizeof(ngx_mp4_stts_entry_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stts atom too small", mp4->file.name.data); @@ -2606,7 +2606,7 @@ ngx_http_mp4_read_stss_atom(ngx_http_mp4 atom->last = atom_table; if (ngx_mp4_atom_data_size(ngx_http_mp4_stss_atom_t) - + entries * sizeof(uint32_t) > atom_data_size) + + (uint64_t) entries * sizeof(uint32_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stss atom too small", mp4->file.name.data); @@ -2811,7 +2811,7 @@ ngx_http_mp4_read_ctts_atom(ngx_http_mp4 atom->last = atom_table; if (ngx_mp4_atom_data_size(ngx_mp4_ctts_atom_t) - + entries * sizeof(ngx_mp4_ctts_entry_t) > atom_data_size) + + (uint64_t) entries * sizeof(ngx_mp4_ctts_entry_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 ctts atom too small", mp4->file.name.data); @@ -2993,7 +2993,7 @@ ngx_http_mp4_read_stsc_atom(ngx_http_mp4 "sample-to-chunk entries:%uD", entries); if (ngx_mp4_atom_data_size(ngx_mp4_stsc_atom_t) - + entries * sizeof(ngx_mp4_stsc_entry_t) > atom_data_size) + + (uint64_t) entries * sizeof(ngx_mp4_stsc_entry_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stsc atom too small", mp4->file.name.data); @@ -3363,7 +3363,7 @@ ngx_http_mp4_read_stsz_atom(ngx_http_mp4 if (size == 0) { if (ngx_mp4_atom_data_size(ngx_mp4_stsz_atom_t) - + entries * sizeof(uint32_t) > atom_data_size) + + (uint64_t) entries * sizeof(uint32_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stsz atom too small", @@ -3536,7 +3536,7 @@ ngx_http_mp4_read_stco_atom(ngx_http_mp4 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "chunks:%uD", entries); if (ngx_mp4_atom_data_size(ngx_mp4_stco_atom_t) - + entries * sizeof(uint32_t) > atom_data_size) + + (uint64_t) entries * sizeof(uint32_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 stco atom too small", mp4->file.name.data); @@ -3754,7 +3754,7 @@ ngx_http_mp4_read_co64_atom(ngx_http_mp4 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "chunks:%uD", entries); if (ngx_mp4_atom_data_size(ngx_mp4_co64_atom_t) - + entries * sizeof(uint64_t) > atom_data_size) + + (uint64_t) entries * sizeof(uint64_t) > atom_data_size) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "\"%s\" mp4 co64 atom too small", mp4->file.name.data); From mdounin at mdounin.ru Tue Mar 10 01:39:22 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Tue, 10 Mar 2026 04:39:22 +0300 Subject: [nginx] Mp4: fixed off-by-one in stco/co64 chunk number tests. Message-ID: details: http://freenginx.org/hg/nginx/rev/a7c60a17c215 branches: changeset: 9477:a7c60a17c215 user: Maxim Dounin date: Tue Mar 10 04:31:31 2026 +0300 description: Mp4: fixed off-by-one in stco/co64 chunk number tests. Previously, if trak->start_chunk was exactly equal to the number of entries in the stco/co64 atom (trak->chunks), it wasn't rejected by the tests in ngx_http_mp4_update_stco_atom() (and ngx_http_mp4_update_co64_atom()), and the following code accessed a value past the stco/co64 atom, potentially resulting in segmentation faults. diffstat: src/http/modules/ngx_http_mp4_module.c | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diffs (21 lines): diff --git a/src/http/modules/ngx_http_mp4_module.c b/src/http/modules/ngx_http_mp4_module.c --- a/src/http/modules/ngx_http_mp4_module.c +++ b/src/http/modules/ngx_http_mp4_module.c @@ -3606,7 +3606,7 @@ ngx_http_mp4_update_stco_atom(ngx_http_m return NGX_ERROR; } - if (trak->start_chunk > trak->chunks) { + if (trak->start_chunk >= trak->chunks) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "start time is out mp4 stco chunks in \"%s\"", mp4->file.name.data); @@ -3823,7 +3823,7 @@ ngx_http_mp4_update_co64_atom(ngx_http_m return NGX_ERROR; } - if (trak->start_chunk > trak->chunks) { + if (trak->start_chunk >= trak->chunks) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, "start time is out mp4 co64 chunks in \"%s\"", mp4->file.name.data); From mdounin at mdounin.ru Tue Mar 10 01:41:14 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Tue, 10 Mar 2026 04:41:14 +0300 Subject: [nginx-tests] Tests: moved bad mp4 test to a separate file. Message-ID: details: http://freenginx.org/hg/nginx-tests/rev/d6429eb46e37 branches: changeset: 2042:d6429eb46e37 user: Maxim Dounin date: Tue Mar 10 04:32:36 2026 +0300 description: Tests: moved bad mp4 test to a separate file. diffstat: mp4.t | 35 +------------------ mp4_bad.t | 116 ++++++++++++++----------------------------------------------- 2 files changed, 28 insertions(+), 123 deletions(-) diffs (217 lines): diff --git a/mp4.t b/mp4.t --- a/mp4.t +++ b/mp4.t @@ -65,23 +65,7 @@ system('ffmpeg -nostdin -loglevel quiet . "${\($t->testdir())}/no_mdat.mp4") == 0 or die "Can't create mp4 file: $!"; -my $sbad = <<'EOF'; -00000000: 00 00 00 1c 66 74 79 70 69 73 6f 6d 00 00 02 00 |....ftypisom....| -00000010: 69 73 6f 6d 69 73 6f 32 6d 70 34 31 00 00 00 09 |isomiso2mp41....| -00000020: 6d 64 61 74 00 00 00 00 94 6d 6f 6f 76 00 00 00 |mdat.....moov...| -00000030: 8c 74 72 61 6b 00 00 00 84 6d 64 69 61 00 00 00 |.trak....mdia...| -00000040: 7c 6d 69 6e 66 00 00 00 74 73 74 62 6c 00 00 00 ||minf...tstbl...| -00000050: 18 73 74 74 73 00 00 00 00 00 00 00 01 00 00 03 |.stts...........| -00000060: 3a 00 00 04 00 00 00 00 28 73 74 73 63 00 00 00 |:.......(stsc...| -00000070: 00 00 00 00 02 00 00 00 01 00 00 03 0f 00 00 00 |................| -00000080: 01 00 00 00 02 00 00 00 2b 00 00 00 01 00 00 00 |........+.......| -00000090: 14 73 74 73 7a 00 00 00 00 00 00 05 a9 00 00 03 |.stsz...........| -000000a0: 3b 00 00 00 18 63 6f 36 34 00 00 00 00 00 00 00 |;....co64.......| -000000b0: 01 ff ff ff ff f0 0f fb e7 |.........| -EOF - -$t->write_file('bad.mp4', unhex($sbad)); -$t->run()->plan(27); +$t->run()->plan(26); ############################################################################### @@ -115,10 +99,6 @@ like(http_head("$test_uri?start=21"), qr $test_uri = '/no_mdat.mp4', goto again unless $test_uri eq '/no_mdat.mp4'; -# corrupted formats - -like(http_get("/bad.mp4?start=0.5"), qr/500 Internal/, 'co64 chunk beyond EOF'); - ############################################################################### sub durations { @@ -143,17 +123,4 @@ sub durations { sprintf "%.1f %.1f", $r =~ /duration=(\d+\.\d+)/g; } -sub unhex { - my ($input) = @_; - my $buffer = ''; - - for my $l ($input =~ m/: +((?:[0-9a-f]{2,4} +)+) /gms) { - for my $v ($l =~ m/[0-9a-f]{2}/g) { - $buffer .= chr(hex($v)); - } - } - - return $buffer; -} - ############################################################################### diff --git a/mp4.t b/mp4_bad.t copy from mp4.t copy to mp4_bad.t --- a/mp4.t +++ b/mp4_bad.t @@ -1,10 +1,10 @@ #!/usr/bin/perl +# (C) Maxim Dounin # (C) Sergey Kandaurov # (C) Nginx, Inc. -# Tests for mp4 module. -# Ensures that requested stream duration is given with sane accuracy. +# Tests for mp4 module, various bad mp4 files. ############################################################################### @@ -16,15 +16,14 @@ use Test::More; BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; -use Test::Nginx qw/ :DEFAULT http_content /; +use Test::Nginx; ############################################################################### select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http mp4/)->has_daemon('ffprobe') - ->has_daemon('ffmpeg') +my $t = Test::Nginx->new()->has(qw/http mp4/)->plan(1) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -49,100 +48,39 @@ http { EOF -plan(skip_all => 'no lavfi') - unless grep /lavfi/, `ffmpeg -loglevel quiet -formats`; -system('ffmpeg -nostdin -loglevel quiet -y ' - . '-f lavfi -i testsrc=duration=10:size=320x200:rate=15 ' - . '-f lavfi -i testsrc=duration=20:size=320x200:rate=15 ' - . '-map 0:0 -map 1:0 -pix_fmt yuv420p -g 15 -c:v mpeg4 ' - . "${\($t->testdir())}/test.mp4") == 0 - or die "Can't create mp4 file: $!"; -system('ffmpeg -nostdin -loglevel quiet -y ' - . '-f lavfi -i testsrc=duration=10:size=320x200:rate=15 ' - . '-f lavfi -i testsrc=duration=20:size=320x200:rate=15 ' - . '-map 0:0 -map 1:0 -pix_fmt yuv420p -g 15 -c:v mpeg4 ' - . '-movflags +faststart ' - . "${\($t->testdir())}/no_mdat.mp4") == 0 - or die "Can't create mp4 file: $!"; +# chunk offset in stco/co64 atom beyond the end of file -my $sbad = <<'EOF'; +my $bad_co64 = <<'EOF'; 00000000: 00 00 00 1c 66 74 79 70 69 73 6f 6d 00 00 02 00 |....ftypisom....| -00000010: 69 73 6f 6d 69 73 6f 32 6d 70 34 31 00 00 00 09 |isomiso2mp41....| -00000020: 6d 64 61 74 00 00 00 00 94 6d 6f 6f 76 00 00 00 |mdat.....moov...| -00000030: 8c 74 72 61 6b 00 00 00 84 6d 64 69 61 00 00 00 |.trak....mdia...| -00000040: 7c 6d 69 6e 66 00 00 00 74 73 74 62 6c 00 00 00 ||minf...tstbl...| -00000050: 18 73 74 74 73 00 00 00 00 00 00 00 01 00 00 03 |.stts...........| -00000060: 3a 00 00 04 00 00 00 00 28 73 74 73 63 00 00 00 |:.......(stsc...| -00000070: 00 00 00 00 02 00 00 00 01 00 00 03 0f 00 00 00 |................| -00000080: 01 00 00 00 02 00 00 00 2b 00 00 00 01 00 00 00 |........+.......| -00000090: 14 73 74 73 7a 00 00 00 00 00 00 05 a9 00 00 03 |.stsz...........| -000000a0: 3b 00 00 00 18 63 6f 36 34 00 00 00 00 00 00 00 |;....co64.......| -000000b0: 01 ff ff ff ff f0 0f fb e7 |.........| +00000000: 69 73 6f 6d 69 73 6f 32 6d 70 34 31 |isomiso2mp41| +00000000: 00 00 00 08 6d 64 61 74 |....mdat| +00000000: 00 00 00 94 6d 6f 6f 76 |....moov| +00000000: 00 00 00 8c 74 72 61 6b |....trak| +00000000: 00 00 00 84 6d 64 69 61 |....mdia| +00000000: 00 00 00 7c 6d 69 6e 66 |....minf| +00000000: 00 00 00 74 73 74 62 6c |....stbl| +00000000: 00 00 00 18 73 74 74 73 00 00 00 00 00 00 00 01 |....stts........| +00000000: 00 00 03 3a 00 00 04 00 |........| +00000000: 00 00 00 28 73 74 73 63 00 00 00 00 00 00 00 01 |....stsc........| +00000000: 00 00 00 01 ff ff ff ff 00 00 00 00 |............| +00000000: 00 00 00 02 ff ff ff ff 00 00 00 00 |............| +00000000: 00 00 00 14 73 74 73 7a 00 00 00 00 00 00 05 a9 |....stsz........| +00000000: 00 00 03 3b |....| +00000000: 00 00 00 18 63 6f 36 34 00 00 00 00 00 00 00 01 |....co64........| +00000000: ff ff ff ff f0 0f fb e7 |........| EOF -$t->write_file('bad.mp4', unhex($sbad)); -$t->run()->plan(27); +$t->write_file('bad_co64.mp4', unhex($bad_co64)); + +$t->run(); ############################################################################### -my $test_uri = '/test.mp4'; - -again: - -is(durations($t, 0.0), '10.0 20.0', 'start zero'); -is(durations($t, 2), '8.0 18.0', 'start integer'); -is(durations($t, 7.1), '2.9 12.9', 'start float'); - -is(durations($t, 6, 9), '3.0 3.0', 'start end integer'); -is(durations($t, 2.7, 5.6), '2.9 2.9', 'start end float'); - -is(durations($t, undef, 9), '9.0 9.0', 'end integer'); -is(durations($t, undef, 5.6), '5.6 5.6', 'end float'); - -# invalid range results in ignoring end argument - -like(http_head("$test_uri?start=1&end=1"), qr/200 OK/, 'zero range'); -like(http_head("$test_uri?start=1&end=0"), qr/200 OK/, 'negative range'); - -# start/end values exceeding track/file duration - -unlike(http_head("$test_uri?end=11"), qr!HTTP/1.1 500!, - 'end beyond short track'); -unlike(http_head("$test_uri?end=21"), qr!HTTP/1.1 500!, 'end beyond EOF'); -unlike(http_head("$test_uri?start=11"), qr!HTTP/1.1 500!, - 'start beyond short track'); -like(http_head("$test_uri?start=21"), qr!HTTP/1.1 500!, 'start beyond EOF'); - -$test_uri = '/no_mdat.mp4', goto again unless $test_uri eq '/no_mdat.mp4'; - -# corrupted formats - -like(http_get("/bad.mp4?start=0.5"), qr/500 Internal/, 'co64 chunk beyond EOF'); +like(http_get("/bad_co64.mp4?start=0.5"), qr/500 Internal/, + 'co64 chunk after eof'); ############################################################################### -sub durations { - my ($t, $start, $end) = @_; - my $path = $t->{_testdir} . '/frag.mp4'; - - my $uri = $test_uri; - if (defined $start) { - $uri .= "?start=$start"; - if (defined $end) { - $uri .= "&end=$end"; - } - - } elsif (defined $end) { - $uri .= "?end=$end"; - } - - $t->write_file('frag.mp4', http_content(http_get($uri))); - - my $r = `ffprobe -show_streams $path 2>/dev/null`; - Test::Nginx::log_core('||', $r); - sprintf "%.1f %.1f", $r =~ /duration=(\d+\.\d+)/g; -} - sub unhex { my ($input) = @_; my $buffer = ''; From mdounin at mdounin.ru Tue Mar 10 01:41:14 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Tue, 10 Mar 2026 04:41:14 +0300 Subject: [nginx-tests] Tests: additional tests for various mp4 issues. Message-ID: details: http://freenginx.org/hg/nginx-tests/rev/cbb91250b488 branches: changeset: 2043:cbb91250b488 user: Maxim Dounin date: Tue Mar 10 04:32:38 2026 +0300 description: Tests: additional tests for various mp4 issues. diffstat: mp4_bad.t | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 files changed, 117 insertions(+), 1 deletions(-) diffs (150 lines): diff --git a/mp4_bad.t b/mp4_bad.t --- a/mp4_bad.t +++ b/mp4_bad.t @@ -23,7 +23,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http mp4/)->plan(1) +my $t = Test::Nginx->new()->has(qw/http mp4/)->plan(4) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -42,6 +42,8 @@ http { location / { mp4; + mp4_start_key_frame on; + postpone_output 0; } } } @@ -70,7 +72,106 @@ 00000000: 00 00 00 18 63 6f 36 34 00 0 00000000: ff ff ff ff f0 0f fb e7 |........| EOF +# zero entry in stss causes incorrect access during stts processing + +my $bad_key_frame = <<'EOF'; +00000000: 00 00 00 1c 66 74 79 70 69 73 6f 6d 00 00 02 00 |....ftypisom....| +00000000: 69 73 6f 6d 69 73 6f 32 6d 70 34 31 |isomiso2mp41| +00000000: 00 00 00 08 6d 64 61 74 |....mdat| +00000000: 00 00 00 b4 6d 6f 6f 76 |....moov| +00000000: 00 00 00 ac 74 72 61 6b |....trak| +00000000: 00 00 00 a4 6d 64 69 61 |....mdia| +00000000: 00 00 00 9c 6d 69 6e 66 |....minf| +00000000: 00 00 00 94 73 74 62 6c |....stbl| +00000000: 00 00 00 18 73 74 74 73 00 00 00 00 00 00 00 01 |....stts........| +00000000: 00 00 03 3a 00 00 04 00 |........| +00000000: 00 00 00 28 73 74 73 63 00 00 00 00 00 00 00 02 |....stsc........| +00000000: 00 00 00 01 ff ff ff ff 00 00 00 00 |............| +00000000: 00 00 00 02 ff ff ff ff 00 00 00 00 |............| +00000000: 00 00 00 14 73 74 73 7a 00 00 00 00 00 00 05 a9 |....stsz........| +00000000: 00 00 03 3b |....| +00000000: 00 00 00 18 73 74 63 6f 00 00 00 00 00 00 00 02 |....stco........| +00000000: 00 00 00 00 00 00 ff ff |........| +00000000: 00 00 00 20 73 74 73 73 00 00 00 00 00 00 00 01 |....stss........| +00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +EOF + +# entries check overflow on 32bit platforms + +my $bad_entries_overflow = <<'EOF'; +00000000: 00 00 00 1c 66 74 79 70 69 73 6f 6d 00 00 02 00 |....ftypisom....| +00000000: 69 73 6f 6d 69 73 6f 32 6d 70 34 31 |isomiso2mp41| +00000000: 00 00 00 08 6d 64 61 74 |....mdat| +00000000: 00 00 00 c4 6d 6f 6f 76 |....moov| +00000000: 00 00 00 bc 74 72 61 6b |....trak| +00000000: 00 00 00 b4 6d 64 69 61 |....mdia| +00000000: 00 00 00 20 6d 64 68 64 00 00 00 00 |....mdhd....| +00000000: 00 00 00 00 00 00 00 00 00 00 03 e8 ff ff ff ff |................| +00000000: 00 00 00 00 |....| +00000000: 00 00 00 8c 6d 69 6e 66 |....minf| +00000000: 00 00 00 84 73 74 62 6c |....stbl| +00000000: 00 00 00 18 73 74 74 73 00 00 00 00 00 00 00 01 |....stts........| +00000000: ff ff ff ff 00 00 00 01 |........| +00000000: 00 00 00 28 73 74 73 63 00 00 00 00 00 00 00 01 |....stsc........| +00000000: 00 00 00 01 00 00 00 01 00 00 00 00 |............| +00000000: 00 ff ff ff 00 00 00 ff 00 00 00 00 |............| +00000000: 00 00 00 14 73 74 73 7a 00 00 00 00 00 00 05 a9 |....stsz........| +00000000: 00 00 03 3b |....| +00000000: 00 00 00 28 73 74 63 6f 00 00 00 00 40 00 00 01 |....stco........| +00000000: 00 00 00 00 00 00 00 00 |........| +00000000: 00 00 00 00 00 00 00 00 |........| +00000000: 00 00 00 00 00 00 00 00 |........| +EOF + +# stco/co64 entries check off-by-one + +my $bad_entries_offbyone = <<'EOF'; +00000000: 00 00 00 1c 66 74 79 70 69 73 6f 6d 00 00 02 00 |....ftypisom....| +00000000: 69 73 6f 6d 69 73 6f 32 6d 70 34 31 |isomiso2mp41| +00000000: 00 00 00 08 6d 64 61 74 |....mdat| +00000000: 00 00 01 80 6d 6f 6f 76 |....moov| +00000000: 00 00 00 bc 74 72 61 6b |....trak| +00000000: 00 00 00 b4 6d 64 69 61 |....mdia| +00000000: 00 00 00 20 6d 64 68 64 00 00 00 00 |....mdhd....| +00000000: 00 00 00 00 00 00 00 00 00 00 03 e8 ff ff ff ff |................| +00000000: 00 00 00 00 |....| +00000000: 00 00 00 8c 6d 69 6e 66 |....minf| +00000000: 00 00 00 84 73 74 62 6c |....stbl| +00000000: 00 00 00 18 73 74 74 73 00 00 00 00 00 00 00 01 |....stts........| +00000000: ff ff ff ff 00 00 00 01 |........| +00000000: 00 00 00 28 73 74 73 63 00 00 00 00 00 00 00 02 |....stsc........| +00000000: 00 00 00 01 00 00 00 01 00 00 00 00 |............| +00000000: 00 ff ff ff 00 00 00 ff 00 00 00 00 |............| +00000000: 00 00 00 14 73 74 73 7a 00 00 00 00 00 00 05 a9 |....stsz........| +00000000: 00 00 03 3b |....| +00000000: 00 00 00 28 73 74 63 6f 00 00 00 00 00 00 00 06 |....stco........| +00000000: 00 00 00 00 00 00 00 00 |........| +00000000: 00 00 00 00 00 00 00 00 |........| +00000000: 00 00 00 00 00 00 00 00 |........| +00000000: 00 00 00 bc 74 72 61 6b |....trak| +00000000: 00 00 00 b4 6d 64 69 61 |....mdia| +00000000: 00 00 00 20 6d 64 68 64 00 00 00 00 |....mdhd....| +00000000: 00 00 00 00 00 00 00 00 00 00 03 e8 ff ff ff ff |................| +00000000: 00 00 00 00 |....| +00000000: 00 00 00 8c 6d 69 6e 66 |....minf| +00000000: 00 00 00 84 73 74 62 6c |....stbl| +00000000: 00 00 00 18 73 74 74 73 00 00 00 00 00 00 00 01 |....stts........| +00000000: ff ff ff ff 00 00 00 01 |........| +00000000: 00 00 00 28 73 74 73 63 00 00 00 00 00 00 00 02 |....stsc........| +00000000: 00 00 00 01 00 00 00 01 00 00 00 00 |............| +00000000: 00 ff ff ff 00 00 00 ff 00 00 00 00 |............| +00000000: 00 00 00 14 73 74 73 7a 00 00 00 00 00 00 05 a9 |....stsz........| +00000000: 00 00 03 3b |....| +00000000: 00 00 00 28 73 74 63 6f 00 00 00 00 00 00 00 01 |....stco........| +00000000: 00 00 00 00 00 00 00 00 |........| +00000000: 00 00 00 00 00 00 00 00 |........| +00000000: 00 00 00 00 00 00 00 00 |........| +EOF + $t->write_file('bad_co64.mp4', unhex($bad_co64)); +$t->write_file('bad_key_frame.mp4', unhex($bad_key_frame)); +$t->write_file('bad_entries_overflow.mp4', unhex($bad_entries_overflow)); +$t->write_file('bad_entries_offbyone.mp4', unhex($bad_entries_offbyone)); $t->run(); @@ -79,6 +180,21 @@ EOF like(http_get("/bad_co64.mp4?start=0.5"), qr/500 Internal/, 'co64 chunk after eof'); +TODO: { +local $TODO = 'not yet', $t->todo_alerts() + unless $t->has_version('1.29.6'); + +like(http_get("/bad_key_frame.mp4?start=0.5"), qr/200 OK/, + 'stss with zero entry'); + +like(http_get("/bad_entries_overflow.mp4?start=0.005"), qr/500 Internal/, + 'stco entries 32bit overflow'); + +like(http_get("/bad_entries_offbyone.mp4?start=0.001"), qr/500 Internal/, + 'stco entries off-by-one'); + +} + ############################################################################### sub unhex { From mdounin at mdounin.ru Tue Mar 10 03:54:50 2026 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 10 Mar 2026 06:54:50 +0300 Subject: freenginx-1.29.6 changes draft Message-ID: Hello! Changes with freenginx 1.29.6 10 Mar 2026 *) Bugfix: incorrect "upstream server temporarily disabled" messages might be logged when using variables in the "proxy_pass" directive. *) Bugfix: retrying a request to the next gRPC upstream server might not work correctly. Thanks to David Carlier. *) Bugfix: a segmentation fault might occur in a worker process if the ngx_http_xslt_filter_module was used. *) Bugfix: a segmentation fault might occur in a worker process if the ngx_http_mp4_module was used. *) Bugfix: in the ngx_http_uwsgi_module and ngx_http_scgi_module modules. *) Bugfix: in memory allocation error handling. ????????? ? freenginx 1.29.6 10.03.2026 *) ???????????: ??? ????????????? ?????????? ? proxy_pass ? ??? ????? ???????? ???????????? ????????? "upstream server temporarily disabled". *) ???????????: ????????? ???????? ??????? ?? ?????? gRPC-?????? ????? ???????? ???????????. ??????? David Carlier. *) ???????????: ? ??????? ???????? ??? ????????? segmentation fault, ???? ????????????? ?????? ngx_http_xslt_filter_module. *) ???????????: ? ??????? ???????? ??? ????????? segmentation fault, ???? ????????????? ?????? ngx_http_mp4_module. *) ???????????: ? ??????? ngx_http_uwsgi_module ? ngx_http_scgi_module. *) ???????????: ? ????????? ?????? ????????? ??????. -- Maxim Dounin http://mdounin.ru/ From osa at freebsd.org.ru Tue Mar 10 14:22:01 2026 From: osa at freebsd.org.ru (Sergey A. Osokin) Date: Tue, 10 Mar 2026 17:22:01 +0300 Subject: freenginx-1.29.6 changes draft In-Reply-To: References: Message-ID: Hi Maxim, On Tue, Mar 10, 2026 at 06:54:50AM +0300, Maxim Dounin wrote: > > Changes with freenginx 1.29.6 10 Mar 2026 > > *) Bugfix: incorrect "upstream server temporarily disabled" messages > might be logged when using variables in the "proxy_pass" directive. > > *) Bugfix: retrying a request to the next gRPC upstream server might not > work correctly. > Thanks to David Carlier. > > *) Bugfix: a segmentation fault might occur in a worker process if the > ngx_http_xslt_filter_module was used. > > *) Bugfix: a segmentation fault might occur in a worker process if the > ngx_http_mp4_module was used. > > *) Bugfix: in the ngx_http_uwsgi_module and ngx_http_scgi_module > modules. > > *) Bugfix: in memory allocation error handling. > > > ????????? ? freenginx 1.29.6 10.03.2026 > > *) ???????????: ??? ????????????? ?????????? ? proxy_pass ? ??? ????? > ???????? ???????????? ????????? "upstream server temporarily > disabled". > > *) ???????????: ????????? ???????? ??????? ?? ?????? gRPC-?????? ????? > ???????? ???????????. > ??????? David Carlier. > > *) ???????????: ? ??????? ???????? ??? ????????? segmentation fault, > ???? ????????????? ?????? ngx_http_xslt_filter_module. > > *) ???????????: ? ??????? ???????? ??? ????????? segmentation fault, > ???? ????????????? ?????? ngx_http_mp4_module. > > *) ???????????: ? ??????? ngx_http_uwsgi_module ? ngx_http_scgi_module. > > *) ???????????: ? ????????? ?????? ????????? ??????. Looks good to me, thank you! -- Sergey A. Osokin https://tipi.work/ From mdounin at mdounin.ru Tue Mar 10 14:49:10 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Tue, 10 Mar 2026 17:49:10 +0300 Subject: [nginx] Updated zlib used for win32 builds. Message-ID: details: http://freenginx.org/hg/nginx/rev/4f89350ba20b branches: changeset: 9478:4f89350ba20b user: Maxim Dounin date: Tue Mar 10 07:06:10 2026 +0300 description: Updated zlib used for win32 builds. diffstat: misc/GNUmakefile | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diffs (12 lines): diff --git a/misc/GNUmakefile b/misc/GNUmakefile --- a/misc/GNUmakefile +++ b/misc/GNUmakefile @@ -7,7 +7,7 @@ TEMP = tmp CC = cl OBJS = objs.msvc8 OPENSSL = openssl-3.0.19 -ZLIB = zlib-1.3.1 +ZLIB = zlib-1.3.2 PCRE = pcre2-10.47 From mdounin at mdounin.ru Tue Mar 10 14:49:10 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Tue, 10 Mar 2026 17:49:10 +0300 Subject: [nginx] freenginx-1.29.6-RELEASE Message-ID: details: http://freenginx.org/hg/nginx/rev/e4207f631186 branches: changeset: 9479:e4207f631186 user: Maxim Dounin date: Tue Mar 10 17:42:16 2026 +0300 description: freenginx-1.29.6-RELEASE diffstat: docs/xml/nginx/changes.xml | 71 ++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 71 insertions(+), 0 deletions(-) diffs (81 lines): diff --git a/docs/xml/nginx/changes.xml b/docs/xml/nginx/changes.xml --- a/docs/xml/nginx/changes.xml +++ b/docs/xml/nginx/changes.xml @@ -7,6 +7,77 @@
+ + + + +??? ????????????? ?????????? ? proxy_pass +? ??? ????? ???????? ???????????? ????????? +"upstream server temporarily disabled". + + +incorrect "upstream server temporarily disabled" +messages might be logged +when using variables in the "proxy_pass" directive. + + + + + +????????? ???????? ??????? ?? ?????? gRPC-?????? +????? ???????? ???????????.
+??????? David Carlier. +
+ +retrying a request to the next gRPC upstream server +might not work correctly.
+Thanks to David Carlier. +
+
+ + + +? ??????? ???????? ??? ????????? segmentation fault, +???? ????????????? ?????? ngx_http_xslt_filter_module. + + +a segmentation fault might occur in a worker process +if the ngx_http_xslt_filter_module was used. + + + + + +? ??????? ???????? ??? ????????? segmentation fault, +???? ????????????? ?????? ngx_http_mp4_module. + + +a segmentation fault might occur in a worker process +if the ngx_http_mp4_module was used. + + + + + +? ??????? ngx_http_uwsgi_module ? ngx_http_scgi_module. + + +in the ngx_http_uwsgi_module and ngx_http_scgi_module modules. + + + + + +? ????????? ?????? ????????? ??????. + + +in memory allocation error handling. + + + +
+ + From mdounin at mdounin.ru Tue Mar 10 14:49:11 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Tue, 10 Mar 2026 17:49:11 +0300 Subject: [nginx] release-1.29.6 tag Message-ID: details: http://freenginx.org/hg/nginx/rev/66b28e8ff0cb branches: changeset: 9480:66b28e8ff0cb user: Maxim Dounin date: Tue Mar 10 17:42:17 2026 +0300 description: release-1.29.6 tag diffstat: .hgtags | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diffs (8 lines): diff --git a/.hgtags b/.hgtags --- a/.hgtags +++ b/.hgtags @@ -491,3 +491,4 @@ 56d817adaa1dd4f522114e921b1ff599c2ab4db2 2175d3bea2a84f7265b90c6f9efea7fb9b41bdd7 release-1.29.3 567870bfeb23f2e9c91e2a110d6d332c27c1ceb1 release-1.29.4 4f4280557d20bc46ebbdc240ffd365f5ca6ce939 release-1.29.5 +e4207f631186855d37ac286799c8cd4c9477d166 release-1.29.6 From mdounin at mdounin.ru Tue Mar 10 14:49:39 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Tue, 10 Mar 2026 17:49:39 +0300 Subject: [nginx-site] freenginx-1.29.6 Message-ID: details: http://freenginx.org/hg/nginx-site/rev/4978a58d1538 branches: changeset: 3129:4978a58d1538 user: Maxim Dounin date: Tue Mar 10 17:47:02 2026 +0300 description: freenginx-1.29.6 diffstat: text/en/CHANGES | 21 +++++++++++++++++++++ text/ru/CHANGES.ru | 21 +++++++++++++++++++++ xml/index.xml | 7 +++++++ xml/versions.xml | 1 + 4 files changed, 50 insertions(+), 0 deletions(-) diffs (86 lines): diff --git a/text/en/CHANGES b/text/en/CHANGES --- a/text/en/CHANGES +++ b/text/en/CHANGES @@ -1,4 +1,25 @@ +Changes with freenginx 1.29.6 10 Mar 2026 + + *) Bugfix: incorrect "upstream server temporarily disabled" messages + might be logged when using variables in the "proxy_pass" directive. + + *) Bugfix: retrying a request to the next gRPC upstream server might not + work correctly. + Thanks to David Carlier. + + *) Bugfix: a segmentation fault might occur in a worker process if the + ngx_http_xslt_filter_module was used. + + *) Bugfix: a segmentation fault might occur in a worker process if the + ngx_http_mp4_module was used. + + *) Bugfix: in the ngx_http_uwsgi_module and ngx_http_scgi_module + modules. + + *) Bugfix: in memory allocation error handling. + + Changes with freenginx 1.29.5 10 Feb 2026 *) Feature: optimized SSL_sendfile() usage on FreeBSD. diff --git a/text/ru/CHANGES.ru b/text/ru/CHANGES.ru --- a/text/ru/CHANGES.ru +++ b/text/ru/CHANGES.ru @@ -1,4 +1,25 @@ +????????? ? freenginx 1.29.6 10.03.2026 + + *) ???????????: ??? ????????????? ?????????? ? proxy_pass ? ??? ????? + ???????? ???????????? ????????? "upstream server temporarily + disabled". + + *) ???????????: ????????? ???????? ??????? ?? ?????? gRPC-?????? ????? + ???????? ???????????. + ??????? David Carlier. + + *) ???????????: ? ??????? ???????? ??? ????????? segmentation fault, + ???? ????????????? ?????? ngx_http_xslt_filter_module. + + *) ???????????: ? ??????? ???????? ??? ????????? segmentation fault, + ???? ????????????? ?????? ngx_http_mp4_module. + + *) ???????????: ? ??????? ngx_http_uwsgi_module ? ngx_http_scgi_module. + + *) ???????????: ? ????????? ?????? ????????? ??????. + + ????????? ? freenginx 1.29.5 10.02.2026 *) ??????????: ??????????? ????????????? SSL_sendfile() ?? FreeBSD. diff --git a/xml/index.xml b/xml/index.xml --- a/xml/index.xml +++ b/xml/index.xml @@ -8,6 +8,13 @@ + + +freenginx-1.29.6 +mainline version has been released. + + + freenginx-1.29.5 diff --git a/xml/versions.xml b/xml/versions.xml --- a/xml/versions.xml +++ b/xml/versions.xml @@ -9,6 +9,7 @@ + From mdounin at mdounin.ru Tue Mar 10 14:56:56 2026 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 10 Mar 2026 17:56:56 +0300 Subject: freenginx-1.29.6 changes draft In-Reply-To: References: Message-ID: Hello! On Tue, Mar 10, 2026 at 05:22:01PM +0300, Sergey A. Osokin wrote: > On Tue, Mar 10, 2026 at 06:54:50AM +0300, Maxim Dounin wrote: > > > > Changes with freenginx 1.29.6 10 Mar 2026 > > > > *) Bugfix: incorrect "upstream server temporarily disabled" messages > > might be logged when using variables in the "proxy_pass" directive. > > > > *) Bugfix: retrying a request to the next gRPC upstream server might not > > work correctly. > > Thanks to David Carlier. > > > > *) Bugfix: a segmentation fault might occur in a worker process if the > > ngx_http_xslt_filter_module was used. > > > > *) Bugfix: a segmentation fault might occur in a worker process if the > > ngx_http_mp4_module was used. > > > > *) Bugfix: in the ngx_http_uwsgi_module and ngx_http_scgi_module > > modules. > > > > *) Bugfix: in memory allocation error handling. > > > > > > ????????? ? freenginx 1.29.6 10.03.2026 > > > > *) ???????????: ??? ????????????? ?????????? ? proxy_pass ? ??? ????? > > ???????? ???????????? ????????? "upstream server temporarily > > disabled". > > > > *) ???????????: ????????? ???????? ??????? ?? ?????? gRPC-?????? ????? > > ???????? ???????????. > > ??????? David Carlier. > > > > *) ???????????: ? ??????? ???????? ??? ????????? segmentation fault, > > ???? ????????????? ?????? ngx_http_xslt_filter_module. > > > > *) ???????????: ? ??????? ???????? ??? ????????? segmentation fault, > > ???? ????????????? ?????? ngx_http_mp4_module. > > > > *) ???????????: ? ??????? ngx_http_uwsgi_module ? ngx_http_scgi_module. > > > > *) ???????????: ? ????????? ?????? ????????? ??????. > > Looks good to me, thank you! Thanks for looking, released. -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Sun Mar 15 12:08:24 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Sun, 15 Mar 2026 15:08:24 +0300 Subject: [PATCH 1 of 5] SSL: compatibility with X509_get_subject_name() in OpenSSL 4.0 Message-ID: # HG changeset patch # User Maxim Dounin # Date 1773534572 -10800 # Sun Mar 15 03:29:32 2026 +0300 # Node ID f4b3140bee29158dbd3fe9c82f748d17bb25546a # Parent 66b28e8ff0cb5ace2d9ecbedb6f49946c8f77f07 SSL: compatibility with X509_get_subject_name() in OpenSSL 4.0. In OpenSSL 4.0 alpha 1, X509_get_subject_name() and X509_get_issuer_name() return "const X509_NAME *" results. To avoid warnings the "const" qualifier added to corresponding variables. Note that in some cases it is safe to add qualifier unconditionally, since all functions being used accept const arguments (in all supported OpenSSL versions). In particular, in ngx_ssl_ocsp_create_key() the name is only used in X509_NAME_digest(), which accepts a const argument since at least OpenSSL 0.9.8, and therefore it is safe to use "const" unconditionally. In other cases conditional compilation is required, since at least some functions being used require non-const arguments. In particular, X509_NAME_oneline() and X509_NAME_print_ex() accept const only starting with OpenSSL 1.1.0. diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -1278,6 +1278,9 @@ ngx_ssl_verify_callback(int ok, X509_STO char *subject, *issuer; int err, depth; X509 *cert; +#if OPENSSL_VERSION_NUMBER >= 0x40000000L + const +#endif X509_NAME *sname, *iname; ngx_connection_t *c; ngx_ssl_conn_t *ssl_conn; @@ -6328,6 +6331,9 @@ ngx_ssl_get_subject_dn(ngx_connection_t { BIO *bio; X509 *cert; +#if OPENSSL_VERSION_NUMBER >= 0x40000000L + const +#endif X509_NAME *name; s->len = 0; @@ -6382,6 +6388,9 @@ ngx_ssl_get_issuer_dn(ngx_connection_t * { BIO *bio; X509 *cert; +#if OPENSSL_VERSION_NUMBER >= 0x40000000L + const +#endif X509_NAME *name; s->len = 0; @@ -6438,6 +6447,9 @@ ngx_ssl_get_subject_dn_legacy(ngx_connec char *p; size_t len; X509 *cert; +#if OPENSSL_VERSION_NUMBER >= 0x40000000L + const +#endif X509_NAME *name; s->len = 0; @@ -6486,6 +6498,9 @@ ngx_ssl_get_issuer_dn_legacy(ngx_connect char *p; size_t len; X509 *cert; +#if OPENSSL_VERSION_NUMBER >= 0x40000000L + const +#endif X509_NAME *name; s->len = 0; diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c --- a/src/event/ngx_event_openssl_stapling.c +++ b/src/event/ngx_event_openssl_stapling.c @@ -2629,9 +2629,9 @@ ngx_ssl_ocsp_cache_store(ngx_ssl_ocsp_ct static ngx_int_t ngx_ssl_ocsp_create_key(ngx_ssl_ocsp_ctx_t *ctx) { - u_char *p; - X509_NAME *name; - ASN1_INTEGER *serial; + u_char *p; + ASN1_INTEGER *serial; + const X509_NAME *name; p = ngx_pnalloc(ctx->pool, 60); if (p == NULL) { From mdounin at mdounin.ru Sun Mar 15 12:08:25 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Sun, 15 Mar 2026 15:08:25 +0300 Subject: [PATCH 2 of 5] SSL: compatibility with opaque ASN1_NUMBER in OpenSSL 4.0 In-Reply-To: References: Message-ID: <0e0da695221f4b79ce86.1773576505@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1773535427 -10800 # Sun Mar 15 03:43:47 2026 +0300 # Node ID 0e0da695221f4b79ce86b415d2ca979a96c24aa4 # Parent f4b3140bee29158dbd3fe9c82f748d17bb25546a SSL: compatibility with opaque ASN1_NUMBER in OpenSSL 4.0. Previously, ASN1_NUMBER was used directly in ngx_ssl_ocsp_create_key(), which is no longer possible with OpenSSL 4.0 alpha 1. The fix is to convert it to BIGNUM with the ASN1_INTEGER_to_BN() function, and then copy to the buffer with BN_bn2bin(). This introduces an otherwise unneeded memory allocation for BIGNUM, but seems to be the only valid way to access the data. Alternative approach would be to use ASN1_STRING_length() and ASN1_STRING_get0_data(), but ASN1_STRING_length(3) explicitly says that ASN1_STRING functions should not be used for ASN1_INTEGER. diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c --- a/src/event/ngx_event_openssl_stapling.c +++ b/src/event/ngx_event_openssl_stapling.c @@ -2629,7 +2629,9 @@ ngx_ssl_ocsp_cache_store(ngx_ssl_ocsp_ct static ngx_int_t ngx_ssl_ocsp_create_key(ngx_ssl_ocsp_ctx_t *ctx) { + int n; u_char *p; + BIGNUM *bn; ASN1_INTEGER *serial; const X509_NAME *name; @@ -2655,12 +2657,23 @@ ngx_ssl_ocsp_create_key(ngx_ssl_ocsp_ctx p += 20; serial = X509_get_serialNumber(ctx->cert); - if (serial->length > 20) { - return NGX_ERROR; + + bn = ASN1_INTEGER_to_BN(serial, NULL); + if (bn == NULL) { + return NGX_ERROR; } - p = ngx_cpymem(p, serial->data, serial->length); - ngx_memzero(p, 20 - serial->length); + if (BN_num_bytes(bn) > 20) { + BN_free(bn); + return NGX_ERROR; + } + + n = BN_bn2bin(bn, p); + p += n; + + ngx_memzero(p, 20 - n); + + BN_free(bn); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0, "ssl ocsp key %xV", &ctx->key); From mdounin at mdounin.ru Sun Mar 15 12:08:26 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Sun, 15 Mar 2026 15:08:26 +0300 Subject: [PATCH 3 of 5] SSL: adjusted debug logging of SSL_get_error() In-Reply-To: References: Message-ID: <0909ff44dc383f6869ad.1773576506@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1773535431 -10800 # Sun Mar 15 03:43:51 2026 +0300 # Node ID 0909ff44dc383f6869ad127c0c71eaf394af94fa # Parent 0e0da695221f4b79ce86b415d2ca979a96c24aa4 SSL: adjusted debug logging of SSL_get_error(). Previously, debug logging of SSL_get_error() result in ngx_ssl_write() and ngx_ssl_sendfile() was done after error code modifications to fix obvious bugs: incorrect SSL_ERROR_ZERO_RETURN, which is not at all defined for write operations, and missing SSL_ERROR_SYSCALL in SSL_sendfile(). This approach, however, somewhat complicates testing of new OpenSSL versions, where error codes might be returned differently. As such, now logging is done immediately after SSL_get_error(), before any modifications. diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -3377,6 +3377,8 @@ ngx_ssl_write(ngx_connection_t *c, u_cha sslerr = SSL_get_error(c->ssl->connection, n); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); + if (sslerr == SSL_ERROR_ZERO_RETURN) { /* @@ -3389,8 +3391,6 @@ ngx_ssl_write(ngx_connection_t *c, u_cha sslerr = SSL_ERROR_SYSCALL; } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); - if (sslerr == SSL_ERROR_WANT_WRITE) { if (c->ssl->saved_read_handler) { @@ -3655,6 +3655,8 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng sslerr = SSL_get_error(c->ssl->connection, n); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); + if (sslerr == SSL_ERROR_ZERO_RETURN) { /* @@ -3679,8 +3681,6 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng sslerr = SSL_ERROR_SYSCALL; } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); - if (sslerr == SSL_ERROR_WANT_WRITE) { if (c->ssl->saved_read_handler) { From mdounin at mdounin.ru Sun Mar 15 12:08:27 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Sun, 15 Mar 2026 15:08:27 +0300 Subject: [PATCH 4 of 5] SSL: clarified clean shutdown condition In-Reply-To: References: Message-ID: # HG changeset patch # User Maxim Dounin # Date 1773535443 -10800 # Sun Mar 15 03:44:03 2026 +0300 # Node ID e13d7e2a2a80a32bffd14e444edc05039b35c066 # Parent 0909ff44dc383f6869ad127c0c71eaf394af94fa SSL: clarified clean shutdown condition. OpenSSL up to 3.0, when a TCP connection is closed by the peer, returns SSL_ERROR_SYSCALL without an error queue, and with errno set to 0. Starting with OpenSSL 3.0 and with SSL_OP_IGNORE_UNEXPECTED_EOF, the SSL_ERROR_ZERO_RETURN is reported. Closing the connection without close_notify alert is incorrect, yet quite common in the real world, and therefore this is handled as a non-error condition. Potential truncation attacks are expected to be handled at the protocol level (notably, truncation attacks are not at all possible for HTTP/1.x requests, and only possible for HTTP/1.x responses if the server uses neither Content-Length nor chunked transfer encoding). Still, previously "ERR_peek_error() == 0" was checked to catch this, which seems too broad. This condition also catches TCP-level errors, such as ECONNRESET, which are better to be explicitly reported as errors. With this change, only clean TCP close is reported as clean connection close. Most notably, connection resets now result in "SSL_read() failed" errors. diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -2430,6 +2430,17 @@ ngx_ssl_handshake(ngx_connection_t *c) return NGX_AGAIN; } + if (sslerr == SSL_ERROR_SYSCALL && ERR_peek_error() == 0 && err == 0) { + + /* + * OpenSSL up to 3.0 returns SSL_ERROR_SYSCALL + * without an error queue and with errno set to 0 + * if connection is closed cleanly + */ + + sslerr = SSL_ERROR_ZERO_RETURN; + } + if (sslerr != SSL_ERROR_SYSCALL) { err = 0; } @@ -2438,7 +2449,7 @@ ngx_ssl_handshake(ngx_connection_t *c) c->ssl->no_send_shutdown = 1; c->read->eof = 1; - if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) { + if (sslerr == SSL_ERROR_ZERO_RETURN) { ngx_connection_error(c, err, "peer closed connection in SSL handshake"); @@ -2581,6 +2592,17 @@ ngx_ssl_try_early_data(ngx_connection_t return NGX_AGAIN; } + if (sslerr == SSL_ERROR_SYSCALL && ERR_peek_error() == 0 && err == 0) { + + /* + * OpenSSL up to 3.0 returns SSL_ERROR_SYSCALL + * without an error queue and with errno set to 0 + * if connection is closed cleanly + */ + + sslerr = SSL_ERROR_ZERO_RETURN; + } + if (sslerr != SSL_ERROR_SYSCALL) { err = 0; } @@ -2589,7 +2611,7 @@ ngx_ssl_try_early_data(ngx_connection_t c->ssl->no_send_shutdown = 1; c->read->eof = 1; - if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) { + if (sslerr == SSL_ERROR_ZERO_RETURN) { ngx_connection_error(c, err, "peer closed connection in SSL handshake"); @@ -3101,6 +3123,17 @@ ngx_ssl_handle_recv(ngx_connection_t *c, return NGX_AGAIN; } + if (sslerr == SSL_ERROR_SYSCALL && ERR_peek_error() == 0 && err == 0) { + + /* + * OpenSSL up to 3.0 returns SSL_ERROR_SYSCALL + * without an error queue and with errno set to 0 + * if connection is closed cleanly + */ + + sslerr = SSL_ERROR_ZERO_RETURN; + } + if (sslerr != SSL_ERROR_SYSCALL) { err = 0; } @@ -3108,7 +3141,7 @@ ngx_ssl_handle_recv(ngx_connection_t *c, c->ssl->no_wait_shutdown = 1; c->ssl->no_send_shutdown = 1; - if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) { + if (sslerr == SSL_ERROR_ZERO_RETURN) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "peer shutdown SSL cleanly"); return NGX_DONE; @@ -3892,7 +3925,18 @@ ngx_ssl_shutdown(ngx_connection_t *c) return NGX_AGAIN; } - if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) { + if (sslerr == SSL_ERROR_SYSCALL && ERR_peek_error() == 0 && err == 0) { + + /* + * OpenSSL up to 3.0 returns SSL_ERROR_SYSCALL + * without an error queue and with errno set to 0 + * if connection is closed cleanly + */ + + sslerr = SSL_ERROR_ZERO_RETURN; + } + + if (sslerr == SSL_ERROR_ZERO_RETURN) { goto done; } From mdounin at mdounin.ru Sun Mar 15 12:08:28 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Sun, 15 Mar 2026 15:08:28 +0300 Subject: [PATCH 5 of 5] SSL: compatibility with OpenSSL 4.0 error handling In-Reply-To: References: Message-ID: # HG changeset patch # User Maxim Dounin # Date 1773536056 -10800 # Sun Mar 15 03:54:16 2026 +0300 # Node ID fa4e9722724a24462c5162c5ab0a081beb50d787 # Parent e13d7e2a2a80a32bffd14e444edc05039b35c066 SSL: compatibility with OpenSSL 4.0 error handling. As of OpenSSL 4.0 alpha 1, errors during reading are remembered in the SSL connection structure, and further attempts to write to the connections are rejected with SSL_ERROR_SSL error and no additional details. While rejecting such attempts is probably correct, lack of the additional error details makes it hard to figure out what actually happened, and to do appropriate logging. In particular, "[crit] ... SSL_write() failed" errors were observed in the ssl_stapling.t test, where the socket is closed right after sending the request, leading to RST sent with TLSv1.3 in response to the tickets sent after the handshake, and often observed by the server while reading the request (but not yet processed). To make sure such errors are not reported as "[crit] ... SSL_write() failed", we now don't try to call SSL_write() after an error was detected by ngx_ssl_recv(). diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -3378,6 +3378,12 @@ ngx_ssl_write(ngx_connection_t *c, u_cha } #endif + if (c->ssl->last == NGX_ERROR) { + c->write->ready = 0; + c->write->error = 1; + return NGX_ERROR; + } + ngx_ssl_clear_error(c->log); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL to write: %uz", size); @@ -3490,6 +3496,12 @@ ngx_ssl_write_early(ngx_connection_t *c, size_t written; ngx_err_t err; + if (c->ssl->last == NGX_ERROR) { + c->write->ready = 0; + c->write->error = 1; + return NGX_ERROR; + } + ngx_ssl_clear_error(c->log); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL to write: %uz", size); From mdounin at mdounin.ru Sun Mar 15 12:09:33 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Sun, 15 Mar 2026 15:09:33 +0300 Subject: [PATCH] Tests: adjusted ECH tests to require OpenSSL 4.0 Message-ID: # HG changeset patch # User Maxim Dounin # Date 1773576363 -10800 # Sun Mar 15 15:06:03 2026 +0300 # Node ID d7cbb4aa6548e5bcf520ada9053327600829f8dd # Parent cbb91250b488d5932c46703f67edd0635073c0b9 Tests: adjusted ECH tests to require OpenSSL 4.0. ECH support is in the OpenSSL master branch now, and available for testing in OpenSSL 4.0 alpha 1. diff --git a/ssl_encrypted_hello.t b/ssl_encrypted_hello.t --- a/ssl_encrypted_hello.t +++ b/ssl_encrypted_hello.t @@ -187,7 +187,7 @@ SKIP: { skip 'no openssl client ech', 4 if `openssl s_client -help 2>&1` !~ /-ech_config_list/; -# Tests with OpenSSL s_client from ECH feature branch +# Tests with OpenSSL s_client with ECH support # Note that OpenSSL s_client prints confusing "ECH: BAD NAME: -102" status # when it is not able to verify server certificate. To make sure proper @@ -220,7 +220,7 @@ log_in($out); TODO: { local $TODO = 'OpenSSL too old' if $t->has_module('OpenSSL') && !$t->has_module('BoringSSL') - && !$t->has_feature('openssl:3.6.0'); + && !$t->has_feature('openssl:4.0.0'); local $TODO = 'LibreSSL has no support yet' if $t->has_module('LibreSSL'); @@ -251,23 +251,11 @@ like($out, qr/^ECH: NOT CONFIGURED.*secr # Tests with client certificate verification, # mostly to check if the $ssl_encrypted_hello variable is correct, notably # with failed client certificate verification. -# -# Currently fails with OpenSSL ECH feature branch on the server, -# the error is as follows: -# -# ... [crit] ... SSL_do_handshake() failed (SSL: error:0A000100:SSL routines:: -# missing fatal)... -# -# This is expected to be fixed by -# https://github.com/openssl/openssl/pull/28555. TODO: { -local $TODO = 'OpenSSL broken verify' - if $t->has_module('OpenSSL') && !$t->has_module('BoringSSL') - && $t->has_feature('openssl:3.6.0'); local $TODO = 'OpenSSL too old' if $t->has_module('OpenSSL') && !$t->has_module('BoringSSL') - && !$t->has_feature('openssl:3.6.0'); + && !$t->has_feature('openssl:4.0.0'); local $TODO = 'LibreSSL has no support yet' if $t->has_module('LibreSSL'); @@ -335,7 +323,7 @@ log_in($out); TODO: { local $TODO = 'OpenSSL too old' if $t->has_module('OpenSSL') && !$t->has_module('BoringSSL') - && !$t->has_feature('openssl:3.6.0'); + && !$t->has_feature('openssl:4.0.0'); local $TODO = 'LibreSSL has no support yet' if $t->has_module('LibreSSL'); @@ -368,12 +356,9 @@ like($out, qr/Encrypted ClientHello: no. # with failed client certificate verification. TODO: { -local $TODO = 'OpenSSL broken verify' - if $t->has_module('OpenSSL') && !$t->has_module('BoringSSL') - && $t->has_feature('openssl:3.6.0'); local $TODO = 'OpenSSL too old' if $t->has_module('OpenSSL') && !$t->has_module('BoringSSL') - && !$t->has_feature('openssl:3.6.0'); + && !$t->has_feature('openssl:4.0.0'); local $TODO = 'LibreSSL has no support yet' if $t->has_module('LibreSSL'); From mdounin at mdounin.ru Wed Mar 18 12:47:56 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 18 Mar 2026 15:47:56 +0300 Subject: [PATCH 1 of 5] OCSP stapling: added missing event handling after writing Message-ID: # HG changeset patch # User Maxim Dounin # Date 1773836275 -10800 # Wed Mar 18 15:17:55 2026 +0300 # Node ID ead30bfe050fc52c6327701e67b59566fb5c9318 # Parent 66b28e8ff0cb5ace2d9ecbedb6f49946c8f77f07 OCSP stapling: added missing event handling after writing. If we need to be notified about further events, ngx_handle_write_event() needs to be called after a write event is processed. Without this, an event can be removed from the kernel and won't be reported again, notably when using oneshot event methods, such as eventport on Solaris. diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c --- a/src/event/ngx_event_openssl_stapling.c +++ b/src/event/ngx_event_openssl_stapling.c @@ -1598,6 +1598,10 @@ ngx_ssl_ocsp_write_handler(ngx_event_t * if (!wev->timer_set && ctx->timeout) { ngx_add_timer(wev, ctx->timeout); } + + if (ngx_handle_write_event(wev, 0) != NGX_OK) { + ngx_ssl_ocsp_error(ctx); + } } From mdounin at mdounin.ru Wed Mar 18 12:47:57 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 18 Mar 2026 15:47:57 +0300 Subject: [PATCH 2 of 5] Mail: added missing event handling in auth http code In-Reply-To: References: Message-ID: # HG changeset patch # User Maxim Dounin # Date 1773836277 -10800 # Wed Mar 18 15:17:57 2026 +0300 # Node ID d3607b79bfd01cbb4a42f54766118876cf410731 # Parent ead30bfe050fc52c6327701e67b59566fb5c9318 Mail: added missing event handling in auth http code. If we need to be notified about further events, ngx_handle_read_event() (or ngx_handle_write_event()) needs to be called after an event is processed. Without this, an event can be removed from the kernel and won't be reported again, notably when using oneshot event methods, such as eventport on Solaris. While here, ngx_mail_auth_http_read_handler() was also modified to properly detect and report premature connection close. To do so, ngx_mail_auth_http_read_handler() is modified to use a loop around ngx_recv(), ctx->handler() now reports if there was an error or we are waiting for additional data, and ngx_handle_read_event() is called when ngx_recv() reports NGX_AGAIN. The code is now similar to ngx_ssl_ocsp_read_handler() (which in turn was originally based on the auth http code). Similarly, ngx_mail_auth_http_write_handler() is modified to call ngx_handle_write_event() when the request is not fully sent to the socket. diff --git a/src/mail/ngx_mail_auth_http_module.c b/src/mail/ngx_mail_auth_http_module.c --- a/src/mail/ngx_mail_auth_http_module.c +++ b/src/mail/ngx_mail_auth_http_module.c @@ -31,7 +31,7 @@ typedef struct { typedef struct ngx_mail_auth_http_ctx_s ngx_mail_auth_http_ctx_t; -typedef void (*ngx_mail_auth_http_handler_pt)(ngx_mail_session_t *s, +typedef ngx_int_t (*ngx_mail_auth_http_handler_pt)(ngx_mail_session_t *s, ngx_mail_auth_http_ctx_t *ctx); struct ngx_mail_auth_http_ctx_s { @@ -65,9 +65,9 @@ struct ngx_mail_auth_http_ctx_s { static void ngx_mail_auth_http_write_handler(ngx_event_t *wev); static void ngx_mail_auth_http_read_handler(ngx_event_t *rev); -static void ngx_mail_auth_http_ignore_status_line(ngx_mail_session_t *s, +static ngx_int_t ngx_mail_auth_http_ignore_status_line(ngx_mail_session_t *s, ngx_mail_auth_http_ctx_t *ctx); -static void ngx_mail_auth_http_process_headers(ngx_mail_session_t *s, +static ngx_int_t ngx_mail_auth_http_process_headers(ngx_mail_session_t *s, ngx_mail_auth_http_ctx_t *ctx); static void ngx_mail_auth_sleep_handler(ngx_event_t *rev); static void ngx_mail_auth_send_error(ngx_mail_session_t *s); @@ -307,13 +307,20 @@ ngx_mail_auth_http_write_handler(ngx_eve ahcf = ngx_mail_get_module_srv_conf(s, ngx_mail_auth_http_module); ngx_add_timer(wev, ahcf->timeout); } + + if (ngx_handle_write_event(wev, 0) != NGX_OK) { + ngx_close_connection(c); + ngx_destroy_pool(ctx->pool); + ngx_mail_session_internal_server_error(s); + } } static void ngx_mail_auth_http_read_handler(ngx_event_t *rev) { - ssize_t n, size; + ssize_t n, size; + ngx_int_t rc; ngx_connection_t *c; ngx_mail_session_t *s; ngx_mail_auth_http_ctx_t *ctx; @@ -345,19 +352,41 @@ ngx_mail_auth_http_read_handler(ngx_even } } - size = ctx->response->end - ctx->response->last; + for ( ;; ) { + + size = ctx->response->end - ctx->response->last; + + n = ngx_recv(c, ctx->response->pos, size); - n = ngx_recv(c, ctx->response->pos, size); + if (n > 0) { + ctx->response->last += n; + + rc = ctx->handler(s, ctx); + + if (rc == NGX_ERROR || rc == NGX_DONE) { + return; + } - if (n > 0) { - ctx->response->last += n; + continue; + } - ctx->handler(s, ctx); - return; + if (n == NGX_AGAIN) { + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_close_connection(c); + ngx_destroy_pool(ctx->pool); + ngx_mail_session_internal_server_error(s); + } + + return; + } + + break; } - if (n == NGX_AGAIN) { - return; + if (n == 0) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "auth http server %V prematurely closed connection", + ctx->peer.name); } ngx_close_connection(c); @@ -366,7 +395,7 @@ ngx_mail_auth_http_read_handler(ngx_even } -static void +static ngx_int_t ngx_mail_auth_http_ignore_status_line(ngx_mail_session_t *s, ngx_mail_auth_http_ctx_t *ctx) { @@ -448,17 +477,19 @@ ngx_mail_auth_http_ignore_status_line(ng ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "auth http server %V sent invalid response", ctx->peer.name); + ngx_close_connection(ctx->peer.connection); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + + return NGX_ERROR; } } ctx->response->pos = p; ctx->state = state; - return; + return NGX_AGAIN; next: @@ -469,11 +500,11 @@ done: ctx->response->pos = p + 1; ctx->state = 0; ctx->handler = ngx_mail_auth_http_process_headers; - ctx->handler(s, ctx); + return ctx->handler(s, ctx); } -static void +static ngx_int_t ngx_mail_auth_http_process_headers(ngx_mail_session_t *s, ngx_mail_auth_http_ctx_t *ctx) { @@ -499,7 +530,7 @@ ngx_mail_auth_http_process_headers(ngx_m ngx_close_connection(ctx->peer.connection); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } h->key.len = ctx->header_name_end - ctx->header_name_start; @@ -562,7 +593,7 @@ ngx_mail_auth_http_process_headers(ngx_m ngx_close_connection(ctx->peer.connection); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } ctx->err.data = p; @@ -627,7 +658,7 @@ ngx_mail_auth_http_process_headers(ngx_m ngx_close_connection(ctx->peer.connection); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } ngx_memcpy(s->login.data, ctx->header_start, s->login.len); @@ -649,7 +680,7 @@ ngx_mail_auth_http_process_headers(ngx_m ngx_close_connection(ctx->peer.connection); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } ngx_memcpy(s->passwd.data, ctx->header_start, s->passwd.len); @@ -687,7 +718,7 @@ ngx_mail_auth_http_process_headers(ngx_m ngx_close_connection(ctx->peer.connection); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } ngx_memcpy(ctx->errcode.data, ctx->header_start, @@ -722,7 +753,7 @@ ngx_mail_auth_http_process_headers(ngx_m ngx_close_connection(ctx->peer.connection); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } ctx->errsasl.len = size; @@ -770,7 +801,7 @@ ngx_mail_auth_http_process_headers(ngx_m if (p == NULL) { ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } ctx->err.data = p; @@ -790,14 +821,14 @@ ngx_mail_auth_http_process_headers(ngx_m if (timer == 0) { s->auth_quit = 1; ngx_mail_auth_send_error(s); - return; + return NGX_DONE; } ngx_add_timer(s->connection->read, (ngx_msec_t) (timer * 1000)); s->connection->read->handler = ngx_mail_auth_sleep_handler; - return; + return NGX_DONE; } if (s->auth_wait) { @@ -807,14 +838,14 @@ ngx_mail_auth_http_process_headers(ngx_m if (timer == 0) { ngx_mail_auth_http_init(s); - return; + return NGX_DONE; } ngx_add_timer(s->connection->read, (ngx_msec_t) (timer * 1000)); s->connection->read->handler = ngx_mail_auth_sleep_handler; - return; + return NGX_DONE; } if (ctx->addr.len == 0 || ctx->port.len == 0) { @@ -823,7 +854,7 @@ ngx_mail_auth_http_process_headers(ngx_m ctx->peer.name); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } if (s->passwd.data == NULL @@ -834,14 +865,14 @@ ngx_mail_auth_http_process_headers(ngx_m ctx->peer.name); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } peer = ngx_pcalloc(s->connection->pool, sizeof(ngx_addr_t)); if (peer == NULL) { ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } rc = ngx_parse_addr(s->connection->pool, peer, @@ -861,7 +892,7 @@ ngx_mail_auth_http_process_headers(ngx_m default: ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } port = ngx_atoi(ctx->port.data, ctx->port.len); @@ -872,7 +903,7 @@ ngx_mail_auth_http_process_headers(ngx_m ctx->peer.name, &ctx->port); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } ngx_inet_set_port(peer->sockaddr, (in_port_t) port); @@ -885,7 +916,7 @@ ngx_mail_auth_http_process_headers(ngx_m if (peer->name.data == NULL) { ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } len = ctx->addr.len; @@ -900,17 +931,17 @@ ngx_mail_auth_http_process_headers(ngx_m if (ngx_mail_limit_conn_handler(s) != NGX_OK) { ngx_destroy_pool(ctx->pool); - return; + return NGX_ERROR; } ngx_destroy_pool(ctx->pool); ngx_mail_proxy_init(s, peer); - return; + return NGX_DONE; } if (rc == NGX_AGAIN ) { - return; + return NGX_AGAIN; } /* rc == NGX_ERROR */ @@ -918,12 +949,15 @@ ngx_mail_auth_http_process_headers(ngx_m ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "auth http server %V sent invalid header in response", ctx->peer.name); + ngx_close_connection(ctx->peer.connection); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } + + /* not reached */ } From mdounin at mdounin.ru Wed Mar 18 12:47:58 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 18 Mar 2026 15:47:58 +0300 Subject: [PATCH 3 of 5] Mail: fixed missing return in error handling In-Reply-To: References: Message-ID: <363a706d8c137aa1b374.1773838078@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1773836279 -10800 # Wed Mar 18 15:17:59 2026 +0300 # Node ID 363a706d8c137aa1b3745143889ab8e618852e67 # Parent d3607b79bfd01cbb4a42f54766118876cf410731 Mail: fixed missing return in error handling. diff --git a/src/mail/ngx_mail_proxy_module.c b/src/mail/ngx_mail_proxy_module.c --- a/src/mail/ngx_mail_proxy_module.c +++ b/src/mail/ngx_mail_proxy_module.c @@ -882,6 +882,7 @@ ngx_mail_proxy_write_handler(ngx_event_t if (ngx_handle_write_event(wev, 0) != NGX_OK) { ngx_mail_proxy_internal_server_error(s); + return; } if (c->read->ready) { From mdounin at mdounin.ru Wed Mar 18 12:47:59 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 18 Mar 2026 15:47:59 +0300 Subject: [PATCH 4 of 5] Mail: improved error reporting while connecting to upstream In-Reply-To: References: Message-ID: <54cdb3f960974e519bfd.1773838079@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1773836280 -10800 # Wed Mar 18 15:18:00 2026 +0300 # Node ID 54cdb3f960974e519bfd951c8d225676bba95ac1 # Parent 363a706d8c137aa1b3745143889ab8e618852e67 Mail: improved error reporting while connecting to upstream. Previously, when c->send() failed to fully send a command after connecting to the upstream server, no error was logged, yet the connection was closed with an internal error. diff --git a/src/mail/ngx_mail_proxy_module.c b/src/mail/ngx_mail_proxy_module.c --- a/src/mail/ngx_mail_proxy_module.c +++ b/src/mail/ngx_mail_proxy_module.c @@ -227,6 +227,7 @@ static void ngx_mail_proxy_pop3_handler(ngx_event_t *rev) { u_char *p; + ssize_t n; ngx_int_t rc; ngx_str_t line; ngx_connection_t *c; @@ -344,11 +345,20 @@ ngx_mail_proxy_pop3_handler(ngx_event_t break; } - if (c->send(c, line.data, line.len) < (ssize_t) line.len) { + n = c->send(c, line.data, line.len); + + if (n == NGX_ERROR) { + ngx_mail_proxy_internal_server_error(s); + return; + } + + if (n != (ssize_t) line.len) { /* * we treat the incomplete sending as NGX_ERROR * because it is very strange here */ + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "sent only %z of %uz", n, line.len); ngx_mail_proxy_internal_server_error(s); return; } @@ -367,6 +377,7 @@ static void ngx_mail_proxy_imap_handler(ngx_event_t *rev) { u_char *p; + ssize_t n; ngx_int_t rc; ngx_str_t line; ngx_connection_t *c; @@ -505,11 +516,20 @@ ngx_mail_proxy_imap_handler(ngx_event_t break; } - if (c->send(c, line.data, line.len) < (ssize_t) line.len) { + n = c->send(c, line.data, line.len); + + if (n == NGX_ERROR) { + ngx_mail_proxy_internal_server_error(s); + return; + } + + if (n != (ssize_t) line.len) { /* * we treat the incomplete sending as NGX_ERROR * because it is very strange here */ + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "sent only %z of %uz", n, line.len); ngx_mail_proxy_internal_server_error(s); return; } @@ -528,6 +548,7 @@ static void ngx_mail_proxy_smtp_handler(ngx_event_t *rev) { u_char *p; + ssize_t n; ngx_int_t rc; ngx_str_t line, auth, encoded; ngx_buf_t *b; @@ -842,11 +863,20 @@ ngx_mail_proxy_smtp_handler(ngx_event_t break; } - if (c->send(c, line.data, line.len) < (ssize_t) line.len) { + n = c->send(c, line.data, line.len); + + if (n == NGX_ERROR) { + ngx_mail_proxy_internal_server_error(s); + return; + } + + if (n != (ssize_t) line.len) { /* * we treat the incomplete sending as NGX_ERROR * because it is very strange here */ + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "sent only %z of %uz", n, line.len); ngx_mail_proxy_internal_server_error(s); return; } From mdounin at mdounin.ru Wed Mar 18 12:48:00 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 18 Mar 2026 15:48:00 +0300 Subject: [PATCH 5 of 5] Mail: fixed occasional mail proxy errors on Windows In-Reply-To: References: Message-ID: <966eeb7c1bea317f2144.1773838080@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1773836283 -10800 # Wed Mar 18 15:18:03 2026 +0300 # Node ID 966eeb7c1bea317f2144ada6411751646ee2787c # Parent 54cdb3f960974e519bfd951c8d225676bba95ac1 Mail: fixed occasional mail proxy errors on Windows. On Windows, in some cases a read event is reported by select() before a write event signals that a connection to a backend server is established. This results in c->write->ready not being set when the read handler is called, and subsequent c->send() returns NGX_AGAIN, as ngx_wsasend() does not do anything unless c->write->ready is set. And the connection establishment code assumes that short initial commands can be sent without blocking, further resulting in an internal error being reported to the client. It is not clear why this happens, though it was observed in practice when running tests on Windows 10 (22H2). The fix is to block read handlers from doing anything unless c->write->ready is set, much like we do when sending the PROXY protocol header. diff --git a/src/mail/ngx_mail_proxy_module.c b/src/mail/ngx_mail_proxy_module.c --- a/src/mail/ngx_mail_proxy_module.c +++ b/src/mail/ngx_mail_proxy_module.c @@ -248,7 +248,7 @@ ngx_mail_proxy_pop3_handler(ngx_event_t return; } - if (s->proxy->proxy_protocol) { + if (s->proxy->proxy_protocol || !c->write->ready) { ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "mail proxy pop3 busy"); if (ngx_handle_read_event(c->read, 0) != NGX_OK) { @@ -398,7 +398,7 @@ ngx_mail_proxy_imap_handler(ngx_event_t return; } - if (s->proxy->proxy_protocol) { + if (s->proxy->proxy_protocol || !c->write->ready) { ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "mail proxy imap busy"); if (ngx_handle_read_event(c->read, 0) != NGX_OK) { @@ -571,7 +571,7 @@ ngx_mail_proxy_smtp_handler(ngx_event_t return; } - if (s->proxy->proxy_protocol) { + if (s->proxy->proxy_protocol || !c->write->ready) { ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "mail proxy smtp busy"); if (ngx_handle_read_event(c->read, 0) != NGX_OK) { From mdounin at mdounin.ru Sun Mar 22 13:37:47 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Sun, 22 Mar 2026 16:37:47 +0300 Subject: [nginx-tests] Tests: adjusted ECH tests to require OpenSSL 4.0. Message-ID: details: http://freenginx.org/hg/nginx-tests/rev/d7cbb4aa6548 branches: changeset: 2044:d7cbb4aa6548 user: Maxim Dounin date: Sun Mar 15 15:06:03 2026 +0300 description: Tests: adjusted ECH tests to require OpenSSL 4.0. ECH support is in the OpenSSL master branch now, and available for testing in OpenSSL 4.0 alpha 1. diffstat: ssl_encrypted_hello.t | 25 +++++-------------------- 1 files changed, 5 insertions(+), 20 deletions(-) diffs (69 lines): diff --git a/ssl_encrypted_hello.t b/ssl_encrypted_hello.t --- a/ssl_encrypted_hello.t +++ b/ssl_encrypted_hello.t @@ -187,7 +187,7 @@ SKIP: { skip 'no openssl client ech', 4 if `openssl s_client -help 2>&1` !~ /-ech_config_list/; -# Tests with OpenSSL s_client from ECH feature branch +# Tests with OpenSSL s_client with ECH support # Note that OpenSSL s_client prints confusing "ECH: BAD NAME: -102" status # when it is not able to verify server certificate. To make sure proper @@ -220,7 +220,7 @@ log_in($out); TODO: { local $TODO = 'OpenSSL too old' if $t->has_module('OpenSSL') && !$t->has_module('BoringSSL') - && !$t->has_feature('openssl:3.6.0'); + && !$t->has_feature('openssl:4.0.0'); local $TODO = 'LibreSSL has no support yet' if $t->has_module('LibreSSL'); @@ -251,23 +251,11 @@ like($out, qr/^ECH: NOT CONFIGURED.*secr # Tests with client certificate verification, # mostly to check if the $ssl_encrypted_hello variable is correct, notably # with failed client certificate verification. -# -# Currently fails with OpenSSL ECH feature branch on the server, -# the error is as follows: -# -# ... [crit] ... SSL_do_handshake() failed (SSL: error:0A000100:SSL routines:: -# missing fatal)... -# -# This is expected to be fixed by -# https://github.com/openssl/openssl/pull/28555. TODO: { -local $TODO = 'OpenSSL broken verify' - if $t->has_module('OpenSSL') && !$t->has_module('BoringSSL') - && $t->has_feature('openssl:3.6.0'); local $TODO = 'OpenSSL too old' if $t->has_module('OpenSSL') && !$t->has_module('BoringSSL') - && !$t->has_feature('openssl:3.6.0'); + && !$t->has_feature('openssl:4.0.0'); local $TODO = 'LibreSSL has no support yet' if $t->has_module('LibreSSL'); @@ -335,7 +323,7 @@ log_in($out); TODO: { local $TODO = 'OpenSSL too old' if $t->has_module('OpenSSL') && !$t->has_module('BoringSSL') - && !$t->has_feature('openssl:3.6.0'); + && !$t->has_feature('openssl:4.0.0'); local $TODO = 'LibreSSL has no support yet' if $t->has_module('LibreSSL'); @@ -368,12 +356,9 @@ like($out, qr/Encrypted ClientHello: no. # with failed client certificate verification. TODO: { -local $TODO = 'OpenSSL broken verify' - if $t->has_module('OpenSSL') && !$t->has_module('BoringSSL') - && $t->has_feature('openssl:3.6.0'); local $TODO = 'OpenSSL too old' if $t->has_module('OpenSSL') && !$t->has_module('BoringSSL') - && !$t->has_feature('openssl:3.6.0'); + && !$t->has_feature('openssl:4.0.0'); local $TODO = 'LibreSSL has no support yet' if $t->has_module('LibreSSL'); From mdounin at mdounin.ru Sun Mar 22 13:38:10 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Sun, 22 Mar 2026 16:38:10 +0300 Subject: [nginx] Version bump. Message-ID: details: http://freenginx.org/hg/nginx/rev/a49c6a923dc3 branches: changeset: 9481:a49c6a923dc3 user: Maxim Dounin date: Sun Mar 22 16:26:25 2026 +0300 description: Version bump. diffstat: src/core/nginx.h | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diffs (14 lines): diff --git a/src/core/nginx.h b/src/core/nginx.h --- a/src/core/nginx.h +++ b/src/core/nginx.h @@ -9,8 +9,8 @@ #define _NGINX_H_INCLUDED_ -#define nginx_version 1029006 -#define NGINX_VERSION "1.29.6" +#define nginx_version 1029007 +#define NGINX_VERSION "1.29.7" #define freenginx 1 From mdounin at mdounin.ru Sun Mar 22 13:38:10 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Sun, 22 Mar 2026 16:38:10 +0300 Subject: [nginx] SSL: compatibility with X509_get_subject_name() in OpenS... Message-ID: details: http://freenginx.org/hg/nginx/rev/91f88a8688ed branches: changeset: 9482:91f88a8688ed user: Maxim Dounin date: Sun Mar 22 16:26:31 2026 +0300 description: SSL: compatibility with X509_get_subject_name() in OpenSSL 4.0. In OpenSSL 4.0 alpha 1, X509_get_subject_name() and X509_get_issuer_name() return "const X509_NAME *" results. To avoid warnings the "const" qualifier added to corresponding variables. Note that in some cases it is safe to add qualifier unconditionally, since all functions being used accept const arguments (in all supported OpenSSL versions). In particular, in ngx_ssl_ocsp_create_key() the name is only used in X509_NAME_digest(), which accepts a const argument since at least OpenSSL 0.9.8, and therefore it is safe to use "const" unconditionally. In other cases conditional compilation is required, since at least some functions being used require non-const arguments. In particular, X509_NAME_oneline() and X509_NAME_print_ex() accept const only starting with OpenSSL 1.1.0. diffstat: src/event/ngx_event_openssl.c | 15 +++++++++++++++ src/event/ngx_event_openssl_stapling.c | 6 +++--- 2 files changed, 18 insertions(+), 3 deletions(-) diffs (69 lines): diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -1278,6 +1278,9 @@ ngx_ssl_verify_callback(int ok, X509_STO char *subject, *issuer; int err, depth; X509 *cert; +#if OPENSSL_VERSION_NUMBER >= 0x40000000L + const +#endif X509_NAME *sname, *iname; ngx_connection_t *c; ngx_ssl_conn_t *ssl_conn; @@ -6328,6 +6331,9 @@ ngx_ssl_get_subject_dn(ngx_connection_t { BIO *bio; X509 *cert; +#if OPENSSL_VERSION_NUMBER >= 0x40000000L + const +#endif X509_NAME *name; s->len = 0; @@ -6382,6 +6388,9 @@ ngx_ssl_get_issuer_dn(ngx_connection_t * { BIO *bio; X509 *cert; +#if OPENSSL_VERSION_NUMBER >= 0x40000000L + const +#endif X509_NAME *name; s->len = 0; @@ -6438,6 +6447,9 @@ ngx_ssl_get_subject_dn_legacy(ngx_connec char *p; size_t len; X509 *cert; +#if OPENSSL_VERSION_NUMBER >= 0x40000000L + const +#endif X509_NAME *name; s->len = 0; @@ -6486,6 +6498,9 @@ ngx_ssl_get_issuer_dn_legacy(ngx_connect char *p; size_t len; X509 *cert; +#if OPENSSL_VERSION_NUMBER >= 0x40000000L + const +#endif X509_NAME *name; s->len = 0; diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c --- a/src/event/ngx_event_openssl_stapling.c +++ b/src/event/ngx_event_openssl_stapling.c @@ -2629,9 +2629,9 @@ ngx_ssl_ocsp_cache_store(ngx_ssl_ocsp_ct static ngx_int_t ngx_ssl_ocsp_create_key(ngx_ssl_ocsp_ctx_t *ctx) { - u_char *p; - X509_NAME *name; - ASN1_INTEGER *serial; + u_char *p; + ASN1_INTEGER *serial; + const X509_NAME *name; p = ngx_pnalloc(ctx->pool, 60); if (p == NULL) { From mdounin at mdounin.ru Sun Mar 22 13:38:10 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Sun, 22 Mar 2026 16:38:10 +0300 Subject: [nginx] SSL: compatibility with opaque ASN1_NUMBER in OpenSSL 4.0. Message-ID: details: http://freenginx.org/hg/nginx/rev/a3b7c937d940 branches: changeset: 9483:a3b7c937d940 user: Maxim Dounin date: Sun Mar 22 16:26:34 2026 +0300 description: SSL: compatibility with opaque ASN1_NUMBER in OpenSSL 4.0. Previously, ASN1_NUMBER was used directly in ngx_ssl_ocsp_create_key(), which is no longer possible with OpenSSL 4.0 alpha 1. The fix is to convert it to BIGNUM with the ASN1_INTEGER_to_BN() function, and then copy to the buffer with BN_bn2bin(). This introduces an otherwise unneeded memory allocation for BIGNUM, but seems to be the only valid way to access the data. Alternative approach would be to use ASN1_STRING_length() and ASN1_STRING_get0_data(), but ASN1_STRING_length(3) explicitly says that ASN1_STRING functions should not be used for ASN1_INTEGER. diffstat: src/event/ngx_event_openssl_stapling.c | 21 +++++++++++++++++---- 1 files changed, 17 insertions(+), 4 deletions(-) diffs (41 lines): diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c --- a/src/event/ngx_event_openssl_stapling.c +++ b/src/event/ngx_event_openssl_stapling.c @@ -2629,7 +2629,9 @@ ngx_ssl_ocsp_cache_store(ngx_ssl_ocsp_ct static ngx_int_t ngx_ssl_ocsp_create_key(ngx_ssl_ocsp_ctx_t *ctx) { + int n; u_char *p; + BIGNUM *bn; ASN1_INTEGER *serial; const X509_NAME *name; @@ -2655,12 +2657,23 @@ ngx_ssl_ocsp_create_key(ngx_ssl_ocsp_ctx p += 20; serial = X509_get_serialNumber(ctx->cert); - if (serial->length > 20) { - return NGX_ERROR; + + bn = ASN1_INTEGER_to_BN(serial, NULL); + if (bn == NULL) { + return NGX_ERROR; } - p = ngx_cpymem(p, serial->data, serial->length); - ngx_memzero(p, 20 - serial->length); + if (BN_num_bytes(bn) > 20) { + BN_free(bn); + return NGX_ERROR; + } + + n = BN_bn2bin(bn, p); + p += n; + + ngx_memzero(p, 20 - n); + + BN_free(bn); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0, "ssl ocsp key %xV", &ctx->key); From mdounin at mdounin.ru Sun Mar 22 13:38:10 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Sun, 22 Mar 2026 16:38:10 +0300 Subject: [nginx] SSL: adjusted debug logging of SSL_get_error(). Message-ID: details: http://freenginx.org/hg/nginx/rev/026cfb103af3 branches: changeset: 9484:026cfb103af3 user: Maxim Dounin date: Sun Mar 22 16:26:47 2026 +0300 description: SSL: adjusted debug logging of SSL_get_error(). Previously, debug logging of SSL_get_error() result in ngx_ssl_write() and ngx_ssl_sendfile() was done after error code modifications to fix obvious bugs: incorrect SSL_ERROR_ZERO_RETURN, which is not at all defined for write operations, and missing SSL_ERROR_SYSCALL in SSL_sendfile(). This approach, however, somewhat complicates testing of new OpenSSL versions, where error codes might be returned differently. As such, now logging is done immediately after SSL_get_error(), before any modifications. diffstat: src/event/ngx_event_openssl.c | 8 ++++---- 1 files changed, 4 insertions(+), 4 deletions(-) diffs (39 lines): diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -3377,6 +3377,8 @@ ngx_ssl_write(ngx_connection_t *c, u_cha sslerr = SSL_get_error(c->ssl->connection, n); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); + if (sslerr == SSL_ERROR_ZERO_RETURN) { /* @@ -3389,8 +3391,6 @@ ngx_ssl_write(ngx_connection_t *c, u_cha sslerr = SSL_ERROR_SYSCALL; } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); - if (sslerr == SSL_ERROR_WANT_WRITE) { if (c->ssl->saved_read_handler) { @@ -3655,6 +3655,8 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng sslerr = SSL_get_error(c->ssl->connection, n); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); + if (sslerr == SSL_ERROR_ZERO_RETURN) { /* @@ -3679,8 +3681,6 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng sslerr = SSL_ERROR_SYSCALL; } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); - if (sslerr == SSL_ERROR_WANT_WRITE) { if (c->ssl->saved_read_handler) { From mdounin at mdounin.ru Sun Mar 22 13:38:10 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Sun, 22 Mar 2026 16:38:10 +0300 Subject: [nginx] SSL: clarified clean shutdown condition. Message-ID: details: http://freenginx.org/hg/nginx/rev/4c0b66b5046f branches: changeset: 9485:4c0b66b5046f user: Maxim Dounin date: Sun Mar 22 16:26:54 2026 +0300 description: SSL: clarified clean shutdown condition. OpenSSL up to 3.0, when a TCP connection is closed by the peer, returns SSL_ERROR_SYSCALL without an error queue, and with errno set to 0. Starting with OpenSSL 3.0 and with SSL_OP_IGNORE_UNEXPECTED_EOF, the SSL_ERROR_ZERO_RETURN is reported. Closing the connection without close_notify alert is incorrect, yet quite common in the real world, and therefore this is handled as a non-error condition. Potential truncation attacks are expected to be handled at the protocol level (notably, truncation attacks are not at all possible for HTTP/1.x requests, and only possible for HTTP/1.x responses if the server uses neither Content-Length nor chunked transfer encoding). Still, previously "ERR_peek_error() == 0" was checked to catch this, which seems too broad. This condition also catches TCP-level errors, such as ECONNRESET, which are better to be explicitly reported as errors. With this change, only clean TCP close is reported as clean connection close. Most notably, connection resets now result in "SSL_read() failed" errors. diffstat: src/event/ngx_event_openssl.c | 52 +++++++++++++++++++++++++++++++++++++++--- 1 files changed, 48 insertions(+), 4 deletions(-) diffs (104 lines): diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -2430,6 +2430,17 @@ ngx_ssl_handshake(ngx_connection_t *c) return NGX_AGAIN; } + if (sslerr == SSL_ERROR_SYSCALL && ERR_peek_error() == 0 && err == 0) { + + /* + * OpenSSL up to 3.0 returns SSL_ERROR_SYSCALL + * without an error queue and with errno set to 0 + * if connection is closed cleanly + */ + + sslerr = SSL_ERROR_ZERO_RETURN; + } + if (sslerr != SSL_ERROR_SYSCALL) { err = 0; } @@ -2438,7 +2449,7 @@ ngx_ssl_handshake(ngx_connection_t *c) c->ssl->no_send_shutdown = 1; c->read->eof = 1; - if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) { + if (sslerr == SSL_ERROR_ZERO_RETURN) { ngx_connection_error(c, err, "peer closed connection in SSL handshake"); @@ -2581,6 +2592,17 @@ ngx_ssl_try_early_data(ngx_connection_t return NGX_AGAIN; } + if (sslerr == SSL_ERROR_SYSCALL && ERR_peek_error() == 0 && err == 0) { + + /* + * OpenSSL up to 3.0 returns SSL_ERROR_SYSCALL + * without an error queue and with errno set to 0 + * if connection is closed cleanly + */ + + sslerr = SSL_ERROR_ZERO_RETURN; + } + if (sslerr != SSL_ERROR_SYSCALL) { err = 0; } @@ -2589,7 +2611,7 @@ ngx_ssl_try_early_data(ngx_connection_t c->ssl->no_send_shutdown = 1; c->read->eof = 1; - if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) { + if (sslerr == SSL_ERROR_ZERO_RETURN) { ngx_connection_error(c, err, "peer closed connection in SSL handshake"); @@ -3101,6 +3123,17 @@ ngx_ssl_handle_recv(ngx_connection_t *c, return NGX_AGAIN; } + if (sslerr == SSL_ERROR_SYSCALL && ERR_peek_error() == 0 && err == 0) { + + /* + * OpenSSL up to 3.0 returns SSL_ERROR_SYSCALL + * without an error queue and with errno set to 0 + * if connection is closed cleanly + */ + + sslerr = SSL_ERROR_ZERO_RETURN; + } + if (sslerr != SSL_ERROR_SYSCALL) { err = 0; } @@ -3108,7 +3141,7 @@ ngx_ssl_handle_recv(ngx_connection_t *c, c->ssl->no_wait_shutdown = 1; c->ssl->no_send_shutdown = 1; - if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) { + if (sslerr == SSL_ERROR_ZERO_RETURN) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "peer shutdown SSL cleanly"); return NGX_DONE; @@ -3892,7 +3925,18 @@ ngx_ssl_shutdown(ngx_connection_t *c) return NGX_AGAIN; } - if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) { + if (sslerr == SSL_ERROR_SYSCALL && ERR_peek_error() == 0 && err == 0) { + + /* + * OpenSSL up to 3.0 returns SSL_ERROR_SYSCALL + * without an error queue and with errno set to 0 + * if connection is closed cleanly + */ + + sslerr = SSL_ERROR_ZERO_RETURN; + } + + if (sslerr == SSL_ERROR_ZERO_RETURN) { goto done; } From mdounin at mdounin.ru Sun Mar 22 13:38:11 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Sun, 22 Mar 2026 16:38:11 +0300 Subject: [nginx] SSL: compatibility with OpenSSL 4.0 error handling. Message-ID: details: http://freenginx.org/hg/nginx/rev/ff5becaf5444 branches: changeset: 9486:ff5becaf5444 user: Maxim Dounin date: Sun Mar 22 16:27:12 2026 +0300 description: SSL: compatibility with OpenSSL 4.0 error handling. As of OpenSSL 4.0 alpha 1, errors during reading are remembered in the SSL connection structure, and further attempts to write to the connections are rejected with SSL_ERROR_SSL error and no additional details. While rejecting such attempts is probably correct, lack of the additional error details makes it hard to figure out what actually happened, and to do appropriate logging. In particular, "[crit] ... SSL_write() failed" errors were observed in the ssl_stapling.t test, where the socket is closed right after sending the request, leading to RST sent with TLSv1.3 in response to the tickets sent after the handshake, and often observed by the server while reading the request (but not yet processed). To make sure such errors are not reported as "[crit] ... SSL_write() failed", we now don't try to call SSL_write() after an error was detected by ngx_ssl_recv(). diffstat: src/event/ngx_event_openssl.c | 12 ++++++++++++ 1 files changed, 12 insertions(+), 0 deletions(-) diffs (29 lines): diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -3378,6 +3378,12 @@ ngx_ssl_write(ngx_connection_t *c, u_cha } #endif + if (c->ssl->last == NGX_ERROR) { + c->write->ready = 0; + c->write->error = 1; + return NGX_ERROR; + } + ngx_ssl_clear_error(c->log); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL to write: %uz", size); @@ -3490,6 +3496,12 @@ ngx_ssl_write_early(ngx_connection_t *c, size_t written; ngx_err_t err; + if (c->ssl->last == NGX_ERROR) { + c->write->ready = 0; + c->write->error = 1; + return NGX_ERROR; + } + ngx_ssl_clear_error(c->log); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL to write: %uz", size); From mdounin at mdounin.ru Thu Mar 26 04:02:24 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 26 Mar 2026 07:02:24 +0300 Subject: [nginx] OCSP stapling: added missing event handling after writing. Message-ID: details: http://freenginx.org/hg/nginx/rev/15ab3e70ab16 branches: changeset: 9487:15ab3e70ab16 user: Maxim Dounin date: Thu Mar 26 06:51:22 2026 +0300 description: OCSP stapling: added missing event handling after writing. If we need to be notified about further events, ngx_handle_write_event() needs to be called after a write event is processed. Without this, an event can be removed from the kernel and won't be reported again, notably when using oneshot event methods, such as eventport on Solaris. diffstat: src/event/ngx_event_openssl_stapling.c | 4 ++++ 1 files changed, 4 insertions(+), 0 deletions(-) diffs (14 lines): diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c --- a/src/event/ngx_event_openssl_stapling.c +++ b/src/event/ngx_event_openssl_stapling.c @@ -1598,6 +1598,10 @@ ngx_ssl_ocsp_write_handler(ngx_event_t * if (!wev->timer_set && ctx->timeout) { ngx_add_timer(wev, ctx->timeout); } + + if (ngx_handle_write_event(wev, 0) != NGX_OK) { + ngx_ssl_ocsp_error(ctx); + } } From mdounin at mdounin.ru Thu Mar 26 04:02:24 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 26 Mar 2026 07:02:24 +0300 Subject: [nginx] Mail: added missing event handling in auth http code. Message-ID: details: http://freenginx.org/hg/nginx/rev/87034744d6e1 branches: changeset: 9488:87034744d6e1 user: Maxim Dounin date: Thu Mar 26 06:51:27 2026 +0300 description: Mail: added missing event handling in auth http code. If we need to be notified about further events, ngx_handle_read_event() (or ngx_handle_write_event()) needs to be called after an event is processed. Without this, an event can be removed from the kernel and won't be reported again, notably when using oneshot event methods, such as eventport on Solaris. While here, ngx_mail_auth_http_read_handler() was also modified to properly detect and report premature connection close. To do so, ngx_mail_auth_http_read_handler() is modified to use a loop around ngx_recv(), ctx->handler() now reports if there was an error or we are waiting for additional data, and ngx_handle_read_event() is called when ngx_recv() reports NGX_AGAIN. The code is now similar to ngx_ssl_ocsp_read_handler() (which in turn was originally based on the auth http code). Similarly, ngx_mail_auth_http_write_handler() is modified to call ngx_handle_write_event() when the request is not fully sent to the socket. diffstat: src/mail/ngx_mail_auth_http_module.c | 110 ++++++++++++++++++++++------------ 1 files changed, 72 insertions(+), 38 deletions(-) diffs (329 lines): diff --git a/src/mail/ngx_mail_auth_http_module.c b/src/mail/ngx_mail_auth_http_module.c --- a/src/mail/ngx_mail_auth_http_module.c +++ b/src/mail/ngx_mail_auth_http_module.c @@ -31,7 +31,7 @@ typedef struct { typedef struct ngx_mail_auth_http_ctx_s ngx_mail_auth_http_ctx_t; -typedef void (*ngx_mail_auth_http_handler_pt)(ngx_mail_session_t *s, +typedef ngx_int_t (*ngx_mail_auth_http_handler_pt)(ngx_mail_session_t *s, ngx_mail_auth_http_ctx_t *ctx); struct ngx_mail_auth_http_ctx_s { @@ -65,9 +65,9 @@ struct ngx_mail_auth_http_ctx_s { static void ngx_mail_auth_http_write_handler(ngx_event_t *wev); static void ngx_mail_auth_http_read_handler(ngx_event_t *rev); -static void ngx_mail_auth_http_ignore_status_line(ngx_mail_session_t *s, +static ngx_int_t ngx_mail_auth_http_ignore_status_line(ngx_mail_session_t *s, ngx_mail_auth_http_ctx_t *ctx); -static void ngx_mail_auth_http_process_headers(ngx_mail_session_t *s, +static ngx_int_t ngx_mail_auth_http_process_headers(ngx_mail_session_t *s, ngx_mail_auth_http_ctx_t *ctx); static void ngx_mail_auth_sleep_handler(ngx_event_t *rev); static void ngx_mail_auth_send_error(ngx_mail_session_t *s); @@ -307,13 +307,20 @@ ngx_mail_auth_http_write_handler(ngx_eve ahcf = ngx_mail_get_module_srv_conf(s, ngx_mail_auth_http_module); ngx_add_timer(wev, ahcf->timeout); } + + if (ngx_handle_write_event(wev, 0) != NGX_OK) { + ngx_close_connection(c); + ngx_destroy_pool(ctx->pool); + ngx_mail_session_internal_server_error(s); + } } static void ngx_mail_auth_http_read_handler(ngx_event_t *rev) { - ssize_t n, size; + ssize_t n, size; + ngx_int_t rc; ngx_connection_t *c; ngx_mail_session_t *s; ngx_mail_auth_http_ctx_t *ctx; @@ -345,19 +352,41 @@ ngx_mail_auth_http_read_handler(ngx_even } } - size = ctx->response->end - ctx->response->last; + for ( ;; ) { + + size = ctx->response->end - ctx->response->last; + + n = ngx_recv(c, ctx->response->pos, size); - n = ngx_recv(c, ctx->response->pos, size); + if (n > 0) { + ctx->response->last += n; + + rc = ctx->handler(s, ctx); + + if (rc == NGX_ERROR || rc == NGX_DONE) { + return; + } - if (n > 0) { - ctx->response->last += n; + continue; + } - ctx->handler(s, ctx); - return; + if (n == NGX_AGAIN) { + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_close_connection(c); + ngx_destroy_pool(ctx->pool); + ngx_mail_session_internal_server_error(s); + } + + return; + } + + break; } - if (n == NGX_AGAIN) { - return; + if (n == 0) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "auth http server %V prematurely closed connection", + ctx->peer.name); } ngx_close_connection(c); @@ -366,7 +395,7 @@ ngx_mail_auth_http_read_handler(ngx_even } -static void +static ngx_int_t ngx_mail_auth_http_ignore_status_line(ngx_mail_session_t *s, ngx_mail_auth_http_ctx_t *ctx) { @@ -448,17 +477,19 @@ ngx_mail_auth_http_ignore_status_line(ng ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "auth http server %V sent invalid response", ctx->peer.name); + ngx_close_connection(ctx->peer.connection); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + + return NGX_ERROR; } } ctx->response->pos = p; ctx->state = state; - return; + return NGX_AGAIN; next: @@ -469,11 +500,11 @@ done: ctx->response->pos = p + 1; ctx->state = 0; ctx->handler = ngx_mail_auth_http_process_headers; - ctx->handler(s, ctx); + return ctx->handler(s, ctx); } -static void +static ngx_int_t ngx_mail_auth_http_process_headers(ngx_mail_session_t *s, ngx_mail_auth_http_ctx_t *ctx) { @@ -499,7 +530,7 @@ ngx_mail_auth_http_process_headers(ngx_m ngx_close_connection(ctx->peer.connection); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } h->key.len = ctx->header_name_end - ctx->header_name_start; @@ -562,7 +593,7 @@ ngx_mail_auth_http_process_headers(ngx_m ngx_close_connection(ctx->peer.connection); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } ctx->err.data = p; @@ -627,7 +658,7 @@ ngx_mail_auth_http_process_headers(ngx_m ngx_close_connection(ctx->peer.connection); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } ngx_memcpy(s->login.data, ctx->header_start, s->login.len); @@ -649,7 +680,7 @@ ngx_mail_auth_http_process_headers(ngx_m ngx_close_connection(ctx->peer.connection); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } ngx_memcpy(s->passwd.data, ctx->header_start, s->passwd.len); @@ -687,7 +718,7 @@ ngx_mail_auth_http_process_headers(ngx_m ngx_close_connection(ctx->peer.connection); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } ngx_memcpy(ctx->errcode.data, ctx->header_start, @@ -722,7 +753,7 @@ ngx_mail_auth_http_process_headers(ngx_m ngx_close_connection(ctx->peer.connection); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } ctx->errsasl.len = size; @@ -770,7 +801,7 @@ ngx_mail_auth_http_process_headers(ngx_m if (p == NULL) { ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } ctx->err.data = p; @@ -790,14 +821,14 @@ ngx_mail_auth_http_process_headers(ngx_m if (timer == 0) { s->auth_quit = 1; ngx_mail_auth_send_error(s); - return; + return NGX_DONE; } ngx_add_timer(s->connection->read, (ngx_msec_t) (timer * 1000)); s->connection->read->handler = ngx_mail_auth_sleep_handler; - return; + return NGX_DONE; } if (s->auth_wait) { @@ -807,14 +838,14 @@ ngx_mail_auth_http_process_headers(ngx_m if (timer == 0) { ngx_mail_auth_http_init(s); - return; + return NGX_DONE; } ngx_add_timer(s->connection->read, (ngx_msec_t) (timer * 1000)); s->connection->read->handler = ngx_mail_auth_sleep_handler; - return; + return NGX_DONE; } if (ctx->addr.len == 0 || ctx->port.len == 0) { @@ -823,7 +854,7 @@ ngx_mail_auth_http_process_headers(ngx_m ctx->peer.name); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } if (s->passwd.data == NULL @@ -834,14 +865,14 @@ ngx_mail_auth_http_process_headers(ngx_m ctx->peer.name); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } peer = ngx_pcalloc(s->connection->pool, sizeof(ngx_addr_t)); if (peer == NULL) { ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } rc = ngx_parse_addr(s->connection->pool, peer, @@ -861,7 +892,7 @@ ngx_mail_auth_http_process_headers(ngx_m default: ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } port = ngx_atoi(ctx->port.data, ctx->port.len); @@ -872,7 +903,7 @@ ngx_mail_auth_http_process_headers(ngx_m ctx->peer.name, &ctx->port); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } ngx_inet_set_port(peer->sockaddr, (in_port_t) port); @@ -885,7 +916,7 @@ ngx_mail_auth_http_process_headers(ngx_m if (peer->name.data == NULL) { ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } len = ctx->addr.len; @@ -900,17 +931,17 @@ ngx_mail_auth_http_process_headers(ngx_m if (ngx_mail_limit_conn_handler(s) != NGX_OK) { ngx_destroy_pool(ctx->pool); - return; + return NGX_ERROR; } ngx_destroy_pool(ctx->pool); ngx_mail_proxy_init(s, peer); - return; + return NGX_DONE; } if (rc == NGX_AGAIN ) { - return; + return NGX_AGAIN; } /* rc == NGX_ERROR */ @@ -918,12 +949,15 @@ ngx_mail_auth_http_process_headers(ngx_m ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "auth http server %V sent invalid header in response", ctx->peer.name); + ngx_close_connection(ctx->peer.connection); ngx_destroy_pool(ctx->pool); ngx_mail_session_internal_server_error(s); - return; + return NGX_ERROR; } + + /* not reached */ } From mdounin at mdounin.ru Thu Mar 26 04:02:24 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 26 Mar 2026 07:02:24 +0300 Subject: [nginx] Mail: fixed missing return in error handling. Message-ID: details: http://freenginx.org/hg/nginx/rev/038d5bdde5cd branches: changeset: 9489:038d5bdde5cd user: Maxim Dounin date: Thu Mar 26 06:51:35 2026 +0300 description: Mail: fixed missing return in error handling. diffstat: src/mail/ngx_mail_proxy_module.c | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diffs (11 lines): diff --git a/src/mail/ngx_mail_proxy_module.c b/src/mail/ngx_mail_proxy_module.c --- a/src/mail/ngx_mail_proxy_module.c +++ b/src/mail/ngx_mail_proxy_module.c @@ -882,6 +882,7 @@ ngx_mail_proxy_write_handler(ngx_event_t if (ngx_handle_write_event(wev, 0) != NGX_OK) { ngx_mail_proxy_internal_server_error(s); + return; } if (c->read->ready) { From mdounin at mdounin.ru Thu Mar 26 04:02:24 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 26 Mar 2026 07:02:24 +0300 Subject: [nginx] Mail: improved error reporting while connecting to upstr... Message-ID: details: http://freenginx.org/hg/nginx/rev/5b4f651713d7 branches: changeset: 9490:5b4f651713d7 user: Maxim Dounin date: Thu Mar 26 06:51:47 2026 +0300 description: Mail: improved error reporting while connecting to upstream. Previously, when c->send() failed to fully send a command after connecting to the upstream server, no error was logged, yet the connection was closed with an internal error. diffstat: src/mail/ngx_mail_proxy_module.c | 36 +++++++++++++++++++++++++++++++++--- 1 files changed, 33 insertions(+), 3 deletions(-) diffs (93 lines): diff --git a/src/mail/ngx_mail_proxy_module.c b/src/mail/ngx_mail_proxy_module.c --- a/src/mail/ngx_mail_proxy_module.c +++ b/src/mail/ngx_mail_proxy_module.c @@ -227,6 +227,7 @@ static void ngx_mail_proxy_pop3_handler(ngx_event_t *rev) { u_char *p; + ssize_t n; ngx_int_t rc; ngx_str_t line; ngx_connection_t *c; @@ -344,11 +345,20 @@ ngx_mail_proxy_pop3_handler(ngx_event_t break; } - if (c->send(c, line.data, line.len) < (ssize_t) line.len) { + n = c->send(c, line.data, line.len); + + if (n == NGX_ERROR) { + ngx_mail_proxy_internal_server_error(s); + return; + } + + if (n != (ssize_t) line.len) { /* * we treat the incomplete sending as NGX_ERROR * because it is very strange here */ + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "sent only %z of %uz", n, line.len); ngx_mail_proxy_internal_server_error(s); return; } @@ -367,6 +377,7 @@ static void ngx_mail_proxy_imap_handler(ngx_event_t *rev) { u_char *p; + ssize_t n; ngx_int_t rc; ngx_str_t line; ngx_connection_t *c; @@ -505,11 +516,20 @@ ngx_mail_proxy_imap_handler(ngx_event_t break; } - if (c->send(c, line.data, line.len) < (ssize_t) line.len) { + n = c->send(c, line.data, line.len); + + if (n == NGX_ERROR) { + ngx_mail_proxy_internal_server_error(s); + return; + } + + if (n != (ssize_t) line.len) { /* * we treat the incomplete sending as NGX_ERROR * because it is very strange here */ + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "sent only %z of %uz", n, line.len); ngx_mail_proxy_internal_server_error(s); return; } @@ -528,6 +548,7 @@ static void ngx_mail_proxy_smtp_handler(ngx_event_t *rev) { u_char *p; + ssize_t n; ngx_int_t rc; ngx_str_t line, auth, encoded; ngx_buf_t *b; @@ -842,11 +863,20 @@ ngx_mail_proxy_smtp_handler(ngx_event_t break; } - if (c->send(c, line.data, line.len) < (ssize_t) line.len) { + n = c->send(c, line.data, line.len); + + if (n == NGX_ERROR) { + ngx_mail_proxy_internal_server_error(s); + return; + } + + if (n != (ssize_t) line.len) { /* * we treat the incomplete sending as NGX_ERROR * because it is very strange here */ + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "sent only %z of %uz", n, line.len); ngx_mail_proxy_internal_server_error(s); return; } From mdounin at mdounin.ru Thu Mar 26 04:02:24 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 26 Mar 2026 07:02:24 +0300 Subject: [nginx] Mail: fixed occasional mail proxy errors on Windows. Message-ID: details: http://freenginx.org/hg/nginx/rev/04172e7f25c6 branches: changeset: 9491:04172e7f25c6 user: Maxim Dounin date: Thu Mar 26 06:51:59 2026 +0300 description: Mail: fixed occasional mail proxy errors on Windows. On Windows, in some cases a read event is reported by select() before a write event signals that a connection to a backend server is established. This results in c->write->ready not being set when the read handler is called, and subsequent c->send() returns NGX_AGAIN, as ngx_wsasend() does not do anything unless c->write->ready is set. And the connection establishment code assumes that short initial commands can be sent without blocking, further resulting in an internal error being reported to the client. It is not clear why this happens, though it was observed in practice when running tests on Windows 10 (22H2). The fix is to block read handlers from doing anything unless c->write->ready is set, much like we do when sending the PROXY protocol header. diffstat: src/mail/ngx_mail_proxy_module.c | 6 +++--- 1 files changed, 3 insertions(+), 3 deletions(-) diffs (30 lines): diff --git a/src/mail/ngx_mail_proxy_module.c b/src/mail/ngx_mail_proxy_module.c --- a/src/mail/ngx_mail_proxy_module.c +++ b/src/mail/ngx_mail_proxy_module.c @@ -248,7 +248,7 @@ ngx_mail_proxy_pop3_handler(ngx_event_t return; } - if (s->proxy->proxy_protocol) { + if (s->proxy->proxy_protocol || !c->write->ready) { ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "mail proxy pop3 busy"); if (ngx_handle_read_event(c->read, 0) != NGX_OK) { @@ -398,7 +398,7 @@ ngx_mail_proxy_imap_handler(ngx_event_t return; } - if (s->proxy->proxy_protocol) { + if (s->proxy->proxy_protocol || !c->write->ready) { ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "mail proxy imap busy"); if (ngx_handle_read_event(c->read, 0) != NGX_OK) { @@ -571,7 +571,7 @@ ngx_mail_proxy_smtp_handler(ngx_event_t return; } - if (s->proxy->proxy_protocol) { + if (s->proxy->proxy_protocol || !c->write->ready) { ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "mail proxy smtp busy"); if (ngx_handle_read_event(c->read, 0) != NGX_OK) { From mdounin at mdounin.ru Mon Mar 30 02:16:01 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Mon, 30 Mar 2026 05:16:01 +0300 Subject: [PATCH] Mail: escaping of client host name Message-ID: # HG changeset patch # User Maxim Dounin # Date 1774836085 -10800 # Mon Mar 30 05:01:25 2026 +0300 # Node ID bbadfe1e7cb0a100ea18e1f6f227579abd6cd637 # Parent 04172e7f25c62ee20690f369801a1a846ee04ad2 Mail: escaping of client host name. When resolver is configured along with SMTP proxying, client host name is determined with DNS PTR lookup (and additionally validated with A/AAAA lookup). Though resolved name might contain arbitrary characters, and using it without additional validation or escaping might lead to issues (CVE-2026-28753). With this change, client host name is escaped when sending it to auth_http server, as well as when sending it to the backend server in the XCLIENT command. In requests to auth_http it is escaped using the same URI escaping as used for "Auth-User" and "Auth-Pass". And in XCLIENT it is escaped using the xtext encoding per RFC 1891 / RFC 3461, as supported by Postfix since version 2.3. Additionally, XCLIENT LOGIN= now also uses xtext encoding, which might be beneficial for systems with complex logins. See also: https://github.com/nginx/nginx/commit/6f3145006b41a4ec464eed4093553a335d35e8ac diff --git a/src/core/ngx_string.c b/src/core/ngx_string.c --- a/src/core/ngx_string.c +++ b/src/core/ngx_string.c @@ -1964,6 +1964,65 @@ ngx_escape_json(u_char *dst, u_char *src } +uintptr_t +ngx_escape_xtext(u_char *dst, u_char *src, size_t size) +{ + u_char ch; + ngx_uint_t n; + static u_char hex[] = "0123456789ABCDEF"; + + /* + * RFC 1891 / 3461 xtext encoding: + * + * xtext = *( xchar / hexchar ) + * + * xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive, + * except for "+" and "=". + * + * hexchar = ASCII "+" immediately followed by two upper case + * hexadecimal digits + * + * Mostly equivalent to URI escaping, but uses "+" instead of "%". + */ + + if (dst == NULL) { + + /* find the number of the characters to be escaped */ + + n = 0; + + while (size) { + ch = *src++; + + if (ch <= 0x20 || ch >= 0x7f || ch == '+' || ch == '=') { + n++; + } + + size--; + } + + return (uintptr_t) n; + } + + while (size) { + ch = *src++; + + if (ch <= 0x20 || ch >= 0x7f || ch == '+' || ch == '=') { + *dst++ = '+'; + *dst++ = hex[ch >> 4]; + *dst++ = hex[ch & 0xf]; + + } else { + *dst++ = ch; + } + + size--; + } + + return (uintptr_t) dst; +} + + void ngx_str_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) diff --git a/src/core/ngx_string.h b/src/core/ngx_string.h --- a/src/core/ngx_string.h +++ b/src/core/ngx_string.h @@ -212,6 +212,7 @@ uintptr_t ngx_escape_uri(u_char *dst, u_ void ngx_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type); uintptr_t ngx_escape_html(u_char *dst, u_char *src, size_t size); uintptr_t ngx_escape_json(u_char *dst, u_char *src, size_t size); +uintptr_t ngx_escape_xtext(u_char *dst, u_char *src, size_t size); typedef struct { diff --git a/src/mail/ngx_mail_auth_http_module.c b/src/mail/ngx_mail_auth_http_module.c --- a/src/mail/ngx_mail_auth_http_module.c +++ b/src/mail/ngx_mail_auth_http_module.c @@ -1267,7 +1267,7 @@ ngx_mail_auth_http_create_request(ngx_ma { size_t len; ngx_buf_t *b; - ngx_str_t login, passwd; + ngx_str_t login, passwd, host; ngx_connection_t *c; #if (NGX_MAIL_SSL) ngx_str_t protocol, cipher, verify, subject, issuer, @@ -1285,6 +1285,10 @@ ngx_mail_auth_http_create_request(ngx_ma return NULL; } + if (ngx_mail_auth_http_escape(pool, &s->host, &host) != NGX_OK) { + return NULL; + } + c = s->connection; #if (NGX_MAIL_SSL) @@ -1382,7 +1386,7 @@ ngx_mail_auth_http_create_request(ngx_ma + sizeof(CRLF) - 1 + sizeof("Client-IP: ") - 1 + s->connection->addr_text.len + sizeof(CRLF) - 1 - + sizeof("Client-Host: ") - 1 + s->host.len + sizeof(CRLF) - 1 + + sizeof("Client-Host: ") - 1 + host.len + sizeof(CRLF) - 1 + ahcf->header.len + sizeof(CRLF) - 1; @@ -1483,10 +1487,10 @@ ngx_mail_auth_http_create_request(ngx_ma s->connection->addr_text.len); *b->last++ = CR; *b->last++ = LF; - if (s->host.len) { + if (host.len) { b->last = ngx_cpymem(b->last, "Client-Host: ", sizeof("Client-Host: ") - 1); - b->last = ngx_copy(b->last, s->host.data, s->host.len); + b->last = ngx_copy(b->last, host.data, host.len); *b->last++ = CR; *b->last++ = LF; } diff --git a/src/mail/ngx_mail_proxy_module.c b/src/mail/ngx_mail_proxy_module.c --- a/src/mail/ngx_mail_proxy_module.c +++ b/src/mail/ngx_mail_proxy_module.c @@ -646,7 +646,11 @@ ngx_mail_proxy_smtp_handler(ngx_event_t line.len = sizeof("XCLIENT ADDR= LOGIN= NAME=" CRLF) - 1 - + s->connection->addr_text.len + s->login.len + s->host.len; + + s->connection->addr_text.len + + s->login.len + + 2 * ngx_escape_xtext(NULL, s->login.data, s->login.len) + + s->host.len + + 2 * ngx_escape_xtext(NULL, s->host.data, s->host.len); #if (NGX_HAVE_INET6) if (s->connection->sockaddr->sa_family == AF_INET6) { @@ -675,11 +679,11 @@ ngx_mail_proxy_smtp_handler(ngx_event_t if (s->login.len && !pcf->smtp_auth) { p = ngx_cpymem(p, " LOGIN=", sizeof(" LOGIN=") - 1); - p = ngx_copy(p, s->login.data, s->login.len); + p = (u_char *) ngx_escape_xtext(p, s->login.data, s->login.len); } p = ngx_cpymem(p, " NAME=", sizeof(" NAME=") - 1); - p = ngx_copy(p, s->host.data, s->host.len); + p = (u_char *) ngx_escape_xtext(p, s->host.data, s->host.len); *p++ = CR; *p++ = LF; From mdounin at mdounin.ru Mon Mar 30 02:17:20 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Mon, 30 Mar 2026 05:17:20 +0300 Subject: [PATCH] Tests: added XCLIENT escaping test In-Reply-To: References: Message-ID: <950057e70ea4ce340cc1.1774837040@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1774836314 -10800 # Mon Mar 30 05:05:14 2026 +0300 # Node ID 950057e70ea4ce340cc1537c84d9da617cca7504 # Parent d7cbb4aa6548e5bcf520ada9053327600829f8dd Tests: added XCLIENT escaping test. diff --git a/mail_smtp_xclient.t b/mail_smtp_xclient.t --- a/mail_smtp_xclient.t +++ b/mail_smtp_xclient.t @@ -24,7 +24,7 @@ select STDOUT; $| = 1; local $SIG{PIPE} = 'IGNORE'; -my $t = Test::Nginx->new()->has(qw/mail smtp http rewrite/)->plan(6) +my $t = Test::Nginx->new()->has(qw/mail smtp http rewrite/)->plan(7) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -139,4 +139,18 @@ my $s = Test::Nginx::SMTP->new(); $s->send('RCPT TO:'); $s->ok('xclient, ehlo, from'); +# xclient argument escaping + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.29.7'); + +$s = Test::Nginx::SMTP->new(); +$s->read(); +$s->send('AUTH PLAIN ' . encode_base64("\0test\nfoo\@example.com\0secret", '')); +$s->read(); +$s->send('QUIT'); +$s->ok("xclient xtext"); + +} + ############################################################################### From mdounin at mdounin.ru Mon Mar 30 02:26:30 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Mon, 30 Mar 2026 05:26:30 +0300 Subject: [PATCH] Dav: destination validation for COPY and MOVE with "alias" Message-ID: <7fdbec4cfc46573e1c9e.1774837590@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1774837227 -10800 # Mon Mar 30 05:20:27 2026 +0300 # Node ID 7fdbec4cfc46573e1c9e26680488c27d79664083 # Parent 04172e7f25c62ee20690f369801a1a846ee04ad2 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 (CVE-2026-27654). 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. See also: https://github.com/nginx/nginx/commit/9739e755b8dddba82e65ca2a08d079f4c9826b75 diff --git a/src/http/modules/ngx_http_dav_module.c b/src/http/modules/ngx_http_dav_module.c --- a/src/http/modules/ngx_http_dav_module.c +++ b/src/http/modules/ngx_http_dav_module.c @@ -535,19 +535,20 @@ ngx_http_dav_mkcol_handler(ngx_http_requ static ngx_int_t ngx_http_dav_copy_move_handler(ngx_http_request_t *r) { - u_char *p, *host, *last, ch; - size_t len, root; - ngx_err_t err; - ngx_int_t rc, depth; - ngx_uint_t overwrite, slash, dir, flags; - ngx_str_t path, uri, duri, args; - ngx_tree_ctx_t tree; - ngx_copy_file_t cf; - ngx_file_info_t fi; - ngx_table_elt_t *dest, *over; - ngx_ext_rename_file_t ext; - ngx_http_dav_copy_ctx_t copy; - ngx_http_dav_loc_conf_t *dlcf; + u_char *p, *host, *last, ch; + size_t len, root, alias; + ngx_err_t err; + ngx_int_t rc, depth; + ngx_uint_t overwrite, slash, dir, flags; + ngx_str_t path, uri, duri, args; + ngx_tree_ctx_t tree; + ngx_copy_file_t cf; + ngx_file_info_t fi; + ngx_table_elt_t *dest, *over; + ngx_ext_rename_file_t ext; + ngx_http_dav_copy_ctx_t copy; + ngx_http_dav_loc_conf_t *dlcf; + ngx_http_core_loc_conf_t *clcf; if (r->headers_in.content_length_n > 0 || r->headers_in.chunked) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, @@ -644,6 +645,22 @@ destination_done: return NGX_HTTP_CONFLICT; } + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + alias = clcf->alias; + + if (alias && alias != NGX_MAX_SIZE_T_VALUE) { + + if (alias > duri.len + || ngx_filename_cmp(duri.data, r->uri.data, alias) != 0) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "\"Destination\" URI \"%V\" must be " + "within location prefix when using \"alias\"", + &dest->value); + return NGX_HTTP_BAD_REQUEST; + } + } + depth = ngx_http_dav_depth(r, NGX_HTTP_DAV_INFINITY_DEPTH); if (depth != NGX_HTTP_DAV_INFINITY_DEPTH) { diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -1912,6 +1912,12 @@ ngx_http_map_uri_to_path(ngx_http_reques return NULL; } + if (alias > r->uri.len && alias != NGX_MAX_SIZE_T_VALUE) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "URI shorter than aliased URI part"); + return NULL; + } + if (clcf->root_lengths == NULL) { *root_length = clcf->root.len; From mdounin at mdounin.ru Mon Mar 30 02:28:09 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Mon, 30 Mar 2026 05:28:09 +0300 Subject: [PATCH] Tests: tests for DAV COPY with alias In-Reply-To: <7fdbec4cfc46573e1c9e.1774837590@vm-bsd.mdounin.ru> References: <7fdbec4cfc46573e1c9e.1774837590@vm-bsd.mdounin.ru> Message-ID: <123afa1ea4ab41b10c1f.1774837689@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1774837256 -10800 # Mon Mar 30 05:20:56 2026 +0300 # Node ID 123afa1ea4ab41b10c1f5e16ade219f202d23095 # Parent d7cbb4aa6548e5bcf520ada9053327600829f8dd Tests: tests for DAV COPY with alias. diff --git a/dav.t b/dav.t --- a/dav.t +++ b/dav.t @@ -21,7 +21,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http dav/)->plan(28); +my $t = Test::Nginx->new()->has(qw/http dav/)->plan(31); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -180,6 +180,8 @@ EOF like($r, qr/204 No Content/, 'copy file overwrite'); is(-s $t->testdir() . '/file.exist', 10, 'target file truncated'); +# tests in location with alias + $r = http(<testdir() . '/alias', 10, 'put alias size'); +$r = http(<has_version('1.29.7'); + +$r = http(<todo_alerts() + unless $t->has_version('1.29.7'); +todo_skip 'leaves coredump', 1 + unless $t->has_version('1.29.7') + or $ENV{TEST_NGINX_UNSAFE}; + +$r = http(< details: http://freenginx.org/hg/nginx/rev/cb1dc66f3f88 branches: changeset: 9492:cb1dc66f3f88 user: Maxim Dounin date: Tue Mar 31 06:54:30 2026 +0300 description: Mail: escaping of client host name. When resolver is configured along with SMTP proxying, client host name is determined with DNS PTR lookup (and additionally validated with A/AAAA lookup). Though resolved name might contain arbitrary characters, and using it without additional validation or escaping might lead to issues (CVE-2026-28753). With this change, client host name is escaped when sending it to auth_http server, as well as when sending it to the backend server in the XCLIENT command. In requests to auth_http it is escaped using the same URI escaping as used for "Auth-User" and "Auth-Pass". And in XCLIENT it is escaped using the xtext encoding per RFC 1891 / RFC 3461, as supported by Postfix since version 2.3. Additionally, XCLIENT LOGIN= now also uses xtext encoding, which might be beneficial for systems with complex logins. See also: https://github.com/nginx/nginx/commit/6f3145006b41a4ec464eed4093553a335d35e8ac diffstat: src/core/ngx_string.c | 59 ++++++++++++++++++++++++++++++++++++ src/core/ngx_string.h | 1 + src/mail/ngx_mail_auth_http_module.c | 12 ++++-- src/mail/ngx_mail_proxy_module.c | 10 ++++- 4 files changed, 75 insertions(+), 7 deletions(-) diffs (155 lines): diff --git a/src/core/ngx_string.c b/src/core/ngx_string.c --- a/src/core/ngx_string.c +++ b/src/core/ngx_string.c @@ -1964,6 +1964,65 @@ ngx_escape_json(u_char *dst, u_char *src } +uintptr_t +ngx_escape_xtext(u_char *dst, u_char *src, size_t size) +{ + u_char ch; + ngx_uint_t n; + static u_char hex[] = "0123456789ABCDEF"; + + /* + * RFC 1891 / 3461 xtext encoding: + * + * xtext = *( xchar / hexchar ) + * + * xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive, + * except for "+" and "=". + * + * hexchar = ASCII "+" immediately followed by two upper case + * hexadecimal digits + * + * Mostly equivalent to URI escaping, but uses "+" instead of "%". + */ + + if (dst == NULL) { + + /* find the number of the characters to be escaped */ + + n = 0; + + while (size) { + ch = *src++; + + if (ch <= 0x20 || ch >= 0x7f || ch == '+' || ch == '=') { + n++; + } + + size--; + } + + return (uintptr_t) n; + } + + while (size) { + ch = *src++; + + if (ch <= 0x20 || ch >= 0x7f || ch == '+' || ch == '=') { + *dst++ = '+'; + *dst++ = hex[ch >> 4]; + *dst++ = hex[ch & 0xf]; + + } else { + *dst++ = ch; + } + + size--; + } + + return (uintptr_t) dst; +} + + void ngx_str_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) diff --git a/src/core/ngx_string.h b/src/core/ngx_string.h --- a/src/core/ngx_string.h +++ b/src/core/ngx_string.h @@ -212,6 +212,7 @@ uintptr_t ngx_escape_uri(u_char *dst, u_ void ngx_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type); uintptr_t ngx_escape_html(u_char *dst, u_char *src, size_t size); uintptr_t ngx_escape_json(u_char *dst, u_char *src, size_t size); +uintptr_t ngx_escape_xtext(u_char *dst, u_char *src, size_t size); typedef struct { diff --git a/src/mail/ngx_mail_auth_http_module.c b/src/mail/ngx_mail_auth_http_module.c --- a/src/mail/ngx_mail_auth_http_module.c +++ b/src/mail/ngx_mail_auth_http_module.c @@ -1267,7 +1267,7 @@ ngx_mail_auth_http_create_request(ngx_ma { size_t len; ngx_buf_t *b; - ngx_str_t login, passwd; + ngx_str_t login, passwd, host; ngx_connection_t *c; #if (NGX_MAIL_SSL) ngx_str_t protocol, cipher, verify, subject, issuer, @@ -1285,6 +1285,10 @@ ngx_mail_auth_http_create_request(ngx_ma return NULL; } + if (ngx_mail_auth_http_escape(pool, &s->host, &host) != NGX_OK) { + return NULL; + } + c = s->connection; #if (NGX_MAIL_SSL) @@ -1382,7 +1386,7 @@ ngx_mail_auth_http_create_request(ngx_ma + sizeof(CRLF) - 1 + sizeof("Client-IP: ") - 1 + s->connection->addr_text.len + sizeof(CRLF) - 1 - + sizeof("Client-Host: ") - 1 + s->host.len + sizeof(CRLF) - 1 + + sizeof("Client-Host: ") - 1 + host.len + sizeof(CRLF) - 1 + ahcf->header.len + sizeof(CRLF) - 1; @@ -1483,10 +1487,10 @@ ngx_mail_auth_http_create_request(ngx_ma s->connection->addr_text.len); *b->last++ = CR; *b->last++ = LF; - if (s->host.len) { + if (host.len) { b->last = ngx_cpymem(b->last, "Client-Host: ", sizeof("Client-Host: ") - 1); - b->last = ngx_copy(b->last, s->host.data, s->host.len); + b->last = ngx_copy(b->last, host.data, host.len); *b->last++ = CR; *b->last++ = LF; } diff --git a/src/mail/ngx_mail_proxy_module.c b/src/mail/ngx_mail_proxy_module.c --- a/src/mail/ngx_mail_proxy_module.c +++ b/src/mail/ngx_mail_proxy_module.c @@ -646,7 +646,11 @@ ngx_mail_proxy_smtp_handler(ngx_event_t line.len = sizeof("XCLIENT ADDR= LOGIN= NAME=" CRLF) - 1 - + s->connection->addr_text.len + s->login.len + s->host.len; + + s->connection->addr_text.len + + s->login.len + + 2 * ngx_escape_xtext(NULL, s->login.data, s->login.len) + + s->host.len + + 2 * ngx_escape_xtext(NULL, s->host.data, s->host.len); #if (NGX_HAVE_INET6) if (s->connection->sockaddr->sa_family == AF_INET6) { @@ -675,11 +679,11 @@ ngx_mail_proxy_smtp_handler(ngx_event_t if (s->login.len && !pcf->smtp_auth) { p = ngx_cpymem(p, " LOGIN=", sizeof(" LOGIN=") - 1); - p = ngx_copy(p, s->login.data, s->login.len); + p = (u_char *) ngx_escape_xtext(p, s->login.data, s->login.len); } p = ngx_cpymem(p, " NAME=", sizeof(" NAME=") - 1); - p = ngx_copy(p, s->host.data, s->host.len); + p = (u_char *) ngx_escape_xtext(p, s->host.data, s->host.len); *p++ = CR; *p++ = LF; From mdounin at mdounin.ru Tue Mar 31 04:00:16 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Tue, 31 Mar 2026 07:00:16 +0300 Subject: [nginx-tests] Tests: added XCLIENT escaping test. Message-ID: details: http://freenginx.org/hg/nginx-tests/rev/091163f6b148 branches: changeset: 2045:091163f6b148 user: Maxim Dounin date: Tue Mar 31 06:57:43 2026 +0300 description: Tests: added XCLIENT escaping test. diffstat: mail_smtp_xclient.t | 16 +++++++++++++++- 1 files changed, 15 insertions(+), 1 deletions(-) diffs (31 lines): diff --git a/mail_smtp_xclient.t b/mail_smtp_xclient.t --- a/mail_smtp_xclient.t +++ b/mail_smtp_xclient.t @@ -24,7 +24,7 @@ select STDOUT; $| = 1; local $SIG{PIPE} = 'IGNORE'; -my $t = Test::Nginx->new()->has(qw/mail smtp http rewrite/)->plan(6) +my $t = Test::Nginx->new()->has(qw/mail smtp http rewrite/)->plan(7) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -139,4 +139,18 @@ my $s = Test::Nginx::SMTP->new(); $s->send('RCPT TO:'); $s->ok('xclient, ehlo, from'); +# xclient argument escaping + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.29.7'); + +$s = Test::Nginx::SMTP->new(); +$s->read(); +$s->send('AUTH PLAIN ' . encode_base64("\0test\nfoo\@example.com\0secret", '')); +$s->read(); +$s->send('QUIT'); +$s->ok("xclient xtext"); + +} + ############################################################################### From mdounin at mdounin.ru Tue Mar 31 04:02:22 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Tue, 31 Mar 2026 07:02:22 +0300 Subject: [nginx] Dav: destination validation for COPY and MOVE with "alias". Message-ID: details: http://freenginx.org/hg/nginx/rev/aae28b083279 branches: changeset: 9493:aae28b083279 user: Maxim Dounin date: Tue Mar 31 06:54:50 2026 +0300 description: 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 (CVE-2026-27654). 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. See also: https://github.com/nginx/nginx/commit/9739e755b8dddba82e65ca2a08d079f4c9826b75 diffstat: src/http/modules/ngx_http_dav_module.c | 43 +++++++++++++++++++++++---------- src/http/ngx_http_core_module.c | 6 ++++ 2 files changed, 36 insertions(+), 13 deletions(-) diffs (76 lines): diff --git a/src/http/modules/ngx_http_dav_module.c b/src/http/modules/ngx_http_dav_module.c --- a/src/http/modules/ngx_http_dav_module.c +++ b/src/http/modules/ngx_http_dav_module.c @@ -535,19 +535,20 @@ ngx_http_dav_mkcol_handler(ngx_http_requ static ngx_int_t ngx_http_dav_copy_move_handler(ngx_http_request_t *r) { - u_char *p, *host, *last, ch; - size_t len, root; - ngx_err_t err; - ngx_int_t rc, depth; - ngx_uint_t overwrite, slash, dir, flags; - ngx_str_t path, uri, duri, args; - ngx_tree_ctx_t tree; - ngx_copy_file_t cf; - ngx_file_info_t fi; - ngx_table_elt_t *dest, *over; - ngx_ext_rename_file_t ext; - ngx_http_dav_copy_ctx_t copy; - ngx_http_dav_loc_conf_t *dlcf; + u_char *p, *host, *last, ch; + size_t len, root, alias; + ngx_err_t err; + ngx_int_t rc, depth; + ngx_uint_t overwrite, slash, dir, flags; + ngx_str_t path, uri, duri, args; + ngx_tree_ctx_t tree; + ngx_copy_file_t cf; + ngx_file_info_t fi; + ngx_table_elt_t *dest, *over; + ngx_ext_rename_file_t ext; + ngx_http_dav_copy_ctx_t copy; + ngx_http_dav_loc_conf_t *dlcf; + ngx_http_core_loc_conf_t *clcf; if (r->headers_in.content_length_n > 0 || r->headers_in.chunked) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, @@ -644,6 +645,22 @@ destination_done: return NGX_HTTP_CONFLICT; } + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + alias = clcf->alias; + + if (alias && alias != NGX_MAX_SIZE_T_VALUE) { + + if (alias > duri.len + || ngx_filename_cmp(duri.data, r->uri.data, alias) != 0) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "\"Destination\" URI \"%V\" must be " + "within location prefix when using \"alias\"", + &dest->value); + return NGX_HTTP_BAD_REQUEST; + } + } + depth = ngx_http_dav_depth(r, NGX_HTTP_DAV_INFINITY_DEPTH); if (depth != NGX_HTTP_DAV_INFINITY_DEPTH) { diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -1912,6 +1912,12 @@ ngx_http_map_uri_to_path(ngx_http_reques return NULL; } + if (alias > r->uri.len && alias != NGX_MAX_SIZE_T_VALUE) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "URI shorter than aliased URI part"); + return NULL; + } + if (clcf->root_lengths == NULL) { *root_length = clcf->root.len; From mdounin at mdounin.ru Tue Mar 31 04:02:53 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Tue, 31 Mar 2026 07:02:53 +0300 Subject: [nginx-tests] Tests: tests for DAV COPY with alias. Message-ID: details: http://freenginx.org/hg/nginx-tests/rev/1b153e4703cf branches: changeset: 2046:1b153e4703cf user: Maxim Dounin date: Tue Mar 31 07:01:04 2026 +0300 description: Tests: tests for DAV COPY with alias. diffstat: dav.t | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 files changed, 47 insertions(+), 1 deletions(-) diffs (72 lines): diff --git a/dav.t b/dav.t --- a/dav.t +++ b/dav.t @@ -21,7 +21,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http dav/)->plan(28); +my $t = Test::Nginx->new()->has(qw/http dav/)->plan(31); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -180,6 +180,8 @@ EOF like($r, qr/204 No Content/, 'copy file overwrite'); is(-s $t->testdir() . '/file.exist', 10, 'target file truncated'); +# tests in location with alias + $r = http(<testdir() . '/alias', 10, 'put alias size'); +$r = http(<has_version('1.29.7'); + +$r = http(<todo_alerts() + unless $t->has_version('1.29.7'); +todo_skip 'leaves coredump', 1 + unless $t->has_version('1.29.7') + or $ENV{TEST_NGINX_UNSAFE}; + +$r = http(< Hello! Changes with freenginx 1.29.7 31 Mar 2026 *) Feature: OpenSSL 4.0 compatibility. *) Bugfix: a segmentation fault might occur in a worker process if the ngx_http_dav_module was used to handle COPY and MOVE methods in a location with the "alias" directive. *) Bugfix: client host name was not escaped in requests to the authentication server and in the XCLIENT command if the "resolver" directive was used in the SMTP proxy. *) Bugfix: in the mail proxy module. ????????? ? freenginx 1.29.7 31.03.2026 *) ??????????: ????????????? ? OpenSSL 4.0. *) ???????????: ? ??????? ???????? ??? ????????? segmentation fault, ???? ?????? ngx_http_dav_module ????????????? ??? ????????? ??????? COPY ? MOVE ? location ? ?????????? alias. *) ???????????: ??? ????????????? ????????? resolver ? SMTP ??????-??????? ??? ????? ??????? ?? ?????????????? ??? ???????? ?? ?????? ?????????????? ? ? ??????? XCLIENT. *) ???????????: ? ???????? ??????-???????. -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Tue Mar 31 15:17:27 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Tue, 31 Mar 2026 18:17:27 +0300 Subject: [nginx] freenginx-1.29.7-RELEASE Message-ID: details: http://freenginx.org/hg/nginx/rev/cac0fa572138 branches: changeset: 9494:cac0fa572138 user: Maxim Dounin date: Tue Mar 31 18:07:23 2026 +0300 description: freenginx-1.29.7-RELEASE diffstat: docs/xml/nginx/changes.xml | 53 ++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 53 insertions(+), 0 deletions(-) diffs (63 lines): diff --git a/docs/xml/nginx/changes.xml b/docs/xml/nginx/changes.xml --- a/docs/xml/nginx/changes.xml +++ b/docs/xml/nginx/changes.xml @@ -7,6 +7,59 @@
+ + + + +????????????? ? OpenSSL 4.0. + + +OpenSSL 4.0 compatibility. + + + + + +? ??????? ???????? ??? ????????? segmentation fault, +???? ?????? ngx_http_dav_module ????????????? +??? ????????? ??????? COPY ? MOVE +? location ? ?????????? alias. + + +a segmentation fault might occur in a worker process +if the ngx_http_dav_module was used +to handle COPY and MOVE methods +in a location with the "alias" directive. + + + + + +??? ????????????? ????????? resolver ? SMTP ??????-??????? +??? ????? ??????? ?? ?????????????? +??? ???????? ?? ?????? ?????????????? +? ? ??????? XCLIENT. + + +client host name was not escaped +in requests to the authentication server +and in the XCLIENT command +if the "resolver" directive was used in the SMTP proxy. + + + + + +? ???????? ??????-???????. + + +in the mail proxy module. + + + + + + From mdounin at mdounin.ru Tue Mar 31 15:17:27 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Tue, 31 Mar 2026 18:17:27 +0300 Subject: [nginx] release-1.29.7 tag Message-ID: details: http://freenginx.org/hg/nginx/rev/74fe1629a1a4 branches: changeset: 9495:74fe1629a1a4 user: Maxim Dounin date: Tue Mar 31 18:07:24 2026 +0300 description: release-1.29.7 tag diffstat: .hgtags | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diffs (8 lines): diff --git a/.hgtags b/.hgtags --- a/.hgtags +++ b/.hgtags @@ -492,3 +492,4 @@ 2175d3bea2a84f7265b90c6f9efea7fb9b41bdd7 567870bfeb23f2e9c91e2a110d6d332c27c1ceb1 release-1.29.4 4f4280557d20bc46ebbdc240ffd365f5ca6ce939 release-1.29.5 e4207f631186855d37ac286799c8cd4c9477d166 release-1.29.6 +cac0fa5721386abbec57dcc2bb317f2531456e19 release-1.29.7 From mdounin at mdounin.ru Tue Mar 31 15:18:06 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Tue, 31 Mar 2026 18:18:06 +0300 Subject: [nginx-site] freenginx-1.29.7 Message-ID: details: http://freenginx.org/hg/nginx-site/rev/4f28af710772 branches: changeset: 3130:4f28af710772 user: Maxim Dounin date: Tue Mar 31 18:15:25 2026 +0300 description: freenginx-1.29.7 diffstat: text/en/CHANGES | 15 +++++++++++++++ text/ru/CHANGES.ru | 15 +++++++++++++++ xml/index.xml | 7 +++++++ xml/versions.xml | 1 + 4 files changed, 38 insertions(+), 0 deletions(-) diffs (74 lines): diff --git a/text/en/CHANGES b/text/en/CHANGES --- a/text/en/CHANGES +++ b/text/en/CHANGES @@ -1,4 +1,19 @@ +Changes with freenginx 1.29.7 31 Mar 2026 + + *) Feature: OpenSSL 4.0 compatibility. + + *) Bugfix: a segmentation fault might occur in a worker process if the + ngx_http_dav_module was used to handle COPY and MOVE methods in a + location with the "alias" directive. + + *) Bugfix: client host name was not escaped in requests to the + authentication server and in the XCLIENT command if the "resolver" + directive was used in the SMTP proxy. + + *) Bugfix: in the mail proxy module. + + Changes with freenginx 1.29.6 10 Mar 2026 *) Bugfix: incorrect "upstream server temporarily disabled" messages diff --git a/text/ru/CHANGES.ru b/text/ru/CHANGES.ru --- a/text/ru/CHANGES.ru +++ b/text/ru/CHANGES.ru @@ -1,4 +1,19 @@ +????????? ? freenginx 1.29.7 31.03.2026 + + *) ??????????: ????????????? ? OpenSSL 4.0. + + *) ???????????: ? ??????? ???????? ??? ????????? segmentation fault, + ???? ?????? ngx_http_dav_module ????????????? ??? ????????? ??????? + COPY ? MOVE ? location ? ?????????? alias. + + *) ???????????: ??? ????????????? ????????? resolver ? SMTP + ??????-??????? ??? ????? ??????? ?? ?????????????? ??? ???????? ?? + ?????? ?????????????? ? ? ??????? XCLIENT. + + *) ???????????: ? ???????? ??????-???????. + + ????????? ? freenginx 1.29.6 10.03.2026 *) ???????????: ??? ????????????? ?????????? ? proxy_pass ? ??? ????? diff --git a/xml/index.xml b/xml/index.xml --- a/xml/index.xml +++ b/xml/index.xml @@ -8,6 +8,13 @@ + + +freenginx-1.29.7 +mainline version has been released. + + + freenginx-1.29.6 diff --git a/xml/versions.xml b/xml/versions.xml --- a/xml/versions.xml +++ b/xml/versions.xml @@ -9,6 +9,7 @@ +