Skip to content

DoS via unbounded WAV header values in AudioSegment.from_wav() #877

Description

@peteypivots

pydub_wav_dos_pocs.zip

Describe the bug

`AudioSegment.from_wav()` and `AudioSegment.from_file()` parse WAV headers and trust `nSamplesPerSec` and `nChannels` 
without bounds checking. Malformed WAV files with absurd values load successfully but cause CPU exhaustion or OOM when 
downstream operations like `export()`, `len()`, or `frame_rate` calculations trigger `ffmpeg` processing.

This allows a 44-byte file to DoS any service that uses `pydub` to process user-uploaded WAVs.

1. Download `pydub_wav_dos_pocs.zip` attached to this issue
2. Extract and run:

```python
from pydub import AudioSegment

# PoC 1: Samplerate overflow - HANG/OOM on export
a = AudioSegment.from_wav('evil.wav') # loads: frame_rate=100000000
a.export('out.mp3', format='mp3') # hangs/allocates huge memory

# PoC 2: Channel overflow - ffmpeg error/OOM on export
b = AudioSegment.from_wav('evil_channels.wav') # loads: channels=65535
b.export('out.mp3', format='mp3') # ffmpeg: Invalid channel layout
  1. Observe: Files load without error, then exhaust resources during processing.

Expected behavior
pydub should reject WAV files with unreasonable header values before returning an AudioSegment. Similar to how ffmpeg itself rejects data_size=0xFFFFFFFF with "invalid start code", pydub should fail fast on sample_rate > 384000 or channels > 8.

Actual behavior

>>> a = AudioSegment.from_wav('evil.wav')
>>> a.frame_rate
100000000 # accepted
>>> a.channels
1
>>> len(a)
0 # duration calc underflows but doesn't error

Environment

  • pydub: 0.25.1
  • Python: 3.9.16
  • ffmpeg: 5.1.4
  • OS: Linux x86_64

Impact
Any service accepting user WAV uploads via pydub is vulnerable to DoS. A 44-byte file can cause:

  1. CPU exhaustion: ffmpeg attempts to resample 100MHz audio
  2. Memory exhaustion: ffmpeg attempts 64k-channel processing
  3. Request thread hangs until killed by timeout

Additional test cases in zip:

  • evil_bits.wav: bits_per_sample=65535 — loads, fails faster on export
  • evil_data_size.wav: data_size=0xFFFFFFFF — already rejected by ffmpeg, included to show where validation exists

Suggested fix
Add bounds checks after parsing fmt chunk in audio_segment.py:

MAX_SAMPLE_RATE = 384000 # DXD/DSD rates
MIN_SAMPLE_RATE = 8000 # telephone quality
MAX_CHANNELS = 8 # 7.1 surround

if not MIN_SAMPLE_RATE <= self.frame_rate <= MAX_SAMPLE_RATE:
    raise CouldntDecodeError(f"Sample rate {self.frame_rate} out of bounds")
if not 1 <= self.channels <= MAX_CHANNELS:
    raise CouldntDecodeError(f"Channel count {self.channels} out of bounds")
if self.sample_width not in (1, 2, 3, 4): # 8,16,24,32-bit
    raise CouldntDecodeError(f"Sample width {self.sample_width} unsupported")

Happy to submit a PR if maintainers agree with the approach.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions