[Security] Cap GLMGA video sampling to prevent request-driven resource exhaustion (#54935)
Signed-off-by: jperezde <jperezde@redhat.com> Signed-off-by: Isotr0py <Isotr0py@outlook.com> Co-authored-by: Isotr0py <Isotr0py@outlook.com>
This commit is contained in:
co-authored by
Isotr0py
parent
c615b1fd67
commit
8b6de0eb9a
@@ -22,6 +22,7 @@ from vllm.multimodal.video import (
|
||||
DynamicVideoBackend,
|
||||
Glm5NextVideoBackend,
|
||||
GLM46VVideoBackend,
|
||||
GLMGAVideoBackend,
|
||||
Molmo2VideoBackend,
|
||||
Qwen2VLVideoBackend,
|
||||
Qwen3VLVideoBackend,
|
||||
@@ -1473,6 +1474,59 @@ def test_glm46v_duration_estimation_from_fps():
|
||||
assert all(0 <= idx < 90 for idx in indices)
|
||||
|
||||
|
||||
class TestGLMGASamplingCaps:
|
||||
"""Regression tests for GHSA-58v5-2m8f-94pr: request-controlled fps
|
||||
and max_frames must not create intermediate allocations larger than
|
||||
the actual frame count."""
|
||||
|
||||
@staticmethod
|
||||
def _source(
|
||||
total_frames: int, fps: float = 30.0, duration: float = 0
|
||||
) -> VideoSourceMetadata:
|
||||
if duration == 0 and fps > 0 and total_frames > 1:
|
||||
duration = round((total_frames - 1) / fps) + 1
|
||||
return VideoSourceMetadata(total_frames, fps, duration)
|
||||
|
||||
def test_extreme_values_bounded_by_total_frames(self):
|
||||
source = self._source(total_frames=2, fps=2.0, duration=1.0)
|
||||
target = VideoTargetMetadata(num_frames=-1, fps=500_000, max_duration=-1)
|
||||
indices = GLMGAVideoBackend.compute_frames_index_to_sample(
|
||||
source,
|
||||
target,
|
||||
max_frames=500_000,
|
||||
)
|
||||
assert len(indices) <= 2
|
||||
|
||||
def test_class_cap_overrides_kwargs_max_frames(self):
|
||||
source = self._source(total_frames=10_000, fps=30.0)
|
||||
target = VideoTargetMetadata(num_frames=-1, fps=30, max_duration=-1)
|
||||
indices = GLMGAVideoBackend.compute_frames_index_to_sample(
|
||||
source,
|
||||
target,
|
||||
max_frames=100_000,
|
||||
)
|
||||
assert len(indices) <= GLMGAVideoBackend._MAX_FRAMES
|
||||
|
||||
def test_class_cap_overrides_target_fps(self):
|
||||
source = self._source(total_frames=10_000, fps=30.0)
|
||||
target = VideoTargetMetadata(num_frames=-1, fps=500_000, max_duration=-1)
|
||||
indices = GLMGAVideoBackend.compute_frames_index_to_sample(
|
||||
source,
|
||||
target,
|
||||
)
|
||||
assert len(indices) <= GLMGAVideoBackend._MAX_FRAMES
|
||||
|
||||
def test_normal_operation_unchanged(self):
|
||||
source = self._source(total_frames=1000, fps=30.0, duration=33.0)
|
||||
target = VideoTargetMetadata(num_frames=-1, fps=2, max_duration=-1)
|
||||
indices = GLMGAVideoBackend.compute_frames_index_to_sample(
|
||||
source,
|
||||
target,
|
||||
)
|
||||
assert 0 < len(indices) <= GLMGAVideoBackend._MAX_FRAMES
|
||||
assert all(0 <= idx < 1000 for idx in indices)
|
||||
|
||||
|
||||
def test_glm5next_backend_selected_for_processor():
|
||||
"""Glm5NextVideoProcessor maps to the glm5next loader so only the
|
||||
sampled frames are decoded instead of the whole container. Both the
|
||||
|
||||
@@ -781,6 +781,9 @@ class Glm5NextVideoBackend(VideoBackend):
|
||||
video_processor="GlmgaVideoProcessor",
|
||||
)
|
||||
class GLMGAVideoBackend(VideoBackend):
|
||||
_MAX_FRAMES: ClassVar[int] = 640
|
||||
_MAX_FPS: ClassVar[int] = 30
|
||||
|
||||
@classmethod
|
||||
def _prepare_source(cls, source: VideoSourceMetadata) -> VideoSourceMetadata:
|
||||
# Estimate duration from frame count and fps when the container
|
||||
@@ -806,14 +809,14 @@ class GLMGAVideoBackend(VideoBackend):
|
||||
total_frames_num = source.total_frames_num
|
||||
duration = source.duration
|
||||
original_fps = source.original_fps
|
||||
target_fps = target.fps
|
||||
target_fps = min(target.fps, cls._MAX_FPS)
|
||||
max_frame_idx = source.total_frames_num - 1
|
||||
max_frames = kwargs.get("max_frames", 640)
|
||||
max_frames = min(kwargs.get("max_frames", cls._MAX_FRAMES), cls._MAX_FRAMES)
|
||||
|
||||
duration = duration or round(max_frame_idx / original_fps) + 1
|
||||
|
||||
extract_t = int(duration * target_fps)
|
||||
extract_t = min(extract_t, max_frames)
|
||||
extract_t = min(extract_t, max_frames, total_frames_num)
|
||||
|
||||
duration_per_frame = 1 / original_fps
|
||||
|
||||
|
||||
Reference in New Issue
Block a user