Skip to content

Commit 8d16f15

Browse files
gh-119452: Fix OOM vulnerability in http.server
The CGI server on Windows could consume the amount of memory specified in the Content-Length header of the request even if the client does not send such much data. Now it reads the POST request body by chunks, therefore the momory consumption is proportional to the amont of sent data.
1 parent 858b9e8 commit 8d16f15

File tree

3 files changed

+52
-1
lines changed

3 files changed

+52
-1
lines changed

Lib/http/server.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@
127127

128128
DEFAULT_ERROR_CONTENT_TYPE = "text/html;charset=utf-8"
129129

130+
# Data larger than this will be read in chunks, to prevent extreme
131+
# overallocation.
132+
SAFE_BUF_SIZE = 1 << 20
133+
130134
class HTTPServer(socketserver.TCPServer):
131135

132136
allow_reuse_address = 1 # Seems to make sense in testing environment
@@ -1224,7 +1228,13 @@ def run_cgi(self):
12241228
env = env
12251229
)
12261230
if self.command.lower() == "post" and nbytes > 0:
1227-
data = self.rfile.read(nbytes)
1231+
cursize = 0
1232+
data = self.rfile.read(min(nbytes, SAFE_BUF_SIZE))
1233+
while (len(data) < nbytes and len(data) != cursize and
1234+
select.select([self.rfile._sock], [], [], 0)[0]):
1235+
cursize = len(data)
1236+
delta = min(cursize, nbytes - cursize)
1237+
data += self.rfile.read(delta)
12281238
else:
12291239
data = None
12301240
# throw away additional data [see bug #427345]

Lib/test/test_httpservers.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,20 @@ def test_html_escape_filename(self):
695695
print("</pre>")
696696
"""
697697

698+
cgi_file7 = """\
699+
#!%s
700+
import os
701+
import sys
702+
703+
print("Content-type: text/plain")
704+
print()
705+
706+
content_length = int(os.environ["CONTENT_LENGTH"])
707+
body = sys.stdin.buffer.read(content_length)
708+
709+
print(f"{content_length} {len(body)}")
710+
"""
711+
698712

699713
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
700714
"This test can't be run reliably as root (issue #13308).")
@@ -734,6 +748,8 @@ def setUp(self):
734748
self.file3_path = None
735749
self.file4_path = None
736750
self.file5_path = None
751+
self.file6_path = None
752+
self.file7_path = None
737753

738754
# The shebang line should be pure ASCII: use symlink if possible.
739755
# See issue #7668.
@@ -788,6 +804,11 @@ def setUp(self):
788804
file6.write(cgi_file6 % self.pythonexe)
789805
os.chmod(self.file6_path, 0o777)
790806

807+
self.file7_path = os.path.join(self.cgi_dir, 'file7.py')
808+
with open(self.file7_path, 'w', encoding='utf-8') as file7:
809+
file7.write(cgi_file7 % self.pythonexe)
810+
os.chmod(self.file7_path, 0o777)
811+
791812
os.chdir(self.parent_dir)
792813

793814
def tearDown(self):
@@ -810,6 +831,8 @@ def tearDown(self):
810831
os.remove(self.file5_path)
811832
if self.file6_path:
812833
os.remove(self.file6_path)
834+
if self.file7_path:
835+
os.remove(self.file7_path)
813836
os.rmdir(self.cgi_child_dir)
814837
os.rmdir(self.cgi_dir)
815838
os.rmdir(self.cgi_dir_in_sub_dir)
@@ -882,6 +905,21 @@ def test_post(self):
882905

883906
self.assertEqual(res.read(), b'1, python, 123456' + self.linesep)
884907

908+
def test_large_content_length(self):
909+
for w in range(15, 25):
910+
size = 1 << w
911+
body = b'X' * size
912+
headers = {'Content-Length' : str(size)}
913+
res = self.request('/cgi-bin/file7.py', 'POST', body, headers)
914+
self.assertEqual(res.read(), b'%d %d' % (size, size) + self.linesep)
915+
916+
def test_large_content_length_truncated(self):
917+
for w in range(18, 65):
918+
size = 1 << w
919+
headers = {'Content-Length' : str(size)}
920+
res = self.request('/cgi-bin/file1.py', 'POST', b'x', headers)
921+
self.assertEqual(res.read(), b'Hello World' + self.linesep)
922+
885923
def test_invaliduri(self):
886924
res = self.request('/cgi-bin/invalid')
887925
res.read()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix OOM vulnerability in :mod:`http.server`, when handling the POST request
2+
in the CGI server on Windows could cause consuming an arbitrary amount of
3+
memory.

0 commit comments

Comments
 (0)