Merge pull request #3808 from MarcZX100/codex/apr-command-size-validation-1930
fix(libkernel): validate APR command payload sizes
This commit is contained in:
commit
4a8efe0699
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue