# vLLM has RCE In Video Processing

vLLM video_url fetch feeds attacker bytes to OpenCV/FFmpeg JPEG2000 decoder; malicious cdef box remaps Y into U plane causing heap overflow and RCE.

- Payout: Research
- Severity: critical
- Program: vLLM
- Found by: dan-sec-ops
- Disclosed: 2026-02-02
- CVE: CVE-2026-22778
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-4r2x-xpjr-7cvv
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-4r2x-xpjr-7cvv
- Tags: area:ai domain:ai domain:oss impact:data-leak impact:rce kind:research platform:ghsa sev:critical tech:llm tech:python technique:chained vuln:info-disclosure vuln:memory-corruption vuln:rce

**Target:** vLLM OpenAI-compatible /v1/chat/completions and /v1/invocations video_url handling, OpenCV cv2.VideoCapture, bundled FFmpeg 5.1 JPEG2000 decoder
**Root cause:** JPEG2000 cdef box allows channel remapping; mapping full-res Y into quarter-res U plane makes the decoder write W×H bytes into a (W/2)×(H/2) buffer, overflowing the heap. PIL error messages returned to clients leak a BytesIO heap address, defeating ASLR.
**Steps:**
1. Send invalid image to multimodal endpoint; response error contains `cannot identify image file <_io.BytesIO object at 0x...>` leaking heap address (~10.33 GB before libc, reducing ASLR to ~8 guesses).
2. Craft JPEG2000 frames with a `cdef` box: N=3, channel 0 Cn=0 Typ=0 Asoc=2 (Y→U), channel 1 Cn=1 Typ=0 Asoc=1, channel 2 Cn=2 Typ=0 Asoc=3.
3. Wrap frames in a .mov container and host it; POST to `/v1/chat/completions` or `/v1/invocations` with `video_url` pointing at it.
4. vLLM downloads raw bytes and passes to `cv2.VideoCapture(BytesIO(data))`; bundled FFmpeg/libopenjp2 decodes, writing 0.75×W×H bytes past U buffer (e.g. 7200 bytes at 150x64).
5. Overflow overwrites an AVBuffer struct's `free` function pointer; set `free = system()` and `opaque = "command string"` using the leaked address.
6. On buffer release, `system("our command")` executes on the server.
**How found:** Reporter traced vLLM's video_url path into cv2.VideoCapture and its bundled FFmpeg, then studied the JPEG2000 cdef channel-remapping semantics and noticed Y (W×H) could be mapped into the smaller U ((W/2)×(H/2)) plane buffer, producing a deterministic overflow; separately noticed PIL error strings returned to clients embed BytesIO heap addresses usable to defeat ASLR.
**Exploitation:** Unauthenticated attacker hosts a malicious .mov built from JPEG2000 frames with a crafted cdef box, submits its URL as video_url to a vLLM instance serving a video model. vLLM fetches and hands bytes to OpenCV's bundled FFmpeg; the cdef remap overflows the U-plane heap buffer, overwriting an AVBuffer free pointer with system() and opaque with the command, yielding command execution when the buffer is freed. ASLR bypassed via PIL BytesIO address leak from an invalid image request. Works pre-auth even with api-key enabled via the invocations route.
**Escalation:** Chained info leak (ASLR bypass) with heap overflow; overflow size scales with image dimensions (0.75×W×H), enabling larger controlled writes.
**Insight:** Media decoders reached through 'just pass bytes to cv2/FFmpeg' wrappers inherit codec-level bugs; JPEG2000 cdef channel association (Asoc) is a rarely-audited field that lets you redirect a full-res plane into a quarter-res buffer. Also, any framework echoing Python repr of objects in error responses leaks heap addresses — check error paths of image/video loaders for ASLR bypasses.

## Proof of concept (copied exactly from the original report)

**Malicious JPEG2000 cdef box remapping Y into U**

```
Offset  Size  Field           Value
0       4     Box Length      0x00000016 (22 bytes)
4       4     Box Type        'cdef'
8       2     N (channels)    0x0003
10      2     Channel 0 Cn    0x0000 (Y channel)
12      2     Channel 0 Typ   0x0000 (color)
14      2     Channel 0 Asoc  0x0002 (→ maps Y into U plane)
16      2     Channel 1 Cn    0x0001 (U channel)
18      2     Channel 1 Typ   0x0000 (color)
20      2     Channel 1 Asoc  0x0001 (→ maps U into Y plane)
22      2     Channel 2 Cn    0x0002 (V channel)
24      2     Channel 2 Typ   0x0000 (color)
26      2     Channel 2 Asoc  0x0003 (→ maps V plane)
Key control: `Asoc=2` for channel 0 forces Y data into the U buffer, triggering the overflow.
```

**vLLM video_url download and cv2 decode chain**

```
if url_spec.scheme.startswith("http"):
    data = connection.get_bytes(url, timeout=fetch_timeout, allow_redirects=...)
    return media_io.load_bytes(data)

Source: `src/vllm/multimodal/utils.py` (`MediaConnector.load_from_url`).

---

### 2) Decode: vLLM uses OpenCV (cv2) VideoCapture on an in-memory byte stream

The default video backend is OpenCV, and it constructs `cv2.VideoCapture` over a `BytesIO` buffer containing the downloaded bytes:

backend = cls().get_cv2_video_api()
cap = cv2.VideoCapture(BytesIO(data), backend, [])
if not cap.isOpened():
    raise ValueError("Could not open video stream")
```

**PIL BytesIO address leak error message**

```
cannot identify image file <_io.BytesIO object at 0x7a95e299e750>
                                                   ^^^^^^^^^^^^^^^^
                                                   LEAKED ADDRESS!
```

