8000 Reject some messages a conformant sender would not have sent by DemiMarie · Pull Request #728 · nginx/nginx · GitHub
[go: up one dir, main page]

Skip to content

Reject some messages a conformant sender would not have sent #728

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 21 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
80b41e1
HTTP: Use common header validation function for HTTP/2 and HTTP/3
DemiMarie Mar 25, 2025
3897c97
Strip leading and trailing whitespace from HTTP field values
DemiMarie Apr 7, 2025
c7d26cc
HTTP/3: Do not allow invalid pseudo-header fields
DemiMarie Mar 21, 2025
17ce4bc
HTTP: Use common header code for v2 and v3
DemiMarie Mar 22, 2025
1e30988
HTTP: Reject HTTP/2 and HTTP/3 requests with Transfer-Encoding
DemiMarie Apr 8, 2025
b1c4b07
HTTP: Reject invalid header names
DemiMarie Mar 14, 2025
ae76c64
HTTP: Reject invalid field values
DemiMarie Apr 9, 2025
279ae48
HTTP: Reject hop-by-hop headers in HTTP/2 and HTTP/3 requests
DemiMarie Mar 13, 2025
98d2669
Proxy: Reject Transfer-Encoding or Content-Length trailers
DemiMarie Mar 27, 2025
dab8c0e
HTTP: Do not allow header lines with no colon
DemiMarie Apr 25, 2025
e30ddb7
HTTP: Do not allow multiple CRs before LF
DemiMarie Mar 14, 2025
0d2c875
HTTP: Do not allow request lines to end with bare CR
DemiMarie Mar 16, 2025
1be6976
Reject malformed chunk extensions
DemiMarie Mar 23, 2025
1c0426c
Forbid malformed HTTP/1.1 trailers
DemiMarie Mar 27, 2025
626b1d9
HTTP: Reject trailers involved in framing
DemiMarie Mar 27, 2025
436282f
HTTP: Remove redundant state from chunk parsing
DemiMarie Apr 7, 2025
bb0a2b9
HTTP: Allow rejecting leading and trailing whitespace in HTTP2+ fields
DemiMarie Mar 25, 2025
bd37faf
HTTP: do not allow headers to end with a bare LF
DemiMarie Mar 16, 2025
7032a60
HTTP: do not allow status line to end with bare LF
DemiMarie Mar 16, 2025
9950dfb
Do not allow trailers to end with bare LF
DemiMarie Jun 10, 2025
9b8e4a1
Do not support bad whitespace in chunk extensions
DemiMarie Jun 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Strip leading and trailing whitespace from HTTP field values
Per RFC9110, HTTP field values never contain leading or trailing
whitespace.  Strip all such whitespace from HTTP and HTTP field values.
The HTTP/1.x parser already stripped spaces but didn't strip tabs, so
change the parser to
10000
 strip tabs as well.  In HTTP/2+, the stripping is
done during validation.  This requires modifying the value.

There are three ways to modify the value:

1. Modify the data in-place with memmove().
2. Move the data pointer to point to after the leading whitespace.
3. Allocate a new buffer and replace the data pointer.

Both HPACK and QPACK decompression make a copy of the data, but some
code might assume that the data pointer of a field value can safely be
passed to ngx_pfree().  Therefore, the first option is chosen.  Existing
code ensures that header values are NUL-terminated, so the stripping
code NUL-pads header values to ensure that the stripped strings have at
least as many terminating NUL bytes as they did before being stripped.

The stripping code has been tested in a standalone program to make sure
that it works correctly, and it correctly strips leading and trailing
whitespace from a variety of strings.  This code has also been tested
with real HTTP/3 requests from Cloudflare's h3i tool.

Fixes: #598
  • Loading branch information
