From dae96acef25d8c95aff9673e29aa5ddee862e9b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Sat, 19 Jun 2021 20:46:09 +0200 Subject: [PATCH] Backport bpo-44022: Fix http client infinite line reading (DoS) after a HTTP 100 Continue (GH-25916) Backport the fix from the following commit: commit 47895e31b6f626bc6ce47d175fe9d43c1098909d Author: Gen Xu Date: 2021-05-06 00:42:41 +0200 bpo-44022: Fix http client infinite line reading (DoS) after a HTTP 100 Continue (GH-25916) 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 Instead of reusing the header reading code, I have just added explicit counter to avoid having to refactor the old code. Plus the improved test from: commit e60ab843cbb016fb6ff8b4f418641ac05a9b2fcc Author: Gregory P. Smith Date: 2021-06-03 05:43:38 +0200 bpo-44022: Improve the regression test. (GH-26503) It wasn't actually detecting the regression due to the assertion being too lenient. --- Lib/httplib.py | 5 ++++- Lib/test/test_httplib.py | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Lib/httplib.py b/Lib/httplib.py index 81a08d5d714fc9..ebfa59ff93c615 100644 --- a/Lib/httplib.py +++ b/Lib/httplib.py @@ -453,11 +453,14 @@ def begin(self): if status != CONTINUE: break # skip the header from the 100 response + header_count = 0 while True: skip = self.fp.readline(_MAXLINE + 1) if len(skip) > _MAXLINE: raise LineTooLong("header line") - skip = skip.strip() + header_count += 1 + if header_count > _MAXHEADERS: + raise HTTPException("got more than %d headers" % _MAXHEADERS) if not skip: break if self.debuglevel > 0: diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index e20a0986dce4a9..5f6b1d0b20e312 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -675,6 +675,19 @@ def test_overflowing_header_line(self): resp = httplib.HTTPResponse(FakeSocket(body)) self.assertRaises(httplib.LineTooLong, resp.begin) + def test_overflowing_header_limit_after_100(self): + body = ( + 'HTTP/1.1 100 OK\r\n' + 'r\n' * 32768 + ) + resp = httplib.HTTPResponse(FakeSocket(body)) + with self.assertRaises(httplib.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 = ( 'HTTP/1.1 200 OK\r\n'