diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c index 7e08df702..713c0ddf0 100644 --- a/src/http/modules/ngx_http_proxy_module.c +++ b/src/http/modules/ngx_http_proxy_module.c @@ -2579,7 +2579,24 @@ ngx_http_proxy_process_trailer(ngx_http_request_t *r, ngx_buf_t *buf) if (rc == NGX_OK) { - /* a header line has been parsed successfully */ + /* A trailer line has been parsed successfully. + * Do not allow trailers that would, if turned into + * headers, interfere with request framing. */ + switch (r->header_name_end - r->header_name_start) { +#define X(x) \ + case sizeof(x "") - 1: \ + /* The size is always less than the number of bytes in \ + * the pre-casefolded area. */ \ + if (memcmp(r->lowcase_header, x, sizeof(x) - 1) == 0) { \ + return NGX_ERROR; \ + } else break + X("transfer-encoding"); + X("content-length"); + X("upgrade"); +#undef X + default: + break; + } h = ngx_list_push(&r->upstream->headers_in.trailers); if (h == NULL) { diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index e1328da04..ff3ce6c31 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -101,6 +101,11 @@ static uint32_t usual[] = { #endif +static inline ngx_int_t +ngx_http_field_value_char(u_char ch) +{ + return ch >= 0x20 ? ch != 0x7f : ch == 0x09; +} /* gcc, icc, msvc and others compile these switches as an jump table */ @@ -866,6 +871,40 @@ done: return NGX_OK; } +static ngx_int_t +ngx_http_non_alnum_dash_header_char(u_char ch) +{ + switch (ch) { + case '!': + case '#': + case '$': + case '%': + case '&': + case '\'': + case '*': + case '+': + case '.': + case '^': + case '_': + case '`': + case '|': + case '~': + return 1; + default: + return 0; + } +} + +static ngx_int_t +ngx_http_token_char(u_char ch) +{ + u_char c = (ch | 0x20); + if (('a' <= c && c <= 'z') || ('0' <= ch && ch <= '9') || ch == '-') { + return 1; + } + + return ngx_http_non_alnum_dash_header_char(ch); +} ngx_int_t ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, @@ -879,7 +918,6 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, sw_space_before_value, sw_value, sw_space_after_value, - sw_ignore_line, sw_almost_done, sw_header_almost_done } state; @@ -930,22 +968,14 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, break; } - if (ch == '_') { - if (allow_underscores) { - hash = ngx_hash(0, ch); - r->lowcase_header[0] = ch; - i = 1; - - } else { - hash = 0; - i = 0; - r->invalid_header = 1; - } - + if (ch == '_' && allow_underscores) { + hash = ngx_hash(0, ch); + r->lowcase_header[0] = ch; + i = 1; break; } - if (ch <= 0x20 || ch == 0x7f || ch == ':') { + if (!ngx_http_non_alnum_dash_header_char(ch)) { r->header_end = p; return NGX_HTTP_PARSE_INVALID_HEADER; } @@ -989,32 +1019,7 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, break; } - if (ch == CR) { - r->header_name_end = p; - r->header_start = p; - r->header_end = p; - state = sw_almost_done; - break; - } - - if (ch == LF) { - r->header_name_end = p; - r->header_start = p; - r->header_end = p; - goto done; - } - - /* IIS may send the duplicate "HTTP/1.1 ..." lines */ - if (ch == '/' - && r->upstream - && p - r->header_name_start == 4 - && ngx_strncmp(r->header_name_start, "HTTP", 4) == 0) - { - state = sw_ignore_line; - break; - } - - if (ch <= 0x20 || ch == 0x7f) { + if (!ngx_http_non_alnum_dash_header_char(ch)) { r->header_end = p; return NGX_HTTP_PARSE_INVALID_HEADER; } @@ -1027,6 +1032,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; @@ -1037,13 +1043,14 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, r->header_start = p; r->header_end = p; goto done; - case '\0': + default: + if (ch > 0x20 && ch != 0x7f) { + r->header_start = p; + state = sw_value; + break; + } r->header_end = p; return NGX_HTTP_PARSE_INVALID_HEADER; - default: - r->header_start = p; - state = sw_value; - break; } break; @@ -1051,6 +1058,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; @@ -1061,7 +1069,9 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, case LF: r->header_end = p; goto done; - case '\0': + default: + if (ch > 0x20 && ch != 0x7f) + break; r->header_end = p; return NGX_HTTP_PARSE_INVALID_HEADER; } @@ -1071,29 +1081,20 @@ 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; break; case LF: goto done; - case '\0': + default: + if (ch > 0x20 && ch != 0x7f) { + state = sw_value; + break; + } r->header_end = p; return NGX_HTTP_PARSE_INVALID_HEADER; - default: - state = sw_value; - break; - } - break; - - /* ignore header line */ - case sw_ignore_line: - switch (ch) { - case LF: - state = sw_start; - break; - default: - break; } break; @@ -1102,8 +1103,6 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, switch (ch) { case LF: goto done; - case CR: - break; default: return NGX_HTTP_PARSE_INVALID_HEADER; } @@ -1857,6 +1856,11 @@ ngx_http_parse_status_line(ngx_http_request_t *r, ngx_buf_t *b, break; case LF: goto done; + default: + if (ch < 0x20 || ch == 0x7f) { + return NGX_ERROR; + } + break; } break; @@ -2222,25 +2226,27 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, enum { sw_chunk_start = 0, sw_chunk_size, + sw_chunk_extension_before_semi, + sw_chunk_extension_before_semi_bws, sw_chunk_extension, + sw_chunk_extension_bws_before_equal, + sw_chunk_extension_name, + sw_chunk_extension_value_start, + sw_chunk_extension_quoted_value, + sw_chunk_extension_value_quoted_backslash, + sw_chunk_extension_unquoted_value, sw_chunk_extension_almost_done, sw_chunk_data, - sw_after_data, sw_after_data_almost_done, - sw_last_chunk_extension, - sw_last_chunk_extension_almost_done, sw_trailer, sw_trailer_almost_done, - sw_trailer_header, + sw_trailer_name, + sw_trailer_value, sw_trailer_header_almost_done } state; state = ctx->state; - if (state == sw_chunk_data && ctx->size == 0) { - state = sw_after_data; - } - rc = NGX_AGAIN; for (pos = b->pos; pos < b->last; pos++) { @@ -2285,62 +2291,60 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, ctx->size = ctx->size * 16 + (c - 'a' + 10); break; } + /* fall through */ - if (ctx->size == 0) { - - switch (ch) { - case CR: - state = sw_last_chunk_extension_almost_done; - break; - case ';': - case ' ': - case '\t': - state = sw_last_chunk_extension; - break; - default: - goto invalid; - } - - break; - } - + case sw_chunk_extension_before_semi: +before_semi: switch (ch) { case CR: state = sw_chunk_extension_almost_done; break; case ';': - case ' ': - case '\t': state = sw_chunk_extension; break; + case ' ': + case '\t': + state = sw_chunk_extension_before_semi_bws; + break; /* BWS */ default: goto invalid; } break; - case sw_chunk_extension: + case sw_chunk_extension_before_semi_bws: switch (ch) { - case CR: - state = sw_chunk_extension_almost_done; + case ' ': + case '\t': break; - case LF: + case ';': + state = sw_chunk_extension; + break; + default: goto invalid; } break; - case sw_chunk_extension_almost_done: - if (ch == LF) { - state = sw_chunk_data; + case sw_chunk_extension: + if (ngx_http_token_char(ch)) { + state = sw_chunk_extension_name; break; } - goto invalid; + switch (ch) { + case ' ': + case '\t': + break; /* BWS */ + default: + goto invalid; + } + break; case sw_chunk_data: - rc = NGX_OK; - goto data; + if (ctx->size != 0) { + rc = NGX_OK; + goto data; + } - case sw_after_data: switch (ch) { case CR: state = sw_after_data_almost_done; @@ -2357,18 +2361,79 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, } goto invalid; - case sw_last_chunk_extension: - switch (ch) { - case CR: - state = sw_last_chunk_extension_almost_done; + + case sw_chunk_extension_name: + if (ngx_http_token_char(ch)) { break; - case LF: + } + + state = sw_chunk_extension_bws_before_equal; + + /* fall through */ + + case sw_chunk_extension_bws_before_equal: + switch (ch) { + case ' ': + case '\t': + break; /* BWS */ + case '=': + state = sw_chunk_extension_value_start; + break; + default: goto invalid; } break; - case sw_last_chunk_extension_almost_done: + case sw_chunk_extension_value_start: + if (ngx_http_token_char(ch)) { + state = sw_chunk_extension_unquoted_value; + break; + } + switch (ch) { + case ' ': + case '\t': + break; /* BWS */ + case '"': + state = sw_chunk_extension_quoted_value; + break; + default: + goto invalid; + } + break; + + case sw_chunk_extension_quoted_value: + if (ch == '"') { + state = sw_chunk_extension_before_semi; + break; + } + if (ch == '\\') { + state = sw_chunk_extension_value_quoted_backslash; + break; + } + if (ngx_http_field_value_char(ch)) { + break; + } + goto invalid; + + case sw_chunk_extension_value_quoted_backslash: + if (ngx_http_field_value_char(ch)) { + state = sw_chunk_extension_quoted_value; + break; + } + goto invalid; + + case sw_chunk_extension_unquoted_value: + if (ngx_http_token_char(ch)) { + break; + } + goto before_semi; + + case sw_chunk_extension_almost_done: if (ch == LF) { + if (ctx->size) { + state = sw_chunk_data; + break; + } if (keep_trailers) { goto done; } @@ -2378,16 +2443,17 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, goto invalid; case sw_trailer: - switch (ch) { - case CR: + if (ch == CR) { state = sw_trailer_almost_done; break; - case LF: - goto invalid; - default: - state = sw_trailer_header; } - break; + if (ngx_http_token_char(ch)) { + state = sw_trailer_name; + r->lowcase_index = 1; + r->lowcase_header[0] = (ch | 0x20); + break; + } + goto invalid; case sw_trailer_almost_done: if (ch == LF) { @@ -2395,15 +2461,44 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, } goto invalid; - case sw_trailer_header: - switch (ch) { - case CR: + case sw_trailer_name: + if (ngx_http_token_char(ch)) { + if (r->lowcase_index < NGX_HTTP_LC_HEADER_LEN) { + /* ASCII uppercase letters become the lowercase ones. + * '-' is unchanged. */ + r->lowcase_header[r->lowcase_index++] = (ch | 0x20); + } + break; + } + if (ch == ':') { + switch (r->lowcase_index) { +#define X(v) \ + case sizeof(v "") - 1: \ + if (memcmp(r->lowcase_header, v, r->lowcase_index) == 0) { \ + goto invalid; \ + } \ + break + X("transfer-encoding"); + X("content-length"); + X("upgrade"); +#undef X + default: + break; + } + state = sw_trailer_value; + break; + } + goto invalid; + + case sw_trailer_value: + if (ngx_http_field_value_char(ch)) { + break; + } + if (ch == CR) { state = sw_trailer_header_almost_done; break; - case LF: - goto invalid; } - break; + goto invalid; case sw_trailer_header_almost_done: if (ch == LF) { @@ -2420,53 +2515,63 @@ data: ctx->state = state; b->pos = pos; - if (ctx->size > NGX_MAX_OFF_T_VALUE - 5) { + if (ctx->size > NGX_MAX_OFF_T_VALUE - 13) { goto invalid; } - + off_t min_length = (ctx->size ? ctx->size + 7 /* CRLF "0" CRLF CRLF */ + : 2 /* CRLF */); switch (state) { - case sw_chunk_start: ctx->length = 5 /* "0" CRLF CRLF */; break; case sw_chunk_size: - ctx->length = 2 /* CRLF */ - + (ctx->size ? ctx->size + 7 /* CRLF "0" CRLF CRLF */ - : 2 /* CRLF */); - break; - case sw_chunk_extension: - ctx->length = 2 /* CRLF */ + ctx->size + 7 /* CRLF "0" CRLF CRLF */; + case sw_chunk_extension_before_semi: + case sw_chunk_extension_unquoted_value: + ctx->length = 2 /* CRLF */ + min_length; break; case sw_chunk_extension_almost_done: - ctx->length = 1 /* LF */ + ctx->size + 7 /* CRLF "0" CRLF CRLF */; + ctx->length = 1 /* LF */ + min_length; + break; + case sw_chunk_extension_before_semi_bws: + ctx->length = 6 /* ;a=b CRLF */ + min_length; + break; + case sw_chunk_extension: + ctx->length = 5 /* a=b CRLF */ + min_length; + break; + case sw_chunk_extension_bws_before_equal: + case sw_chunk_extension_name: + ctx->length = 4 /* =b CRLF */ + min_length; + break; + case sw_chunk_extension_value_start: + ctx->length = 3 /* b CRLF */ + min_length; + break; + case sw_chunk_extension_quoted_value: + ctx->length = 3 /* " CRLF */ + min_length; + break; + case sw_chunk_extension_value_quoted_backslash: + ctx->length = 4 /* a" CRLF */ + min_length; break; case sw_chunk_data: - ctx->length = ctx->size + 7 /* CRLF "0" CRLF CRLF */; - break; - case sw_after_data: - ctx->length = 7 /* CRLF "0" CRLF CRLF */; + ctx->length = min_length; break; case sw_after_data_almost_done: ctx->length = 6 /* LF "0" CRLF CRLF */; break; - case sw_last_chunk_extension: - ctx->length = 4 /* CRLF CRLF */; - break; - case sw_last_chunk_extension_almost_done: - ctx->length = 3 /* LF CRLF */; - break; case sw_trailer: ctx->length = 2 /* CRLF */; break; - case sw_trailer_almost_done: - ctx->length = 1 /* LF */; + case sw_trailer_name: + ctx->length = 5 /* : CRLF CRLF */; break; - case sw_trailer_header: + case sw_trailer_value: ctx->length = 4 /* CRLF CRLF */; break; case sw_trailer_header_almost_done: ctx->length = 3 /* LF CRLF */; break; + case sw_trailer_almost_done: + ctx->length = 1 /* LF */; + break; }