summaryrefslogtreecommitdiff
path: root/backport-CVE-2023-38039.patch
blob: 03a879b9b9d81f9b1d60d23b69bcc18c9c501036 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
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