Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f68d2d6

Browse filesBrowse 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
Copy full SHA for f68d2d6

File tree

Expand file treeCollapse file tree

3 files changed

+32
-18
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+32
-18
lines changed

‎Lib/http/client.py

Copy file name to clipboardExpand all lines: Lib/http/client.py
+21-17Lines 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

Copy file name to clipboardExpand all lines: Lib/test/test_httplib.py
+9-1Lines 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"}
+2Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.