Skip to content

Security: Division by zero + stack overflow via crafted WAV header #21

@ByamB4

Description

@ByamB4

Summary

Found 2 vulnerabilities in the WAV file reader that can be triggered by crafted .wav files. Both stem from missing validation of the NumChannels header field.

Vulnerabilities

1. Division by Zero When NumChannels=0 (CWE-369)

Location: tinywav_open_read(), line 220

tw->numFramesInHeader = tw->h.Subchunk2Size / (tw->numChannels * tw->sampFmt);

tw->numChannels is set directly from the WAV file header's NumChannels field (line 208) without validation. When NumChannels=0, the divisor evaluates to 0.

Trigger: Craft a WAV file with NumChannels=0 at byte offset 22-23 in the fmt subchunk.

Impact: Crash (SIGFPE/exception) → denial of service.

2. Stack Buffer Overflow via alloca with Attacker-Controlled NumChannels (CWE-770/CWE-190)

Location: tinywav_read_f(), lines 242 and 279

TW_ALLOC(int16_t, interleaved_data, tw->numChannels*len);
// Default: alloca((tw->numChannels*len)*sizeof(int16_t))

tw->numChannels comes from the file header (uint16_t, up to 65535). The default TW_ALLOC uses alloca(), allocating numChannels * len * sizeof(type) bytes on the stack.

  • NumChannels=60000, len=1024 → alloca(60000 * 1024 * 2) = ~117MB on the stack → stack overflow
  • Additionally, tw->numChannels is int16_t while tw->h.NumChannels is uint16_t. Values >32767 wrap negative, making the product negative, which wraps to enormous when cast to size_t by alloca.

Trigger: Craft a WAV file with NumChannels=60000. Any call to tinywav_read_f() crashes.

Impact: Stack overflow → crash → denial of service.

PoC

import struct

# Division by zero
wav = bytearray()
wav += b'RIFF'
wav += struct.pack('<I', 36)
wav += b'WAVEfmt '
wav += struct.pack('<I', 16)
wav += struct.pack('<H', 1)      # AudioFormat (PCM)
wav += struct.pack('<H', 0)      # NumChannels = 0
wav += struct.pack('<I', 44100)  # SampleRate
wav += struct.pack('<I', 0)      # ByteRate
wav += struct.pack('<H', 0)      # BlockAlign
wav += struct.pack('<H', 16)     # BitsPerSample
wav += b'data'
wav += struct.pack('<I', 0)
with open('crash_divzero.wav', 'wb') as f:
    f.write(wav)

Suggested Fix

Add validation after reading the header in tinywav_open_read():

tw->numChannels = tw->h.NumChannels;

if (tw->numChannels < 1 || tw->numChannels > 128) {
    tinywav_close_read(tw);
    return -1;
}

And consider adding a size bound before alloca in tinywav_read_f():

size_t alloc_samples = (size_t)tw->numChannels * len;
if (alloc_samples > SOME_REASONABLE_MAX) {
    return -1;
}

Found through manual source code audit. Reported responsibly.

Metadata

Metadata

Assignees

Labels

ai-generatedThe issue was (most likely) submitted via AI, either directly or indirectly (text was written by AI)

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions