-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
[3.14] gh-119452: Fix a potential virtual memory allocation denial of service in http.server #119455
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[3.14] gh-119452: Fix a potential virtual memory allocation denial of service in http.server #119455
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -134,6 +134,10 @@ | |
|
|
||
| DEFAULT_ERROR_CONTENT_TYPE = "text/html;charset=utf-8" | ||
|
|
||
| # Data larger than this will be read in chunks, to prevent extreme | ||
| # overallocation. | ||
| _MIN_READ_BUF_SIZE = 1 << 20 | ||
|
|
||
| class HTTPServer(socketserver.TCPServer): | ||
|
|
||
| allow_reuse_address = True # Seems to make sense in testing environment | ||
|
|
@@ -1284,7 +1288,16 @@ def run_cgi(self): | |
| env = env | ||
| ) | ||
| if self.command.lower() == "post" and nbytes > 0: | ||
| data = self.rfile.read(nbytes) | ||
| cursize = 0 | ||
| data = self.rfile.read(min(nbytes, _MIN_READ_BUF_SIZE)) | ||
| while (len(data) < nbytes and len(data) != cursize and | ||
| select.select([self.rfile._sock], [], [], 0)[0]): | ||
| cursize = len(data) | ||
| # This is a geometric increase in read size (never more | ||
| # than doubling our the current length of data per loop | ||
| # iteration). | ||
| delta = min(cursize, nbytes - cursize) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a comment that this is a geometric increase in read size (never more than doubling our current the length of data per loop iteration) could be good. otherwise it takes some staring at the code to understand what it is doing with the delta. not a big deal though given CGIHTTPRequestHandler is gone in 3.15 onwards. :) |
||
| data += self.rfile.read(delta) | ||
| else: | ||
| data = None | ||
| # throw away additional data [see bug #427345] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| Fix a potential memory denial of service in the :mod:`http.server` module. | ||
| When a malicious user is connected to the CGI server on Windows, it could cause | ||
| an arbitrary amount of memory to be allocated. | ||
| This could have led to symptoms including a :exc:`MemoryError`, swapping, out | ||
| of memory (OOM) killed processes or containers, or even system crashes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the timeout=0 fourth arg here makes this non-blocking. the loop won't wait for further data to arrive. so we wind up not processing all of the data.
(Claude Code Opus 4.5 looking at the buildbot failure, issue, pr & branch triaged this FWIW)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why select at all here given we just want to block on reading another chunk? if the socket closes or errors, that'd return anyways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quick PR to make it blocking: #142176
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What should this do if the data doesn't arrive?
Wait forever, as in did before the fix?
Or doses this PR also meant fix the DoS? (I don't see how, is there a timeout setting or an implicit one?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was so long ago that I don't remember my train of thought, but playing around with the code I think I've recreated it.
I tested this code on Linux with
have_fork = False. The tests failed because the underlying file object is unbuffered andread(n)can return less thannbytes. So the loop differs from loops in other fixes -- it continues until it reads 0 bytes.select()is needed to avoid blocking. Until now all worked on Linux and Windows on my computer and in CI tests. Perhaps this depends on the system load or on non-debug build (I only tested with the debug build).Perhaps we need to use non-zero timeout (
self.timeout?) and set it to non-None for tests.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
xmlrpc/server.py has a similar code path
cpython/Lib/xmlrpc/server.py
Lines 483 to 493 in d3c888b
but without a select. The last change there was ec1712a in 2012 adding the break if no data was read. So without a timeout it will suffer the same problem if
content-lengthlies and less data is sent?OTOH, does anybody out there operate a server without a timeout?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes. this is intentionally a blocking read until sufficient data arrives or the connection dies. :)
fwiw, i missed this myself in the first code review. cursed unnamed parameters and presumptions about what 0 means. 😁
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we do not need to add timeout logic here. just remove the select. this path was always intended to block until it got enough data or the socket is closed. the issue being protected against has nothing to do with blocking by holding a connection open, it's just about memory allocation.
This 1990s era server code was never meant to provide a robust server. There's a reason we got rid of it in 3.15.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need timeout logic here to be able to test this change.