From b5df6ce8d01d4478d2897f0cfa44f0ce9acf314b Mon Sep 17 00:00:00 2001 From: panchengzhong <1144372483@qq.com> Date: Fri, 23 Sep 2022 11:29:49 +0800 Subject: [PATCH 1/2] CVE-2021-3737 Signed-off-by: panchengzhong <1144372483@qq.com> --- Lib/http/client.py | 38 +++++++++++++++++++++----------------- Lib/test/test_httplib.py | 17 +++++++++++++++-- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/Lib/http/client.py b/Lib/http/client.py index 16afc87..5909731 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -201,15 +201,11 @@ def getallmatchingheaders(self, name): lst.append(line) return lst -def parse_headers(fp, _class=HTTPMessage): - """Parses only RFC2822 headers from a file pointer. - - email Parser wants to see strings rather than bytes. - But a TextIOWrapper around self.rfile would buffer too many bytes - from the stream, bytes which we later need to read as bytes. - So we read the correct bytes here, as bytes, for email Parser - to parse. +def _read_headers(fp): + """Reads potential header lines into a list from a file pointer. + Length of line is limited by _MAXLINE, and number of + headers is limited by _MAXHEADERS. """ headers = [] while True: @@ -221,6 +217,19 @@ def parse_headers(fp, _class=HTTPMessage): raise HTTPException("got more than %d headers" % _MAXHEADERS) if line in (b'\r\n', b'\n', b''): break + return headers + +def parse_headers(fp, _class=HTTPMessage): + """Parses only RFC2822 headers from a file pointer. + + email Parser wants to see strings rather than bytes. + But a TextIOWrapper around self.rfile would buffer too many bytes + from the stream, bytes which we later need to read as bytes. + So we read the correct bytes here, as bytes, for email Parser + to parse. + + """ + headers = _read_headers(fp) hstring = b''.join(headers).decode('iso-8859-1') return email.parser.Parser(_class=_class).parsestr(hstring) @@ -308,15 +317,10 @@ def begin(self): if status != CONTINUE: break # skip the header from the 100 response - while True: - skip = self.fp.readline(_MAXLINE + 1) - if len(skip) > _MAXLINE: - raise LineTooLong("header line") - skip = skip.strip() - if not skip: - break - if self.debuglevel > 0: - print("header:", skip) + skipped_headers = _read_headers(self.fp) + if self.debuglevel > 0: + print("headers:", skipped_headers) + del skipped_headers self.code = self.status = status self.reason = reason.strip() diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index 28943f0..66bd05d 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -1002,7 +1002,20 @@ def test_overflowing_header_line(self): 'X-Foo: bar' + 'r' * 65536 + '\r\n\r\n' ) resp = client.HTTPResponse(FakeSocket(body)) - self.assertRaises(client.LineTooLong, resp.begin) + with self.assertRaises(client.HTTPException) as cm: + resp.begin() + # We must assert more because other reasonable errors that we + # do not want can also be HTTPException derived. + self.assertIn('got more than ', str(cm.exception)) + self.assertIn('headers', str(cm.exception)) + + def test_overflowing_header_limit_after_100(self): + body = ( + 'HTTP/1.1 100 OK\r\n' + 'r\n' * 32768 + ) + resp = client.HTTPResponse(FakeSocket(body)) + self.assertRaises(client.HTTPException, resp.begin) def test_overflowing_chunked_line(self): body = ( @@ -1405,7 +1418,7 @@ def readline(self, limit): class OfflineTest(TestCase): def test_all(self): # Documented objects defined in the module should be in __all__ - expected = {"responses"} # White-list documented dict() object + expected = {"responses"} # Allowlist documented dict() object # HTTPMessage, parse_headers(), and the HTTP status code constants are # intentionally omitted for simplicity blacklist = {"HTTPMessage", "parse_headers"} -- Gitee From 0a097b5234ea7c52b8e2342e014a5ee9fc216dc2 Mon Sep 17 00:00:00 2001 From: panchengzhong <1144372483@qq.com> Date: Fri, 23 Sep 2022 14:17:01 +0800 Subject: [PATCH 2/2] CVE-2021-3737 Signed-off-by: panchengzhong <1144372483@qq.com> --- Lib/test/test_httplib.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index 66bd05d..0e04231 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -1002,12 +1002,7 @@ def test_overflowing_header_line(self): 'X-Foo: bar' + 'r' * 65536 + '\r\n\r\n' ) resp = client.HTTPResponse(FakeSocket(body)) - with self.assertRaises(client.HTTPException) as cm: - resp.begin() - # We must assert more because other reasonable errors that we - # do not want can also be HTTPException derived. - self.assertIn('got more than ', str(cm.exception)) - self.assertIn('headers', str(cm.exception)) + self.assertRaises(client.LineTooLong, resp.begin) def test_overflowing_header_limit_after_100(self): body = ( @@ -1015,7 +1010,12 @@ def test_overflowing_header_limit_after_100(self): 'r\n' * 32768 ) resp = client.HTTPResponse(FakeSocket(body)) - self.assertRaises(client.HTTPException, resp.begin) + with self.assertRaises(client.HTTPException) as cm: + resp.begin() + # We must assert more because other reasonable errors that we + # do not want can also be HTTPException derived. + self.assertIn('got more than ', str(cm.exception)) + self.assertIn('headers', str(cm.exception)) def test_overflowing_chunked_line(self): body = ( -- Gitee