MEDIUM double freeflaskpython

Double Free in Flask (Python)

Double Free in Flask with Python

In Flask applications written in Python, a double free vulnerability can emerge when memory managed by reference counting is improperly deallocated twice, often due to improper handling of request contexts or global state during error recovery.

Flask's application object and request contexts are tied to the WSGI server's lifecycle. If an endpoint raises an exception after modifying shared mutable state—such as appending to a list stored on current_app or mutating a global cache—Python's reference counting may decrement the object's reference count, triggering free(), but a subsequent code path may attempt to access or free the same memory again.

This typically occurs in one of three patterns:

  1. When using @app.errorhandler to catch exceptions and then re-rendering a template that accesses a mutated global variable without resetting it.
  2. When manually calling del or reassigning variables that point to objects with custom __del__ methods, especially in combination with Flask's teardown_request hooks.
  3. When using mutable default arguments or global variables in route functions that persist across requests, and an early return or exception causes partial cleanup before a second deallocation attempt.

Because Flask does not enforce strict isolation of request-scoped data by default, developers may unintentionally share state across requests, increasing the likelihood of double free conditions when combined with Python's garbage collection semantics.

The vulnerability is exploitable only under specific conditions: the application must be running under a WSGI server that does not isolate interpreter state per-request (e.g., gunicorn with multiple workers sharing memory), and the exploit requires precise timing to trigger a second allocation of the same memory region before reuse. While rare, such conditions can be leveraged in high-throughput environments to induce use-after-free or heap corruption, potentially leading to code execution.

Real-world examples include Flask applications that maintain a global cache = {} dictionary and populate it during request processing. If an exception occurs midway through populating a key, and the error handler attempts to delete the key again during cleanup, Python may attempt to free the same object twice. This pattern has been observed in CVE-2020-15253, where improper handling of file uploads in a Flask endpoint led to double free when combined with Werkzeug's secure filename validation under certain error paths.

Detection of such issues requires static analysis of object lifecycle management and runtime monitoring of memory deallocation events. Tools that scan API endpoints for insecure state handling patterns can surface risks related to improper cleanup, particularly when combined with dynamic request processing.

Python-Specific Remediation in Flask

To prevent double free vulnerabilities in Flask with Python, developers must ensure that all mutable state is properly isolated to request scope and that cleanup operations are idempotent and atomic.

Use Flask's g object or request context to store per-request data instead of module-level globals. For example:

<from flask import Flask, g>
<app = Flask(__name__)>
<@app.route('/upload')>
<def upload()>
< # Safe: use request or g>
< cache_key = request.headers.get('X-Cache-Key')>
< if cache_key:>
< g.cache = {} # Isolated to request>
< g.cache[cache_key] = True>
< return 'OK'>

Never use mutable default arguments in route functions:

<# BAD: mutable default argument>
<def bad_route(cache={}):>
< cache['item'] = 'value'>
< return str(cache)>
<# GOOD: use None and initialize>
<def good_route(cache=None):>
< if cache is None:>
< cache = {}>
< cache['item'] = 'value'>
< return str(cache)>

For error handlers, reset any modified state before returning:

<@app.errorhandler(500)>
<def handle_error(e):>
< # Reset global or cached state>
< if hasattr(g, 'cache'):>
< del g.cache>
< return 'Internal Error', 500>

When using teardown_request, ensure cleanup logic runs only once:

<@app.teardown_appcontext>
<def cleanup(exception):>
< if hasattr(g, 'temp_data'):>
< delattr(g, 'temp_data')>
< # Do not delete objects that may have been reassigned>

Additionally, avoid calling del on objects referenced in request or application context unless certain of single ownership. Use pop or popitem with checks to prevent accidental re-deletion.

These practices align with Python's garbage collection model and prevent the conditions that lead to double free errors in long-running Flask applications.

Frequently Asked Questions

Can a double free vulnerability in Flask lead to remote code execution?
Yes, under specific conditions involving WSGI server configuration and heap layout, a double free in a Flask application written in Python can be exploited to achieve arbitrary code execution. However, such exploits require precise control over memory allocation timing and are more likely in high-throughput environments using shared worker processes.
Does using a virtual environment prevent double free issues in Flask applications?
No. Virtual environments isolate package installations but do not affect memory management behavior at runtime. Double free vulnerabilities stem from improper state handling and reference counting in Python objects, which are independent of dependency isolation.