| From a91c22a072cbb32e296f1efba3502f1b7775dfaf Mon Sep 17 00:00:00 2001 |
| From: Daniel Stenberg <daniel@haxx.se> |
| Date: Sun, 26 Jun 2022 11:00:48 +0200 |
| Subject: [PATCH] cookie: apply limits |
| |
| - Send no more than 150 cookies per request |
| - Cap the max length used for a cookie: header to 8K |
| - Cap the max number of received Set-Cookie: headers to 50 |
| |
| Bug: https://curl.se/docs/CVE-2022-32205.html |
| CVE-2022-32205 |
| Reported-by: Harry Sintonen |
| Closes #9048 |
| |
| Upstream-Status: Backport [https://github.com/curl/curl/commit/48d7064a49148f0394] |
| Signed-off-by: Robert Joslyn <robert.joslyn@redrectangle.org> |
| --- |
| lib/cookie.c | 14 ++++++++++++-- |
| lib/cookie.h | 21 +++++++++++++++++++-- |
| lib/http.c | 13 +++++++++++-- |
| lib/urldata.h | 1 + |
| 4 files changed, 43 insertions(+), 6 deletions(-) |
| |
| diff --git a/lib/cookie.c b/lib/cookie.c |
| index 1b8c8f9..8a6aa1a 100644 |
| --- a/lib/cookie.c |
| +++ b/lib/cookie.c |
| @@ -477,6 +477,10 @@ Curl_cookie_add(struct Curl_easy *data, |
| (void)data; |
| #endif |
| |
| + DEBUGASSERT(MAX_SET_COOKIE_AMOUNT <= 255); /* counter is an unsigned char */ |
| + if(data->req.setcookies >= MAX_SET_COOKIE_AMOUNT) |
| + return NULL; |
| + |
| /* First, alloc and init a new struct for it */ |
| co = calloc(1, sizeof(struct Cookie)); |
| if(!co) |
| @@ -816,7 +820,7 @@ Curl_cookie_add(struct Curl_easy *data, |
| freecookie(co); |
| return NULL; |
| } |
| - |
| + data->req.setcookies++; |
| } |
| else { |
| /* |
| @@ -1354,7 +1358,8 @@ static struct Cookie *dup_cookie(struct Cookie *src) |
| * |
| * It shall only return cookies that haven't expired. |
| */ |
| -struct Cookie *Curl_cookie_getlist(struct CookieInfo *c, |
| +struct Cookie *Curl_cookie_getlist(struct Curl_easy *data, |
| + struct CookieInfo *c, |
| const char *host, const char *path, |
| bool secure) |
| { |
| @@ -1409,6 +1414,11 @@ struct Cookie *Curl_cookie_getlist(struct CookieInfo *c, |
| mainco = newco; |
| |
| matches++; |
| + if(matches >= MAX_COOKIE_SEND_AMOUNT) { |
| + infof(data, "Included max number of cookies (%u) in request!", |
| + matches); |
| + break; |
| + } |
| } |
| else |
| goto fail; |
| diff --git a/lib/cookie.h b/lib/cookie.h |
| index 0ffe08e..7411980 100644 |
| --- a/lib/cookie.h |
| +++ b/lib/cookie.h |
| @@ -81,10 +81,26 @@ struct CookieInfo { |
| */ |
| #define MAX_COOKIE_LINE 5000 |
| |
| -/* This is the maximum length of a cookie name or content we deal with: */ |
| +/* Maximum length of an incoming cookie name or content we deal with. Longer |
| + cookies are ignored. */ |
| #define MAX_NAME 4096 |
| #define MAX_NAME_TXT "4095" |
| |
| +/* Maximum size for an outgoing cookie line libcurl will use in an http |
| + request. This is the default maximum length used in some versions of Apache |
| + httpd. */ |
| +#define MAX_COOKIE_HEADER_LEN 8190 |
| + |
| +/* Maximum number of cookies libcurl will send in a single request, even if |
| + there might be more cookies that match. One reason to cap the number is to |
| + keep the maximum HTTP request within the maximum allowed size. */ |
| +#define MAX_COOKIE_SEND_AMOUNT 150 |
| + |
| +/* Maximum number of Set-Cookie: lines accepted in a single response. If more |
| + such header lines are received, they are ignored. This value must be less |
| + than 256 since an unsigned char is used to count. */ |
| +#define MAX_SET_COOKIE_AMOUNT 50 |
| + |
| struct Curl_easy; |
| /* |
| * Add a cookie to the internal list of cookies. The domain and path arguments |
| @@ -97,7 +113,8 @@ struct Cookie *Curl_cookie_add(struct Curl_easy *data, |
| const char *domain, const char *path, |
| bool secure); |
| |
| -struct Cookie *Curl_cookie_getlist(struct CookieInfo *c, const char *host, |
| +struct Cookie *Curl_cookie_getlist(struct Curl_easy *data, |
| + struct CookieInfo *c, const char *host, |
| const char *path, bool secure); |
| void Curl_cookie_freelist(struct Cookie *cookies); |
| void Curl_cookie_clearall(struct CookieInfo *cookies); |
| diff --git a/lib/http.c b/lib/http.c |
| index 4433824..2c8b0c4 100644 |
| --- a/lib/http.c |
| +++ b/lib/http.c |
| @@ -2709,12 +2709,14 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn, |
| } |
| |
| #if !defined(CURL_DISABLE_COOKIES) |
| + |
| CURLcode Curl_http_cookies(struct Curl_easy *data, |
| struct connectdata *conn, |
| struct dynbuf *r) |
| { |
| CURLcode result = CURLE_OK; |
| char *addcookies = NULL; |
| + bool linecap = FALSE; |
| if(data->set.str[STRING_COOKIE] && |
| !Curl_checkheaders(data, STRCONST("Cookie"))) |
| addcookies = data->set.str[STRING_COOKIE]; |
| @@ -2732,7 +2734,7 @@ CURLcode Curl_http_cookies(struct Curl_easy *data, |
| !strcmp(host, "127.0.0.1") || |
| !strcmp(host, "[::1]") ? TRUE : FALSE; |
| Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); |
| - co = Curl_cookie_getlist(data->cookies, host, data->state.up.path, |
| + co = Curl_cookie_getlist(data, data->cookies, host, data->state.up.path, |
| secure_context); |
| Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); |
| } |
| @@ -2746,6 +2748,13 @@ CURLcode Curl_http_cookies(struct Curl_easy *data, |
| if(result) |
| break; |
| } |
| + if((Curl_dyn_len(r) + strlen(co->name) + strlen(co->value) + 1) >= |
| + MAX_COOKIE_HEADER_LEN) { |
| + infof(data, "Restricted outgoing cookies due to header size, " |
| + "'%s' not sent", co->name); |
| + linecap = TRUE; |
| + break; |
| + } |
| result = Curl_dyn_addf(r, "%s%s=%s", count?"; ":"", |
| co->name, co->value); |
| if(result) |
| @@ -2756,7 +2765,7 @@ CURLcode Curl_http_cookies(struct Curl_easy *data, |
| } |
| Curl_cookie_freelist(store); |
| } |
| - if(addcookies && !result) { |
| + if(addcookies && !result && !linecap) { |
| if(!count) |
| result = Curl_dyn_addn(r, STRCONST("Cookie: ")); |
| if(!result) { |
| diff --git a/lib/urldata.h b/lib/urldata.h |
| index e006495..54faf7d 100644 |
| --- a/lib/urldata.h |
| +++ b/lib/urldata.h |
| @@ -707,6 +707,7 @@ struct SingleRequest { |
| #ifndef CURL_DISABLE_DOH |
| struct dohdata *doh; /* DoH specific data for this request */ |
| #endif |
| + unsigned char setcookies; |
| BIT(header); /* incoming data has HTTP header */ |
| BIT(content_range); /* set TRUE if Content-Range: was found */ |
| BIT(upload_done); /* set to TRUE when doing chunked transfer-encoding |