diff --git a/3rdparty/LibAtrac9 b/3rdparty/LibAtrac9 index ec8899dad..946e05a92 160000 --- a/3rdparty/LibAtrac9 +++ b/3rdparty/LibAtrac9 @@ -1 +1 @@ -Subproject commit ec8899dadf393f655f2871a94e0fe4b3d6220c9a +Subproject commit 946e05a9212976626a9f5e52f29c0a7202871f29 diff --git a/core/libs/prx/libSceAjm.native/src/Ajm.cpp b/core/libs/prx/libSceAjm.native/src/Ajm.cpp index 65c6748e1..48fbaf898 100644 --- a/core/libs/prx/libSceAjm.native/src/Ajm.cpp +++ b/core/libs/prx/libSceAjm.native/src/Ajm.cpp @@ -359,9 +359,9 @@ void RunAt9(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, c } int used = 0; switch (encoding) { - case 0: decodeStatus = Atrac9Decode(instance.decoder, input.data() + consumed, reinterpret_cast(pcm.data()), &used, 0); break; - case 1: decodeStatus = Atrac9DecodeS32(instance.decoder, input.data() + consumed, reinterpret_cast(pcm.data()), &used, 0); break; - default: decodeStatus = Atrac9DecodeF32(instance.decoder, input.data() + consumed, reinterpret_cast(pcm.data()), &used, 0); break; + case 0: decodeStatus = Atrac9Decode(instance.decoder, input.data() + consumed, static_cast(instance.superframeRemaining), reinterpret_cast(pcm.data()), &used, 0); break; + case 1: decodeStatus = Atrac9DecodeS32(instance.decoder, input.data() + consumed, static_cast(instance.superframeRemaining), reinterpret_cast(pcm.data()), &used, 0); break; + default: decodeStatus = Atrac9DecodeF32(instance.decoder, input.data() + consumed, static_cast(instance.superframeRemaining), reinterpret_cast(pcm.data()), &used, 0); break; } if (decodeStatus != 0 || used <= 0 || static_cast(used) > instance.superframeRemaining) { result |= AJM_RESULT_INVALID_DATA; diff --git a/core/libs/prx/libSceAudiodec/src/Codecs.cpp b/core/libs/prx/libSceAudiodec/src/Codecs.cpp index 36d021763..dfa428f9d 100644 --- a/core/libs/prx/libSceAudiodec/src/Codecs.cpp +++ b/core/libs/prx/libSceAudiodec/src/Codecs.cpp @@ -162,8 +162,8 @@ public: for (int frame = 0; frame < info.framesInSuperframe; ++frame) { int used = 0; const int status = wordSize == WORD_SIZE_FLOAT - ? Atrac9DecodeF32(handle, data + offset, reinterpret_cast(pcm + result.produced), &used, 0) - : Atrac9Decode(handle, data + offset, reinterpret_cast(pcm + result.produced), &used, 0); + ? Atrac9DecodeF32(handle, data + offset, static_cast(superframeSize - offset), reinterpret_cast(pcm + result.produced), &used, 0) + : Atrac9Decode(handle, data + offset, static_cast(superframeSize - offset), reinterpret_cast(pcm + result.produced), &used, 0); if (status != 0 || used <= 0 || offset + static_cast(used) > superframeSize) { Reset(); return {DecodeStatus::InvalidData}; diff --git a/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp b/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp index e9b180347..3a59cf433 100644 --- a/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp @@ -81,7 +81,7 @@ static void DecodeSuperframe(Ngs2Voice& voice, Ngs2Block& block) { std::size_t consumed = 0; for (std::uint32_t frame = 0; frame < atrac9.framesInSuperframe; frame++, end += frameValues) { int used = 0; - const int status = Atrac9DecodeF32(atrac9.decoder.get(), superframe + consumed, atrac9.window.data() + end, &used, 0); + const int status = Atrac9DecodeF32(atrac9.decoder.get(), superframe + consumed, static_cast(atrac9.superframeBytes - consumed), atrac9.window.data() + end, &used, 0); consumed += static_cast(std::max(used, 0)); if (status != 0 || consumed > atrac9.superframeBytes) throw std::runtime_error("NGS2: ATRAC9 decode failed with " + Ngs2Hex(static_cast(status))); } diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Atrac9.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Atrac9.cpp index 0026cf7d8..739d8ae3c 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Atrac9.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Atrac9.cpp @@ -4,8 +4,20 @@ #include #include +#include +#include #include +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#include +#endif + static constexpr std::uint32_t Config = 0xFE7007F0; static constexpr std::uint32_t SuperframeBytes = 256; static constexpr std::uint32_t SuperframeSamples = 1024; @@ -37,7 +49,7 @@ static std::vector Reference() { int offset = 0; for (std::uint32_t frame = 0; frame < 4; frame++) { int used = 0; - Require(Atrac9DecodeF32(decoder, Superframe + offset, pcm.data() + frame * 256, &used, 0) == 0); + Require(Atrac9DecodeF32(decoder, Superframe + offset, static_cast(SuperframeBytes) - offset, pcm.data() + frame * 256, &used, 0) == 0); offset += used; } Atrac9ReleaseHandle(decoder); @@ -178,11 +190,52 @@ static void TestCalcBlock() { Require(sceNgs2CalcWaveformBlock(&silent, 0, 1, &block) == SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT); } +static std::uint8_t* SuperframeBeforeGuardPage() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + const std::size_t page = info.dwPageSize; + auto* region = static_cast(VirtualAlloc(nullptr, 2 * page, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + DWORD previous = 0; + Require(region != nullptr && VirtualProtect(region + page, page, PAGE_NOACCESS, &previous) != 0); +#else + const auto page = static_cast(sysconf(_SC_PAGESIZE)); + void* mapping = mmap(nullptr, 2 * page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(mapping != MAP_FAILED); + auto* region = static_cast(mapping); + Require(mprotect(region + page, page, PROT_NONE) == 0); +#endif + auto* superframe = region + page - SuperframeBytes; + std::mt19937 random(50); + for (std::uint32_t i = 0; i < SuperframeBytes; i++) superframe[i] = static_cast(random()); + return superframe; +} + +static void TestCorruptSuperframeAtPageEnd() { + const auto* superframe = SuperframeBeforeGuardPage(); + const auto system = CreateSystem(); + const auto voice = Voice(CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER)); + Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP, Ngs2SamplerVoiceSetupParam{{}, {SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 1, 48000, Config, 0, 0}}); + const Ngs2WaveformBlock block{0, SuperframeBytes, 0, 0, SuperframeSamples, 0, 0}; + Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, superframe, 0, 1, &block}); + Event(voice, SCE_NGS2_VOICE_EVENT_PLAY); + Patch(voice, Mastering(system, 1)); + bool rejected = false; + try { + Render(system, Grain); + } catch (const std::runtime_error& error) { + rejected = std::strstr(error.what(), "ATRAC9 decode failed") != nullptr; + } + Require(rejected); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + int main() { const auto reference = Reference(); TestSkipAndBlockEnd(reference); TestRepeatAndState(reference); TestParse(); TestCalcBlock(); + TestCorruptSuperframeAtPageEnd(); return 0; }