Double Free in Fastapi
How Double Free Manifests in Fastapi
In Fastapi, a double-free pattern can arise when request lifecycle dependencies are released or cleaned up more than once, typically because the application or an underlying library processes a cleanup step after an early exit or exception. Common scenarios include manual resource handling in dependency callbacks, misuse of context managers, or incorrect interaction between middleware and endpoint logic.
Consider a dependency that opens a network or file handle and registers a cleanup callback. If the endpoint raises an exception before the dependency completes normally, Fastapi may still attempt to run cleanup again when the request context tears down, leading to a double-free class of vulnerability. For example:
from fastapi import Depends, FastAPI, HTTPException
from pydantic import BaseModel
import some_external_library
app = FastAPI()
class Item(BaseModel):
name: str
def get_resource():
handle = some_external_library.open()
def cleanup():
some_external_library.close(handle)
request_state = some_external_library.allocate_state()
request_state.callback = cleanup
return handle
@app.post("/items/")
async def create_item(item: Item, handle: str = Depends(get_resource)):
if not item.name:
raise HTTPException(status_code=400, detail="name required")
some_external_library.process(handle, item.name)
return {"status": "ok"}
If some_external_library.process raises an exception, Fastapi’s exception handlers may still invoke the dependency’s cleanup, while the runtime or the library itself may also attempt to release the same resource, resulting in a double-free condition. This can corrupt internal bookkeeping, crash worker processes, or lead to undefined behavior in native extensions.
Another Fastapi-specific pattern involves nested dependencies where one dependency performs partial cleanup and another dependency or middleware tries to clean the same resource. For instance, using Depends with yield for setup/teardown alongside global middleware that also interacts with the same resource can create overlapping release points:
from fastapi import Depends, FastAPI
from contextlib import asynccontextmanager
app = FastAPI()
@asynccontextmanager
async def lifespan(app: FastAPI):
resource = acquire()
try:
yield resource
finally:
release(resource)
async def get_db():
db = acquire_db()
try:
yield db
finally:
release_db(db)
@app.post("/data/")
async def handle(db = Depends(get_db)):
# If an exception occurs before yield, Fastapi may still run both
# the dependency teardown and the lifespan cleanup for overlapping resources
pass
Although Fastapi does not manage memory manually, these patterns can propagate to native extensions or external services where double-free vulnerabilities are exploitable. The risk is especially pronounced when C extensions or libraries used by Fastapi do not guard against repeated release calls.
Fastapi-Specific Detection
Detecting double-free risks in Fastapi involves correlating dependency lifecycles, exception paths, and resource management patterns. Static analysis can highlight missing guards around cleanup routines, but runtime scanning is essential to observe actual execution paths and teardown behavior under errors.
Using middleBrick, you can scan a Fastapi endpoint to surface security findings across categories including unsafe consumption and unsafe resource handling. The scanner exercises unauthenticated attack surfaces and maps findings to the endpoint’s OpenAPI spec, cross-referencing definitions with runtime behavior. For example, it flags dependencies that yield resources without strict exception guards and highlights endpoints where exceptions trigger multiple teardown attempts.
A practical detection flow with the CLI is straightforward:
middlebrick scan https://api.example.com/openapi.json
The output includes a per-category breakdown, prioritized findings, and remediation guidance. In the context of Fastapi, findings may point to dependencies using yield without ensuring idempotent cleanup or to endpoints where validation errors skip normal release paths. The scanner’s input validation and unsafe consumption checks are especially useful for identifying paths that lead to resource misuse.
Because middleBrick runs black-box scans without agents or credentials, it can be integrated into CI/CD to validate that changes to dependencies or error handling do not introduce or regress double-free conditions. The GitHub Action can enforce a minimum security score before allowing merges, while the MCP Server lets you trigger scans directly from AI coding assistants as you write dependency code.
Fastapi-Specific Remediation
Remediation focuses on making cleanup idempotent and ensuring a single release point per resource. Use Fastapi’s native dependency system carefully by avoiding manual double-release in both success and error paths. Implement guards such as flags or state checks inside cleanup routines, and prefer context managers that handle repeated calls safely.
Refactor yield-based dependencies to use explicit try/finally blocks and ensure that exceptions do not bypass teardown logic. Centralize resource management in a dedicated service that tracks released state:
from fastapi import Depends, FastAPI, HTTPException
from contextlib import asynccontextmanager
app = FastAPI()
class ResourceManager:
def __init__(self):
self._released = set()
def acquire(self):
# simplified
return "handle"
def release(self, handle):
if handle in self._released:
return # idempotent guard
# actual cleanup
self._released.add(handle)
manager = ResourceManager()
def get_safe_resource():
handle = manager.acquire()
def cleanup():
manager.release(handle)
request_state = {"cleanup": cleanup}
return handle
@app.post("/items/")
async def create_item(handle: str = Depends(get_safe_resource)):
try:
# process item
pass
except Exception:
# trigger cleanup explicitly if needed
manager.release(handle)
raise
Alternatively, use Fastapi’s Depends with a context manager to guarantee that cleanup runs once, even when errors occur:
from contextlib import asynccontextmanager
from fastapi import Depends, FastAPI
app = FastAPI()
@asynccontextmanager
async def database_session():
db = acquire_db()
try:
yield db
finally:
# ensure idempotent release in the db driver
safe_release_db(db)
@app.get("/items/")
async def list_items(db = Depends(database_session)):
return list(db.query("items"))
Validate that external libraries implement idempotent release functions and, where possible, wrap them with safe guards. Combine these practices with middleBrick’s continuous monitoring to detect regressions early, ensuring that dependency changes do not reintroduce double-free risks.