Files
ppsspp/Common/Data/Format/RIFF.cpp
T
Henrik RydgårdandClaude Sonnet 5 406033dc3d RIFF/BackgroundAudio: fix OOB reads on short/corrupt WAV chunks
RIFFReader::ReadData() trusted its count argument completely and
memcpy'd straight from the internal buffer with no bounds check.
Hardened it to clamp against the buffer and zero-fill any shortfall,
as defense in depth.

The actual reachable bug was in BackgroundAudio.cpp: it read a WAV
'smpl' chunk into a vector sized by GetCurrentChunkSize(), then
unconditionally indexed smplData[28] (and, for the loop array,
smplData[36]) with no check that the chunk was actually that large -
a short/corrupt chunk in a game's background-music WAV caused a heap
OOB read. Also fixes &smplData[0] being UB when the chunk is empty.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4QAoxV2KY7ek4PcZw3WvY
2026-08-09 19:31:02 +02:00

120 lines
2.7 KiB
C++

#include <cstring>
#include "Common/Log.h"
#include "Common/Data/Format/RIFF.h"
inline uint32_t flipID(uint32_t id) {
return ((id >> 24) & 0xFF) | ((id >> 8) & 0xFF00) | ((id << 8) & 0xFF0000) | ((id << 24) & 0xFF000000);
}
RIFFReader::RIFFReader(const uint8_t *data, int dataSize) {
data_ = new uint8_t[dataSize];
memcpy(data_, data, dataSize);
depth_ = 0;
pos_ = 0;
eof_ = dataSize;
fileSize_ = dataSize;
}
RIFFReader::~RIFFReader() {
delete[] data_;
}
int RIFFReader::ReadInt() {
int value = 0;
if (data_ && pos_ < eof_ - 3) {
pos_ += 4;
memcpy(&value, data_ + pos_ - 4, 4);
}
return value;
}
bool RIFFReader::Descend(uint32_t intoId) {
if (depth_ > 30)
return false;
intoId = flipID(intoId);
bool found = false;
// save information to restore after the next Ascend
stack[depth_].parentStartLocation = pos_;
stack[depth_].parentEOF = eof_;
// let's search through children..
while (pos_ < eof_) {
int id = ReadInt();
int length = ReadInt();
int startLocation = pos_;
if (pos_ + length > fileSize_) {
ERROR_LOG(Log::IO, "Block extends outside of RIFF file - failing descend");
pos_ = stack[depth_].parentStartLocation;
return false;
}
if (id == intoId) {
stack[depth_].ID = intoId;
stack[depth_].length = length;
stack[depth_].startLocation = startLocation;
found = true;
break;
} else {
if (length > 0) {
pos_ += length; // try next block
} else {
ERROR_LOG(Log::IO, "Bad data in RIFF file : block length %d. Not descending.", length);
pos_ = stack[depth_].parentStartLocation;
return false;
}
}
}
// if we found nothing, return false so the caller can skip this
if (!found) {
pos_ = stack[depth_].parentStartLocation;
return false;
}
// descend into it
// pos was set inside the loop above
eof_ = stack[depth_].startLocation + stack[depth_].length;
depth_++;
return true;
}
void RIFFReader::Ascend() {
// ascend, and restore information
depth_--;
pos_ = stack[depth_].parentStartLocation;
eof_ = stack[depth_].parentEOF;
}
void RIFFReader::ReadData(void *what, int count) {
if (count > 0) {
int available = pos_ < fileSize_ ? fileSize_ - pos_ : 0;
int toRead = count < available ? count : available;
if (toRead > 0) {
memcpy(what, data_ + pos_, toRead);
}
if (toRead < count) {
// Truncated/corrupt file - don't read past the buffer. Zero the rest so
// callers don't read uninitialized data.
ERROR_LOG(Log::IO, "RIFFReader::ReadData: wanted %d bytes but only %d available", count, toRead);
memset((uint8_t *)what + toRead, 0, count - toRead);
}
}
pos_ += count;
count &= 3;
if (count) {
count = 4 - count;
pos_ += count;
}
}
int RIFFReader::GetCurrentChunkSize() {
if (depth_)
return stack[depth_ - 1].length;
else
return 0;
}