vLLM: Request-selected PyNvVideoCodec GPU decode bypasses static VRAM reservation
Medium6.5CVE-2026-69147 · Published Sep 17, 2026
## Summary Current vLLM `main` lets an inference request choose the PyNvVideoCodec GPU video decoder through `media_io_kwargs.video.video_backend`, but engine GPU memory reservation is computed only from static startup configuration and `VLLM_VIDEO_LOADER_BACKEND`. If the server starts with the default OpenCV/software backend and no `--mm-ipc-gpu-memory-gb` budget, a client can still route a video request into the PyNvVideoCodec path after startup, causing frontend CUDA-context, decoder-surface, and decoded-frame GPU allocations that were not carved out of the engine KV-cache budget. ## Technical Details The vulnerable boundary is the split between request-time media decoding choices in the API server and startup-time memory budgeting in the engine worker. Request bodies for Chat Completions and Responses expose `media_io_kwargs`, and those values are forwarded to the shared media connector. For video inputs, `MediaConnector.fetch_video()` copies `self.media_io_kwargs["video"]` into `video_io_kwargs`, only setting a model-derived backend when `video_backend` is absent. `VideoMediaIO.__init__()` then consumes `video_backend` from those kwargs and loads that backend from `VIDEO_LO...
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| vllm PyPI | < 0.28.0 | 0.28.0 |
Details and references
## Summary Current vLLM `main` lets an inference request choose the PyNvVideoCodec GPU video decoder through `media_io_kwargs.video.video_backend`, but engine GPU memory reservation is computed only from static startup configuration and `VLLM_VIDEO_LOADER_BACKEND`. If the server starts with the default OpenCV/software backend and no `--mm-ipc-gpu-memory-gb` budget, a client can still route a video request into the PyNvVideoCodec path after startup, causing frontend CUDA-context, decoder-surface, and decoded-frame GPU allocations that were not carved out of the engine KV-cache budget. ## Technical Details The vulnerable boundary is the split between request-time media decoding choices in the API server and startup-time memory budgeting in the engine worker. Request bodies for Chat Completions and Responses expose `media_io_kwargs`, and those values are forwarded to the shared media connector. For video inputs, `MediaConnector.fetch_video()` copies `self.media_io_kwargs["video"]` into `video_io_kwargs`, only setting a model-derived backend when `video_backend` is absent. `VideoMediaIO.__init__()` then consumes `video_backend` from those kwargs and loads that backend from `VIDEO_LOADER_REGISTRY`. The relevant request-side source path is: ```python video_io_kwargs = dict(self.media_io_kwargs.get("video", {})) if "video_backend" not in video_io_kwargs and ( video_backend := get_video_loader_backend_for_processor(video_processor) ): video_io_kwargs["video_backend"] = video_backend video_io = VideoMediaIO(image_io, **video_io_kwargs) ``` ```python video_loader_backend = ( kwargs.pop("video_backend", None) or envs.VLLM_VIDEO_LOADER_BACKEND ) self.video_loader = VIDEO_LOADER_REGISTRY.load(video_loader_backend) ``` `VideoBackend.load_bytes()` then dispatches `backend == "pynvvideocodec"` into `decode_frames_pynvvideocodec()`, which constructs a PyNvVideoCodec decoder, creates or uses a CUDA stream, reads stream metadata, decodes selected frames on the GPU, and copies those frames into pinned host memory. The new frontend GPU memory pool accounts only for raw decoded frame bytes when a pool exists; it does not make request-time backend selection safe when no startup reservation was made. The engine-side reservation code makes its decision from static model config and environment only: ```python def _uses_pynvvideocodec_video_backend(mm_config) -> bool: video_kwargs = mm_config.media_io_kwargs.get("video", {}) video_loader_backend = ( video_kwargs.get("video_backend") or envs.VLLM_VIDEO_LOADER_BACKEND ) codec_backend = video_kwargs.get("backend") return ( video_loader_backend == PYNVVIDEOCODEC_VIDEO_BACKEND or codec_backend == PYNVVIDEOCODEC_VIDEO_BACKEND ) ``` ```python decoder_reserved_bytes = ( num_api_servers * per_server_decoder_bytes if self._uses_pynvvideocodec_video_backend(mm_config) else 0 ) reserved_bytes = raw_frame_reserved_bytes + decoder_reserved_bytes if reserved_bytes <= 0: return available_kv_cache_memory_bytes ``` With default static video configuration, `mm_config.media_io_kwargs["video"]` does not name PyNvVideoCodec and `VLLM_VIDEO_LOADER_BACKEND` defaults to OpenCV/software decoding. The worker therefore reserves no PyNv decoder/CUDA-context bytes. A later request can still set `media_io_kwargs.video.video_backend="pynvvideocodec"` and reach the GPU decoder path because that runtime field is intentionally honored by `VideoMediaIO`. ## PoV An ordinary multimodal inference request can carry the backend override in the request body: ```json { "model": "served-vlm", "messages": [ { "role": "user", "content": [ {"type": "text", "text": "summarize this clip"}, {"type": "video_url", "video_url": {"url": "data:video/mp4;base64,<small-mp4>"}} ] } ], "media_io_kwargs": { "video": { "video_backend": "pynvvideocodec" } } } ``` The following bounded source-level check confir
- CVSS 3.1
- CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
- Severity from
- GitHub (reviewed advisory)
- Weakness
- CWE-400, CWE-770
- Also known as
- CVE-2026-69147
- github.com/vllm-project/vllm/security/advisories/GHSA-8pw2-6jv3-mj5j
- nvd.nist.gov/vuln/detail/CVE-2026-69147
- github.com/vllm-project/vllm/pull/47259
- github.com/vllm-project/vllm/commit/283893c72292ede38d277e3cd2b9b64c3e4f1dda
- github.com/vllm-project/vllm/commit/ba22152096b2484faa3579624a253d54804d876d
- github.com/vllm-project/vllm
- github.com/vllm-project/vllm/releases/tag/v0.25.0
More vLLM advisories
All vLLM| Date | Advisory | Severity | Fixed in |
|---|---|---|---|
| Sep 16 | vLLM: Unauthenticated audio decompression-bomb DoS in /v1/chat/completions | Medium6.5 | 0.24.0 |
| Sep 12 | vLLM: remote code execution | High7.8 | 0.28.0 |
| Sep 8 | vLLM: server-side request forgery | Medium6.5 | 0.26.0 |
| Sep 8 | vLLM: Cross-User Data Leak Vulnerability | Medium5.3 | 0.27.0 |
| Sep 4 | vLLM: Incomplete CVE-2025-62164 remediation can be bypassed by concurrent prompt parts | Medium | 0.26.0 |
| Sep 4 | vLLM: denial of service | Medium5.3 | 0.26.0 |