From 7dab091a95a7253fa5e2ae2f1a46c88ef85dcb2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubo=C5=A1=20Uhliarik?= Date: Mon, 20 Jul 2026 10:11:08 +0200 Subject: [PATCH] Fixes CVE-2026-9256, CVE-2026-42055 and HTTP/2: Remote Denial of Service via compression bomb and Slowloris-style attack Resolves: RHEL-178674 - nginx: code execution and denial of service (CVE-2026-9256) Resolves: RHEL-182545 - nginx: HTTP/2: Remote Denial of Service via compression bomb and Slowloris-style attack Resolves: RHEL-188404 - nginx: NGINX: Arbitrary code execution or. Denial of Service via heap-based buffer overflow with crafted HTTP/2 headers (CVE-2026-42055) --- ...er-overflow-with-overlapping-capture.patch | 72 ++++ 0014-Added-max_headers-directive.patch | 319 ++++++++++++++++++ ...it-header-length-for-HTTP-2-and-gRPC.patch | 99 ++++++ nginx.spec | 12 + 4 files changed, 502 insertions(+) create mode 100644 0013-Rewrite-fix-buffer-overflow-with-overlapping-capture.patch create mode 100644 0014-Added-max_headers-directive.patch create mode 100644 0015-Upstream-limit-header-length-for-HTTP-2-and-gRPC.patch diff --git a/0013-Rewrite-fix-buffer-overflow-with-overlapping-capture.patch b/0013-Rewrite-fix-buffer-overflow-with-overlapping-capture.patch new file mode 100644 index 0000000..1141285 --- /dev/null +++ b/0013-Rewrite-fix-buffer-overflow-with-overlapping-capture.patch @@ -0,0 +1,72 @@ +From d2e284542086632811980b08670852102226acae Mon Sep 17 00:00:00 2001 +From: Roman Arutyunyan +Date: Thu, 14 May 2026 18:42:18 +0400 +Subject: [PATCH] Rewrite: fix buffer overflow with overlapping captures + +When the rewrite replacement string had no variables, but had +overlapping captures, the length of the allocated buffer could be +smaller than the replacement string. This could happen either +when the "redirect" parameter is specified, or when arguments are +present in the replacement string. + +The following configurations resulted in heap buffer overflow when +using URI "/++++++++++++++++++++++++++++++": + + location / { + rewrite ^/((.*))$ http://127.0.0.1:8080/$1$2 redirect; + return 200 foo; + } + + location / { + rewrite ^/((.*))$ http://127.0.0.1:8080/?$1$2; + return 200 foo; + } + +Reported by Mufeed VH of Winfunc Research. +--- + src/http/ngx_http_script.c | 20 +++++++++++++------- + 1 file changed, 13 insertions(+), 7 deletions(-) + +diff --git a/src/http/ngx_http_script.c b/src/http/ngx_http_script.c +index 2ea6113..8a28e23 100644 +--- a/src/http/ngx_http_script.c ++++ b/src/http/ngx_http_script.c +@@ -1037,6 +1037,8 @@ ngx_http_script_start_args_code(ngx_http_script_engine_t *e) + void + ngx_http_script_regex_start_code(ngx_http_script_engine_t *e) + { ++ int *cap; ++ u_char *p; + size_t len; + ngx_int_t rc; + ngx_uint_t n; +@@ -1143,15 +1145,19 @@ ngx_http_script_regex_start_code(ngx_http_script_engine_t *e) + if (code->lengths == NULL) { + e->buf.len = code->size; + +- if (code->uri) { +- if (r->ncaptures && (r->quoted_uri || r->plus_in_uri)) { +- e->buf.len += 2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len, +- NGX_ESCAPE_ARGS); +- } +- } ++ cap = r->captures; ++ p = r->captures_data; + + for (n = 2; n < r->ncaptures; n += 2) { +- e->buf.len += r->captures[n + 1] - r->captures[n]; ++ e->buf.len += cap[n + 1] - cap[n]; ++ ++ if (code->uri) { ++ if (r->quoted_uri || r->plus_in_uri) { ++ e->buf.len += 2 * ngx_escape_uri(NULL, &p[cap[n]], ++ cap[n + 1] - cap[n], ++ NGX_ESCAPE_ARGS); ++ } ++ } + } + + } else { +-- +2.44.0 + diff --git a/0014-Added-max_headers-directive.patch b/0014-Added-max_headers-directive.patch new file mode 100644 index 0000000..5afd520 --- /dev/null +++ b/0014-Added-max_headers-directive.patch @@ -0,0 +1,319 @@ +From 972780352ac6255796a877300e353484f04a148b Mon Sep 17 00:00:00 2001 +From: Maxim Dounin +Date: Fri, 24 May 2024 00:20:01 +0300 +Subject: [PATCH] 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. + +The original patch was modified so that the header count and the limit are +stored in a separate module, ngx_http_header_count_module, instead of the +core module. This allows to avoid changing the size of existing structures +and thus avoid potential ABI breakage and keep all the 3rd party modules +compatible with the new version of nginx without recompilation. + +Signed-off-by: Elijah Zupancic +Origin: +--- + auto/modules | 1 + + src/http/ngx_http_core_module.c | 73 +++++++++++++++++++++++++++++++ + src/http/ngx_http_header_count.hh | 14 ++++++ + src/http/ngx_http_request.c | 24 ++++++++++ + src/http/v2/ngx_http_v2.c | 14 ++++++ + src/http/v3/ngx_http_v3_request.c | 14 ++++++ + 6 files changed, 140 insertions(+) + create mode 100644 src/http/ngx_http_header_count.hh + +diff --git a/auto/modules b/auto/modules +index 1a5e421..e7632fd 100644 +--- a/auto/modules ++++ b/auto/modules +@@ -67,6 +67,7 @@ if [ $HTTP = YES ]; then + ngx_module_name="ngx_http_module \ + ngx_http_core_module \ + ngx_http_log_module \ ++ ngx_http_header_count_module \ + ngx_http_upstream_module" + ngx_module_incs="src/http src/http/modules" + ngx_module_deps="src/http/ngx_http.h \ +diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c +index 033a3bf..c736d83 100644 +--- a/src/http/ngx_http_core_module.c ++++ b/src/http/ngx_http_core_module.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + + + typedef struct { +@@ -39,6 +40,10 @@ static void *ngx_http_core_create_loc_conf(ngx_conf_t *cf); + static char *ngx_http_core_merge_loc_conf(ngx_conf_t *cf, + void *parent, void *child); + ++static void *ngx_http_header_count_create_srv_conf(ngx_conf_t *cf); ++static char *ngx_http_header_count_merge_srv_conf(ngx_conf_t *cf, ++ void *parent, void *child); ++ + static char *ngx_http_core_server(ngx_conf_t *cf, ngx_command_t *cmd, + void *dummy); + static char *ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, +@@ -812,6 +817,48 @@ ngx_module_t ngx_http_core_module = { + NGX_MODULE_V1_PADDING + }; + ++static ngx_command_t ngx_http_header_count_commands[] = { ++ ++ { 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_header_count_conf_t, max_headers), ++ NULL }, ++ ++ ngx_null_command ++}; ++ ++static ngx_http_module_t ngx_http_header_count_module_ctx = { ++ NULL, /* preconfiguration */ ++ NULL, /* postconfiguration */ ++ ++ NULL, /* create main configuration */ ++ NULL, /* init main configuration */ ++ ++ ngx_http_header_count_create_srv_conf, /* create server configuration */ ++ ngx_http_header_count_merge_srv_conf, /* merge server configuration */ ++ ++ NULL, /* create location configuration */ ++ NULL /* merge location configuration */ ++}; ++ ++ ++ngx_module_t ngx_http_header_count_module = { ++ NGX_MODULE_V1, ++ &ngx_http_header_count_module_ctx, /* module context */ ++ ngx_http_header_count_commands, /* module directives */ ++ NGX_HTTP_MODULE, /* module type */ ++ NULL, /* init master */ ++ NULL, /* init module */ ++ NULL, /* init process */ ++ NULL, /* init thread */ ++ NULL, /* exit thread */ ++ NULL, /* exit process */ ++ NULL, /* exit master */ ++ NGX_MODULE_V1_PADDING ++}; ++ + + ngx_str_t ngx_http_core_get_method = { 3, (u_char *) "GET" }; + +@@ -3653,6 +3700,32 @@ ngx_http_core_create_loc_conf(ngx_conf_t *cf) + } + + ++static void * ++ngx_http_header_count_create_srv_conf(ngx_conf_t *cf) ++{ ++ ngx_http_header_count_conf_t *hccf; ++ ++ hccf = ngx_pcalloc(cf->pool, sizeof(ngx_http_header_count_conf_t)); ++ if (hccf == NULL) { ++ return NULL; ++ } ++ ++ hccf->max_headers = NGX_CONF_UNSET_UINT; ++ return hccf; ++} ++ ++static char * ++ngx_http_header_count_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ++{ ++ ngx_http_header_count_conf_t *prev = parent; ++ ngx_http_header_count_conf_t *conf = child; ++ ++ ngx_conf_merge_uint_value(conf->max_headers, prev->max_headers, 1000); ++ ++ return NGX_CONF_OK; ++} ++ ++ + static ngx_str_t ngx_http_core_text_html_type = ngx_string("text/html"); + static ngx_str_t ngx_http_core_image_gif_type = ngx_string("image/gif"); + static ngx_str_t ngx_http_core_image_jpeg_type = ngx_string("image/jpeg"); +diff --git a/src/http/ngx_http_header_count.hh b/src/http/ngx_http_header_count.hh +new file mode 100644 +index 0000000..6a8b86d +--- /dev/null ++++ b/src/http/ngx_http_header_count.hh +@@ -0,0 +1,14 @@ ++#ifndef _NGX_HTTP_HEADER_COUNT_H_INCLUDED_ ++#define _NGX_HTTP_HEADER_COUNT_H_INCLUDED_ ++ ++typedef struct { ++ ngx_uint_t count; ++} ngx_http_header_count_t; ++ ++typedef struct { ++ ngx_uint_t max_headers; ++} ngx_http_header_count_conf_t; ++ ++extern ngx_module_t ngx_http_header_count_module; ++ ++#endif /* _NGX_HTTP_HEADER_COUNT_H_INCLUDED_ */ +diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c +index 9593b7f..88165b4 100644 +--- a/src/http/ngx_http_request.c ++++ b/src/http/ngx_http_request.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + + + static void ngx_http_wait_request_handler(ngx_event_t *ev); +@@ -565,6 +566,7 @@ ngx_http_alloc_request(ngx_connection_t *c) + ngx_http_connection_t *hc; + ngx_http_core_srv_conf_t *cscf; + ngx_http_core_main_conf_t *cmcf; ++ ngx_http_header_count_t *hc_ctx; + + hc = c->data; + +@@ -626,6 +628,13 @@ ngx_http_alloc_request(ngx_connection_t *c) + return NULL; + } + ++ hc_ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_header_count_t)); ++ if (hc_ctx == NULL) { ++ ngx_destroy_pool(r->pool); ++ return NULL; ++ } ++ ngx_http_set_ctx(r, hc_ctx, ngx_http_header_count_module); ++ + #if (NGX_HTTP_SSL) + if (c->ssl && !c->ssl->sendfile) { + r->main_filter_need_in_memory = 1; +@@ -1399,6 +1408,8 @@ ngx_http_process_request_headers(ngx_event_t *rev) + ngx_http_request_t *r; + ngx_http_core_srv_conf_t *cscf; + ngx_http_core_main_conf_t *cmcf; ++ ngx_http_header_count_conf_t *hccf; ++ ngx_http_header_count_t *hc_ctx; + + c = rev->data; + r = c->data; +@@ -1472,6 +1483,10 @@ ngx_http_process_request_headers(ngx_event_t *rev) + rc = ngx_http_parse_header_line(r, r->header_in, + cscf->underscores_in_headers); + ++ ++ hccf = ngx_http_get_module_srv_conf(r, ngx_http_header_count_module); ++ hc_ctx = ngx_http_get_module_ctx(r, ngx_http_header_count_module); ++ + if (rc == NGX_OK) { + + r->request_length += r->header_in->pos - r->header_name_start; +@@ -1489,6 +1504,15 @@ ngx_http_process_request_headers(ngx_event_t *rev) + + /* a header line has been parsed successfully */ + ++ if (hc_ctx->count++ >= hccf->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/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c +index 0f5bd3d..b340789 100644 +--- a/src/http/v2/ngx_http_v2.c ++++ b/src/http/v2/ngx_http_v2.c +@@ -9,6 +9,7 @@ + #include + #include + #include ++#include + + + /* errors */ +@@ -1712,6 +1713,8 @@ ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos, + ngx_http_v2_header_t *header; + ngx_http_core_srv_conf_t *cscf; + ngx_http_core_main_conf_t *cmcf; ++ ngx_http_header_count_conf_t *hccf; ++ ngx_http_header_count_t *hc_ctx; + + static ngx_str_t cookie = ngx_string("cookie"); + +@@ -1817,6 +1820,17 @@ 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); ++ hccf = ngx_http_get_module_srv_conf(r, ngx_http_header_count_module); ++ hc_ctx = ngx_http_get_module_ctx(r, ngx_http_header_count_module); ++ ++ if (hc_ctx->count++ >= hccf->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 0faddd2..a3e10ae 100644 +--- a/src/http/v3/ngx_http_v3_request.c ++++ b/src/http/v3/ngx_http_v3_request.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + + + static void ngx_http_v3_init_request_stream(ngx_connection_t *c); +@@ -618,6 +619,8 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_http_header_t *hh; + ngx_http_core_srv_conf_t *cscf; + ngx_http_core_main_conf_t *cmcf; ++ ngx_http_header_count_conf_t *hccf; ++ ngx_http_header_count_t *hc_ctx; + + static ngx_str_t cookie = ngx_string("cookie"); + +@@ -665,6 +668,17 @@ 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); ++ hccf = ngx_http_get_module_srv_conf(r, ngx_http_header_count_module); ++ hc_ctx = ngx_http_get_module_ctx(r, ngx_http_header_count_module); ++ ++ if (hc_ctx->count++ >= hccf->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); +-- +2.44.0 + diff --git a/0015-Upstream-limit-header-length-for-HTTP-2-and-gRPC.patch b/0015-Upstream-limit-header-length-for-HTTP-2-and-gRPC.patch new file mode 100644 index 0000000..16e5158 --- /dev/null +++ b/0015-Upstream-limit-header-length-for-HTTP-2-and-gRPC.patch @@ -0,0 +1,99 @@ +From 83cc0876c6d8f880b7c943e39d922e2286c94a41 Mon Sep 17 00:00:00 2001 +From: Roman Arutyunyan +Date: Tue, 2 Jun 2026 19:37:17 +0400 +Subject: [PATCH] Upstream: limit header length for HTTP/2 and gRPC + +The change applies the HTTP/2 header length limits to avoid buffer +overflow. See 58a7bc3406ac for details. + +Reported by Mufeed VH of Winfunc Research. +--- + src/http/modules/ngx_http_grpc_module.c | 44 +++++++++++++++++++++++++ + 1 file changed, 44 insertions(+) + +diff --git a/src/http/modules/ngx_http_grpc_module.c b/src/http/modules/ngx_http_grpc_module.c +index 9f13089..bb636f3 100644 +--- a/src/http/modules/ngx_http_grpc_module.c ++++ b/src/http/modules/ngx_http_grpc_module.c +@@ -740,6 +740,12 @@ ngx_http_grpc_create_request(ngx_http_request_t *r) + tmp_len = 0; + + } else { ++ if (r->method_name.len > NGX_HTTP_V2_MAX_FIELD) { ++ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, ++ "too long http2 method: \"%V\"", &r->method_name); ++ return NGX_ERROR; ++ } ++ + len += 1 + NGX_HTTP_V2_INT_OCTETS + r->method_name.len; + tmp_len = r->method_name.len; + } +@@ -760,6 +766,12 @@ ngx_http_grpc_create_request(ngx_http_request_t *r) + uri_len = r->uri.len + escape + sizeof("?") - 1 + r->args.len; + } + ++ if (uri_len > NGX_HTTP_V2_MAX_FIELD) { ++ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, ++ "too long http2 URI"); ++ return NGX_ERROR; ++ } ++ + len += 1 + NGX_HTTP_V2_INT_OCTETS + uri_len; + + if (tmp_len < uri_len) { +@@ -769,6 +781,12 @@ ngx_http_grpc_create_request(ngx_http_request_t *r) + /* :authority header */ + + if (!glcf->host_set) { ++ if (ctx->host.len > NGX_HTTP_V2_MAX_FIELD) { ++ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, ++ "too long http2 host: \"%V\"", &ctx->host); ++ return NGX_ERROR; ++ } ++ + len += 1 + NGX_HTTP_V2_INT_OCTETS + ctx->host.len; + + if (tmp_len < ctx->host.len) { +@@ -799,6 +817,18 @@ ngx_http_grpc_create_request(ngx_http_request_t *r) + continue; + } + ++ if (key_len > NGX_HTTP_V2_MAX_FIELD) { ++ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, ++ "too long http2 header name"); ++ return NGX_ERROR; ++ } ++ ++ if (val_len > NGX_HTTP_V2_MAX_FIELD) { ++ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, ++ "too long http2 header value"); ++ return NGX_ERROR; ++ } ++ + len += 1 + NGX_HTTP_V2_INT_OCTETS + key_len + + NGX_HTTP_V2_INT_OCTETS + val_len; + +@@ -833,6 +863,20 @@ ngx_http_grpc_create_request(ngx_http_request_t *r) + continue; + } + ++ if (header[i].key.len > NGX_HTTP_V2_MAX_FIELD) { ++ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, ++ "too long http2 header name: \"%V\"", ++ &header[i].key); ++ return NGX_ERROR; ++ } ++ ++ if (header[i].value.len > NGX_HTTP_V2_MAX_FIELD) { ++ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, ++ "too long http2 header value: \"%V: %V\"", ++ &header[i].key, &header[i].value); ++ return NGX_ERROR; ++ } ++ + len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len + + NGX_HTTP_V2_INT_OCTETS + header[i].value.len; + +-- +2.44.0 + diff --git a/nginx.spec b/nginx.spec index 622fef1..5833366 100644 --- a/nginx.spec +++ b/nginx.spec @@ -139,6 +139,18 @@ Patch10: 0011-Mp4-avoid-zero-size-buffers-in-output.patch # upstream patch - https://github.com/nginx/nginx/commit/524977e7 Patch11: 0012-Rewrite-fixed-escaping-and-possible-buffer-overrun.patch +# https://redhat.atlassian.net/browse/RHEL-178669 +# upstream patch - https://github.com/nginx/nginx/commit/ca4f92a27464ae6c2082245e4f67048c633aa032 +Patch12: 0013-Rewrite-fix-buffer-overflow-with-overlapping-capture.patch + +# https://redhat.atlassian.net/browse/RHEL-182544 +# upstream patch - https://github.com/nginx/nginx/commit/365694160a85229a7cb006738de9260d49ff5fa2 +Patch13: 0014-Added-max_headers-directive.patch + +# https://redhat.atlassian.net/browse/RHEL-188418 +# upstream patch - https://github.com/nginx/nginx/commit/26d824ec3a2f819300edce0ab3b055751c9843ff.patch +Patch14: 0015-Upstream-limit-header-length-for-HTTP-2-and-gRPC.patch + BuildRequires: make BuildRequires: gcc BuildRequires: gnupg2