Skip to content

Conversation

@serhiy-storchaka
Copy link
Member

@serhiy-storchaka serhiy-storchaka commented May 23, 2024

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 memory consumption is proportional to the amount of sent data.

@gpshead
Copy link
Member

gpshead commented May 24, 2024

I've marked this Draft for now as discussion on this on the security response team list is not complete. (we'll summarize that in a public issue once it has settled)

@encukou
Copy link
Member

encukou commented Jan 27, 2025

See #119514 (comment) for results of the PSRT discussion.

@serhiy-storchaka serhiy-storchaka added the needs backport to 3.14 bugs and security fixes label May 8, 2025
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.
@serhiy-storchaka serhiy-storchaka changed the base branch from main to 3.14 November 18, 2025 13:53
@serhiy-storchaka serhiy-storchaka marked this pull request as ready for review November 18, 2025 13:53
@serhiy-storchaka serhiy-storchaka changed the title gh-119452: Fix OOM vulnerability in http.server [3.14] gh-119452: Fix OOM vulnerability in http.server Nov 18, 2025
@serhiy-storchaka serhiy-storchaka removed the needs backport to 3.14 bugs and security fixes label Nov 18, 2025
while (len(data) < nbytes and len(data) != cursize and
select.select([self.rfile._sock], [], [], 0)[0]):
cursize = len(data)
delta = min(cursize, nbytes - cursize)
Copy link
Member

Choose a reason for hiding this comment

The 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. :)

@miss-islington-app
Copy link

Thanks @serhiy-storchaka for the PR 🌮🎉.. I'm working now to backport this PR to: 3.10, 3.11, 3.12, 3.13.
🐍🍒⛏🤖

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Dec 1, 2025
…ial of service in http.server (pythonGH-119455)

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,
so that the memory consumption is proportional to the amount of sent
data.
(cherry picked from commit 29c657a)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Dec 1, 2025
…ial of service in http.server (pythonGH-119455)

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,
so that the memory consumption is proportional to the amount of sent
data.
(cherry picked from commit 29c657a)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
@bedevere-app
Copy link

bedevere-app bot commented Dec 1, 2025

GH-142130 is a backport of this pull request to the 3.13 branch.

@bedevere-app bedevere-app bot removed the needs backport to 3.13 bugs and security fixes label Dec 1, 2025
miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Dec 1, 2025
…ial of service in http.server (pythonGH-119455)

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,
so that the memory consumption is proportional to the amount of sent
data.
(cherry picked from commit 29c657a)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
@bedevere-app
Copy link

bedevere-app bot commented Dec 1, 2025

GH-142131 is a backport of this pull request to the 3.12 branch.

@bedevere-app bedevere-app bot removed the needs backport to 3.12 only security fixes label Dec 1, 2025
miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Dec 1, 2025
…ial of service in http.server (pythonGH-119455)

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,
so that the memory consumption is proportional to the amount of sent
data.
(cherry picked from commit 29c657a)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
@bedevere-app
Copy link

bedevere-app bot commented Dec 1, 2025

GH-142132 is a backport of this pull request to the 3.11 branch.

@bedevere-app bedevere-app bot removed the needs backport to 3.11 only security fixes label Dec 1, 2025
@bedevere-app
Copy link

bedevere-app bot commented Dec 1, 2025

GH-142133 is a backport of this pull request to the 3.10 branch.

@bedevere-app bedevere-app bot removed the needs backport to 3.10 only security fixes label Dec 1, 2025
serhiy-storchaka added a commit that referenced this pull request Dec 1, 2025
… service in http.server (GH-119455) (GH-142130)

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,
so that the memory consumption is proportional to the amount of sent
data.
(cherry picked from commit 29c657a)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
@serhiy-storchaka serhiy-storchaka deleted the http-server branch December 1, 2025 15:26
@bedevere-bot
Copy link

⚠️⚠️⚠️ Buildbot failure ⚠️⚠️⚠️

Hi! The buildbot ARM64 MacOS M1 Refleaks NoGIL 3.13 (tier-2) has failed when building commit 6c922bb.

What do you need to do:

  1. Don't panic.
  2. Check the buildbot page in the devguide if you don't know what the buildbots are or how they work.
  3. Go to the page of the buildbot that failed (https://buildbot.python.org/#/builders/1396/builds/1823) and take a look at the build logs.
  4. Check if the failure is related to this commit (6c922bb) or if it is a false positive.
  5. If the failure is related to this commit, please, reflect that on the issue and make a new Pull Request with a fix.

You can take a look at the buildbot page here:

https://buildbot.python.org/#/builders/1396/builds/1823

Failed tests:

  • test.test_multiprocessing_forkserver.test_processes

Failed subtests:

  • test_repr_rlock - test.test_multiprocessing_forkserver.test_processes.WithProcessesTestLock.test_repr_rlock
  • test_remote_shutdown_receives_trailing_data_on_slow_socket - test.test_asyncio.test_ssl.TestSSL.test_remote_shutdown_receives_trailing_data_on_slow_socket

Summary of the results of the build (if available):

==

Click to see traceback logs
Traceback (most recent call last):
  File "/Users/buildbot/buildarea/3.13.itamaro-macos-arm64-aws.macos-with-brew.refleak.nogil/build/Lib/test/_test_multiprocessing.py", line 1493, in test_repr_rlock
    self.assertEqual('<RLock(SomeOtherThread, nonzero)>', repr(lock))
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: '<RLock(SomeOtherThread, nonzero)>' != '<RLock(None, 0)>'
- <RLock(SomeOtherThread, nonzero)>
+ <RLock(None, 0)>


Traceback (most recent call last):
  File "/Users/buildbot/buildarea/3.13.itamaro-macos-arm64-aws.macos-with-brew.refleak.nogil/build/Lib/test/test_asyncio/test_ssl.py", line 1595, in test_remote_shutdown_receives_trailing_data_on_slow_socket
    self.loop.run_until_complete(client(srv.addr))
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "/Users/buildbot/buildarea/3.13.itamaro-macos-arm64-aws.macos-with-brew.refleak.nogil/build/Lib/asyncio/base_events.py", line 725, in run_until_complete
    return future.result()
           ~~~~~~~~~~~~~^^
  File "/Users/buildbot/buildarea/3.13.itamaro-macos-arm64-aws.macos-with-brew.refleak.nogil/build/Lib/test/test_asyncio/test_ssl.py", line 1573, in client
    wraps=socket_transport.write,
          ^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'write'

@colesbury
Copy link
Contributor

colesbury commented Dec 2, 2025

The 3.14 buildbot failure looks related to this change, but I don't see it reported on this PR: https://buildbot.python.org/#/builders/1684/builds/326

======================================================================
FAIL: test_large_content_length (test.test_httpservers.CGIHTTPServerTestCase.test_large_content_length)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "b:\uildarea\3.14.ware-win11.nondebug\build\Lib\test\test_httpservers.py", line 1132, in test_large_content_length
    self.assertEqual(res.read(), b'%d %d' % (size, size) + self.linesep)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: b'4194304 2226830\r\n' != b'4194304 4194304\r\n'

----------------------------------------------------------------------
Ran 1 test in 0.336s

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]):
Copy link
Member

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)

Copy link
Member

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.

Copy link
Member

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

Copy link
Member

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?)

Copy link
Member Author

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 and read(n) can return less than n bytes. 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.

Copy link
Member

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

max_chunk_size = 10*1024*1024
size_remaining = int(self.headers["content-length"])
L = []
while size_remaining:
chunk_size = min(size_remaining, max_chunk_size)
chunk = self.rfile.read(chunk_size)
if not chunk:
break
L.append(chunk)
size_remaining -= len(L[-1])
data = b''.join(L)

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-length lies and less data is sent?

OTOH, does anybody out there operate a server without a timeout?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait forever, as in did before the fix?

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. 😁

Copy link
Member

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.

Copy link
Member Author

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.

hugovk added a commit to hugovk/cpython that referenced this pull request Dec 2, 2025
…tion denial of service in http.server (pythonGH-119455)"

This reverts commit 29c657a.
@bedevere-app
Copy link

bedevere-app bot commented Dec 2, 2025

GH-142184 is a backport of this pull request to the 3.14 branch.

hugovk added a commit to hugovk/cpython that referenced this pull request Dec 2, 2025
Yhg1s pushed a commit that referenced this pull request Dec 2, 2025
…enial of service in http.server (GH-119455) (GH-142130)" (#142185)

Revert "[3.13] gh-119452: Fix a potential virtual memory allocation denial of service in http.server (GH-119455) (GH-142130)"

This reverts commit 6c922bb.
hugovk added a commit that referenced this pull request Dec 2, 2025
…enial of service in http.server (GH-119455)" (#142184)

Fix a potential virtual memory allocation denial of service in http.server (GH-119455)"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type-security A security issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants