From 390767e6ec87e4957a1da6ba631790ff7e54fd3e Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 30 Mar 2026 19:26:17 +0400 Subject: [PATCH 01/25] Version bump. --- src/core/nginx.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/nginx.h b/src/core/nginx.h index c870991fc..6267a90c9 100644 --- a/src/core/nginx.h +++ b/src/core/nginx.h @@ -9,8 +9,8 @@ #define _NGINX_H_INCLUDED_ -#define nginx_version 1029007 -#define NGINX_VERSION "1.29.7" +#define nginx_version 1029008 +#define NGINX_VERSION "1.29.8" #define NGINX_VER "nginx/" NGINX_VERSION #ifdef NGX_BUILD From 0d025b4a9483b18237243c0aaf9b8d4201aebcd8 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 10 Mar 2026 16:28:04 +0400 Subject: [PATCH 02/25] SSL: compatibility with OpenSSL 4.0. X509_get_issuer_name() and X509_get_subject_name() were changed to return a const value. Since it is passed to functions with a non const argument in older versions, the const modifier is conditionally compiled as needed. ASN1_INTEGER was made opaque. ASN1_STRING accessors are used to preserve the behaviour. ASN1_STRING_get0_data() compat shim is provided for OpenSSL < 1.1.0 where it does not exist. --- src/event/ngx_event_openssl.c | 25 ++++++++++++++++++++++--- src/event/ngx_event_openssl.h | 5 +++++ src/event/ngx_event_openssl_stapling.c | 15 +++++++++------ 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index d1386d3a6..99ec65444 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -948,6 +948,10 @@ ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, char *err; X509 *x509; X509_NAME *name; +#if (OPENSSL_VERSION_NUMBER >= 0x40000000L) + const +#endif + X509_NAME *sname; X509_STORE *store; STACK_OF(X509) *chain; STACK_OF(X509_NAME) *list; @@ -1003,8 +1007,8 @@ ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, return NGX_ERROR; } - name = X509_get_subject_name(x509); - if (name == NULL) { + sname = X509_get_subject_name(x509); + if (sname == NULL) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_get_subject_name(\"%s\") failed", cert->data); sk_X509_NAME_pop_free(list, X509_NAME_free); @@ -1012,7 +1016,7 @@ ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, return NGX_ERROR; } - name = X509_NAME_dup(name); + name = X509_NAME_dup(sname); if (name == NULL) { sk_X509_NAME_pop_free(list, X509_NAME_free); sk_X509_pop_free(chain, X509_free); @@ -1197,6 +1201,9 @@ ngx_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store) 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; @@ -6012,6 +6019,9 @@ ngx_ssl_get_subject_dn(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) { BIO *bio; X509 *cert; +#if (OPENSSL_VERSION_NUMBER >= 0x40000000L) + const +#endif X509_NAME *name; s->len = 0; @@ -6066,6 +6076,9 @@ ngx_ssl_get_issuer_dn(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) { BIO *bio; X509 *cert; +#if (OPENSSL_VERSION_NUMBER >= 0x40000000L) + const +#endif X509_NAME *name; s->len = 0; @@ -6122,6 +6135,9 @@ ngx_ssl_get_subject_dn_legacy(ngx_connection_t *c, ngx_pool_t *pool, char *p; size_t len; X509 *cert; +#if (OPENSSL_VERSION_NUMBER >= 0x40000000L) + const +#endif X509_NAME *name; s->len = 0; @@ -6170,6 +6186,9 @@ ngx_ssl_get_issuer_dn_legacy(ngx_connection_t *c, ngx_pool_t *pool, 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.h b/src/event/ngx_event_openssl.h index d86ffb8da..79ae39503 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -67,6 +67,11 @@ #endif +#if (OPENSSL_VERSION_NUMBER < 0x1010000fL) +#define ASN1_STRING_get0_data(x) (x)->data +#endif + + #if (OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined SSL_get_peer_certificate) #define SSL_get_peer_certificate(s) SSL_get1_peer_certificate(s) #endif diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c index a0a8031c7..0f560f17d 100644 --- a/src/event/ngx_event_openssl_stapling.c +++ b/src/event/ngx_event_openssl_stapling.c @@ -2667,9 +2667,10 @@ ngx_ssl_ocsp_cache_store(ngx_ssl_ocsp_ctx_t *ctx) 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; + ngx_int_t length; + ASN1_INTEGER *serial; + const X509_NAME *name; p = ngx_pnalloc(ctx->pool, 60); if (p == NULL) { @@ -2693,12 +2694,14 @@ ngx_ssl_ocsp_create_key(ngx_ssl_ocsp_ctx_t *ctx) p += 20; serial = X509_get_serialNumber(ctx->cert); - if (serial->length > 20) { + length = ASN1_STRING_length(serial); + + if (length > 20) { return NGX_ERROR; } - p = ngx_cpymem(p, serial->data, serial->length); - ngx_memzero(p, 20 - serial->length); + p = ngx_cpymem(p, ASN1_STRING_get0_data(serial), length); + ngx_memzero(p, 20 - length); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0, "ssl ocsp key %xV", &ctx->key); From 9fd94af4ddfca2f1f725744d6e4a578efef776e4 Mon Sep 17 00:00:00 2001 From: xuruidong Date: Thu, 19 Mar 2026 21:02:05 +0800 Subject: [PATCH 03/25] Update CONTRIBUTING.md --- CONTRIBUTING.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4436414f3..19033141f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,9 +68,14 @@ to 76 characters code; examples include "Upstream:", "QUIC:", or "Core:"; see the commit history to get an idea of the prefixes used -- Reference issues in the the subject line; if the commit fixes an issue, -[name it](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) -accordingly +- If the commit fixes an open issue then you can use the "Closes:" tag/trailer +to reference it and have GitHub automatically close it once it's been merged. +E.g.: + + `Closes: https://github.com/nginx/nginx/issues/9999` + + That should go at the end of the commit message, separated by a blank line, + along with any other tags. ### Before Submitting From 7924a4ec6cb35291ea60a5f2a70ac0a034d94ff7 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 2 Apr 2026 17:41:56 +0400 Subject: [PATCH 04/25] Upstream: fixed processing multiple 103 (early hints) responses. The second 103 response in a row was treated as the final response header. --- src/http/ngx_http_upstream.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index f177f0a28..c84defaa9 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -2550,6 +2550,8 @@ ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u) u->response_received = 1; +again: + rc = u->process_header(r); if (rc == NGX_AGAIN) { @@ -2570,11 +2572,7 @@ ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u) rc = ngx_http_upstream_process_early_hints(r, u); if (rc == NGX_OK) { - rc = u->process_header(r); - - if (rc == NGX_AGAIN) { - continue; - } + goto again; } } From 2ff1a969f3040f27ac2610e9840a4e802bcc39cc Mon Sep 17 00:00:00 2001 From: Eugene Grebenschikov Date: Wed, 1 Apr 2026 11:03:21 -0700 Subject: [PATCH 05/25] Removed CLOCK_MONOTONIC_FAST support. CLOCK_MONOTONIC_FAST, like CLOCK_MONOTONIC_COARSE, has low accuracy. It shows noticeable timing variation for short intervals, which is visible in metrics like $upstream_response_time for fast upstream responses. This change complements the work started in commit f29d7ade5. In addition to the reasons described in f29d7ade5, the performance of CLOCK_MONOTONIC is good enough on modern hardware when using a TSC timecounter. This is especially true when it is accessed through a shared page, as implemented in FreeBSD 10.0 (see git commits 869fd80fd449 and aea810386d8e for details). Co-authored-by: Sergey Kandaurov --- src/core/ngx_times.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/core/ngx_times.c b/src/core/ngx_times.c index 16788c98c..b0057d2ab 100644 --- a/src/core/ngx_times.c +++ b/src/core/ngx_times.c @@ -198,11 +198,7 @@ ngx_monotonic_time(time_t sec, ngx_uint_t msec) #if (NGX_HAVE_CLOCK_MONOTONIC) struct timespec ts; -#if defined(CLOCK_MONOTONIC_FAST) - clock_gettime(CLOCK_MONOTONIC_FAST, &ts); -#else clock_gettime(CLOCK_MONOTONIC, &ts); -#endif sec = ts.tv_sec; msec = ts.tv_nsec / 1000000; From 06c30ec29d392af00157c0b0eecbc545b330e50f Mon Sep 17 00:00:00 2001 From: David Korczynski Date: Wed, 4 Mar 2026 01:27:45 -0800 Subject: [PATCH 06/25] Upstream: fix integer underflow in charset parsing The issue described below was only reproducible prior to https://github.com/nginx/nginx/commit/7924a4ec6cb35291ea60a5f2a70ac0a034d94ff7 When parsing the `charset` parameter in the `Content-Type` header within `ngx_http_upstream_copy_content_type`, an input such as `charset="` resulted in an integer underflow. In this scenario, both `p` and `last` point to the position immediately following the opening quote. The logic to strip a trailing quote checked `*(last - 1)` without verifying that `last > p`. This caused `last` to be decremented to point to the opening quote itself, making `last < p`. The subsequent length calculation `r->headers_out.charset.len = last - p` resulted in -1, which wrapped to `SIZE_MAX` as `len` is a `size_t`. This invalid length was later passed to `ngx_cpymem` in `ngx_http_header_filter`, leading to an out-of-bounds memory access (detected as `negative-size-param` by AddressSanitizer). The fix ensures `last > p` before attempting to strip a trailing quote, correctly resulting in a zero-length charset for malformed input. The oss-fuzz payload that triggers this issue holds multiple 103 status lines, and it's a sequence of 2 of those Content-Type headers that trigger the ASAN report. Co-authored-by: CodeMender Fixes: https://issues.oss-fuzz.com/issues/486561029 Signed-off-by: David Korczynski --- src/http/ngx_http_upstream.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index c84defaa9..918323d9b 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -5652,7 +5652,7 @@ ngx_http_upstream_copy_content_type(ngx_http_request_t *r, ngx_table_elt_t *h, last = h->value.data + h->value.len; - if (*(last - 1) == '"') { + if (last > p && *(last - 1) == '"') { last--; } From 365694160a85229a7cb006738de9260d49ff5fa2 Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Fri, 24 May 2024 00:20:01 +0300 Subject: [PATCH 07/25] Added max_headers directive. The directive limits the number of request headers accepted from clients. While the total amount of headers is believed to be sufficiently limited by the existing buffer size limits (client_header_buffer_size and large_client_header_buffers), the additional limit on the number of headers might be beneficial to better protect backend servers. Requested by Maksim Yevmenkin. Signed-off-by: Elijah Zupancic Origin: --- src/http/ngx_http_core_module.c | 10 ++++++++++ src/http/ngx_http_core_module.h | 2 ++ src/http/ngx_http_request.c | 9 +++++++++ src/http/ngx_http_request.h | 1 + src/http/v2/ngx_http_v2.c | 9 +++++++++ src/http/v3/ngx_http_v3_request.c | 9 +++++++++ 6 files changed, 40 insertions(+) diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index a2ff53f82..0c46106db 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -252,6 +252,13 @@ static ngx_command_t ngx_http_core_commands[] = { offsetof(ngx_http_core_srv_conf_t, large_client_header_buffers), NULL }, + { ngx_string("max_headers"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_core_srv_conf_t, max_headers), + NULL }, + { ngx_string("ignore_invalid_headers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, @@ -3511,6 +3518,7 @@ ngx_http_core_create_srv_conf(ngx_conf_t *cf) cscf->request_pool_size = NGX_CONF_UNSET_SIZE; cscf->client_header_timeout = NGX_CONF_UNSET_MSEC; cscf->client_header_buffer_size = NGX_CONF_UNSET_SIZE; + cscf->max_headers = NGX_CONF_UNSET_UINT; cscf->ignore_invalid_headers = NGX_CONF_UNSET; cscf->merge_slashes = NGX_CONF_UNSET; cscf->underscores_in_headers = NGX_CONF_UNSET; @@ -3552,6 +3560,8 @@ ngx_http_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) return NGX_CONF_ERROR; } + ngx_conf_merge_uint_value(conf->max_headers, prev->max_headers, 1000); + ngx_conf_merge_value(conf->ignore_invalid_headers, prev->ignore_invalid_headers, 1); diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index 6062d3a23..a13d7ade5 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -199,6 +199,8 @@ typedef struct { ngx_msec_t client_header_timeout; + ngx_uint_t max_headers; + ngx_flag_t ignore_invalid_headers; ngx_flag_t merge_slashes; ngx_flag_t underscores_in_headers; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 7305af132..a9573a620 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -1494,6 +1494,15 @@ ngx_http_process_request_headers(ngx_event_t *rev) /* a header line has been parsed successfully */ + if (r->headers_in.count++ >= cscf->max_headers) { + r->lingering_close = 1; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent too many header lines"); + ngx_http_finalize_request(r, + NGX_HTTP_REQUEST_HEADER_TOO_LARGE); + break; + } + h = ngx_list_push(&r->headers_in.headers); if (h == NULL) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index 7a77498eb..48eb43eb0 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -184,6 +184,7 @@ typedef struct { typedef struct { ngx_list_t headers; + ngx_uint_t count; ngx_table_elt_t *host; ngx_table_elt_t *connection; diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index 49ea25ede..efe22903f 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -1823,6 +1823,15 @@ ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos, } } else { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (r->headers_in.count++ >= cscf->max_headers) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent too many header lines"); + ngx_http_finalize_request(r, NGX_HTTP_REQUEST_HEADER_TOO_LARGE); + goto error; + } + h = ngx_list_push(&r->headers_in.headers); if (h == NULL) { return ngx_http_v2_connection_error(h2c, diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 6865e1466..7bb61311d 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -665,6 +665,15 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, } } else { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (r->headers_in.count++ >= cscf->max_headers) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent too many header lines"); + ngx_http_finalize_request(r, NGX_HTTP_REQUEST_HEADER_TOO_LARGE); + return NGX_ERROR; + } + h = ngx_list_push(&r->headers_in.headers); if (h == NULL) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); From 067d766f210ee914b750d79d9284cbf8801058f3 Mon Sep 17 00:00:00 2001 From: Zoey Date: Sun, 5 Apr 2026 11:31:15 +0200 Subject: [PATCH 08/25] Fix $request_port and $is_request_port in subrequests Closes #1247. --- src/http/ngx_http_core_module.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 0c46106db..53ddf39bb 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -2453,6 +2453,8 @@ ngx_http_subrequest(ngx_http_request_t *r, sr->method = NGX_HTTP_GET; sr->http_version = r->http_version; + sr->port = r->port; + sr->request_line = r->request_line; sr->uri = *uri; From 1709bffe6ebb5bfd4d71893d65920fdc4bf82f65 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sun, 15 Mar 2026 15:56:01 +0000 Subject: [PATCH 09/25] Upstream: reset early_hints_length on upstream reinit. When a request was retried to a new upstream after receiving 103 Early Hints from the previous one, the accumulated early_hints_length was not reset, causing valid early hints from the next upstream to be incorrectly rejected as "too big". --- src/http/ngx_http_upstream.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index 918323d9b..b64561369 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -2067,6 +2067,7 @@ ngx_http_upstream_reinit(ngx_http_request_t *r, ngx_http_upstream_t *u) return NGX_ERROR; } + u->early_hints_length = 0; u->keepalive = 0; u->upgrade = 0; u->error = 0; From 5eaf45f11e85459b52c18f876e69320df420ae29 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 6 Apr 2026 21:40:05 +0400 Subject: [PATCH 10/25] nginx-1.29.8-RELEASE --- docs/xml/nginx/changes.xml | 56 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/docs/xml/nginx/changes.xml b/docs/xml/nginx/changes.xml index faa7dabf9..eb1987552 100644 --- a/docs/xml/nginx/changes.xml +++ b/docs/xml/nginx/changes.xml @@ -5,6 +5,62 @@ + + + + +директива max_headers.
+Спасибо Максиму Дунину. +
+ +the "max_headers" directive.
+Thanks to Maxim Dounin. +
+
+ + + +совместимость с OpenSSL 4.0. + + +OpenSSL 4.0 compatibility. + + + + + +теперь директива include внутри блока geo поддерживает маски. + + +now the "include" directive inside the "geo" block supports wildcards. + + + + + +в обработке ответов с кодом HTTP 103 (Early Hints) +от проксируемого бэкенда. + + +in processing of HTTP 103 (Early Hints) responses +from a proxied backend. + + + + + +переменные $request_port и $is_request_port +были недоступны в подзапросах. + + +the $request_port and $is_request_port variables +were not available in subrequests. + + + +
+ + From 9772267278f07fd6c2bd66f22eefbae2acfa5e68 Mon Sep 17 00:00:00 2001 From: Andrew Clayton Date: Mon, 13 Apr 2026 14:18:45 +0100 Subject: [PATCH 11/25] Version bump --- src/core/nginx.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/nginx.h b/src/core/nginx.h index 6267a90c9..f4ccc6183 100644 --- a/src/core/nginx.h +++ b/src/core/nginx.h @@ -9,8 +9,8 @@ #define _NGINX_H_INCLUDED_ -#define nginx_version 1029008 -#define NGINX_VERSION "1.29.8" +#define nginx_version 1031000 +#define NGINX_VERSION "1.31.0" #define NGINX_VER "nginx/" NGINX_VERSION #ifdef NGX_BUILD From b761b0485b5bdc49ce9124126530fe575eb80627 Mon Sep 17 00:00:00 2001 From: Andrew Clayton Date: Wed, 1 Apr 2026 19:17:55 +0100 Subject: [PATCH 12/25] GH: add a workflow to check for the 'version bump' commit This checks pull-requests to make sure the 'Version bump' commit is immediately after the last release commit/tag. The check includes the commits in the pull-request, so if a pull-request is adding this commit it will accept it and also if there are other commits in the pull-request, as long as the 'Version bump' commit is first. --- .github/workflows/check-version-bump.yaml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/check-version-bump.yaml diff --git a/.github/workflows/check-version-bump.yaml b/.github/workflows/check-version-bump.yaml new file mode 100644 index 000000000..d7e12c8e3 --- /dev/null +++ b/.github/workflows/check-version-bump.yaml @@ -0,0 +1,23 @@ +name: Check Version Bump + +on: + pull_request: + types: [ opened, synchronize ] + +jobs: + check-version-bump: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + fetch-depth: 0 + + - name: Check git log + run: | + echo "## Check for 'Version bump' commit" >${GITHUB_STEP_SUMMARY} + subj=$(git log --format=%s $(git describe --abbrev=0 --tags).. | tail -1) + if ! expr "$subj" : 'Version bump' >/dev/null + then + echo "❌ No 'Version bump' commit immediately after release tag" | tee -a ${GITHUB_STEP_SUMMARY} + exit 2 + fi From 00979ba9d843be266529067285b635070f2d1993 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 8 Apr 2026 16:51:07 +0400 Subject: [PATCH 13/25] Remove Proxy-Connection HTTP upstream header As per RFC 9110, this header SHOULD be removed by a proxy. Also, as per RFC 9113, this header MUST be removed when proxying to an HTTP/2 backend. --- src/http/modules/ngx_http_grpc_module.c | 1 + src/http/modules/ngx_http_proxy_module.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/http/modules/ngx_http_grpc_module.c b/src/http/modules/ngx_http_grpc_module.c index cc3aebe59..4a47b74c5 100644 --- a/src/http/modules/ngx_http_grpc_module.c +++ b/src/http/modules/ngx_http_grpc_module.c @@ -514,6 +514,7 @@ static ngx_keyval_t ngx_http_grpc_headers[] = { { ngx_string("TE"), ngx_string("$grpc_internal_trailers") }, { ngx_string("Host"), ngx_string("") }, { ngx_string("Connection"), ngx_string("") }, + { ngx_string("Proxy-Connection"), ngx_string("") }, { ngx_string("Transfer-Encoding"), ngx_string("") }, { ngx_string("Keep-Alive"), ngx_string("") }, { ngx_string("Expect"), ngx_string("") }, diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c index 0b388b30f..e33dc37fd 100644 --- a/src/http/modules/ngx_http_proxy_module.c +++ b/src/http/modules/ngx_http_proxy_module.c @@ -747,6 +747,7 @@ static char ngx_http_proxy_version_11[] = " HTTP/1.1" CRLF; static ngx_keyval_t ngx_http_proxy_headers[] = { { ngx_string("Host"), ngx_string("$proxy_internal_host") }, { ngx_string("Connection"), ngx_string("") }, + { ngx_string("Proxy-Connection"), ngx_string("") }, { ngx_string("Content-Length"), ngx_string("$proxy_internal_body_length") }, { ngx_string("Transfer-Encoding"), ngx_string("$proxy_internal_chunked") }, { ngx_string("TE"), ngx_string("") }, @@ -775,6 +776,7 @@ static ngx_str_t ngx_http_proxy_hide_headers[] = { static ngx_keyval_t ngx_http_proxy_cache_headers[] = { { ngx_string("Host"), ngx_string("$proxy_internal_host") }, { ngx_string("Connection"), ngx_string("") }, + { ngx_string("Proxy-Connection"), ngx_string("") }, { ngx_string("Content-Length"), ngx_string("$proxy_internal_body_length") }, { ngx_string("Transfer-Encoding"), ngx_string("$proxy_internal_chunked") }, { ngx_string("TE"), ngx_string("") }, From d3a76322cf7abedb32b8216d1e5c0cef4858e4d4 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 8 Apr 2026 17:19:24 +0400 Subject: [PATCH 14/25] Restrict connection-specific headers in HTTP/2 and HTTP/3 As per RFC 9113 and RFC 9114, any message containing such headers MUST be treated as malformed. As per RFC 9110, Section 7.6.1, the following headers are considered connection-specific: - Connection - Proxy-Connection - Keep-Alive - TE - Transfer-Encoding - Upgrade The only exception is the TE header field, which MAY be present in a request header, but it MUST NOT contain any value other than "trailers". --- src/http/ngx_http_request.c | 20 ++++++++++++++++ src/http/v2/ngx_http_v2.c | 39 +++++++++++++++++++++++++++++++ src/http/v3/ngx_http_v3_request.c | 39 +++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index a9573a620..a5cd44dcc 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -26,6 +26,8 @@ static ngx_int_t ngx_http_process_host(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_process_connection(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_process_proxy_connection(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_process_user_agent(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); @@ -82,6 +84,9 @@ ngx_http_header_t ngx_http_headers_in[] = { { ngx_string("Connection"), offsetof(ngx_http_headers_in_t, connection), ngx_http_process_connection }, + { ngx_string("Proxy-Connection"), 0, + ngx_http_process_proxy_connection }, + { ngx_string("If-Modified-Since"), offsetof(ngx_http_headers_in_t, if_modified_since), ngx_http_process_unique_header_line }, @@ -1928,6 +1933,21 @@ ngx_http_process_connection(ngx_http_request_t *r, ngx_table_elt_t *h, } +static ngx_int_t +ngx_http_process_proxy_connection(ngx_http_request_t *r, ngx_table_elt_t *h, + ngx_uint_t offset) +{ + if (r->http_version >= NGX_HTTP_VERSION_20) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent \"Proxy-Connection\" header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + return NGX_OK; +} + + static ngx_int_t ngx_http_process_user_agent(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index efe22903f..336718bad 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -3820,6 +3820,45 @@ ngx_http_v2_run_request(ngx_http_request_t *r) r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE; + if (r->headers_in.connection) { + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client sent \"Connection\" header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + goto failed; + } + + if (r->headers_in.keep_alive) { + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client sent \"Keep-Alive\" header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + goto failed; + } + + if (r->headers_in.transfer_encoding) { + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client sent \"Transfer-Encoding\" header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + goto failed; + } + + if (r->headers_in.upgrade) { + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client sent \"Upgrade\" header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + goto failed; + } + + if (r->headers_in.te + && (r->headers_in.te->value.len != 8 + || ngx_strncasecmp(r->headers_in.te->value.data, + (u_char *) "trailers", 8) != 0)) + { + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client sent invalid \"TE\" header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + goto failed; + } + if (r->headers_in.server.len == 0) { ngx_log_error(NGX_LOG_INFO, fc->log, 0, "client sent neither \":authority\" nor \"Host\" header"); diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 7bb61311d..3b0fdbe98 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -1021,6 +1021,45 @@ ngx_http_v3_process_request_header(ngx_http_request_t *r) c = r->connection; + if (r->headers_in.connection) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent \"Connection\" header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + if (r->headers_in.keep_alive) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent \"Keep-Alive\" header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + if (r->headers_in.transfer_encoding) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent \"Transfer-Encoding\" header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + if (r->headers_in.upgrade) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent \"Upgrade\" header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + if (r->headers_in.te + && (r->headers_in.te->value.len != 8 + || ngx_strncasecmp(r->headers_in.te->value.data, + (u_char *) "trailers", 8) != 0)) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent invalid \"TE\" header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) { return NGX_ERROR; } From bbaac1d43fdd20e3e42f72e2ed48f325ff8dd873 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 14 Apr 2026 16:48:30 +0400 Subject: [PATCH 15/25] Updated OpenSSL used for win32 builds --- misc/GNUmakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/GNUmakefile b/misc/GNUmakefile index 165715182..d6fc11036 100644 --- a/misc/GNUmakefile +++ b/misc/GNUmakefile @@ -6,7 +6,7 @@ TEMP = tmp CC = cl OBJS = objs.msvc8 -OPENSSL = openssl-3.5.5 +OPENSSL = openssl-3.5.6 ZLIB = zlib-1.3.2 PCRE = pcre2-10.47 From 06c9d63fc8a2510a0568cc8b83147add987748cb Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Mon, 6 May 2024 00:06:15 +0300 Subject: [PATCH 16/25] SSL: logging level of all "SSL alert number N" errors. Errors about alerts received from peers are generated by OpenSSL by adding peer-provided alert description (from 0 to 255) to SSL_AD_REASON_OFFSET. All such errors, including ones for unknown alerts, are now logged at the "info" level, as these can be caused by a misbehaving client. Signed-off-by: Aleksei Bavshin Origin: --- src/event/ngx_event_openssl.c | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 99ec65444..a466ec2b9 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -3993,33 +3993,8 @@ ngx_ssl_connection_error(ngx_connection_t *c, int sslerr, ngx_err_t err, #ifdef SSL_R_BAD_RECORD_TYPE || n == SSL_R_BAD_RECORD_TYPE /* 443 */ #endif - || n == 1000 /* SSL_R_SSLV3_ALERT_CLOSE_NOTIFY */ -#ifdef SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE - || n == SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE /* 1010 */ - || n == SSL_R_SSLV3_ALERT_BAD_RECORD_MAC /* 1020 */ - || n == SSL_R_TLSV1_ALERT_DECRYPTION_FAILED /* 1021 */ - || n == SSL_R_TLSV1_ALERT_RECORD_OVERFLOW /* 1022 */ - || n == SSL_R_SSLV3_ALERT_DECOMPRESSION_FAILURE /* 1030 */ - || n == SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE /* 1040 */ - || n == SSL_R_SSLV3_ALERT_NO_CERTIFICATE /* 1041 */ - || n == SSL_R_SSLV3_ALERT_BAD_CERTIFICATE /* 1042 */ - || n == SSL_R_SSLV3_ALERT_UNSUPPORTED_CERTIFICATE /* 1043 */ - || n == SSL_R_SSLV3_ALERT_CERTIFICATE_REVOKED /* 1044 */ - || n == SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED /* 1045 */ - || n == SSL_R_SSLV3_ALERT_CERTIFICATE_UNKNOWN /* 1046 */ - || n == SSL_R_SSLV3_ALERT_ILLEGAL_PARAMETER /* 1047 */ - || n == SSL_R_TLSV1_ALERT_UNKNOWN_CA /* 1048 */ - || n == SSL_R_TLSV1_ALERT_ACCESS_DENIED /* 1049 */ - || n == SSL_R_TLSV1_ALERT_DECODE_ERROR /* 1050 */ - || n == SSL_R_TLSV1_ALERT_DECRYPT_ERROR /* 1051 */ - || n == SSL_R_TLSV1_ALERT_EXPORT_RESTRICTION /* 1060 */ - || n == SSL_R_TLSV1_ALERT_PROTOCOL_VERSION /* 1070 */ - || n == SSL_R_TLSV1_ALERT_INSUFFICIENT_SECURITY /* 1071 */ - || n == SSL_R_TLSV1_ALERT_INTERNAL_ERROR /* 1080 */ - || n == SSL_R_TLSV1_ALERT_USER_CANCELLED /* 1090 */ - || n == SSL_R_TLSV1_ALERT_NO_RENEGOTIATION /* 1100 */ -#endif - || n == 1121 /* SSL_R_TLSV1_ALERT_ECH_REQUIRED */ + || (n >= SSL_AD_REASON_OFFSET /* 1000 */ + && n <= SSL_AD_REASON_OFFSET + 255) ) { switch (c->log_error) { From aa09c199921fd104b42c234085146caa9c10b0c3 Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Mon, 6 May 2024 00:07:18 +0300 Subject: [PATCH 17/25] SSL: logging level of "invalid alert" errors. The SSL_R_INVALID_ALERT ("invalid alert") errors are reported by OpenSSL 1.1.1 or newer if the client sends a malformed alert. These errors are now logged at the "info" level. Signed-off-by: Aleksei Bavshin Origin: --- src/event/ngx_event_openssl.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index a466ec2b9..74b2b0066 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -3897,6 +3897,9 @@ ngx_ssl_connection_error(ngx_connection_t *c, int sslerr, ngx_err_t err, || n == SSL_R_NO_SHARED_CIPHER /* 193 */ #ifdef SSL_R_PACKET_LENGTH_TOO_LONG || n == SSL_R_PACKET_LENGTH_TOO_LONG /* 198 */ +#endif +#ifdef SSL_R_INVALID_ALERT + || n == SSL_R_INVALID_ALERT /* 205 */ #endif || n == SSL_R_RECORD_LENGTH_MISMATCH /* 213 */ #ifdef SSL_R_TOO_MANY_WARNING_ALERTS From abc72c5a57890ba4ce235fe3339dbf31af40604e Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Tue, 7 Apr 2026 09:26:40 -0700 Subject: [PATCH 18/25] SSL: compatibility with renamed error codes in OpenSSL 4.0. SSL_R_SSL3_SESSION_ID_TOO_LONG is no longer available when building OpenSSL 4.0 with no-deprecated. Added the new name as a fallback. --- src/event/ngx_event_openssl.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 74b2b0066..f04a462bf 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -3960,6 +3960,8 @@ ngx_ssl_connection_error(ngx_connection_t *c, int sslerr, ngx_err_t err, #endif #ifdef SSL_R_SSL3_SESSION_ID_TOO_LONG || n == SSL_R_SSL3_SESSION_ID_TOO_LONG /* 300 */ +#elif (defined SSL_R_TLS_SESSION_ID_TOO_LONG) + || n == SSL_R_TLS_SESSION_ID_TOO_LONG /* 300 */ #endif #ifdef SSL_R_BAD_ECPOINT || n == SSL_R_BAD_ECPOINT /* 306 */ From 4dd7ec9ae48df272cef2a1ecd4de0a237783828a Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 15 Apr 2026 22:12:28 +0400 Subject: [PATCH 19/25] QUIC: always populate ngx_quic_cbs_recv_rcd() output arguments Although uninitialized values aren't used in practice due to the nature of the OpenSSL code flow, this violates the API contract. Reported by lukefr09 on GitHub. --- src/event/quic/ngx_event_quic_ssl.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 18992ae1b..4e84f8102 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -158,6 +158,7 @@ ngx_quic_cbs_recv_rcd(ngx_ssl_conn_t *ssl_conn, if (b->sync) { /* hole */ + *data = NULL; *bytes_read = 0; break; @@ -169,6 +170,9 @@ ngx_quic_cbs_recv_rcd(ngx_ssl_conn_t *ssl_conn, break; } + *data = NULL; + *bytes_read = 0; + return 1; } From ea72fa1d92af638e23def2da1790b9b0566bd23b Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 15 Apr 2026 22:22:43 +0400 Subject: [PATCH 20/25] QUIC: simplified ngx_quic_cbs_recv_rcd() There's no need in for-loop, a single buffer is fed at a time. --- src/event/quic/ngx_event_quic_ssl.c | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 4e84f8102..705f39f6c 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -152,27 +152,19 @@ ngx_quic_cbs_recv_rcd(ngx_ssl_conn_t *ssl_conn, qc = ngx_quic_get_connection(c); ctx = ngx_quic_get_send_ctx(qc, qc->read_level); - for (cl = ctx->crypto.chain; cl; cl = cl->next) { + cl = ctx->crypto.chain; + + if (cl == NULL || cl->buf->sync) { + *data = NULL; + *bytes_read = 0; + + } else { b = cl->buf; - if (b->sync) { - /* hole */ - - *data = NULL; - *bytes_read = 0; - - break; - } - *data = b->pos; *bytes_read = b->last - b->pos; - - break; } - *data = NULL; - *bytes_read = 0; - return 1; } From ff8221b4db29b1d31ef31f01d989a57ac35a9dd0 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 16 Apr 2026 17:57:13 +0400 Subject: [PATCH 21/25] SSL: logging level of "record layer failure" errors The SSL_R_RECORD_LAYER_FAILURE ("record layer failure") errors are reported by OpenSSL 3.2 or newer as the last record layer error for various low level read errors. Further, a976e6b9e (1.23.4) caused to always log them at the "crit" level. For example, the following errors are observed on OpenSSL 3.2.0 - 4.0: SSL_read() failed (SSL: error:0A000119:SSL routines::decryption failed or bad record mac error:0A000139:SSL routines::record layer failure) SSL_read() failed (SSL: error:1C800066:Provider routines::cipher operation failed error:0A000119:SSL routines::decryption failed or bad record mac error:0A000139:SSL routines::record layer failure) SSL_read() failed (SSL: error:0A00010B:SSL routines::wrong version number error:0A000139:SSL routines::record layer failure) These errors are now logged at the "info" level. Closes: https://github.com/nginx/nginx/issues/961 Co-authored-by: Smeet23 --- src/event/ngx_event_openssl.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index f04a462bf..1653be0c3 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -3966,6 +3966,9 @@ ngx_ssl_connection_error(ngx_connection_t *c, int sslerr, ngx_err_t err, #ifdef SSL_R_BAD_ECPOINT || n == SSL_R_BAD_ECPOINT /* 306 */ #endif +#ifdef SSL_R_RECORD_LAYER_FAILURE + || n == SSL_R_RECORD_LAYER_FAILURE /* 313 */ +#endif #ifdef SSL_R_RENEGOTIATE_EXT_TOO_LONG || n == SSL_R_RENEGOTIATE_EXT_TOO_LONG /* 335 */ || n == SSL_R_RENEGOTIATION_ENCODING_ERR /* 336 */ From 4e89ce224f0b3fe9c1d1bc42eca0a7afecdcafb6 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 15 Apr 2026 13:49:00 +0400 Subject: [PATCH 22/25] Restrict duplicate TE headers in HTTP/2 and HTTP/3 Following d3a76322cf7a, this change rejects requests which have multiple TE headers. Reported-by: geeknik --- src/http/v2/ngx_http_v2.c | 3 ++- src/http/v3/ngx_http_v3_request.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index 336718bad..69cb0ae09 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -3849,7 +3849,8 @@ ngx_http_v2_run_request(ngx_http_request_t *r) } if (r->headers_in.te - && (r->headers_in.te->value.len != 8 + && (r->headers_in.te->next + || r->headers_in.te->value.len != 8 || ngx_strncasecmp(r->headers_in.te->value.data, (u_char *) "trailers", 8) != 0)) { diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 3b0fdbe98..6b487289a 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -1050,7 +1050,8 @@ ngx_http_v3_process_request_header(ngx_http_request_t *r) } if (r->headers_in.te - && (r->headers_in.te->value.len != 8 + && (r->headers_in.te->next + || r->headers_in.te->value.len != 8 || ngx_strncasecmp(r->headers_in.te->value.data, (u_char *) "trailers", 8) != 0)) { From d7dd7e9ae4c2110f753cac02fd702eefef9fce16 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 10 Apr 2026 21:42:18 +0400 Subject: [PATCH 23/25] HTTP/3: optimize encoder stream memory usage Previously, the encoder stream allocated each new inserted field in the connection pool. This memory was not freed until the end of the connection. Now a special insert buffer is used for all inserts. --- src/http/v3/ngx_http_v3_parse.c | 30 +++++++++++++++++++++++++++--- src/http/v3/ngx_http_v3_parse.h | 1 + src/http/v3/ngx_http_v3_table.c | 22 ++++++++++++++++++++++ src/http/v3/ngx_http_v3_table.h | 2 ++ 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index bcbf0dbe1..1ba08c791 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -633,9 +633,23 @@ ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, st->huffstate = 0; } - st->last = ngx_pnalloc(c->pool, n + 1); - if (st->last == NULL) { - return NGX_ERROR; + if (st->buf) { + if ((size_t) (st->buf->end - st->buf->last) < n + 1) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "not enough dynamic table capacity"); + + st->last = NULL; + return NGX_ERROR; + } + + st->last = st->buf->last; + st->buf->last += n + 1; + + } else { + st->last = ngx_pnalloc(c->pool, n + 1); + if (st->last == NULL) { + return NGX_ERROR; + } } st->value.data = st->last; @@ -1486,6 +1500,11 @@ ngx_http_v3_parse_field_inr(ngx_connection_t *c, ch = *b->pos; + st->literal.buf = ngx_http_v3_get_insert_buffer(c); + if (st->literal.buf == NULL) { + return NGX_ERROR; + } + st->dynamic = (ch & 0x40) ? 0 : 1; st->state = sw_name_index; @@ -1590,6 +1609,11 @@ ngx_http_v3_parse_field_iln(ngx_connection_t *c, ch = *b->pos; + st->literal.buf = ngx_http_v3_get_insert_buffer(c); + if (st->literal.buf == NULL) { + return NGX_ERROR; + } + st->literal.huffman = (ch & 0x20) ? 1 : 0; st->state = sw_name_len; diff --git a/src/http/v3/ngx_http_v3_parse.h b/src/http/v3/ngx_http_v3_parse.h index ba004db5d..c7e1e1ebb 100644 --- a/src/http/v3/ngx_http_v3_parse.h +++ b/src/http/v3/ngx_http_v3_parse.h @@ -51,6 +51,7 @@ typedef struct { ngx_str_t value; u_char *last; u_char huffstate; + ngx_buf_t *buf; } ngx_http_v3_parse_literal_t; diff --git a/src/http/v3/ngx_http_v3_table.c b/src/http/v3/ngx_http_v3_table.c index 428e7326b..3b0b7b309 100644 --- a/src/http/v3/ngx_http_v3_table.c +++ b/src/http/v3/ngx_http_v3_table.c @@ -155,6 +155,28 @@ static ngx_http_v3_field_t ngx_http_v3_static_table[] = { }; +ngx_buf_t * +ngx_http_v3_get_insert_buffer(ngx_connection_t *c) +{ + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (dt->insert_buffer == NULL) { + dt->insert_buffer = ngx_create_temp_buf(c->pool, dt->capacity); + if (dt->insert_buffer == NULL) { + return NULL; + } + } + + dt->insert_buffer->last = dt->insert_buffer->pos; + + return dt->insert_buffer; +} + + ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value) diff --git a/src/http/v3/ngx_http_v3_table.h b/src/http/v3/ngx_http_v3_table.h index 1c2fb17b9..6644723d1 100644 --- a/src/http/v3/ngx_http_v3_table.h +++ b/src/http/v3/ngx_http_v3_table.h @@ -29,11 +29,13 @@ typedef struct { uint64_t insert_count; uint64_t ack_insert_count; ngx_event_t send_insert_count; + ngx_buf_t *insert_buffer; } ngx_http_v3_dynamic_table_t; void ngx_http_v3_inc_insert_count_handler(ngx_event_t *ev); void ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c); +ngx_buf_t *ngx_http_v3_get_insert_buffer(ngx_connection_t *c); ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value); ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, From 98fc3bb78e8daef25c3d850c9cba8c2f787fb99e Mon Sep 17 00:00:00 2001 From: Vadim Zhestikov Date: Mon, 2 Feb 2026 14:46:00 -0800 Subject: [PATCH 24/25] Stream: support ALPN for proxy_ssl upstream. Added the proxy_ssl_alpn directive, which sets the list of protocols to advertise via ALPN during upstream TLS handshakes. Each argument is a complex value, so variables are accepted. In particular, proxy_ssl_alpn $ssl_alpn_protocol; inherits the protocol negotiated in the downstream TLS handshake. When all evaluated values are empty or absent, no ALPN extension is sent, equivalent to the directive not being set at all. Closes #406 on GitHub. --- src/stream/ngx_stream_proxy_module.c | 152 +++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/src/stream/ngx_stream_proxy_module.c b/src/stream/ngx_stream_proxy_module.c index 300bdf681..c358c8647 100644 --- a/src/stream/ngx_stream_proxy_module.c +++ b/src/stream/ngx_stream_proxy_module.c @@ -42,6 +42,7 @@ typedef struct { ngx_str_t ssl_ciphers; ngx_stream_complex_value_t *ssl_name; ngx_flag_t ssl_server_name; + ngx_array_t *ssl_alpn; ngx_flag_t ssl_verify; ngx_uint_t ssl_verify_depth; @@ -95,6 +96,8 @@ static char *ngx_stream_proxy_bind(ngx_conf_t *cf, ngx_command_t *cmd, #if (NGX_STREAM_SSL) static ngx_int_t ngx_stream_proxy_send_proxy_protocol(ngx_stream_session_t *s); +static char *ngx_stream_proxy_ssl_alpn_set_slot(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); static char *ngx_stream_proxy_ssl_certificate_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_stream_proxy_ssl_password_file(ngx_conf_t *cf, @@ -105,6 +108,7 @@ static void ngx_stream_proxy_ssl_init_connection(ngx_stream_session_t *s); static void ngx_stream_proxy_ssl_handshake(ngx_connection_t *pc); static void ngx_stream_proxy_ssl_save_session(ngx_connection_t *c); static ngx_int_t ngx_stream_proxy_ssl_name(ngx_stream_session_t *s); +static ngx_int_t ngx_stream_proxy_ssl_alpn(ngx_stream_session_t *s); static ngx_int_t ngx_stream_proxy_ssl_certificate(ngx_stream_session_t *s); static ngx_int_t ngx_stream_proxy_merge_ssl(ngx_conf_t *cf, ngx_stream_proxy_srv_conf_t *conf, ngx_stream_proxy_srv_conf_t *prev); @@ -304,6 +308,13 @@ static ngx_command_t ngx_stream_proxy_commands[] = { offsetof(ngx_stream_proxy_srv_conf_t, ssl_server_name), NULL }, + { ngx_string("proxy_ssl_alpn"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, + ngx_stream_proxy_ssl_alpn_set_slot, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + { ngx_string("proxy_ssl_verify"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, @@ -1043,6 +1054,61 @@ ngx_stream_proxy_send_proxy_protocol(ngx_stream_session_t *s) } +static char * +ngx_stream_proxy_ssl_alpn_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + + ngx_stream_proxy_srv_conf_t *pscf = conf; + + ngx_str_t *value; + ngx_uint_t i; + ngx_stream_complex_value_t *cv; + ngx_stream_compile_complex_value_t ccv; + + if (pscf->ssl_alpn != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + value = cf->args->elts; + + pscf->ssl_alpn = ngx_array_create(cf->pool, cf->args->nelts - 1, + sizeof(ngx_stream_complex_value_t)); + if (pscf->ssl_alpn == NULL) { + return NGX_CONF_ERROR; + } + + cv = ngx_array_push_n(pscf->ssl_alpn, cf->args->nelts - 1); + + for (i = 1; i < cf->args->nelts; i++) { + ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[i]; + ccv.complex_value = &cv[i - 1]; + + if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (cv[i - 1].lengths == NULL && value[i].len > 255) { + return "protocol too long"; + } + } + + return NGX_CONF_OK; + +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the \"proxy_ssl_alpn\" directive requires " + "OpenSSL with ALPN support"); + + return NGX_CONF_ERROR; +#endif +} + + static char * ngx_stream_proxy_ssl_certificate_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) @@ -1200,6 +1266,13 @@ ngx_stream_proxy_ssl_init_connection(ngx_stream_session_t *s) } } + if (pscf->ssl_alpn) { + if (ngx_stream_proxy_ssl_alpn(s) != NGX_OK) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + } + if (pscf->ssl_certificate && pscf->ssl_certificate->value.len && (pscf->ssl_certificate->lengths @@ -1399,6 +1472,82 @@ done: } +static ngx_int_t +ngx_stream_proxy_ssl_alpn(ngx_stream_session_t *s) +{ +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + + size_t len; + u_char *p, *buf; + ngx_str_t proto; + ngx_uint_t i; + ngx_connection_t *c; + ngx_stream_upstream_t *u; + ngx_stream_complex_value_t *cv; + ngx_stream_proxy_srv_conf_t *pscf; + + pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module); + + u = s->upstream; + c = u->peer.connection; + + len = 0; + + cv = pscf->ssl_alpn->elts; + + for (i = 0; i < pscf->ssl_alpn->nelts; i++) { + + if (ngx_stream_complex_value(s, &cv[i], &proto) != NGX_OK) { + return NGX_ERROR; + } + + if (proto.len == 0 || proto.len > 255) { + continue; + } + + len += 1 + proto.len; + } + + if (len == 0) { + return NGX_OK; + } + + buf = ngx_pnalloc(c->pool, len); + if (buf == NULL) { + return NGX_ERROR; + } + + p = buf; + + for (i = 0; i < pscf->ssl_alpn->nelts; i++) { + + if (ngx_stream_complex_value(s, &cv[i], &proto) != NGX_OK) { + return NGX_ERROR; + } + + if (proto.len == 0 || proto.len > 255) { + continue; + } + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "upstream SSL ALPN: \"%V\"", &proto); + + *p++ = proto.len; + p = ngx_cpymem(p, proto.data, proto.len); + } + + if (SSL_set_alpn_protos(c->ssl->connection, buf, p - buf) != 0) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "SSL_set_alpn_protos() failed"); + return NGX_ERROR; + } + +#endif + + return NGX_OK; +} + + static ngx_int_t ngx_stream_proxy_ssl_certificate(ngx_stream_session_t *s) { @@ -2225,6 +2374,7 @@ ngx_stream_proxy_create_srv_conf(ngx_conf_t *cf) conf->ssl_session_reuse = NGX_CONF_UNSET; conf->ssl_name = NGX_CONF_UNSET_PTR; conf->ssl_server_name = NGX_CONF_UNSET; + conf->ssl_alpn = NGX_CONF_UNSET_PTR; conf->ssl_verify = NGX_CONF_UNSET; conf->ssl_verify_depth = NGX_CONF_UNSET_UINT; conf->ssl_certificate = NGX_CONF_UNSET_PTR; @@ -2300,6 +2450,8 @@ ngx_stream_proxy_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->ssl_server_name, prev->ssl_server_name, 0); + ngx_conf_merge_ptr_value(conf->ssl_alpn, prev->ssl_alpn, NULL); + ngx_conf_merge_value(conf->ssl_verify, prev->ssl_verify, 0); ngx_conf_merge_uint_value(conf->ssl_verify_depth, From b0a4d0fb823b2d4820b333ed4ea4a2c0ad9a56c8 Mon Sep 17 00:00:00 2001 From: Andrew Clayton Date: Thu, 19 Mar 2026 04:21:21 +0000 Subject: [PATCH 25/25] Avoid undefined behaviour in ngx_pstrdup() In the third call to ngx_pstrdup() for setting cycle->conf_param.data in ngx_init_cycle() we would pass in a nulled ngx_str_t in the case there was no -g command line option passed to nginx. This would result in a memcpy(dst, NULL, 0) which up to and including C23 is Undefined Behaviour. Currently Clang and GCC (in this particular case) just treat this as a no-op, so things just happen to work. However some undefined behaviour sanitizers will throw an error when this is hit, e.g. Clang and the zig compiler and it's probably best not to rely on this behaviour. It's worth noting that the next C standard will make this (and other NULL related operations) defined behaviour. Link: Closes: https://github.com/nginx/nginx/issues/1079 --- src/core/ngx_cycle.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/core/ngx_cycle.c b/src/core/ngx_cycle.c index e5fd40285..410cc3cd2 100644 --- a/src/core/ngx_cycle.c +++ b/src/core/ngx_cycle.c @@ -114,11 +114,13 @@ ngx_init_cycle(ngx_cycle_t *old_cycle) ngx_cpystrn(cycle->conf_file.data, old_cycle->conf_file.data, old_cycle->conf_file.len + 1); - cycle->conf_param.len = old_cycle->conf_param.len; - cycle->conf_param.data = ngx_pstrdup(pool, &old_cycle->conf_param); - if (cycle->conf_param.data == NULL) { - ngx_destroy_pool(pool); - return NULL; + if (old_cycle->conf_param.len) { + cycle->conf_param.len = old_cycle->conf_param.len; + cycle->conf_param.data = ngx_pstrdup(pool, &old_cycle->conf_param); + if (cycle->conf_param.data == NULL) { + ngx_destroy_pool(pool); + return NULL; + } }