diff --git a/core/libs/prx/libkernel/Apr/src/Apr.cpp b/core/libs/prx/libkernel/Apr/src/Apr.cpp index cd3393056..8e307d014 100644 --- a/core/libs/prx/libkernel/Apr/src/Apr.cpp +++ b/core/libs/prx/libkernel/Apr/src/Apr.cpp @@ -248,6 +248,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; @@ -545,14 +548,15 @@ std::optional _execute(const Apr::CommandBufferObject& buffer, std::ui 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: @@ -598,8 +602,7 @@ std::optional _execute(const Apr::CommandBufferObject& buffer, std::ui 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 6e7904c0b..d2a646ac1 100644 --- a/core/libs/tests/GuestAmpr.cpp +++ b/core/libs/tests/GuestAmpr.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include @@ -633,6 +635,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"; { @@ -1110,6 +1146,7 @@ int main() { TestConstructed(); TestZeroFilledBuffer(); TestResolveFailures(); + TestMalformedCommandSizes(); TestGatherScatter(); TestAmmRangesAboveHintedMaps(); TestAmm();