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.
Summary
Found 2 vulnerabilities in the WAV file reader that can be triggered by crafted
.wavfiles. Both stem from missing validation of theNumChannelsheader field.Vulnerabilities
1. Division by Zero When NumChannels=0 (CWE-369)
Location:
tinywav_open_read(), line 220tw->numChannelsis set directly from the WAV file header'sNumChannelsfield (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 279tw->numChannelscomes from the file header (uint16_t, up to 65535). The defaultTW_ALLOCusesalloca(), allocatingnumChannels * len * sizeof(type)bytes on the stack.alloca(60000 * 1024 * 2)= ~117MB on the stack → stack overflowtw->numChannelsisint16_twhiletw->h.NumChannelsisuint16_t. Values >32767 wrap negative, making the product negative, which wraps to enormous when cast tosize_tby alloca.Trigger: Craft a WAV file with NumChannels=60000. Any call to
tinywav_read_f()crashes.Impact: Stack overflow → crash → denial of service.
PoC
Suggested Fix
Add validation after reading the header in
tinywav_open_read():And consider adding a size bound before alloca in
tinywav_read_f():Found through manual source code audit. Reported responsibly.