mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-13 09:39:33 +01:00
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:
1 parent
5b3ddcc062
commit
b1ffcde9fc
5 files changed
+60
-26
No files matched your search
+16
-9
@@ -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
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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");
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user