Double Free in Fastapi (Python)
Double Free in Fastapi with Python
Double free vulnerabilities occur when memory is deallocated twice without being explicitly reassigned, leading to undefined behavior that attackers can exploit. While traditionally associated with C/C++, the risk surfaces in Python through misused shared references and improper resource handling in asynchronous frameworks like Fastapi.
Fastapi runs on Starlette, which uses uvicorn as the default ASGI server. When handling large payloads or streaming responses, developers may inadvertently reuse mutable objects across request scopes without proper isolation. This creates conditions where Python objects (e.g., database cursors, response buffers) are passed to multiple coroutines or background tasks and later attempted to be freed multiple times.
For example, consider a Fastapi endpoint that processes file uploads using a shared BytesIO buffer:
from fastapi import FastAPI, UploadFileapp = FastAPI()shared_buffer = BytesIO() # Not thread-safe!@app.post('/upload')async def upload(file: UploadFile):shared_buffer.seek(0)shared_buffer.truncate()await file.save(shared_buffer)return {'size': len(shared_buffer.getvalue())}# Later, in a background task or concurrent requestdef process_buffer()data = shared_buffer.getvalue()# ... process data ...shared_buffer = None # Accidentally reassigning None doesn't prevent reuse
This pattern can lead to double free scenarios when multiple requests access the same global mutable state. Python's garbage collector does not immediately free memory, but in long-running processes or when interfacing with C extensions (e.g., via ctypes or uvloop), premature deallocation can occur. If a C extension holds a reference to a Python object and later attempts to free it after the high-level reference is lost, a double free may be triggered in the underlying memory manager.
Additionally, Fastapi's dependency injection system can inadvertently pass long-lived objects to short-lived request contexts. If a dependency returns a mutable object that is cached or reused across requests, and that object is later modified in-place, it may be freed prematurely when the request scope ends, only to be accessed again in another request.
These vulnerabilities are rare but dangerous. They are not caught by standard linters or type checkers and require deep understanding of Python's object model and concurrency primitives. Tools like middleBrick can detect such risks by analyzing endpoint behavior, request flow, and memory usage patterns without requiring source code access or credentials.
Real-world impact includes memory corruption, crashes, or potential code execution when exploited alongside other vulnerabilities. Though uncommon in pure Python, double free risks emerge when Fastapi applications integrate with C-based libraries or use custom async workers that manage low-level memory.
Compliance frameworks like OWASP API Top 10 classify such issues under Broken Object Level Authorization when they lead to data leakage or unauthorized access through memory manipulation. Early detection is critical to prevent exploitation in high-exposure APIs.
Detection via middleBrick: The scanner will flag endpoints that return inconsistent response sizes, exhibit memory leaks, or use banned patterns in shared state. Findings include severity ratings, remediation steps, and mapping to OWASP API Top 10 category A01:2023 - Broken Object Level Authorization.
Prevention: Always avoid global mutable state. Use dependency injection to scope objects per-request. Prefer creating new instances of buffers or containers within handler functions rather than reusing them across calls.
Code Example: Safe Implementation
from fastapi import FastAPI, UploadFile, Fileapp = FastAPI()@app.post('/upload')async def upload(file: UploadFile = File(...)):content = await file.read()size = len(content)return {'size': size}
This version eliminates shared state entirely. The buffer is created and discarded within the request scope, preventing reuse or double free risks.
For applications that must interface with external libraries, ensure all external resources are properly closed and not cached across requests. Use context managers and explicit cleanup.
middleBrick scans for such patterns by analyzing request-response cycles, object lifetimes, and dependency usage. It does not require code execution or access to source repositories, making it ideal for securing third-party or legacy APIs.
Python-Specific Remediation in Fastapi
Remediation of double free vulnerabilities in Fastapi applications centers on strict adherence to object scoping and lifecycle management in Python. Unlike languages with manual memory management, Python relies on reference counting and garbage collection, but vulnerabilities arise when objects are prematurely accessed after being logically freed.
Key remediation strategies include:
- Never use global mutable state for request-scoped data
- Always create fresh instances within handler functions
- Avoid sharing objects between async tasks without synchronization
- Use context managers for resource handling
- Validate return types and avoid in-place mutations of shared objects
Here is a corrected version of the earlier flawed example using proper scoping:
from fastapi import FastAPI, UploadFile, Filefrom io import BytesIOapp = FastAPI()@app.post('/upload')async def upload(file: UploadFile = File(...)):buffer = BytesIO() # Local to this requestawait file.save(buffer)buffer.seek(0)data = buffer.read()return {'size': len(data)}# buffer is automatically closed and eligible for GC
In this version, the BytesIO buffer is instantiated inside the handler function. It is used only during the processing of that specific request and is not stored or reused. Once the function returns, the reference count drops, and Python's memory manager eventually reclaims the memory.
For background tasks, use explicit cleanup:
from fastapi import BackgroundTasksdef cleanup_task(buffer: BytesIO):# Perform cleanup operationsbuffer.close()@app.post('/process')async def process_endpoint(background_tasks: BackgroundTasks):buffer = BytesIO()background_tasks.add_task(cleanup_task, buffer)# ... proceed with processing ...
This ensures that cleanup is scheduled only after the request completes and that no dangling references persist.
Additionally, when integrating with C extensions or external libraries, always close file handles, database cursors, and network connections explicitly:
with sqlite3.connect('db.sqlite') as conn:cursor = conn.cursor()cursor.execute('SELECT ...')results = cursor.fetchall()# cursor and conn auto-closed after block
middleBrick detects improper object lifecycle management by analyzing API response patterns, error messages, and memory pressure indicators. It recommends fixes that align with Python best practices and OWASP guidelines.
By enforcing per-request object creation and avoiding shared state, developers can eliminate the risk of double free vulnerabilities in Fastapi applications.