fix(audio): bound ATRAC9 decoding to the superframe
LibAtrac9 read its bitstream without a length, so a corrupt ATRAC9 frame
made the decoder read past the end of the guest's buffer. Update the
submodule to 946e05a ("Prevent bitstream overruns"), whose decode
functions take the buffer size, and pass the bytes left in the
superframe from NGS2, AJM and Audiodec. The callers already reject a
frame that consumes more than the superframe holds.
ngs2_atrac9 places a synthetic corrupt superframe right before an
inaccessible page and expects the render to report the decode failure;
with the previous LibAtrac9 it crashes with a segmentation fault.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5WJS4ZdQynAJesCZKH5hb
This commit is contained in:
parent
d3ec1f15fb
commit
9879deca69
|
|
@ -1 +1 @@
|
|||
Subproject commit ec8899dadf393f655f2871a94e0fe4b3d6220c9a
|
||||
Subproject commit 946e05a9212976626a9f5e52f29c0a7202871f29
|
||||
|
|
@ -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<short*>(pcm.data()), &used, 0); break;
|
||||
case 1: decodeStatus = Atrac9DecodeS32(instance.decoder, input.data() + consumed, reinterpret_cast<int*>(pcm.data()), &used, 0); break;
|
||||
default: decodeStatus = Atrac9DecodeF32(instance.decoder, input.data() + consumed, reinterpret_cast<float*>(pcm.data()), &used, 0); break;
|
||||
case 0: decodeStatus = Atrac9Decode(instance.decoder, input.data() + consumed, static_cast<int>(instance.superframeRemaining), reinterpret_cast<short*>(pcm.data()), &used, 0); break;
|
||||
case 1: decodeStatus = Atrac9DecodeS32(instance.decoder, input.data() + consumed, static_cast<int>(instance.superframeRemaining), reinterpret_cast<int*>(pcm.data()), &used, 0); break;
|
||||
default: decodeStatus = Atrac9DecodeF32(instance.decoder, input.data() + consumed, static_cast<int>(instance.superframeRemaining), reinterpret_cast<float*>(pcm.data()), &used, 0); break;
|
||||
}
|
||||
if (decodeStatus != 0 || used <= 0 || static_cast<std::uint32_t>(used) > instance.superframeRemaining) {
|
||||
result |= AJM_RESULT_INVALID_DATA;
|
||||
|
|
|
|||
|
|
@ -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<float*>(pcm + result.produced), &used, 0)
|
||||
: Atrac9Decode(handle, data + offset, reinterpret_cast<short*>(pcm + result.produced), &used, 0);
|
||||
? Atrac9DecodeF32(handle, data + offset, static_cast<int>(superframeSize - offset), reinterpret_cast<float*>(pcm + result.produced), &used, 0)
|
||||
: Atrac9Decode(handle, data + offset, static_cast<int>(superframeSize - offset), reinterpret_cast<short*>(pcm + result.produced), &used, 0);
|
||||
if (status != 0 || used <= 0 || offset + static_cast<std::size_t>(used) > superframeSize) {
|
||||
Reset();
|
||||
return {DecodeStatus::InvalidData};
|
||||
|
|
|
|||
|
|
@ -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<int>(atrac9.superframeBytes - consumed), atrac9.window.data() + end, &used, 0);
|
||||
consumed += static_cast<std::size_t>(std::max(used, 0));
|
||||
if (status != 0 || consumed > atrac9.superframeBytes) throw std::runtime_error("NGS2: ATRAC9 decode failed with " + Ngs2Hex(static_cast<std::uint32_t>(status)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,20 @@
|
|||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <random>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#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<float> 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<int>(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<std::uint8_t*>(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<std::size_t>(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<std::uint8_t*>(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<std::uint8_t>(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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue