Audio, PSMF: Check the whole range the decoders touch

- Atrac: the partial-frame buffer was sized from the codec field in the guest's
  context, which a game can change after SetData, so an Atrac3+ decoder could
  write 2048 samples into a 1024-sample buffer. Size it for any frame, and
  decide from the decoder whether a frame fits the output. Check the output
  buffer holds a frame and the input holds the frame read, not just their
  first bytes, and range-check the streaming buffer copy.
- Atrac track parsing is bounded by mapped memory, not only the game's size.
- sceMp3LowLevelDecode and sceAudiocodecDecode check their output holds a
  frame, and the MP3 input is clamped. FindMp3Header read 3 bytes past its
  range.
- PSMF: the EP map address could wrap around to a valid one while the entries
  were read from the unwrapped host pointer, and scePsmfSetPsmf checked one
  byte of the 0x82-byte header it reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-11-05 17:21:36 +01:00
1 parent 5b3ddcc062
commit b1ffcde9fc
5 files changed
+60 -26

No files matched your search

+16 -9
View File
@@ -875,9 +875,13 @@ u32 Atrac2::DecodeInternal(u32 outbufAddr, int *SamplesNum, int *finish) {
int16_t *outPtr;
_dbg_assert_(samplesToDecode <= info.SamplesPerFrame());
if (samplesToDecode != info.SamplesPerFrame()) {
// info.codec is in guest memory and can be changed after SetData, so what the decoder actually
// writes is decided by the decoder.
const int decoderSamples = decoder_->GetAudioType() == PSP_CODEC_AT3PLUS ? ATRAC3PLUS_MAX_SAMPLES : ATRAC3_MAX_SAMPLES;
if (samplesToDecode != decoderSamples) {
if (!decodeTemp_) {
decodeTemp_ = new int16_t[info.SamplesPerFrame() * outputChannels_];
// Big enough for any frame, in any channel count.
decodeTemp_ = new int16_t[ATRAC3PLUS_MAX_SAMPLES * 2]();
}
outPtr = decodeTemp_;
} else {
@@ -887,8 +891,9 @@ u32 Atrac2::DecodeInternal(u32 outbufAddr, int *SamplesNum, int *finish) {
context_->codec.inBuf = inAddr;
context_->codec.outBuf = outbufAddr;
if (!Memory::IsValidAddress(inAddr)) {
ERROR_LOG(Log::Atrac, "DecodeInternal: Bad inAddr %08x", inAddr);
// The decoder reads a whole frame.
if (!Memory::IsValidRange(inAddr, info.sampleSize)) {
ERROR_LOG(Log::Atrac, "DecodeInternal: Bad inAddr %08x (frame size %d)", inAddr, info.sampleSize);
return SCE_ERROR_ATRAC_API_FAIL;
}
@@ -918,8 +923,8 @@ u32 Atrac2::DecodeInternal(u32 outbufAddr, int *SamplesNum, int *finish) {
*finish = 0;
}
u8 *outBuf = outbufAddr ? Memory::GetPointerWriteOrException(outbufAddr) : nullptr;
if (samplesToDecode != info.SamplesPerFrame() && samplesToDecode != 0 && outBuf) {
memcpy(outBuf, decodeTemp_, samplesToDecode * outputChannels_ * sizeof(int16_t));
if (outPtr == decodeTemp_ && samplesToDecode != 0 && outBuf) {
memcpy(outBuf, decodeTemp_, std::min(samplesToDecode, (int)ATRAC3PLUS_MAX_SAMPLES) * outputChannels_ * sizeof(int16_t));
}
// Handle increments and looping.
@@ -976,9 +981,11 @@ u32 Atrac2::DecodeInternal(u32 outbufAddr, int *SamplesNum, int *finish) {
size_t copyLen = info.secondBufferByte % info.sampleSize;
if (copyLen > info.bufferByte)
copyLen = info.bufferByte;
memcpy(Memory::GetPointerWriteOrException(info.buffer),
Memory::GetPointerOrException(info.secondBuffer + (info.secondBufferByte - info.secondBufferByte % info.sampleSize)),
copyLen);
const u32 copySrc = info.secondBuffer + (info.secondBufferByte - info.secondBufferByte % info.sampleSize);
// Both addresses come from the context in guest memory.
if (Memory::IsValidRange(info.buffer, (u32)copyLen) && Memory::IsValidRange(copySrc, (u32)copyLen)) {
memcpy(Memory::GetPointerWriteOrException(info.buffer), Memory::GetPointerOrException(copySrc), copyLen);
}
}
}
}
+19 -11
View File
@@ -420,7 +420,8 @@ static u32 sceAtracDecodeData(int atracID, u32 outAddr, u32 numSamplesAddr, u32
int numSamplesWritten = 0;
int finish = 0;
int remains = 0;
if (outAddr != 0 && !Memory::IsValidAddress(outAddr)) {
// A whole frame is written.
if (outAddr != 0 && !Memory::IsValidRange(outAddr, atrac->GetOutputChannels() * 2 * atrac->SamplesPerFrame())) {
// Dunno what error code to return here, but we have to bail in this case to avoid crashing PPSSPP.
return hleLogError(Log::Atrac, SCE_ERROR_ATRAC_SIZE_TOO_SMALL);
}
@@ -757,6 +758,13 @@ static u32 sceAtracResetPlayPosition(int atracID, int sample, int bytesWrittenFi
return hleDelayResult(res, "reset play pos", 3000);
}
// The track parsers read up to the size they're given, which the game picks. Bound it by what's
// actually mapped, but keep it for invalid pointers so those are still reported as such.
static u32 GuestReadableSize(u32 addr, u32 size) {
return Memory::IsValidAddress(addr) ? Memory::ClampValidSizeAt(addr, size) : size;
}
static u32 sceAtracSetHalfwayBuffer(int atracID, u32 buffer, u32 readSize, u32 bufferSize) {
AtracBase *atrac = getAtrac(atracID);
// Don't use AtracValidateManaged here.
@@ -770,7 +778,7 @@ static u32 sceAtracSetHalfwayBuffer(int atracID, u32 buffer, u32 readSize, u32 b
Track track;
std::string error;
int ret = AnalyzeAtracTrack(Memory::GetPointerOrNull(buffer), readSize, &track, &error);
int ret = AnalyzeAtracTrack(Memory::GetPointerOrNull(buffer), GuestReadableSize(buffer, readSize), &track, &error);
if (ret < 0) {
return hleLogError(Log::Atrac, ret, "%s", error.c_str());
}
@@ -807,7 +815,7 @@ static u32 sceAtracSetData(int atracID, u32 buffer, u32 bufferSize) {
Track track;
std::string error;
int ret = AnalyzeAtracTrack(Memory::GetPointerOrNull(buffer), bufferSize, &track, &error);
int ret = AnalyzeAtracTrack(Memory::GetPointerOrNull(buffer), GuestReadableSize(buffer, bufferSize), &track, &error);
if (ret < 0) {
return hleLogError(Log::Atrac, ret, "%s", error.c_str());
}
@@ -838,7 +846,7 @@ static int sceAtracSetDataAndGetID(u32 buffer, int bufferSize) {
std::string error;
// We let zero-pointer through as zeroes, so the small size check can kick in before it's accessed.
// This is needed in Tomb Raider Legend (#20145).
int ret = AnalyzeAtracTrack(Memory::GetPointerOrNull(buffer), bufferSize, &track, &error);
int ret = AnalyzeAtracTrack(Memory::GetPointerOrNull(buffer), GuestReadableSize(buffer, bufferSize), &track, &error);
if (ret < 0) {
return hleLogError(Log::Atrac, ret, "%s", error.c_str());
}
@@ -864,7 +872,7 @@ static int sceAtracSetHalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 buffer
Track track;
std::string error;
int ret = AnalyzeAtracTrack(Memory::GetPointerOrNull(buffer), readSize, &track, &error);
int ret = AnalyzeAtracTrack(Memory::GetPointerOrNull(buffer), GuestReadableSize(buffer, readSize), &track, &error);
if (ret < 0) {
return hleLogError(Log::Atrac, ret, "%s", error.c_str());
}
@@ -990,7 +998,7 @@ static int sceAtracSetMOutHalfwayBuffer(int atracID, u32 buffer, u32 readSize, u
Track track;
std::string error;
int ret = AnalyzeAtracTrack(Memory::GetPointerOrNull(buffer), readSize, &track, &error);
int ret = AnalyzeAtracTrack(Memory::GetPointerOrNull(buffer), GuestReadableSize(buffer, readSize), &track, &error);
if (ret < 0) {
return hleLogError(Log::Atrac, ret, "%s", error.c_str());
}
@@ -1016,7 +1024,7 @@ static u32 sceAtracSetMOutData(int atracID, u32 buffer, u32 bufferSize) {
Track track;
std::string error;
int ret = AnalyzeAtracTrack(Memory::GetPointerOrNull(buffer), bufferSize, &track, &error);
int ret = AnalyzeAtracTrack(Memory::GetPointerOrNull(buffer), GuestReadableSize(buffer, bufferSize), &track, &error);
if (ret < 0) {
return hleLogError(Log::Atrac, ret, "%s", error.c_str());
}
@@ -1036,7 +1044,7 @@ static u32 sceAtracSetMOutData(int atracID, u32 buffer, u32 bufferSize) {
static int sceAtracSetMOutDataAndGetID(u32 buffer, u32 bufferSize) {
Track track;
std::string error;
int ret = AnalyzeAtracTrack(Memory::GetPointerOrNull(buffer), bufferSize, &track, &error);
int ret = AnalyzeAtracTrack(Memory::GetPointerOrNull(buffer), GuestReadableSize(buffer, bufferSize), &track, &error);
if (ret < 0) {
return hleLogError(Log::Atrac, ret, "%s", error.c_str());
}
@@ -1065,7 +1073,7 @@ static int sceAtracSetMOutHalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 bu
Track track;
std::string error;
int ret = AnalyzeAtracTrack(Memory::GetPointerOrNull(buffer), readSize, &track, &error);
int ret = AnalyzeAtracTrack(Memory::GetPointerOrNull(buffer), GuestReadableSize(buffer, readSize), &track, &error);
if (ret < 0) {
return hleLogError(Log::Atrac, ret, "%s", error.c_str());
}
@@ -1090,7 +1098,7 @@ static int sceAtracSetMOutHalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 bu
static int sceAtracSetAA3DataAndGetID(u32 buffer, u32 bufferSize, u32 fileSize, u32 metadataSizeAddr) {
Track track;
std::string error;
int ret = AnalyzeAA3Track(Memory::GetPointerOrNull(buffer), bufferSize, fileSize, &track, &error);
int ret = AnalyzeAA3Track(Memory::GetPointerOrNull(buffer), GuestReadableSize(buffer, bufferSize), fileSize, &track, &error);
if (ret < 0) {
return hleLogError(Log::Atrac, ret, "%s", error.c_str());
}
@@ -1116,7 +1124,7 @@ static int sceAtracSetAA3HalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 buf
Track track;
std::string error;
int ret = AnalyzeAA3Track(Memory::GetPointerOrNull(buffer), readSize, fileSize, &track, &error);
int ret = AnalyzeAA3Track(Memory::GetPointerOrNull(buffer), GuestReadableSize(buffer, readSize), fileSize, &track, &error);
if (ret < 0) {
return hleLogError(Log::Atrac, ret, "%s", error.c_str());
}
+6
View File
@@ -640,6 +640,12 @@ static int sceAudiocodecDecode(u32 ctxPtr, int codec) {
} else if (codec == PSP_CODEC_AT3PLUS && ctx->fmt.at3.outputChannels == 1) {
outputChannels = 1;
}
// The decoder writes a whole frame, but only the first byte of outBuf was checked.
const u32 maxFrameSamples = codec == PSP_CODEC_AT3PLUS ? 2048 : (codec == PSP_CODEC_MP3 ? 1152 : 1024);
if (!Memory::IsValidRange(ctx->outBuf, maxFrameSamples * outputChannels * 2)) {
ctx->err = 0x20b;
return hleLogError(Log::ME, 0, "Output buffer at %08x can't hold a frame", ctx->outBuf);
}
bool result = decoder->Decode(inBuf, bytesPerFrame, &inDataConsumed, outputChannels, outBuf, &outSamples);
if (!result && (codec == PSP_CODEC_AT3PLUS || codec == PSP_CODEC_AT3)) {
// What the hardware reports for a frame that doesn't decode (0x208 is also possible for
+9 -3
View File
@@ -420,9 +420,10 @@ static int CalculateMp3SamplesPerFrame(int versionBits, int layerBits) {
static int FindMp3Header(AuCtx *ctx, int &header, int end) {
u32 addr = ctx->AuBuf + ctx->AuStreamWorkareaSize();
if (Memory::IsValidRange(addr, end)) {
// A header is four bytes, read at each offset.
if (end >= 4 && Memory::IsValidRange(addr, end)) {
const u8 *ptr = Memory::GetPointerUnchecked(addr);
for (int offset = 0; offset < end; ++offset) {
for (int offset = 0; offset + 4 <= end; ++offset) {
// If we hit valid sync bits, then we've found a header.
if (ptr[offset] == 0xFF && (ptr[offset + 1] & 0xC0) == 0xC0) {
header = bswap32(Memory::ReadUnchecked_U32(addr + offset));
@@ -758,12 +759,17 @@ static u32 sceMp3LowLevelDecode(u32 mp3, u32 sourceAddr, u32 sourceBytesConsumed
return hleLogError(Log::ME, -1, "invalid address in args");
}
// A frame is at most 1152 stereo samples, and the decoder is handed up to 4096 bytes.
if (!Memory::IsValidRange(samplesAddr, 1152 * 2 * 2) || !Memory::IsValidRange(sourceBytesConsumedAddr, 4) || !Memory::IsValidRange(sampleBytesAddr, 4)) {
return hleLogError(Log::ME, -1, "invalid address in args");
}
const u8 *inbuff = Memory::GetPointerWriteUnchecked(sourceAddr);
int16_t *outbuf = (int16_t *)Memory::GetPointerWriteUnchecked(samplesAddr);
const int inBytes = (int)Memory::ClampValidSizeAt(sourceAddr, 4096);
int outSamples = 0;
int inbytesConsumed = 0;
if (!ctx->decoder->Decode(inbuff, 4096, &inbytesConsumed, 2, outbuf, &outSamples)) {
if (!ctx->decoder->Decode(inbuff, inBytes, &inbytesConsumed, 2, outbuf, &outSamples)) {
WARN_LOG(Log::ME, "sceMp3LowLevelDecode: Decode failed");
}
int outBytes = outSamples * sizeof(int16_t) * 2;
+10 -3
View File
@@ -295,9 +295,15 @@ public:
// the unwrapped count.
const u64 epMapBytes = EP_MAP_STRIDE * (u64)psmf->EPMapEntriesNum;
if (psmf->headerOffset != 0) {
if (epMapBytes > 0xFFFFFFFFull - psmf->EPMapOffset || !Memory::IsValidRange(psmf->headerOffset, psmf->EPMapOffset + (u32)epMapBytes)) {
// The entries are read from data + EPMapOffset, so the map has to start inside the
// address space (the sum used to wrap around to a valid address) and be mapped.
const u64 epMapAddr = (u64)psmf->headerOffset + psmf->EPMapOffset;
if (epMapAddr > 0xFFFFFFFFull || !Memory::IsValidAddress((u32)epMapAddr)) {
ERROR_LOG(Log::ME, "Invalid PSMF EP map offset: %08x", psmf->EPMapOffset);
psmf->EPMapEntriesNum = 0;
} else if (epMapBytes > 0xFFFFFFFFull - psmf->EPMapOffset || !Memory::IsValidRange(psmf->headerOffset, psmf->EPMapOffset + (u32)epMapBytes)) {
ERROR_LOG(Log::ME, "Invalid PSMF EP map entry count: %d", psmf->EPMapEntriesNum);
psmf->EPMapEntriesNum = Memory::ClampValidSizeAt(psmf->headerOffset + psmf->EPMapOffset, (u32)epMapBytes) / EP_MAP_STRIDE;
psmf->EPMapEntriesNum = Memory::ClampValidSizeAt((u32)epMapAddr, (u32)epMapBytes) / EP_MAP_STRIDE;
}
} else {
// No guest address to validate against (player tempbuf path,
@@ -770,7 +776,8 @@ static void DelayPsmfStateChange(u32 psmfPlayer, u32 newState, s64 delayUs) {
}
static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData) {
if (!Memory::IsValidAddress(psmfStruct) || !Memory::IsValidAddress(psmfData)) {
// The header fields Psmf reads go up to 0x82.
if (!Memory::IsValidAddress(psmfStruct) || !Memory::IsValidRange(psmfData, 0x82)) {
// Crashes on a PSP.
return hleReportError(Log::ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad address");
}