VYPR
Medium severity6.5NVD Advisory· Published Apr 6, 2026· Updated Apr 20, 2026

CVE-2026-34755

CVE-2026-34755

Description

vLLM is an inference and serving engine for large language models (LLMs). From 0.7.0 to before 0.19.0, the VideoMediaIO.load_base64() method at vllm/multimodal/media/video.py splits video/jpeg data URLs by comma to extract individual JPEG frames, but does not enforce a frame count limit. The num_frames parameter (default: 32), which is enforced by the load_bytes() code path, is completely bypassed in the video/jpeg base64 path. An attacker can send a single API request containing thousands of comma-separated base64-encoded JPEG frames, causing the server to decode all frames into memory and crash with OOM. This vulnerability is fixed in 0.19.0.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
vllmPyPI
>= 0.7.0, < 0.19.00.19.0

Affected products

1
  • cpe:2.3:a:vllm:vllm:*:*:*:*:*:*:*:*
    Range: >=0.7.0,<0.19.0

Patches

1
58ee61422169

(security) Enforce frame limit in VideoMediaIO (#38636)

https://github.com/vllm-project/vllmJuan Pérez de AlgabaApr 1, 2026via ghsa
2 files changed · +69 10
  • tests/multimodal/media/test_video.py+61 9 modified
    @@ -239,6 +239,17 @@ def test_video_media_io_backend_env_var_fallback(monkeypatch: pytest.MonkeyPatch
             assert metadata_missing["video_backend"] == "test_video_backend_override_2"
     
     
    +def _make_jpeg_b64_frames(n: int, width: int = 8, height: int = 8) -> list[str]:
    +    """Return *n* tiny base64-encoded JPEG frames."""
    +    frames: list[str] = []
    +    for i in range(n):
    +        img = Image.new("RGB", (width, height), color=(i % 256, 0, 0))
    +        buf = io.BytesIO()
    +        img.save(buf, format="JPEG")
    +        frames.append(pybase64.b64encode(buf.getvalue()).decode("ascii"))
    +    return frames
    +
    +
     def test_load_base64_jpeg_returns_metadata():
         """Regression test: load_base64 with video/jpeg must return metadata.
     
    @@ -248,16 +259,8 @@ def test_load_base64_jpeg_returns_metadata():
         """
     
         num_test_frames = 3
    -    frame_width, frame_height = 8, 8
    -
    -    # Build a few tiny JPEG frames and base64-encode them
    -    b64_frames = []
    -    for i in range(num_test_frames):
    -        img = Image.new("RGB", (frame_width, frame_height), color=(i * 80, 0, 0))
    -        buf = io.BytesIO()
    -        img.save(buf, format="JPEG")
    -        b64_frames.append(pybase64.b64encode(buf.getvalue()).decode("ascii"))
     
    +    b64_frames = _make_jpeg_b64_frames(num_test_frames)
         data = ",".join(b64_frames)
     
         imageio = ImageMediaIO()
    @@ -287,3 +290,52 @@ def test_load_base64_jpeg_returns_metadata():
         # Default fps=1 → duration == num_frames
         assert metadata["fps"] == 1.0
         assert metadata["duration"] == float(num_test_frames)
    +
    +
    +def test_load_base64_jpeg_enforces_num_frames_limit():
    +    """Frames beyond num_frames must be truncated in the video/jpeg path.
    +
    +    Without the limit an attacker can send thousands of base64 JPEG frames
    +    in a single request and exhaust server memory (OOM).
    +    """
    +    num_frames_limit = 4
    +    sent_frames = 20
    +
    +    b64_frames = _make_jpeg_b64_frames(sent_frames)
    +    data = ",".join(b64_frames)
    +
    +    imageio = ImageMediaIO()
    +    videoio = VideoMediaIO(imageio, num_frames=num_frames_limit)
    +    frames, metadata = videoio.load_base64("video/jpeg", data)
    +
    +    assert frames.shape[0] == num_frames_limit
    +    assert metadata["total_num_frames"] == num_frames_limit
    +    assert metadata["frames_indices"] == list(range(num_frames_limit))
    +
    +
    +def test_load_base64_jpeg_no_limit_when_num_frames_negative():
    +    """When num_frames is -1, all frames should be loaded without truncation."""
    +    sent_frames = 10
    +
    +    b64_frames = _make_jpeg_b64_frames(sent_frames)
    +    data = ",".join(b64_frames)
    +
    +    imageio = ImageMediaIO()
    +    videoio = VideoMediaIO(imageio, num_frames=-1)
    +    frames, metadata = videoio.load_base64("video/jpeg", data)
    +
    +    assert frames.shape[0] == sent_frames
    +    assert metadata["total_num_frames"] == sent_frames
    +    assert metadata["frames_indices"] == list(range(sent_frames))
    +
    +
    +def test_load_base64_jpeg_raises_on_zero_num_frames():
    +    """num_frames=0 is invalid and should raise ValueError."""
    +    b64_frames = _make_jpeg_b64_frames(3)
    +    data = ",".join(b64_frames)
    +
    +    imageio = ImageMediaIO()
    +    videoio = VideoMediaIO(imageio, num_frames=0)
    +
    +    with pytest.raises(ValueError, match="num_frames must be greater than 0 or -1"):
    +        videoio.load_base64("video/jpeg", data)
    
  • vllm/multimodal/media/video.py+8 1 modified
    @@ -80,8 +80,15 @@ def load_base64(
                     "image/jpeg",
                 )
     
    +            if self.num_frames > 0:
    +                frame_parts = data.split(",", self.num_frames)[: self.num_frames]
    +            elif self.num_frames == 0:
    +                raise ValueError("num_frames must be greater than 0 or -1")
    +            else:
    +                frame_parts = data.split(",")
    +
                 frames = np.stack(
    -                [np.asarray(load_frame(frame_data)) for frame_data in data.split(",")]
    +                [np.asarray(load_frame(frame_data)) for frame_data in frame_parts]
                 )
                 total = int(frames.shape[0])
                 fps = float(self.kwargs.get("fps", 1))
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.