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 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 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. + + + +
+ + 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 diff --git a/src/core/nginx.h b/src/core/nginx.h index c870991fc..f4ccc6183 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 1031000 +#define NGINX_VERSION "1.31.0" #define NGINX_VER "nginx/" NGINX_VERSION #ifdef NGX_BUILD 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; + } } 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; diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index d1386d3a6..1653be0c3 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; @@ -3890,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 @@ -3950,10 +3960,15 @@ 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 */ #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 */ @@ -3986,33 +4001,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) { @@ -6012,6 +6002,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 +6059,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 +6118,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 +6169,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); diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 18992ae1b..705f39f6c 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -152,21 +152,17 @@ 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 */ - - *bytes_read = 0; - - break; - } - *data = b->pos; *bytes_read = b->last - b->pos; - - break; } return 1; 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("") }, diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index a2ff53f82..53ddf39bb 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, @@ -2446,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; @@ -3511,6 +3520,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 +3562,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..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 }, @@ -1494,6 +1499,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); @@ -1919,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/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/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index f177f0a28..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; @@ -2550,6 +2551,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 +2573,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; } } @@ -5654,7 +5653,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--; } diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index 49ea25ede..69cb0ae09 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, @@ -3811,6 +3820,46 @@ 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->next + || 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_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_request.c b/src/http/v3/ngx_http_v3_request.c index 6865e1466..6b487289a 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); @@ -1012,6 +1021,46 @@ 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->next + || 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; } 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, diff --git a/src/stream/ngx_stream_proxy_module.c b/src/stream/ngx_stream_proxy_module.c index d129816d6..42f51c592 100644 --- a/src/stream/ngx_stream_proxy_module.c +++ b/src/stream/ngx_stream_proxy_module.c @@ -43,6 +43,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; @@ -98,6 +99,8 @@ static char *ngx_stream_proxy_protocol_tlv(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, @@ -108,6 +111,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); @@ -321,6 +325,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, @@ -1124,6 +1135,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) @@ -1281,6 +1347,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 @@ -1480,6 +1553,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) { @@ -2308,6 +2457,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; @@ -2387,6 +2537,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,