Python 3.12 introduced multiple improvements to its popular asyncio module, promising faster asynchronous code and smarter memory usage. However, the change introduced an overlooked bug: CVE-2024-12254. If you’re using Python 3.12. or later (on macOS or Linux), the asyncio._SelectorSocketTransport.writelines() method doesn’t obey flow control anymore. This means that under specific conditions, your server could run out of memory… fast.

Below, you’ll find an exclusive, straight-to-the-point breakdown of this vulnerability, code snippets for demonstration, links to sources, and hands-on practical exploit details. If you run async Python servers—pay close attention!

What Happened? The Technical Low-Down

Before Python 3.12, when you wrote many lines to a transport (using writelines()), asyncio watched the size of the buffer. If the buffer was too full, it would pause writing and signal your Protocol code to let Python flush the buffer to the network before accepting more data.

In 3.12.+, a new optimization (zero-copy-on-write) changed how writelines() worked. Suddenly, that flow control didn’t happen—even if the buffer grew unbounded.

You use writelines() (not just write()).

If any of the above isn’t true for your code, you’re safe.

Let’s say you run an async server handling connections like this

import asyncio

class EchoProtocol(asyncio.Protocol):
    def connection_made(self, transport):
        self.transport = transport
        print("Client connected")

    def data_received(self, data):
        # Intentionally using writelines, e.g., for batching
        self.transport.writelines([b"Hello\n"]*10000)
        print("Wrote 10,000 lines to buffer")

async def main():
    loop = asyncio.get_running_loop()
    server = await loop.create_server(
        lambda: EchoProtocol(),
        '127...1', 8888
    )
    async with server:
        await server.serve_forever()

asyncio.run(main())

In Python 3.11 and before: If writing 10,000 lines fills the OS buffer, asyncio will PAUSE and call the Protocol’s pause_writing(). Your code can yield, the buffer flushes, and you don’t use too much RAM.

In Python 3.12.x: Pause won’t happen. Every call to writelines() adds more to the buffer, and nothing stops you from appending forever. In a real server, if a client opens a slow connection (or you have a bug), you can easily consume gigabytes of RAM and crash your service.

Test it yourself: Watch your Python process memory grow as you loop writelines() repeatedly!

1. Upgrade Python

This was fixed in Python 3.12.3 and will be in all later releases.
- Python 3.12.3 Release Notes and Downloads

4. Monitoring and Limits

- Always set resource limits and monitor process/memory use for async servers.

Python Security Advisory:

GHSA-r8pj-cgxx-45cr

Upstream Issue (CPython):

asyncio: _SelectorSocketTransport.writelines() does not pause writing

Python Bug Tracker:

bpo-111401

Python 3.12.3 Release Note:

https://docs.python.org/3/whatsnew/changelog.html#python-3-12-3

Final Word

CVE-2024-12254 is a reminder that even high-quality language upgrades can bring surprises. If you operate async servers with asyncio Protocols, upgrade now or check your code for writelines().

Have questions about your code? Drop your snippet and let’s check if you’re at risk!


Stay safe, and keep your Python secure.
If you found this exclusive write-up helpful, share with your async developer friends—Python security is everyone’s business!

Timeline

Published on: 12/06/2024 16:15:20 UTC
Last modified on: 01/06/2025 18:15:18 UTC