DemiMarie committed Jun 10, 2025
commit 3897c97cc314e02d75ad92d84ad4c13924151e8e
6 changes: 5 additions & 1 deletion src/http/ngx_http.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,11 @@ ngx_int_t ngx_http_huff_decode(u_char *state, u_char *src, size_t len,
u_char **dst, ngx_uint_t last, ngx_log_t *log);
size_t ngx_http_huff_encode(u_char *src, size_t len, u_char *dst,
ngx_uint_t lower);
ngx_int_t ngx_http_v23_validate_header(ngx_http_request_t *r,
/*
* Check if a header name and/or value is valid. If the value is valid,
* strip leading and trailing space from it.
*/
ngx_int_t ngx_http_v23_fixup_header(ngx_http_request_t *r,
ngx_str_t *name, ngx_str_t *value);
#endif

Expand Down
67 changes: 66 additions & 1 deletion src/http/ngx_http_parse.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
#include <ngx_http.h>


#if (NGX_HTTP_V2 || NGX_HTTP_V3)
static inline ngx_int_t ngx_isspace(u_char ch);
#endif


static uint32_t usual[] = {
0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */

Expand Down Expand Up @@ -972,6 +977,7 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b,
case sw_space_before_value:
switch (ch) {
case ' ':
case '\t':
break;
case CR:
r->header_start = p;
Expand All @@ -996,6 +1002,7 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b,
case sw_value:
switch (ch) {
case ' ':
case '\t':
r->header_end = p;
state = sw_space_after_value;
break;
Expand All @@ -1016,6 +1023,7 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b,
case sw_space_after_value:
switch (ch) {
case ' ':
case '\t':
break;
case CR:
state = sw_almost_done;
Expand Down Expand Up @@ -1091,11 +1099,21 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b,


#if (NGX_HTTP_V2 || NGX_HTTP_V3)


static inline ngx_int_t
ngx_isspace(u_char ch)
{
return ch == ' ' || ch == '\t';
}


ngx_int_t
ngx_http_v23_validate_header(ngx_http_request_t *r, ngx_str_t *name,
ngx_http_v23_fixup_header(ngx_http_request_t *r, ngx_str_t *name,
ngx_str_t *value)
{
u_char ch;
ngx_str_t tmp;
ngx_uint_t i;
ngx_http_core_srv_conf_t *cscf;

Expand Down Expand Up @@ -1134,6 +1152,11 @@ ngx_http_v23_validate_header(ngx_http_request_t *r, ngx_str_t *name,
r->invalid_header = 1;
}

/* Keep subsequent code from having to special-case empty strings. */
if (value->len == 0) {
return NGX_OK;
}

for (i = 0; i != value->len; i++) {
ch = value->data[i];

Expand All @@ -1147,6 +1170,48 @@ ngx_http_v23_validate_header(ngx_http_request_t *r, ngx_str_t *name,
}
}

tmp = *value;

if (!ngx_isspace(tmp.data[0])
&& !ngx_isspace(tmp.data[tmp.len - 1])) {
/* Fast path: nothing to strip. */
return NGX_OK;
}

/*
* Strip trailing whitespace. Do this first so that
* if the string is all whitespace, tmp.data is not a
* past-the-end pointer (which cannot be safely passed
* to memmove())
*/
while (tmp.len && ngx_isspace(tmp.data[tmp.len - 1])) {
tmp.len--;
}

/* Strip leading whitespace */
if (tmp.len && ngx_isspace(tmp.data[0])) {
/*
* Last loop guaranteed that 'tmp' does not end with whitespace, so
* it's safe to keep going until a non-whitespace character is found.
*/
do {
tmp.len--;
tmp.data++;
} while (ngx_isspace(tmp.data[0]));

/* Move remaining string to start of buffer. */
memmove(value->data, tmp.data, tmp.len);
}

/*
* NUL-pad the data, so that if it was NUL-terminated before, it stil is.
* At least one byte will have been stripped, so value->data + tmp.len
* is not a past-the-end pointer.
*/
memset(value->data + tmp.len, '\0', value->len - tmp.len);

/* Fix up length and return. */
value->len = tmp.len;
return NGX_OK;
}
#endif
Expand Down
3 changes: 1 addition & 2 deletions src/http/v2/ngx_http_v2.c
Original file line number Diff line number Diff line change
Expand Up @@ -1772,8 +1772,7 @@ ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos,
fc = r->connection;

/* TODO Optimization: validate headers while parsing. */
if (ngx_http_v23_validate_header(r, &header->name, &header->value)
!= NGX_OK) {
if (ngx_http_v23_fixup_header(r, &header->name, &header->value) != NGX_OK) {
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
goto error;
}
Expand Down
2 changes: 1 addition & 1 deletion src/http/v3/ngx_http_v3_request.c
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name,

r->v3_parse->header_limit -= len;

if (ngx_http_v23_validate_header(r, name, value) != NGX_OK) {
if (ngx_http_v23_fixup_header(r, name, value) != NGX_OK) {
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return NGX_ERROR;
}
Expand Down
0