diff --git a/Core/HLE/AtracCtx2.cpp b/Core/HLE/AtracCtx2.cpp index ca84d9e32c..32f353971c 100644 --- a/Core/HLE/AtracCtx2.cpp +++ b/Core/HLE/AtracCtx2.cpp @@ -201,6 +201,10 @@ static int ComputeAtracStateAndInitSecondBuffer(SceAtracIdInfo *info, u32 readSi } int InitContextFromTrackInfo(SceAtracContext *ctx, const TrackInfo *wave, u32 bufferAddr, int readSize, int bufferSize) { + // The frame size comes from the file, and InitLengthAndLoop divides by it. + if (wave->blockAlign <= 0) { + return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS; + } (ctx->info).numChan = (char)wave->numChans; const int extraSamples = (ctx->info).codec == 0x1001 ? 0x45 : 0x170; (ctx->info).firstValidSample = extraSamples + wave->firstSampleOffset; @@ -1020,6 +1024,9 @@ int Atrac2::SetData(const Track &track, u32 bufferAddr, u32 readSize, u32 buffer CreateDecoder(info.codec, info.sampleSize, info.numChan, jointStereo_); outputChannels_ = outputChannels; + // Sized for the old frame size and channel count. + delete[] decodeTemp_; + decodeTemp_ = nullptr; INFO_LOG(Log::Atrac, "Atrac: sampleSize: %d buffer: %08x bufferByte: %d firstValidSample: %d\n" @@ -1150,6 +1157,8 @@ void Atrac2::InitLowLevel(const Atrac3LowLevelParams ¶ms, int codecType) { info.numChan = params.encodedChannels; outputChannels_ = params.outputChannels; info.sampleSize = params.bytesPerFrame; + delete[] decodeTemp_; + decodeTemp_ = nullptr; info.dataOff = 0; info.decodePos = 0; info.state = ATRAC_STATUS_LOW_LEVEL; @@ -1255,13 +1264,21 @@ void Atrac2::DecodeForSas(s16 *dstData, int *bytesWritten, int *finish) { return; } - // Compute the part sizes using the current size. + // Compute the part sizes using the current size. The game can shrink the buffer under us, + // so keep both parts inside the assembly buffer. int part1Size = sas_.bufSize[sas_.curBuffer] - sas_.streamOffset; - int part2Size = info.sampleSize - part1Size; _dbg_assert_(part1Size >= 0); - if (part1Size >= 0) { + part1Size = std::clamp(part1Size, 0, (int)info.sampleSize); + int part2Size = info.sampleSize - part1Size; + if (part1Size > 0) { // Grab the partial packet, before we switch over to the other buffer. - Memory::Memcpy(assembly, sas_.bufPtr[sas_.curBuffer] + sas_.streamOffset, part1Size); + const u32 part1Addr = sas_.bufPtr[sas_.curBuffer] + sas_.streamOffset; + if (!Memory::IsValidRange(part1Addr, part1Size)) { + ERROR_LOG(Log::Atrac, "SAS: bad buffer %08x", part1Addr); + *finish = 1; + return; + } + Memory::MemcpyUnchecked(assembly, part1Addr, part1Size); } // Check if we hit the end. diff --git a/Core/HLE/sceAtrac.cpp b/Core/HLE/sceAtrac.cpp index 05aeb9060a..dd414c63a8 100644 --- a/Core/HLE/sceAtrac.cpp +++ b/Core/HLE/sceAtrac.cpp @@ -37,6 +37,7 @@ #include "Core/HLE/sceKernelInterrupt.h" #include "Core/HLE/scePower.h" #include "Core/HLE/sceAtrac.h" +#include "Core/HLE/sceSas.h" #include "Core/HLE/sceAudiocodec.h" #include "Core/HLE/AtracCtx.h" #include "Core/HLE/AtracCtx2.h" @@ -333,6 +334,8 @@ static int AllocAndRegisterAtrac(int codecType) { static int UnregisterAndDeleteAtrac(int atracID) { if (atracID >= 0 && atracID < PSP_MAX_ATRAC_IDS) { if (atracContexts[atracID] != nullptr) { + // The SAS thread might be decoding from it. + __SasWaitForMix(); delete atracContexts[atracID]; atracContexts[atracID] = nullptr; return 0; @@ -776,6 +779,8 @@ static u32 sceAtracSetHalfwayBuffer(int atracID, u32 buffer, u32 readSize, u32 b return hleLogError(Log::Atrac, SCE_ERROR_ATRAC_WRONG_CODECTYPE, "atracID uses different codec type than data"); } + // SetData replaces the decoder, which the SAS thread might be using. + __SasWaitForMix(); ret = atrac->SetData(track, buffer, readSize, bufferSize, 0, 2, false); if (ret < 0) { return AtracSetDataError(ret, track); @@ -811,6 +816,8 @@ static u32 sceAtracSetData(int atracID, u32 buffer, u32 bufferSize) { return hleLogError(Log::Atrac, SCE_ERROR_ATRAC_WRONG_CODECTYPE, "atracID uses different codec type than data"); } + // SetData replaces the decoder, which the SAS thread might be using. + __SasWaitForMix(); ret = atrac->SetData(track, buffer, bufferSize, bufferSize, 0, 2, false); if (ret < 0) { return AtracSetDataError(ret, track); @@ -988,6 +995,8 @@ static int sceAtracSetMOutHalfwayBuffer(int atracID, u32 buffer, u32 readSize, u return hleLogError(Log::Atrac, ret, "%s", error.c_str()); } + // SetData replaces the decoder, which the SAS thread might be using. + __SasWaitForMix(); ret = atrac->SetData(track, buffer, readSize, bufferSize, 0, 1, false); if (ret < 0 && ret != SCE_ERROR_ATRAC_NOT_MONO) { return AtracSetDataError(ret, track); @@ -1012,6 +1021,8 @@ static u32 sceAtracSetMOutData(int atracID, u32 buffer, u32 bufferSize) { return hleLogError(Log::Atrac, ret, "%s", error.c_str()); } + // SetData replaces the decoder, which the SAS thread might be using. + __SasWaitForMix(); ret = atrac->SetData(track, buffer, bufferSize, bufferSize, 0, 1, false); if (ret < 0 && ret != SCE_ERROR_ATRAC_NOT_MONO) { return AtracSetDataError(ret, track); @@ -1225,8 +1236,8 @@ void AtracSasDecodeData(int atracID, u8* outbuf, int *SamplesNum, int *finish) { // The context pointer is assumed to be valid. int AtracSasBindContextAndGetID(u32 contextAddr) { // Ugly hack, but needed to support both old and new contexts. - int atracID = (int)Memory::ReadUnchecked_U32(contextAddr + 0xfc); - if (atracID < PSP_MAX_ATRAC_IDS && atracContexts[atracID] && atracContexts[atracID]->GetContextVersion() == 1) { + int atracID = Memory::IsValid4AlignedAddress(contextAddr + 0xfc) ? (int)Memory::ReadUnchecked_U32(contextAddr + 0xfc) : -1; + if (atracID >= 0 && atracID < PSP_MAX_ATRAC_IDS && atracContexts[atracID] && atracContexts[atracID]->GetContextVersion() == 1) { // We can assume the old atracID hack was used, and atracID is valid. } else { // Let's just loop around the contexts and find it by address. @@ -1244,6 +1255,10 @@ int AtracSasBindContextAndGetID(u32 contextAddr) { } AtracBase *atrac = getAtrac(atracID); + if (!atrac) { + // The SAS voice treats a negative ID as no data. + return -1; + } atrac->CheckForSas(); return atracID; } diff --git a/Core/HLE/sceJpeg.cpp b/Core/HLE/sceJpeg.cpp index ac0f941b7d..494950156a 100644 --- a/Core/HLE/sceJpeg.cpp +++ b/Core/HLE/sceJpeg.cpp @@ -287,7 +287,9 @@ static int DecodeJpeg(u32 jpegAddr, int jpegSize, u32 imageAddr, int &usec) { usec += (width * height) / 14; - if (!Memory::IsValidRange(imageAddr, mjpegWidth * mjpegHeight * 4)) { + // CreateMJpeg doesn't bound the height, so this could overflow in 32 bits. + const u64 outSize = (u64)std::max(mjpegWidth, 0) * (u64)std::max(mjpegHeight, 0) * 4; + if (outSize > 0x3FFFFFFF || !Memory::IsValidRange(imageAddr, (u32)outSize)) { free(jpegBuf); return hleLogError(Log::ME, SCE_KERNEL_ERROR_INVALID_POINTER, "invalid output address"); } diff --git a/Core/HLE/sceMpeg.cpp b/Core/HLE/sceMpeg.cpp index a0625cfcc3..2784f788b9 100644 --- a/Core/HLE/sceMpeg.cpp +++ b/Core/HLE/sceMpeg.cpp @@ -16,6 +16,7 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. // This code is part shamelessly "inspired" from JPSCP. +#include #include #include @@ -274,6 +275,12 @@ static u32 getMpegVersion(u32 mpegRawVersion) { } } +// The game controls both fields, so keep the media engine's buffers to what a savestate can hold. +static int RingbufferStreamSize(const SceMpegRingBuffer *ringbuffer) { + const s64 size = (s64)ringbuffer->packets * ringbuffer->packetSize; + return (int)std::clamp(size, 0, MediaEngine::MAX_RINGBUFFER_SIZE); +} + static void AnalyzeMpeg(const u8 *buffer, u32 validSize, MpegContext *ctx) { ctx->mpegMagic = *(u32_le*)buffer; ctx->mpegRawVersion = *(u32_le*)(buffer + PSMF_STREAM_VERSION_OFFSET); @@ -309,7 +316,7 @@ static void AnalyzeMpeg(const u8 *buffer, u32 validSize, MpegContext *ctx) { // init mediaEngine auto ringbuffer = PSPPointer::Create(ctx->mpegRingbufferAddr); if (ringbuffer.IsValid()) { - ctx->mediaengine->loadStream(buffer, ctx->mpegOffset, ringbuffer->packets * ringbuffer->packetSize); + ctx->mediaengine->loadStream(buffer, ctx->mpegOffset, RingbufferStreamSize(ringbuffer)); } else { // TODO: Does this make any sense? ctx->mediaengine->loadStream(buffer, ctx->mpegOffset, 0); @@ -592,6 +599,8 @@ static int sceMpegDelete(u32 mpeg) { delete ctx; g_mpegCtxs.erase(Memory::ReadUnchecked_U32(mpeg)); + // A new context at the same address needs its PMP codec set up again. + pmp_ContextList.remove(mpeg); return hleDelayResult(hleLogDebug(Log::Mpeg, 0), "mpeg delete", 40000); } @@ -1456,6 +1465,12 @@ static int sceMpegRingbufferAvailableSize(u32 ringbufferAddr) { void PostPutAction::run(MipsCall &call) { auto ringbuffer = PSPPointer::Create(ringAddr_); + // The callback could have changed it. + if (!ringbuffer.IsValid() || (s32)ringbuffer->packets <= 0) { + ERROR_LOG(Log::Mpeg, "sceMpegRingbufferPut: bad ringbuffer %08x", ringAddr_); + call.setReturnValue(SCE_MPEG_ERROR_INVALID_VALUE); + return; + } MpegContext *ctx = getMpegCtx(ringbuffer->mpeg); int writeOffset = ringbuffer->packetsWritePos % (s32)ringbuffer->packets; @@ -1471,13 +1486,19 @@ void PostPutAction::run(MipsCall &call) { return; } + // The count comes from the game's callback, so clamp it before using it for sizes. + if (packetsAddedThisRound > ringbuffer->packets - ringbuffer->packetsAvail) { + WARN_LOG(Log::Mpeg, "sceMpegRingbufferPut clamping packetsAdded old=%i new=%i", packetsAddedThisRound, ringbuffer->packets - ringbuffer->packetsAvail); + packetsAddedThisRound = ringbuffer->packets - ringbuffer->packetsAvail; + } + // It seems validation is done only by older mpeg libs. if (mpegLibVersion < 0x0105 && packetsAddedThisRound > 0) { // TODO: Faster / less wasteful validation. - auto demuxer = std::make_unique(packetsAddedThisRound * 2048, 0); int readOffset = ringbuffer->packetsRead % (s32)ringbuffer->packets; - uint32_t bufSize = Memory::ClampValidSizeAt(ringbuffer->data + readOffset * 2048, packetsAddedThisRound * 2048); - const u8 *buf = Memory::GetPointerOrException(ringbuffer->data + readOffset * 2048); + uint32_t bufSize = Memory::ClampValidSizeAt(ringbuffer->data + readOffset * 2048, (u32)packetsAddedThisRound * 2048); + auto demuxer = std::make_unique(std::max(bufSize, 2048U), 0); + const u8 *buf = Memory::GetPointerUnchecked(ringbuffer->data + readOffset * 2048); bool invalid = false; for (uint32_t i = 0; i < bufSize / 2048; ++i) { demuxer->addStreamData(buf, 2048); @@ -1505,15 +1526,11 @@ void PostPutAction::run(MipsCall &call) { if (ringbuffer->packetsRead == 0 && ctx->mediaengine && packetsAddedThisRound > 0) { // init mediaEngine AnalyzeMpeg(ctx->mpegheader, 2048, ctx); - ctx->mediaengine->loadStream(ctx->mpegheader, 2048, ringbuffer->packets * ringbuffer->packetSize); + ctx->mediaengine->loadStream(ctx->mpegheader, 2048, RingbufferStreamSize(ringbuffer)); } if (packetsAddedThisRound > 0) { - if (packetsAddedThisRound > ringbuffer->packets - ringbuffer->packetsAvail) { - WARN_LOG(Log::Mpeg, "sceMpegRingbufferPut clamping packetsAdded old=%i new=%i", packetsAddedThisRound, ringbuffer->packets - ringbuffer->packetsAvail); - packetsAddedThisRound = ringbuffer->packets - ringbuffer->packetsAvail; - } - const u8 *data = Memory::GetPointerOrException(ringbuffer->data + writeOffset * 2048); - uint32_t dataSize = Memory::ClampValidSizeAt(ringbuffer->data + writeOffset * 2048, packetsAddedThisRound * 2048); + uint32_t dataSize = Memory::ClampValidSizeAt(ringbuffer->data + writeOffset * 2048, (u32)packetsAddedThisRound * 2048); + const u8 *data = dataSize ? Memory::GetPointerUnchecked(ringbuffer->data + writeOffset * 2048) : nullptr; int actuallyAdded = ctx->mediaengine == NULL ? 8 : ctx->mediaengine->addStreamData(data, dataSize) / 2048; if (actuallyAdded != packetsAddedThisRound) { WARN_LOG(Log::Mpeg, "sceMpegRingbufferPut(): unable to enqueue all added packets, going to overwrite some frames (%d != %d)", actuallyAdded, packetsAddedThisRound); @@ -1559,6 +1576,10 @@ static u32 sceMpegRingbufferPut(u32 ringbufferAddr, int numPackets, int availabl return hleLogError(Log::Mpeg, -1, "invalid ringbuffer address"); } + if ((s32)ringbuffer->packets <= 0) { + return hleLogError(Log::Mpeg, SCE_MPEG_ERROR_INVALID_VALUE, "bad ringbuffer packet count"); + } + numPackets = std::min(numPackets, available); // Generally, program will call sceMpegRingbufferAvailableSize() before this func. // Seems still need to check actual available, Patapon 3 for example. diff --git a/Core/HLE/sceMpegbase.cpp b/Core/HLE/sceMpegbase.cpp index 05e8e9c663..58d3c3d715 100644 --- a/Core/HLE/sceMpegbase.cpp +++ b/Core/HLE/sceMpegbase.cpp @@ -595,6 +595,10 @@ static int MpegBaseCscRange(u32 bufferRGB, u32 bufferRGB2, u32 cscAddr, int buff if (bufferWidth == 0) { bufferWidth = g_mpegBaseBufferWidth; } + // The stride goes into the output size and row addresses, so keep that math in range. + if (bufferWidth < 0 || bufferWidth > 4096) { + return hleLogError(Log::Mpeg, -1, "bad buffer width %d", bufferWidth); + } u32 buffers[8]{}; for (int i = 0; i < 8; i++) { @@ -731,6 +735,10 @@ static int sceMpegBaseCscVme(u32 bufferRGB, u32 bufferRGB2, int bufferWidth, u32 if (bufferWidth == 0) { bufferWidth = g_mpegBaseBufferWidth; } + // The stride goes into the output size and row addresses, so keep that math in range. + if (bufferWidth < 0 || bufferWidth > 4096) { + return hleLogError(Log::Mpeg, -1, "bad buffer width %d", bufferWidth); + } auto csc = PSPPointer::Create(cscAddr); u32 buffers[6]; for (int i = 0; i < 6; i++) { diff --git a/Core/HLE/scePsmf.cpp b/Core/HLE/scePsmf.cpp index eec5957a25..f0c8070d27 100644 --- a/Core/HLE/scePsmf.cpp +++ b/Core/HLE/scePsmf.cpp @@ -302,8 +302,9 @@ public: } else { // No guest address to validate against (player tempbuf path, // where the buffer is 64KB): cap so the reads below stay in bounds. - if (epMapBytes > 0x10000) { - psmf->EPMapEntriesNum = 0x10000 / EP_MAP_STRIDE; + const u32 available = psmf->EPMapOffset < 0x10000 ? 0x10000 - psmf->EPMapOffset : 0; + if (epMapBytes > available) { + psmf->EPMapEntriesNum = available / EP_MAP_STRIDE; } } @@ -385,6 +386,10 @@ Psmf::Psmf(const u8 *ptr, u32 data) { if (data != 0 && !Memory::IsValidRange(data, 0x82 + numStreams * 16)) { ERROR_LOG(Log::ME, "Invalid PSMF stream count: %d", numStreams); numStreams = Memory::ClampValidSizeAt(data + 0x82, numStreams * 16) / 16; + } else if (data == 0 && 0x82 + numStreams * 16 > 0x10000) { + // The player's 64KB tempbuf. + ERROR_LOG(Log::ME, "Invalid PSMF stream count: %d", numStreams); + numStreams = (0x10000 - 0x82) / 16; } for (int i = 0; i < numStreams; i++) { diff --git a/Core/HW/Atrac3Standalone.cpp b/Core/HW/Atrac3Standalone.cpp index 91cb6e8453..9ff0186d21 100644 --- a/Core/HW/Atrac3Standalone.cpp +++ b/Core/HW/Atrac3Standalone.cpp @@ -1,3 +1,5 @@ +#include + #include "SimpleAudioDec.h" #include "Common/LogReporting.h" #include "ext/at3_standalone/at3_decoders.h" @@ -16,10 +18,11 @@ inline int16_t clamp16(float f) { class Atrac3Audio : public AudioDecoder { public: Atrac3Audio(PSPAudioType audioType, int channels, size_t blockAlign, const uint8_t *extraData, size_t extraDataSize) - : audioType_(audioType), channels_(channels) { + // The channel count can come straight from the game, and buffers_ only has room for two. + : audioType_(audioType), channels_(std::clamp(channels, 1, 2)) { blockAlign_ = (int)blockAlign; if (audioType_ == PSP_CODEC_AT3) { - at3Ctx_ = atrac3_alloc(channels, &blockAlign_, extraData, (int)extraDataSize); + at3Ctx_ = atrac3_alloc(channels_, &blockAlign_, extraData, (int)extraDataSize); if (at3Ctx_) { codecOpen_ = true; } else { diff --git a/Core/HW/MediaEngine.cpp b/Core/HW/MediaEngine.cpp index c2055c69fa..272ac94ee4 100644 --- a/Core/HW/MediaEngine.cpp +++ b/Core/HW/MediaEngine.cpp @@ -180,7 +180,7 @@ void MediaEngine::DoState(PointerWrap &p) { // All straight from the file. The header ones index m_mpegheader, the ringbuffer size sizes // buffers (the PSP's whole RAM is a generous limit.) if (p.mode == p.MODE_READ && (m_mpegheaderSize < 0 || m_mpegheaderSize > (int)sizeof(m_mpegheader) || - m_mpegheaderReadPos < 0 || m_mpegheaderReadPos > m_mpegheaderSize || m_ringbuffersize < 0 || m_ringbuffersize > 64 * 1024 * 1024)) { + m_mpegheaderReadPos < 0 || m_mpegheaderReadPos > m_mpegheaderSize || m_ringbuffersize < 0 || m_ringbuffersize > MAX_RINGBUFFER_SIZE)) { ERROR_LOG(Log::SaveState, "MediaEngine: bad header size %d, position %d or ringbuffer size %d in savestate", m_mpegheaderSize, m_mpegheaderReadPos, m_ringbuffersize); p.SetError(p.ERROR_FAILURE); return; @@ -479,7 +479,7 @@ int MediaEngine::addStreamData(const u8 *buffer, int addSize) { if (!m_pFormatCtx && m_pdata->getQueueSize() >= 2048) { m_mpegheaderSize = m_pdata->get_front(m_mpegheader, sizeof(m_mpegheader)); int streamOffset = (int)(*(s32_be *)(m_mpegheader + 8)); - if (streamOffset <= m_mpegheaderSize) { + if (streamOffset >= 0 && streamOffset <= m_mpegheaderSize) { m_mpegheaderSize = streamOffset; m_pdata->pop_front(0, m_mpegheaderSize); openContext(); @@ -1000,6 +1000,9 @@ int MediaEngine::writeVideoImageWithRange(u32 bufferPtr, int frameWidth, int vid width = frameWidth; if (height > m_desHeight - ypos) height = m_desHeight - ypos; + // A range starting outside the frame clamps to nothing (or less). + if (xpos < 0 || ypos < 0 || width <= 0 || height <= 0) + return 0; switch (videoPixelMode) { case GE_CMODE_32BIT_ABGR8888: diff --git a/Core/HW/MediaEngine.h b/Core/HW/MediaEngine.h index 6798364d4b..04ee8adbab 100644 --- a/Core/HW/MediaEngine.h +++ b/Core/HW/MediaEngine.h @@ -52,6 +52,9 @@ bool InitFFmpeg(); class MediaEngine { public: + // Also the most DoState accepts. + static constexpr int MAX_RINGBUFFER_SIZE = 64 * 1024 * 1024; + MediaEngine(); ~MediaEngine(); diff --git a/Core/HW/MpegDemux.cpp b/Core/HW/MpegDemux.cpp index 2a88aae6af..c70667702a 100644 --- a/Core/HW/MpegDemux.cpp +++ b/Core/HW/MpegDemux.cpp @@ -26,7 +26,8 @@ MpegDemux::MpegDemux(int size, int offset) : m_audioStream(size) { m_buf = new u8[size]; m_len = size; - m_index = offset; + // The offset comes from the stream header. + m_index = std::clamp(offset, 0, size); m_audioChannel = -1; m_readSize = 0; } @@ -162,7 +163,9 @@ int MpegDemux::demuxStream(bool bdemux, int startCode, int length, int channel) length = readPesHeader(pesHeader, length, startCode); if (pesHeader.channel == channel || channel < 0) { channel = pesHeader.channel; - m_audioStream.push(m_buf + m_index, length, pesHeader.pts); + // The header lengths come from the stream, so don't push past what's been read. + const int pushLength = std::clamp(length, 0, std::max(m_readSize - m_index, 0)); + m_audioStream.push(m_buf + m_index, pushLength, pesHeader.pts); } skip(length); } else { diff --git a/Core/HW/MpegDemux.h b/Core/HW/MpegDemux.h index 61f3fd4ee3..39dfaea49a 100644 --- a/Core/HW/MpegDemux.h +++ b/Core/HW/MpegDemux.h @@ -46,22 +46,30 @@ private: // only cross-checked against the outer packet length, not against how much // data is actually available - a crafted stream can claim more than that, // so guard the actual buffer access here rather than in every caller. - if (m_index >= m_len) { + if (m_index < 0 || m_index >= m_len) { return 0; } return m_buf[m_index++]; } + // The operands of | aren't sequenced, so read into locals to get the byte order right. int read16() { - return (read8() << 8) | read8(); + const int hi = read8(); + const int lo = read8(); + return (hi << 8) | lo; } int read24() { - return (read8() << 16) | (read8() << 8) | read8(); + const int hi = read8(); + const int mid = read8(); + const int lo = read8(); + return (hi << 16) | (mid << 8) | lo; } s64 readPts() { return readPts(read8()); } s64 readPts(int c) { - return (((s64) (c & 0x0E)) << 29) | ((read16() >> 1) << 15) | (read16() >> 1); + const int hi = read16(); + const int lo = read16(); + return (((s64) (c & 0x0E)) << 29) | ((hi >> 1) << 15) | (lo >> 1); } bool isEOF() const { return m_index >= m_readSize;