Skip to content

Commit f68d2d6

Browse files
bpo-44022: Fix http client infinite line reading (DoS) after a HTTP 100 Continue (GH-25916) (GH-25935)
Fixes http.client potential denial of service where it could get stuck reading lines from a malicious server after a 100 Continue response. Co-authored-by: Gregory P. Smith <greg@krypto.org> (cherry picked from commit 47895e3) Co-authored-by: Gen Xu <xgbarry@gmail.com>
1 parent 3fbe961 commit f68d2d6

File tree

3 files changed

+32
-18
lines changed

3 files changed

+32
-18
lines changed

Lib/http/client.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -205,15 +205,11 @@ def getallmatchingheaders(self, name):
205205
lst.append(line)
206206
return lst
207207

208-
def parse_headers(fp, _class=HTTPMessage):
209-
"""Parses only RFC2822 headers from a file pointer.
210-
211-
email Parser wants to see strings rather than bytes.
212-
But a TextIOWrapper around self.rfile would buffer too many bytes
213-
from the stream, bytes which we later need to read as bytes.
214-
So we read the correct bytes here, as bytes, for email Parser
215-
to parse.
208+
def _read_headers(fp):
209+
"""Reads potential header lines into a list from a file pointer.
216210
211+
Length of line is limited by _MAXLINE, and number of
212+
headers is limited by _MAXHEADERS.
217213
"""
218214
headers = []
219215
while True:
@@ -225,6 +221,19 @@ def parse_headers(fp, _class=HTTPMessage):
225221
raise HTTPException("got more than %d headers" % _MAXHEADERS)
226222
if line in (b'\r\n', b'\n', b''):
227223
break
224+
return headers
225+
226+
def parse_headers(fp, _class=HTTPMessage):
227+
"""Parses only RFC2822 headers from a file pointer.
228+
229+
email Parser wants to see strings rather than bytes.
230+
But a TextIOWrapper around self.rfile would buffer too many bytes
231+
from the stream, bytes which we later need to read as bytes.
232+
So we read the correct bytes here, as bytes, for email Parser
233+
to parse.
234+
235+
"""
236+
headers = _read_headers(fp)
228237
hstring = b''.join(headers).decode('iso-8859-1')
229238
return email.parser.Parser(_class=_class).parsestr(hstring)
230239

@@ -312,15 +321,10 @@ def begin(self):
312321
if status != CONTINUE:
313322
break
314323
# skip the header from the 100 response
315-
while True:
316-
skip = self.fp.readline(_MAXLINE + 1)
317-
if len(skip) > _MAXLINE:
318-
raise LineTooLong("header line")
319-
skip = skip.strip()
320-
if not skip:
321-
break
322-
if self.debuglevel > 0:
323-
print("header:", skip)
324+
skipped_headers = _read_headers(self.fp)
325+
if self.debuglevel > 0:
326+
print("headers:", skipped_headers)
327+
del skipped_headers
324328

325329
self.code = self.status = status
326330
self.reason = reason.strip()

Lib/test/test_httplib.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,14 @@ def test_overflowing_header_line(self):
971971
resp = client.HTTPResponse(FakeSocket(body))
972972
self.assertRaises(client.LineTooLong, resp.begin)
973973

974+
def test_overflowing_header_limit_after_100(self):
975+
body = (
976+
'HTTP/1.1 100 OK\r\n'
977+
'r\n' * 32768
978+
)
979+
resp = client.HTTPResponse(FakeSocket(body))
980+
self.assertRaises(client.HTTPException, resp.begin)
981+
974982
def test_overflowing_chunked_line(self):
975983
body = (
976984
'HTTP/1.1 200 OK\r\n'
@@ -1377,7 +1385,7 @@ def readline(self, limit):
13771385
class OfflineTest(TestCase):
13781386
def test_all(self):
13791387
# Documented objects defined in the module should be in __all__
1380-
expected = {"responses"} # White-list documented dict() object
1388+
expected = {"responses"} # Allowlist documented dict() object
13811389
# HTTPMessage, parse_headers(), and the HTTP status code constants are
13821390
# intentionally omitted for simplicity
13831391
blacklist = {"HTTPMessage", "parse_headers"}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
mod:`http.client` now avoids infinitely reading potential HTTP headers after a
2+
``100 Continue`` status response from the server.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy