Skip to content
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

gh-127655: Ensure _SelectorSocketTransport.writelines pauses the protocol if needed #127656

Merged

Conversation

bdraco
Copy link
Contributor

@bdraco bdraco commented Dec 5, 2024

gh-127655: Ensure writelines pauses the protocol if needed

Note for reviewers: 3.12+ only since writelines only exists since 3.12.

@bdraco bdraco changed the title gh-127655: Ensure writelines pauses the protocol if needed gh-127655: Ensure _SelectorSocketTransport.writelines pauses the protocol if needed Dec 5, 2024
@bdraco
Copy link
Contributor Author

bdraco commented Dec 5, 2024

Looks like the CI failure was GitHub being flakey and not an actual problem AFAICT

@gvanrossum
Copy link
Member

gvanrossum commented Dec 5, 2024 via email

@picnixz picnixz added needs backport to 3.12 bug and security fixes needs backport to 3.13 bugs and security fixes labels Dec 6, 2024
Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

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

The one-line fix looks good.

The test doesn't really test anything except that the new logic is in place -- it doesn't prove at all that it does what it claims to do (prevent memory filling up if the other end never reads). But that's hard to test without bringing the test machine to its knees, so as long as you have tested this on one of your actual reproducing scenarios, I'm okay with this.

I suppose some people will be surprised that they are now paused by voluminous writelines() calls where previously they weren't. (What happens if you are paused but you keep calling writelines() anyway?) But consistency with write() is more important, and that may already pause. So I'm okay with this, since writelines() is supposed to be equivalent to multiple write() calls.

@bdraco
Copy link
Contributor Author

bdraco commented Dec 6, 2024

@kumaraditya303
Copy link
Contributor

If the protocol is paused, the example in the PoC won't call writelines anymore until the buffer is drained.

Yeah but if you still continue to call writelines then I guess the memory will go unbounded right?

@kumaraditya303
Copy link
Contributor

@python/organization-owners please add me so I can view the advisory

@bdraco
Copy link
Contributor Author

bdraco commented Dec 6, 2024

If the protocol is paused, the example in the PoC won't call writelines anymore until the buffer is drained.

Yeah but if you still continue to call writelines then I guess the memory will go unbounded right?

Yes if the implementation doesn't drain or otherwise wait to send more when the protocol is paused, the same is true about write, but thats a downstream implementation problem.

@kumaraditya303 kumaraditya303 merged commit e991ac8 into python:main Dec 6, 2024
38 checks passed
@miss-islington-app
Copy link

Thanks @bdraco for the PR, and @kumaraditya303 for merging it 🌮🎉.. I'm working now to backport this PR to: 3.12, 3.13.
🐍🍒⛏🤖

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Dec 6, 2024
…the protocol if needed (pythonGH-127656)

Ensure `_SelectorSocketTransport.writelines` pauses the protocol if it reaches the high water mark as needed.
(cherry picked from commit e991ac8)

Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Dec 6, 2024
…the protocol if needed (pythonGH-127656)

Ensure `_SelectorSocketTransport.writelines` pauses the protocol if it reaches the high water mark as needed.
(cherry picked from commit e991ac8)

Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
@bedevere-app
Copy link

bedevere-app bot commented Dec 6, 2024

GH-127663 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 6, 2024
@bedevere-app
Copy link

bedevere-app bot commented Dec 6, 2024

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

@bedevere-app bedevere-app bot removed the needs backport to 3.12 bug and security fixes label Dec 6, 2024
@sethmlarson
Copy link
Contributor

@kumaraditya303 Do you still need access to the advisory?

@gvanrossum
Copy link
Member

FWIW, it was arguably a design mistake to make write() and writelines() regular functions that must not block, but it's well over a decade too late to fix that. So the convention is that code calling write() or writelines() must also occasionally await drain(). The docstring says it's supposed to be used like this:

          w.write(data)
          await w.drain()

and I'm sure that's the same for writelines(). The problem is that for streams, drain() doesn't do anything if the protocol isn't paused (check _drain_helper()). So an app that uses writelines() together with drain() could still be accumulating memory forever.

Thanks @bdraco for finding, reporting and fixing this!

@bdraco bdraco deleted the missing_flow_control_check_writelines branch December 6, 2024 04:41
@kumaraditya303
Copy link
Contributor

