Use After Free in Fastapi
How Use After Free Manifests in Fastapi
Use After Free (UAF) vulnerabilities occur when a program continues to use a pointer after the memory it references has been freed. In Fastapi applications, this typically manifests through improper resource cleanup, async context mismanagement, or database connection handling.
Async Context Mismanagement
Fastapi's async nature creates unique UAF opportunities. Consider this common pattern:
from fastapi import FastAPI, BackgroundTasks
app = FastAPI()
@app.post("/process/")
async def process_data(data: dict, background_tasks: BackgroundTasks):
# Database connection created
conn = await get_db_connection()
# Background task starts, but original function continues
background_tasks.add_task(long_running_process, conn)
# Connection closed immediately, but background task still uses it
await conn.close()
return {"status": "processing"}
The background task receives a reference to the database connection, but the main function closes it immediately. When the background task executes, it attempts to use freed memory.
Request Lifecycle Issues
Fastapi's request/response cycle can create UAF scenarios:
from fastapi import Request
@app.post("/upload/")
async def upload_file(request: Request):
body = await request.body()
# Request object may be cleaned up before processing completes
process_in_background(body)
return {"status": "accepted"}
The request object's internal buffers might be released before process_in_background completes, causing UAF when accessing body content.
Middleware Resource Leaks
Middleware that doesn't properly handle async cleanup can cause UAF:
class DatabaseMiddleware:
def __init__(self, app: FastAPI):
self.app = app
async def __call__(self, request: Request, call_next):
# Connection created
self.conn = await get_db_connection()
# Request processed
response = await call_next(request)
# Connection closed, but reference may persist in response
await self.conn.close()
return response
If the response handler captures self.conn before it's closed, subsequent operations will access freed memory.
Fastapi-Specific Detection
Detecting Use After Free in Fastapi requires both static analysis and runtime monitoring. middleBrick's black-box scanning approach is particularly effective for Fastapi applications.
middleBrick API Security Scanning
middleBrick scans Fastapi endpoints in 5-15 seconds without requiring credentials or access to source code. It tests for UAF patterns through:
- Authentication bypass attempts that might expose freed resource endpoints
- Rate limiting circumvention to trigger cleanup race conditions
- Input validation bypasses that could cause premature resource release
- Property authorization checks for unauthorized access to freed objects
The scanner tests 12 security categories in parallel, providing a comprehensive security score (A-F) with prioritized findings.
Runtime Monitoring Patterns
Implement these checks in your Fastapi application:
from fastapi import FastAPI, HTTPException
from contextlib import asynccontextmanager
@asynccontextmanager
async def safe_db_connection():
conn = await get_db_connection()
try:
yield conn
finally:
await conn.close()
# Track connection state
conn._is_closed = True
@app.post("/secure_process/")
async def secure_process(data: dict):
async with safe_db_connection() as conn:
if hasattr(conn, '_is_closed'):
raise HTTPException(status_code=500, detail="Connection in invalid state")
result = await conn.execute(data)
return result
Static Analysis Tools
Use tools that understand Fastapi's async patterns:
# mypy with Fastapi plugin
# pip install mypy-fastapi
# Static analysis configuration
[mypy]
plugins = fastapi_typescript.mypy_plugin
# Check for async context issues
[mypy-fastapi]
warn_untyped_defs = True
check_untyped_defs = True
Fastapi-Specific Remediation
Remediating Use After Free in Fastapi requires understanding async resource management and proper cleanup patterns.
Proper Async Context Management
Always use async context managers for resource handling:
from contextlib import asynccontextmanager
from fastapi import FastAPI, BackgroundTasks
@asynccontextmanager
async def managed_db_connection():
conn = await get_db_connection()
try:
yield conn
finally:
if not conn.closed:
await conn.close()
@app.post("/safe_process/")
async def safe_process(data: dict, background_tasks: BackgroundTasks):
async with managed_db_connection() as conn:
# Pass only what's needed to background tasks
safe_data = data.copy()
background_tasks.add_task(
long_running_process,
safe_data, # Don't pass connection
conn.connection_id # Pass identifier instead
)
result = await conn.execute(safe_data)
return result
Request Lifecycle Protection
Ensure request-scoped resources survive the entire request:
from fastapi import Request, Response
from fastapi.middleware.base import RequestResponseEndpoint
class SafeRequestMiddleware:
def __init__(self, app: FastAPI):
self.app = app
async def __call__(self, request: Request, call_next: RequestResponseEndpoint):
# Create request-scoped resources
request.state.db_conn = await get_db_connection()
request.state.processing_active = True
try:
response = await call_next(request)
return response
finally:
# Ensure cleanup happens after response is sent
await self._cleanup_request_resources(request)
async def _cleanup_request_resources(self, request: Request):
if getattr(request.state, 'processing_active', False):
# Wait for background tasks to complete
await self._wait_for_background_tasks(request)
if hasattr(request.state, 'db_conn'):
await request.state.db_conn.close()
async def _wait_for_background_tasks(request: Request):
# Implementation depends on your background task system
pass
Database Connection Pooling
Use connection pooling to prevent premature resource release:
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "postgresql+asyncpg://user:password@localhost/dbname"
engine = create_async_engine(DATABASE_URL, pool_size=10, max_overflow=20)
AsyncSessionLocal = sessionmaker(
engine,
class_=AsyncSession,
expire_on_commit=False
)
@app.middleware("http")
async def db_session_middleware(request: Request, call_next):
async with AsyncSessionLocal() as session:
request.state.session = session
response = await call_next(request)
return response
Background Task Safety
Never pass live connections to background tasks:
from fastapi import BackgroundTasks
from typing import Callable
class SafeBackgroundTask:
def __init__(self):
self.task_registry = {}
def add_task(self, func: Callable, *args, **kwargs):
# Store only serializable data
task_id = uuid.uuid4().hex
self.task_registry[task_id] = {
'func': func.__name__,
'args': args,
'kwargs': kwargs
}
# Schedule actual execution separately
asyncio.create_task(self._execute_task(task_id))
async def _execute_task(self, task_id: str):
task_data = self.task_registry.get(task_id)
if not task_data:
return
func_name = task_data['func']
func = globals().get(func_name)
if func:
await func(*task_data['args'], **task_data['kwargs'])
# Remove from registry after completion
del self.task_registry[task_id]