From: Zhidao HONG Date: Mon, 8 Jun 2026 03:52:24 +0000 (+0000) Subject: Upstream: limit response header field sizes for HTTP/2 and gRPC X-Git-Url: http://git.kaiwu.me/http/doc/static/gitweb.js?a=commitdiff_plain;h=44c66e92c88bf2367dd31efc9d5843c0838d11f2;p=nginx.git Upstream: limit response header field sizes for HTTP/2 and gRPC HTTP/2 and gRPC upstream response header parsing used the HPACK string length to allocate header name and value buffers. The length was not checked against the configured upstream buffer size before allocation. A malicious upstream could force nginx to allocate excessive request-pool memory before the header was rejected. Reject oversized HTTP/2 header name and value lengths before allocation. Also keep a per-header-block limit based on the upstream buffer size, so a header block cannot consume unbounded memory across fields. --- diff --git a/src/http/modules/ngx_http_grpc_module.c b/src/http/modules/ngx_http_grpc_module.c index 32bdc964c..248ec179c 100644 --- a/src/http/modules/ngx_http_grpc_module.c +++ b/src/http/modules/ngx_http_grpc_module.c @@ -106,6 +106,7 @@ typedef struct { ngx_str_t value; u_char *field_end; + size_t header_limit; size_t field_length; size_t field_rest; u_char field_state; @@ -2702,6 +2703,7 @@ ngx_http_grpc_parse_header(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, if (ctx->type == NGX_HTTP_V2_HEADERS_FRAME) { ctx->parsing_headers = 1; ctx->fragment_state = 0; + ctx->header_limit = r->upstream->conf->buffer_size; min = (ctx->flags & NGX_HTTP_V2_PADDED_FLAG ? 1 : 0) + (ctx->flags & NGX_HTTP_V2_PRIORITY_FLAG ? 5 : 0); @@ -2897,7 +2899,7 @@ ngx_http_grpc_parse_fragment(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b) { u_char ch, *p, *last; - size_t size; + size_t len, size; ngx_uint_t index, size_update; enum { sw_start = 0, @@ -3236,6 +3238,14 @@ ngx_http_grpc_parse_fragment(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ctx->name.len = ctx->field_huffman ? ctx->field_length * 8 / 5 : ctx->field_length; + if (ctx->name.len > ctx->header_limit) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too large http2 " + "header name length: %uz", + ctx->name.len); + return NGX_ERROR; + } + ctx->name.data = ngx_pnalloc(r->pool, ctx->name.len + 1); if (ctx->name.data == NULL) { return NGX_ERROR; @@ -3345,6 +3355,14 @@ ngx_http_grpc_parse_fragment(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, ctx->value.len = ctx->field_huffman ? ctx->field_length * 8 / 5 : ctx->field_length; + if (ctx->value.len > ctx->header_limit) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too large http2 " + "header value length: %uz", + ctx->value.len); + return NGX_ERROR; + } + ctx->value.data = ngx_pnalloc(r->pool, ctx->value.len + 1); if (ctx->value.data == NULL) { return NGX_ERROR; @@ -3434,6 +3452,16 @@ ngx_http_grpc_parse_fragment(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, } } + len = ctx->name.len + ctx->value.len; + + if (len > ctx->header_limit) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too large http2 header"); + return NGX_ERROR; + } + + ctx->header_limit -= len; + return NGX_OK; } diff --git a/src/http/modules/ngx_http_proxy_v2_module.c b/src/http/modules/ngx_http_proxy_v2_module.c index 010c02a86..2a1ff74fb 100644 --- a/src/http/modules/ngx_http_proxy_v2_module.c +++ b/src/http/modules/ngx_http_proxy_v2_module.c @@ -77,6 +77,7 @@ typedef struct { ngx_str_t value; u_char *field_end; + size_t header_limit; size_t field_length; size_t field_rest; u_char field_state; @@ -2535,6 +2536,7 @@ ngx_http_proxy_v2_parse_header(ngx_http_request_t *r, if (ctx->type == NGX_HTTP_V2_HEADERS_FRAME) { ctx->parsing_headers = 1; ctx->fragment_state = 0; + ctx->header_limit = r->upstream->conf->buffer_size; min = (ctx->flags & NGX_HTTP_V2_PADDED_FLAG ? 1 : 0) + (ctx->flags & NGX_HTTP_V2_PRIORITY_FLAG ? 5 : 0); @@ -2730,7 +2732,7 @@ ngx_http_proxy_v2_parse_fragment(ngx_http_request_t *r, ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b) { u_char ch, *p, *last; - size_t size; + size_t len, size; ngx_uint_t index, size_update; enum { sw_start = 0, @@ -3070,6 +3072,14 @@ ngx_http_proxy_v2_parse_fragment(ngx_http_request_t *r, ctx->name.len = ctx->field_huffman ? ctx->field_length * 8 / 5 : ctx->field_length; + if (ctx->name.len > ctx->header_limit) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too large http2 " + "header name length: %uz", + ctx->name.len); + return NGX_ERROR; + } + ctx->name.data = ngx_pnalloc(r->pool, ctx->name.len + 1); if (ctx->name.data == NULL) { return NGX_ERROR; @@ -3179,6 +3189,14 @@ ngx_http_proxy_v2_parse_fragment(ngx_http_request_t *r, ctx->value.len = ctx->field_huffman ? ctx->field_length * 8 / 5 : ctx->field_length; + if (ctx->value.len > ctx->header_limit) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too large http2 " + "header value length: %uz", + ctx->value.len); + return NGX_ERROR; + } + ctx->value.data = ngx_pnalloc(r->pool, ctx->value.len + 1); if (ctx->value.data == NULL) { return NGX_ERROR; @@ -3272,6 +3290,16 @@ ngx_http_proxy_v2_parse_fragment(ngx_http_request_t *r, } } + len = ctx->name.len + ctx->value.len; + + if (len > ctx->header_limit) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too large http2 header"); + return NGX_ERROR; + } + + ctx->header_limit -= len; + return NGX_OK; }