CWE: CWE-122 (Heap-based Buffer Overflow), CWE-787 (Out-of-bounds Write). The reachability defect in vLLM is CWE-20 (Improper Input Validation) — untrusted media bytes reach the native parser before any request-scoped validation can run.
Ecosystem / package: pip / vllm
Affected versions: vLLM ≤ 0.25.1 (confirmed on 0.25.1, commit 752a3a504485 , with PyAV 16.1.0 linked against FFmpeg 8.0.1 ). The trigger lives in the native libavformat build; an install that links a fixed FFmpeg is not affected. Maintainers can confirm the exact vulnerable libavformat range at triage.
vLLM's audio transcription endpoint ( /v1/audio/transcriptions ) decodes uploaded audio bytes with soundfile and, when soundfile cannot parse the container, falls back to PyAV (FFmpeg) via av.open() . The only guard applied before decoding is a compressed file-size check, so the raw attacker bytes reach the native FFmpeg demuxer unvalidated.
A malformed IAMF (Immersive Audio Model and Formats) container drives FFmpeg's scalable_channel_layout_config() parser into a write past a 32-byte AVChannelCustom heap allocation. On the confirmed target — PyAV 16.1.0 / FFmpeg 8.0.1 — a one-byte mutation of a valid IAMF file still opens and drives the out-of-bounds write. Repeated uploads corrupt the allocator heap and kill the API process, denying service to every user of that server.
This is memory corruption via a third-party parser bug reached through vLLM's ordinary request path — not a request-controlled preprocessing amplification issue. No opt-in feature is required beyond a deployment that serves the speech endpoint and falls back to PyAV.
This finding is distinct from GHSA-4r2x-xpjr-7cvv ( CVE-2026-22778 ), a JPEG2000 cdef heap overflow in OpenCV reached via load_bytes() : that advisory's fix does not gate the PyAV/FFmpeg av.open() path on IAMF input, so this sibling media sink survives it and remains an open gap.
Links pinned to the confirmed commit 752a3a504485 (v0.25.1):
vllm/entrypoints/speech_to_text/base/serving.py#L160-L201 — SpeechToTextBaseServing._decode_and_chunk_speech() applies only the compressed file-size guard before handing the uploaded bytes to load_audio() .
vllm/multimodal/media/audio.py#L187-L217 — load_audio() falls back to load_audio_pyav() when soundfile is unavailable or cannot detect the format.
vllm/multimodal/media/audio.py#L46-L73 — load_audio_pyav() calls av.open() at L73 directly on the attacker bytes, with no pre-open container gate.
The out-of-bounds write itself is in FFmpeg's libavformat/iamf_parse.c:scalable_channel_layout_config() (a native dependency, not part of the vLLM tree). ASAN placed the first 4-byte heap-buffer-overflow write at iamf_parse.c:426 .
The vulnerable source, copied verbatim from the pinned target. The transcription route hands raw upload bytes to load_audio() with only the compressed size guard upstream:
When soundfile cannot detect the container, load_audio() falls back to PyAV:
load_audio_pyav() then calls av.open() on the raw attacker bytes with no pre-open container gate — the unguarded native sink:
An attacker who can reach /v1/audio/transcriptions on an affected deployment can crash the vLLM API process by uploading a crafted IAMF container. The preconditions are that the audio path falls back to PyAV (i.e. soundfile is unavailable or cannot parse the container) and the linked libavformat is within the vulnerable IAMF range. No authentication beyond ordinary request access is required.
The effect is request-controlled native heap corruption in the speech-processing process, leading to process death and denial of service for every user of that server. The evidence proves allocator corruption and service death (availability). It does not prove control-flow hijack, code execution, or a confidentiality/integrity consequence, so this is a Moderate availability finding and should not be elevated to an RCE band.
Confirmed live on the exact v0.25.1 ( 752a3a504485 ) stack with PyAV 16.1.0 / FFmpeg 8.0.1 .
A one-byte-mutated IAMF file differs from its valid control by a single byte, still opens on the vulnerable stack, and triggers a 4-byte ASAN heap-buffer-overflow inside FFmpeg's IAMF scalable-layer parser at iamf_parse.c:426 . The request models accept the mutant with no client-side validation error.
On the HTTP route replay, ten mutant uploads to /v1/audio/transcriptions returned HTTP 200 ; the eleventh received an empty and /health then became unreachable — the API process had died from accumulated heap corruption.
A fixed-stack negative control (post-mitigation) confirmed the mutant no longer opens once the FFmpeg dependency floor is raised out of the vulnerable range.
The two containers are 20 204 bytes each and differ by exactly one byte (offset 72, 0x24 → 0x04 , inside the scalable-layer config).
The mutant is uploaded to /v1/audio/transcriptions as an ordinary multipart transcription request, in a loop, while /health is polled between uploads to watch for process death:
Step by step, matching the recorded run:
Each POST hands the raw upload bytes to the speech decode path, which applies only the compressed file-size guard (the 20 KB mutant is far under the limit) before decoding.
soundfile cannot parse the IAMF container, so the path falls back to PyAV and calls av.open() on the attacker bytes; the FFmpeg IAMF demuxer runs and the one-byte mutation triggers the out-of-bounds write.
A single overflow is small enough that decoding usually completes: uploads 1 through 10 return HTTP 200 with a normal (empty/near-empty) body, so nothing signals to the client that the heap is being corrupted.
On upload 11 the request receives an empty and the subsequent /health probe is unreachable — the accumulated allocator corruption has killed the API process, denying service to every other user of that server until it is restarted.
The mutant, its valid control, and the reproduction receipts are preserved and can be provided on request.
Reject PyAV container parsing before any av.open() call when the linked libavformat build falls within the vulnerable IAMF range.
Raise the default PyAV/FFmpeg dependency floor to a wheel line that bundles fixed FFmpeg releases (out of the vulnerable IAMF range) across the CUDA, CPU, and ROCm requirement sets.
Apply the same pre-open guard to the request-selectable PyAV video backend so a caller cannot bypass the audio-path fix through a sibling media route.
Coordinate disclosure with FFmpeg/PyAV for the underlying native parser bug if a CVE is not already assigned; the true root fix belongs upstream in FFmpeg, with the vLLM-side fix being input gating plus the dependency-floor bump.
The core change adds a version gate that runs before every av.open() on untrusted bytes. A new vllm/multimodal/pyav_utils.py raises UnsafePyAVError unless the linked libavformat carries the IAMF scalable-layer fix:
The gate is inserted immediately before the av.open() sink in load_audio_pyav() , and load_audio() re-raises UnsafePyAVError so it is not masked as a generic decode error:
The same require_safe_pyav_stack(av.library_versions) call is added before av.open() in the video VideoBackend PyAV path, and the PyAV dependency floor is raised to av>=17.1.0 (pinned to av==18.0.0 in the .txt locks) across setup.py and the CUDA/CPU/ROCm requirement sets, with regression tests for the version boundaries, both pre-open rejection paths, and the dependency floor.
Reported by: Patch the Planet (Trail of Bits + OpenAI collaboration)
This vulnerability was discovered using GPT-5.5-Cyber as part of the Patch the Planet security initiative.
Proposed fix: a fix for this issue is proposed in a public pull request: #51895
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
