fix(security): prevent infinite loop in split_audio with NaN audio sa… (#46463)

Juan Pérez de Algaba committed Jun 23, 2026 at 12:24 UTC 83fa302ca430592bbf547a20b818cc34c9f40e89
2 files changed +43 -2
tests/multimodal/test_audio.py
+36
@@ -760,6 +760,42 @@ class TestAudioChunking:
760 assert chunks[0][0] == audio[0]
761 assert chunks[-1][-1] == audio[-1]
762
763 + def test_find_split_point_nan_input(self):
764 + """find_split_point must not return 0 for all-NaN input."""
765 + from vllm.multimodal.audio import find_split_point
766 +
767 + nan_audio = np.full(32000, float("nan"), dtype=np.float32)
768 + start_idx = 16000
769 + end_idx = 32000
770 +
771 + split_idx = find_split_point(
772 + wav=nan_audio,
773 + start_idx=start_idx,
774 + end_idx=end_idx,
775 + min_energy_window=1600,
776 + )
777 +
778 + # Must return start_idx (the safe fallback), not 0
779 + assert split_idx == start_idx
780 +
781 + def test_split_audio_nan_input_terminates(self):
782 + """split_audio must terminate on all-NaN audio (no infinite loop)."""
783 + # 31 seconds of NaN at 16kHz — longer than max_clip_duration_s
784 + nan_audio = np.full(16000 * 31, float("nan"), dtype=np.float32)
785 +
786 + chunks = split_audio(
787 + audio_data=nan_audio,
788 + sample_rate=16000,
789 + max_clip_duration_s=30.0,
790 + overlap_duration_s=1.0,
791 + min_energy_window_size=1600,
792 + )
793 +
794 + # Must produce at least 2 chunks and cover all samples
795 + assert len(chunks) >= 2
796 + total_samples = sum(c.shape[-1] for c in chunks)
797 + assert total_samples == nan_audio.shape[-1]
798 +
799 def test_split_audio_with_different_sample_rates(self):
800 """Test chunking works with different sample rates."""
801
vllm/multimodal/audio.py
+7 -2
@@ -378,6 +378,11 @@ def split_audio(
378 audio_data, search_start, search_end, min_energy_window_size
379 )
380
381 + # Guarantee forward progress: if split_point didn't advance,
382 + # fall back to the hard chunk boundary.
383 + if split_point <= i:
384 + split_point = min(i + chunk_size, audio_data.shape[-1])
385 +
386 # Extract chunk up to the split point
387 chunks.append(audio_data[..., i:split_point])
388 i = split_point
@@ -423,12 +428,12 @@ def find_split_point(
428
429 # Calculate RMS energy in small windows
430 min_energy = math.inf
426 - quietest_idx = 0
431 + quietest_idx = start_idx
432
433 for i in range(0, len(segment) - min_energy_window, min_energy_window):
434 window = segment[i : i + min_energy_window]
435 energy = (window**2).mean() ** 0.5
431 - if energy < min_energy:
436 + if not math.isnan(energy) and energy < min_energy:
437 quietest_idx = i + start_idx
438 min_energy = energy
439