From b1341a04a1771529ba67e3c398f7d39993283205 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Tue, 2 Jun 2026 14:33:35 -0400 Subject: [PATCH] HTTP: reject leading or trailing HWS in HTTP/2 or HTTP/3 fields Per RFC9113 and RFC9114 these are forbidden. Such fields cannot be serialized in HTTP/1.x, as leading and trailing HWS are stripped from field values there. Furthermore, some servers and clients might even discard the whole field in this case, causing NGINX and the peer to disagree on the meaning of the message. --- src/core/ngx_string.h | 7 +++++++ src/http/modules/ngx_http_grpc_module.c | 10 ++++++++++ src/http/modules/ngx_http_proxy_v2_module.c | 10 ++++++++++ src/http/v2/ngx_http_v2.c | 14 ++++++++++++++ src/http/v3/ngx_http_v3_request.c | 14 ++++++++++++++ 5 files changed, 55 insertions(+) diff --git a/src/core/ngx_string.h b/src/core/ngx_string.h index 183a20521..5add5da9a 100644 --- a/src/core/ngx_string.h +++ b/src/core/ngx_string.h @@ -236,4 +236,11 @@ void ngx_sort(void *base, size_t n, size_t size, #define ngx_value(n) ngx_value_helper(n) +static ngx_inline ngx_int_t +ngx_str_is_hws(ngx_uint_t c) +{ + return c == '\t' || c == ' '; +} + + #endif /* _NGX_STRING_H_INCLUDED_ */ diff --git a/src/http/modules/ngx_http_grpc_module.c b/src/http/modules/ngx_http_grpc_module.c index 4a47b74c5..93103aa42 100644 --- a/src/http/modules/ngx_http_grpc_module.c +++ b/src/http/modules/ngx_http_grpc_module.c @@ -3437,6 +3437,16 @@ ngx_http_grpc_validate_header_value(ngx_http_request_t *r, ngx_str_t *s) u_char ch; ngx_uint_t i; + if (s->len == 0) { + return NGX_OK; + } + + if (ngx_str_is_hws(s->data[0]) + || ngx_str_is_hws(s->data[s->len - 1])) + { + return NGX_ERROR; + } + for (i = 0; i < s->len; i++) { ch = s->data[i]; diff --git a/src/http/modules/ngx_http_proxy_v2_module.c b/src/http/modules/ngx_http_proxy_v2_module.c index 0be5691aa..ae38405eb 100644 --- a/src/http/modules/ngx_http_proxy_v2_module.c +++ b/src/http/modules/ngx_http_proxy_v2_module.c @@ -3275,6 +3275,16 @@ ngx_http_proxy_v2_validate_header_value(ngx_http_request_t *r, ngx_str_t *s) u_char ch; ngx_uint_t i; + if (s->len == 0) { + return NGX_OK; + } + + if (ngx_str_is_hws(s->data[0]) + || ngx_str_is_hws(s->data[s->len - 1])) + { + return NGX_ERROR; + } + for (i = 0; i < s->len; i++) { ch = s->data[i]; diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index 69cb0ae09..2b8382ba3 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -3277,6 +3277,20 @@ ngx_http_v2_validate_header(ngx_http_request_t *r, ngx_http_v2_header_t *header) r->invalid_header = 1; } + if (header->value.len == 0) { + return NGX_OK; + } + + if (ngx_str_is_hws(header->value.data[0]) + || ngx_str_is_hws(header->value.data[header->value.len - 1])) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent header \"%V\" with " + "leading or trailing space or HTAB in value", + &header->name); + return NGX_ERROR; + } + for (i = 0; i != header->value.len; i++) { ch = header->value.data[i]; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 6b487289a..5e8d78390 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -736,6 +736,20 @@ ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_str_t *name, r->invalid_header = 1; } + if (value->len == 0) { + return NGX_OK; + } + + if (ngx_str_is_hws(value->data[0]) + || ngx_str_is_hws(value->data[value->len - 1])) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent header \"%V\" with " + "leading or trailing space or HTAB in value", + name); + return NGX_ERROR; + } + for (i = 0; i != value->len; i++) { ch = value->data[i];