Do you still need access to the advisory?

@sethmlarson Not for this but do add me for any future asyncio issues.

kumaraditya303 added a commit that referenced this pull request Dec 6, 2024
… the protocol if needed (GH-127656) (#127663)

gh-127655: Ensure `_SelectorSocketTransport.writelines` pauses the protocol if needed (GH-127656)

Ensure `_SelectorSocketTransport.writelines` pauses the protocol if it reaches the high water mark as needed.
(cherry picked from commit e991ac8)

Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
kumaraditya303 added a commit that referenced this pull request Dec 6, 2024
… the protocol if needed (GH-127656) (#127664)

gh-127655: Ensure `_SelectorSocketTransport.writelines` pauses the protocol if needed (GH-127656)

Ensure `_SelectorSocketTransport.writelines` pauses the protocol if it reaches the high water mark as needed.
(cherry picked from commit e991ac8)

Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
@bedevere-bot
Copy link

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

Hi! The buildbot aarch64 Android 3.x has failed when building commit e991ac8.

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/1594/builds/745) and take a look at the build logs.
  4. Check if the failure is related to this commit (e991ac8) 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/1594/builds/745

Failed tests:

  • test_android

Failed subtests:

  • test_str - test.test_android.TestAndroidOutput.test_str

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

==

Click to see traceback logs
Traceback (most recent call last):
  File "/data/user/0/org.python.testbed/files/python/lib/python3.14/test/test_android.py", line 63, in assert_log
    line = self.logcat_queue.get(timeout=(deadline - time()))
  File "/data/user/0/org.python.testbed/files/python/lib/python3.14/queue.py", line 212, in get
    raise Empty
_queue.Empty


Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/base_events.py", line 687, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/Volumes/InternalDisk/Users/android/buildarea/3.x.mhsmith-android-aarch64/build/Android/android.py", line 531, in run_testbed
    async with asyncio.TaskGroup() as tg:
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/taskgroups.py", line 134, in __aexit__
    raise propagate_cancellation_error
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/taskgroups.py", line 110, in __aexit__
    await self._on_completed_fut
asyncio.exceptions.CancelledError


Traceback (most recent call last):
  File "/data/user/0/org.python.testbed/files/python/lib/python3.14/test/test_android.py", line 53, in setUp
    self.assert_log("I", tag, message, skip=True, timeout=5)
    ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/data/user/0/org.python.testbed/files/python/lib/python3.14/test/test_android.py", line 65, in assert_log
    self.fail(f"line not found: {expected!r}")
    ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: line not found: 'test.test_android.TestAndroidOutput.test_str 1733459890.9581628'

@bdraco
Copy link
Contributor Author

bdraco commented Dec 6, 2024

Thanks for the quick turnaround on getting this merged. Looking forward to being able to use zero copy writes in production soon.

@JacobCoffee
Copy link
Member

JacobCoffee commented Dec 6, 2024

@kumaraditya303 I don't think this is possible, in a scoped way specific to asyncio, for potential future advisories. You will need to be added ad hoc

algitbot pushed a commit to alpinelinux/aports that referenced this pull request Dec 7, 2024
algitbot pushed a commit to alpinelinux/aports that referenced this pull request Dec 7, 2024
algitbot pushed a commit to alpinelinux/aports that referenced this pull request Dec 7, 2024
@freakboy3742
Copy link
Contributor

The Android buildbot failure appears to be unrelated - it's looks like a timing-related failure, which passed on the next CI pass.

bell-sw pushed a commit to bell-sw/alpaquita-aports that referenced this pull request Dec 9, 2024
[ commit 717301b5302681e860de49ca12981cec9166e057 ]

Add patch to fix CVE-2024-12254: "Unbounded memory buffering in
SelectorSocketTransport.writelines()".

- https://mail.python.org/archives/list/security-announce@python.org/thread/H4O3UBAOAQQXGT4RE3E4XQYR5XLROORB/
- python/cpython#127655
- python/cpython#127656
srinivasreddy pushed a commit to srinivasreddy/cpython that referenced this pull request Jan 8, 2025
…the protocol if needed (python#127656)

Ensure `_SelectorSocketTransport.writelines` pauses the protocol if it reaches the high water mark as needed. 

Co-authored-by: Kumar Aditya <kumaraditya@python.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants