From 7c2eba0a6d6439a9fbea7b00e6b29c0c7e6396ff Mon Sep 17 00:00:00 2001 From: MarcZX100 Date: Sun, 11 Oct 2026 16:57:06 +0000 Subject: [PATCH] fix(libkernel): validate APR command payload sizes --- core/libs/prx/libkernel/Apr/src/Apr.cpp | 11 +++++--- core/libs/tests/GuestAmpr.cpp | 37 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libkernel/Apr/src/Apr.cpp b/core/libs/prx/libkernel/Apr/src/Apr.cpp index 8aa37a332..756ff4fa8 100644 --- a/core/libs/prx/libkernel/Apr/src/Apr.cpp +++ b/core/libs/prx/libkernel/Apr/src/Apr.cpp @@ -242,6 +242,9 @@ bool _waitSatisfied(std::uint32_t compare, std::uint64_t value, std::uint64_t re template TCommand _read(const Apr::CommandBufferObject& buffer, std::uint32_t cursor) { + Apr::CommandHeader header; + std::memcpy(&header, buffer.base + cursor, sizeof(header)); + if (header.bytes < sizeof(TCommand)) throw std::runtime_error("APR: truncated command payload (opcode " + std::to_string(static_cast(header.opcode)) + ", has " + std::to_string(header.bytes) + ", needs " + std::to_string(sizeof(TCommand)) + ")"); TCommand command; std::memcpy(&command, buffer.base + cursor, sizeof(command)); return command; @@ -532,14 +535,15 @@ void _execute(const Apr::CommandBufferObject& buffer) { std::memcpy(&header, buffer.base + cursor, sizeof(header)); if ((static_cast(header.opcode) & 0xffu) == static_cast(Apr::Opcode::CompactReadFileGather)) { if (sizeof(Apr::CompactReadFileGatherCommand) > buffer.offset - cursor) throw std::runtime_error("APR: truncated compact gather command"); - const auto command = _read(buffer, cursor); + Apr::CompactReadFileGatherCommand command; + std::memcpy(&command, buffer.base + cursor, sizeof(command)); const auto offset = static_cast(command.opcodeAndOffsetHigh >> 8u) << 32u | command.offsetLow; if (offset >= 0x10000000000ull) throw std::runtime_error("APR: malformed compact gather offset"); _readResolved(Apr::Opcode::ReadFileGather, Apr::ReadFileCommand{{}, 0, 0, 0, static_cast(command.sizeMinusOne) + 1u, offset}, read); cursor += sizeof(command); continue; } - if (header.bytes < sizeof(header) || cursor + header.bytes > buffer.offset) throw std::runtime_error("APR: malformed command"); + if (header.bytes < sizeof(header) || header.bytes > buffer.offset - cursor) throw std::runtime_error("APR: malformed command"); switch (header.opcode) { case Apr::Opcode::Nop: case Apr::Opcode::PushMarker: @@ -585,8 +589,7 @@ void _execute(const Apr::CommandBufferObject& buffer) { case Apr::Opcode::MapEnd: break; case Apr::Opcode::WriteAddress: { - Apr::WriteAddressCommand command; - std::memcpy(&command, buffer.base + cursor, sizeof(command)); + const auto command = _read(buffer, cursor); _writeAddress(command); break; } diff --git a/core/libs/tests/GuestAmpr.cpp b/core/libs/tests/GuestAmpr.cpp index 43b223fac..621bfa181 100644 --- a/core/libs/tests/GuestAmpr.cpp +++ b/core/libs/tests/GuestAmpr.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include @@ -579,6 +581,40 @@ void TestResolveFailures() { std::remove(paths[0]); } +void TestMalformedCommandSizes() { + const auto requireFailure = [](Apr::Opcode opcode, std::uint32_t recordBytes, std::uint32_t bufferBytes, const char* expected) { + std::array memory{}; + const Apr::CommandHeader header{opcode, recordBytes}; + std::memcpy(memory.data(), &header, sizeof(header)); + Apr::CommandBufferObject buffer{Apr::BufferType::Generic, 0, bufferBytes, 1, bufferBytes, memory.data()}; + bool rejected = false; + try { + sceKernelAprSubmitCommandBuffer(&buffer, 0); + } catch (const std::runtime_error& error) { + rejected = std::string(error.what()).starts_with(expected); + } + Require(rejected); + }; + + requireFailure(Apr::Opcode::ReadFile, sizeof(Apr::CommandHeader), sizeof(Apr::CommandHeader), "APR: truncated command payload"); + requireFailure(Apr::Opcode::WriteAddress, sizeof(Apr::CommandHeader), sizeof(Apr::CommandHeader), "APR: truncated command payload"); + requireFailure(Apr::Opcode::Nop, std::numeric_limits::max(), 2u * sizeof(Apr::CommandHeader), "APR: malformed command"); + + std::array memory{}; + const Apr::CommandHeader first{Apr::Opcode::Nop, sizeof(Apr::CommandHeader)}; + const Apr::CommandHeader overflowing{Apr::Opcode::Nop, std::numeric_limits::max()}; + std::memcpy(memory.data(), &first, sizeof(first)); + std::memcpy(memory.data() + sizeof(first), &overflowing, sizeof(overflowing)); + Apr::CommandBufferObject buffer{Apr::BufferType::Generic, 0, sizeof(memory), 2, sizeof(memory), memory.data()}; + bool rejected = false; + try { + sceKernelAprSubmitCommandBuffer(&buffer, 0); + } catch (const std::runtime_error& error) { + rejected = std::string(error.what()).starts_with("APR: malformed command"); + } + Require(rejected); +} + void TestGatherScatter() { const char* path = "ampr_gather_scatter.bin"; { @@ -1054,6 +1090,7 @@ int main() { TestConstructed(); TestZeroFilledBuffer(); TestResolveFailures(); + TestMalformedCommandSizes(); TestGatherScatter(); TestAmmRangesAboveHintedMaps(); TestAmm();