| From 3ee79c1674fd6f99e8efca52cd7510e08b766770 Mon Sep 17 00:00:00 2001 |
| From: Daniel Stenberg <daniel@haxx.se> |
| Date: Wed, 2 Aug 2023 23:34:48 +0200 |
| Subject: [PATCH] http: return error when receiving too large header set |
| |
| To avoid abuse. The limit is set to 300 KB for the accumulated size of |
| all received HTTP headers for a single response. Incomplete research |
| suggests that Chrome uses a 256-300 KB limit, while Firefox allows up to |
| 1MB. |
| |
| Closes #11582 |
| --- |
| lib/c-hyper.c | 12 +++++++----- |
| lib/cf-h1-proxy.c | 4 +++- |
| lib/http.c | 34 ++++++++++++++++++++++++++++++---- |
| lib/http.h | 9 +++++++++ |
| lib/pingpong.c | 4 +++- |
| lib/urldata.h | 17 ++++++++--------- |
| 6 files changed, 60 insertions(+), 20 deletions(-) |
| |
| diff --git a/lib/c-hyper.c b/lib/c-hyper.c |
| index c29983c0b24a6..0b9d9ab478e67 100644 |
| --- a/lib/c-hyper.c |
| +++ b/lib/c-hyper.c |
| @@ -182,8 +182,11 @@ static int hyper_each_header(void *userdata, |
| } |
| } |
| |
| - data->info.header_size += (curl_off_t)len; |
| - data->req.headerbytecount += (curl_off_t)len; |
| + result = Curl_bump_headersize(data, len, FALSE); |
| + if(result) { |
| + data->state.hresult = result; |
| + return HYPER_ITER_BREAK; |
| + } |
| return HYPER_ITER_CONTINUE; |
| } |
| |
| @@ -313,9 +316,8 @@ static CURLcode status_line(struct Curl_easy *data, |
| if(result) |
| return result; |
| } |
| - data->info.header_size += (curl_off_t)len; |
| - data->req.headerbytecount += (curl_off_t)len; |
| - return CURLE_OK; |
| + result = Curl_bump_headersize(data, len, FALSE); |
| + return result; |
| } |
| |
| /* |
| diff --git a/lib/cf-h1-proxy.c b/lib/cf-h1-proxy.c |
| index c9b157c9bccc7..b1d8cb618b7d1 100644 |
| --- a/lib/cf-h1-proxy.c |
| +++ b/lib/cf-h1-proxy.c |
| @@ -587,7 +587,9 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, |
| return result; |
| } |
| |
| - data->info.header_size += (long)perline; |
| + result = Curl_bump_headersize(data, perline, TRUE); |
| + if(result) |
| + return result; |
| |
| /* Newlines are CRLF, so the CR is ignored as the line isn't |
| really terminated until the LF comes. Treat a following CR |
| diff --git a/lib/http.c b/lib/http.c |
| index f7c71afd7d847..bc78ff97435c4 100644 |
| --- a/lib/http.c |
| +++ b/lib/http.c |
| @@ -3920,6 +3920,29 @@ static CURLcode verify_header(struct Curl_easy *data) |
| return CURLE_OK; |
| } |
| |
| +CURLcode Curl_bump_headersize(struct Curl_easy *data, |
| + size_t delta, |
| + bool connect_only) |
| +{ |
| + size_t bad = 0; |
| + if(delta < MAX_HTTP_RESP_HEADER_SIZE) { |
| + if(!connect_only) |
| + data->req.headerbytecount += (unsigned int)delta; |
| + data->info.header_size += (unsigned int)delta; |
| + if(data->info.header_size > MAX_HTTP_RESP_HEADER_SIZE) |
| + bad = data->info.header_size; |
| + } |
| + else |
| + bad = data->info.header_size + delta; |
| + if(bad) { |
| + failf(data, "Too large response headers: %zu > %zu", |
| + bad, MAX_HTTP_RESP_HEADER_SIZE); |
| + return CURLE_RECV_ERROR; |
| + } |
| + return CURLE_OK; |
| +} |
| + |
| + |
| /* |
| * Read any HTTP header lines from the server and pass them to the client app. |
| */ |
| @@ -4173,8 +4196,9 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, |
| if(result) |
| return result; |
| |
| - data->info.header_size += (long)headerlen; |
| - data->req.headerbytecount += (long)headerlen; |
| + result = Curl_bump_headersize(data, headerlen, FALSE); |
| + if(result) |
| + return result; |
| |
| /* |
| * When all the headers have been parsed, see if we should give |
| @@ -4496,8 +4520,10 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, |
| if(result) |
| return result; |
| |
| - data->info.header_size += Curl_dyn_len(&data->state.headerb); |
| - data->req.headerbytecount += Curl_dyn_len(&data->state.headerb); |
| + result = Curl_bump_headersize(data, Curl_dyn_len(&data->state.headerb), |
| + FALSE); |
| + if(result) |
| + return result; |
| |
| Curl_dyn_reset(&data->state.headerb); |
| } |
| diff --git a/lib/http.h b/lib/http.h |
| index df3b4e38b8a88..4aeabc345938c 100644 |
| --- a/lib/http.h |
| +++ b/lib/http.h |
| @@ -64,6 +64,10 @@ extern const struct Curl_handler Curl_handler_wss; |
| |
| struct dynhds; |
| |
| +CURLcode Curl_bump_headersize(struct Curl_easy *data, |
| + size_t delta, |
| + bool connect_only); |
| + |
| /* Header specific functions */ |
| bool Curl_compareheader(const char *headerline, /* line to check */ |
| const char *header, /* header keyword _with_ colon */ |
| @@ -183,6 +187,11 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data); |
| #define EXPECT_100_THRESHOLD (1024*1024) |
| #endif |
| |
| +/* MAX_HTTP_RESP_HEADER_SIZE is the maximum size of all response headers |
| + combined that libcurl allows for a single HTTP response, any HTTP |
| + version. This count includes CONNECT response headers. */ |
| +#define MAX_HTTP_RESP_HEADER_SIZE (300*1024) |
| + |
| #endif /* CURL_DISABLE_HTTP */ |
| |
| /**************************************************************************** |
| diff --git a/lib/pingpong.c b/lib/pingpong.c |
| index f3f7cb93cb9b7..523bbec189fe6 100644 |
| --- a/lib/pingpong.c |
| +++ b/lib/pingpong.c |
| @@ -341,7 +341,9 @@ CURLcode Curl_pp_readresp(struct Curl_easy *data, |
| ssize_t clipamount = 0; |
| bool restart = FALSE; |
| |
| - data->req.headerbytecount += (long)gotbytes; |
| + result = Curl_bump_headersize(data, gotbytes, FALSE); |
| + if(result) |
| + return result; |
| |
| pp->nread_resp += gotbytes; |
| for(i = 0; i < gotbytes; ptr++, i++) { |
| diff --git a/lib/urldata.h b/lib/urldata.h |
| index e5446b6840f63..d21aa415dc94b 100644 |
| --- a/lib/urldata.h |
| +++ b/lib/urldata.h |
| @@ -629,17 +629,16 @@ struct SingleRequest { |
| curl_off_t bytecount; /* total number of bytes read */ |
| curl_off_t writebytecount; /* number of bytes written */ |
| |
| - curl_off_t headerbytecount; /* only count received headers */ |
| - curl_off_t deductheadercount; /* this amount of bytes doesn't count when we |
| - check if anything has been transferred at |
| - the end of a connection. We use this |
| - counter to make only a 100 reply (without a |
| - following second response code) result in a |
| - CURLE_GOT_NOTHING error code */ |
| - |
| curl_off_t pendingheader; /* this many bytes left to send is actually |
| header and not body */ |
| struct curltime start; /* transfer started at this time */ |
| + unsigned int headerbytecount; /* only count received headers */ |
| + unsigned int deductheadercount; /* this amount of bytes doesn't count when |
| + we check if anything has been transferred |
| + at the end of a connection. We use this |
| + counter to make only a 100 reply (without |
| + a following second response code) result |
| + in a CURLE_GOT_NOTHING error code */ |
| enum { |
| HEADER_NORMAL, /* no bad header at all */ |
| HEADER_PARTHEADER, /* part of the chunk is a bad header, the rest |
| @@ -1089,7 +1088,6 @@ struct PureInfo { |
| int httpversion; /* the http version number X.Y = X*10+Y */ |
| time_t filetime; /* If requested, this is might get set. Set to -1 if the |
| time was unretrievable. */ |
| - curl_off_t header_size; /* size of read header(s) in bytes */ |
| curl_off_t request_size; /* the amount of bytes sent in the request(s) */ |
| unsigned long proxyauthavail; /* what proxy auth types were announced */ |
| unsigned long httpauthavail; /* what host auth types were announced */ |
| @@ -1097,6 +1095,7 @@ struct PureInfo { |
| char *contenttype; /* the content type of the object */ |
| char *wouldredirect; /* URL this would've been redirected to if asked to */ |
| curl_off_t retry_after; /* info from Retry-After: header */ |
| + unsigned int header_size; /* size of read header(s) in bytes */ |
| |
| /* PureInfo members 'conn_primary_ip', 'conn_primary_port', 'conn_local_ip' |
| and, 'conn_local_port' are copied over from the connectdata struct in |