From: Willy Tarreau Date: Wed, 19 Dec 2018 12:08:08 +0000 (+0100) Subject: MEDIUM: h2: properly check and deduplicate the content-length header in HTX X-Git-Tag: v1.9.0~26 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=beefaee4f53ccbe7610e81cf81c97042d7272a4d;p=thirdparty%2Fhaproxy.git MEDIUM: h2: properly check and deduplicate the content-length header in HTX When producing an HTX message, we can't rely on the next-level H1 parser to check and deduplicate the content-length header, so we have to do it while parsing a message. The algorithm is the exact same as used for H1 messages. --- diff --git a/include/common/h2.h b/include/common/h2.h index e0684270ca..acc80c017d 100644 --- a/include/common/h2.h +++ b/include/common/h2.h @@ -155,6 +155,7 @@ enum h2_err { /* various protocol processing functions */ int h2_make_h1_request(struct http_hdr *list, char *out, int osize, unsigned int *msgf); +int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned long long *body_len); int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *msgf); int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *msgf); diff --git a/src/h2.c b/src/h2.c index 883075262b..7c6cbeeac0 100644 --- a/src/h2.c +++ b/src/h2.c @@ -320,6 +320,78 @@ int h2_make_h1_request(struct http_hdr *list, char *out, int osize, unsigned int return -1; } +/* Parse the Content-Length header field of an HTTP/2 request. The function + * checks all possible occurrences of a comma-delimited value, and verifies + * if any of them doesn't match a previous value. It returns <0 if a value + * differs, 0 if the whole header can be dropped (i.e. already known), or >0 + * if the value can be indexed (first one). In the last case, the value might + * be adjusted and the caller must only add the updated value. + */ +int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned long long *body_len) +{ + char *e, *n; + unsigned long long cl; + int not_first = !!(*msgf & H2_MSGF_BODY_CL); + struct ist word; + + word.ptr = value->ptr - 1; // -1 for next loop's pre-increment + e = value->ptr + value->len; + + while (++word.ptr < e) { + /* skip leading delimitor and blanks */ + if (unlikely(HTTP_IS_LWS(*word.ptr))) + continue; + + /* digits only now */ + for (cl = 0, n = word.ptr; n < e; n++) { + unsigned int c = *n - '0'; + if (unlikely(c > 9)) { + /* non-digit */ + if (unlikely(n == word.ptr)) // spaces only + goto fail; + break; + } + if (unlikely(cl > ULLONG_MAX / 10ULL)) + goto fail; /* multiply overflow */ + cl = cl * 10ULL; + if (unlikely(cl + c < cl)) + goto fail; /* addition overflow */ + cl = cl + c; + } + + /* keep a copy of the exact cleaned value */ + word.len = n - word.ptr; + + /* skip trailing LWS till next comma or EOL */ + for (; n < e; n++) { + if (!HTTP_IS_LWS(*n)) { + if (unlikely(*n != ',')) + goto fail; + break; + } + } + + /* if duplicate, must be equal */ + if (*msgf & H2_MSGF_BODY_CL && cl != *body_len) + goto fail; + + /* OK, store this result as the one to be indexed */ + *msgf |= H2_MSGF_BODY_CL; + *body_len = cl; + *value = word; + word.ptr = n; + } + /* here we've reached the end with a single value or a series of + * identical values, all matching previous series if any. The last + * parsed value was sent back into . We just have to decide + * if this occurrence has to be indexed (it's the first one) or + * silently skipped (it's not the first one) + */ + return !not_first; + fail: + return -1; +} + /* Prepare the request line into from pseudo headers stored in . * indicates what was found so far. This should be called once at the * detection of the first general header field or at the end of the request if @@ -424,6 +496,7 @@ int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *ms int i; struct htx_sl *sl = NULL; unsigned int sl_flags = 0; + unsigned long long body_len; lck = ck = -1; // no cookie for now fields = 0; @@ -475,10 +548,14 @@ int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *ms if (isteq(list[idx].n, ist("host"))) fields |= H2_PHDR_FND_HOST; - if ((*msgf & (H2_MSGF_BODY|H2_MSGF_BODY_TUNNEL|H2_MSGF_BODY_CL)) == H2_MSGF_BODY && - isteq(list[idx].n, ist("content-length"))) { - *msgf |= H2_MSGF_BODY_CL; + if (isteq(list[idx].n, ist("content-length"))) { + ret = h2_parse_cont_len_header(msgf, &list[idx].v, &body_len); + if (ret < 0) + goto fail; + sl_flags |= HTX_SL_F_CLEN; + if (ret == 0) + continue; // skip this duplicate } /* these ones are forbidden in requests (RFC7540#8.1.2.2) */ @@ -646,6 +723,7 @@ int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *m int i; struct htx_sl *sl = NULL; unsigned int sl_flags = 0; + unsigned long long body_len; fields = 0; for (idx = 0; list[idx].n.len != 0; idx++) { @@ -693,10 +771,14 @@ int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *m fields |= H2_PHDR_FND_NONE; } - if ((*msgf & (H2_MSGF_BODY|H2_MSGF_BODY_TUNNEL|H2_MSGF_BODY_CL)) == H2_MSGF_BODY && - isteq(list[idx].n, ist("content-length"))) { - *msgf |= H2_MSGF_BODY_CL; + if (isteq(list[idx].n, ist("content-length"))) { + ret = h2_parse_cont_len_header(msgf, &list[idx].v, &body_len); + if (ret < 0) + goto fail; + sl_flags |= HTX_SL_F_CLEN; + if (ret == 0) + continue; // skip this duplicate } /* these ones are forbidden in responses (RFC7540#8.1.2.2) */