Skip to content

Denial of Service (DoS) via Memory Exhaustion in File Upload Handling #6115

@RinZ27

Description

@RinZ27

Summary

A potential Denial of Service (DoS) vulnerability exists in Reflex's backend file upload handling. The current implementation reads the entire content of uploaded files into memory (io.BytesIO) without enforcement of size limits, which can lead to rapid memory exhaustion and server crashes.

Technical Analysis

In reflex/app.py (L1767), the code iterates through incoming files and performs a full read into a BytesIO object:

# reflex/app.py
file_copies = []
for file in files:
    content_copy = io.BytesIO()
    content_copy.write(await file.read())  # <--- Critical Point
    content_copy.seek(0)
    # ...

Impact

An attacker can exploit this by sending multiple concurrent requests with large files. Since await file.read() loads the full payload into RAM, it will quickly consume all available system memory, potentially triggering the OOM killer or rendering the server unresponsive.

Proof of Concept (Isolated Test)

The following script replicates the vulnerable logic and demonstrates the linear memory spike:

import asyncio
import io
import os
import psutil

class MockUploadFile:
    def __init__(self, size):
        self.size = size
        
    async def read(self):
        return b"A" * self.size

async def simulate_reflex_upload():
    process = psutil.Process(os.getpid())
    print(f"[*] Initial Memory Usage: {process.memory_info().rss / 1024 / 1024:.2f} MB")
    
    FILE_SIZE = 500 * 1024 * 1024 # 500MB
    file = MockUploadFile(FILE_SIZE)
    
    print(f"[*] Executing Reflex logic: io.BytesIO().write(await file.read())...")
    content_copy = io.BytesIO()
    content_copy.write(await file.read())
    
    print(f"[*] Post-Execution Memory Usage: {process.memory_info().rss / 1024 / 1024:.2f} MB")

if __name__ == "__main__":
    asyncio.run(simulate_reflex_upload())

Recommended Mitigation

  1. Configurable Size Limits: Introduce a MAX_UPLOAD_SIZE setting to reject large files before they are read.
  2. Streaming/Spilling to Disk: Leverage FastAPI's UploadFile ability to spill to disk or use tempfile.TemporaryFile instead of io.BytesIO for large payloads.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions