Merge pull request #3808 from MarcZX100/codex/apr-command-size-validation-1930

fix(libkernel): validate APR command payload sizes
This commit is contained in:
Adrià 2026-10-12 00:05:34 +02:00 committed by GitHub
commit 4a8efe0699
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 44 additions and 4 deletions

View File

@ -248,6 +248,9 @@ bool _waitSatisfied(std::uint32_t compare, std::uint64_t value, std::uint64_t re
template<class TCommand>
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<std::uint32_t>(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<Deferred> _execute(const Apr::CommandBufferObject& buffer, std::ui
std::memcpy(&header, buffer.base + cursor, sizeof(header));
if ((static_cast<std::uint32_t>(header.opcode) & 0xffu) == static_cast<std::uint32_t>(Apr::Opcode::CompactReadFileGather)) {
if (sizeof(Apr::CompactReadFileGatherCommand) > buffer.offset - cursor) throw std::runtime_error("APR: truncated compact gather command");
const auto command = _read<Apr::CompactReadFileGatherCommand>(buffer, cursor);
Apr::CompactReadFileGatherCommand command;
std::memcpy(&command, buffer.base + cursor, sizeof(command));
const auto offset = static_cast<std::uint64_t>(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<std::uint64_t>(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<Deferred> _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<Apr::WriteAddressCommand>(buffer, cursor);
_writeAddress(command);
break;
}

View File

@ -11,6 +11,8 @@
#include <cstring>
#include <fstream>
#include <future>
#include <limits>
#include <stdexcept>
#include <string>
#include <utility>
@ -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<std::uint8_t, 16> 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<std::uint32_t>::max(), 2u * sizeof(Apr::CommandHeader), "APR: malformed command");
std::array<std::uint8_t, 2 * sizeof(Apr::CommandHeader)> memory{};
const Apr::CommandHeader first{Apr::Opcode::Nop, sizeof(Apr::CommandHeader)};
const Apr::CommandHeader overflowing{Apr::Opcode::Nop, std::numeric_limits<std::uint32_t>::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();