fix(kernel): support page-aligned mprotect for loaded images
This commit is contained in:
parent
0b3ffe47b0
commit
2df58cf09c
|
|
@ -9,10 +9,9 @@
|
|||
extern "C" {
|
||||
|
||||
APS5_EXPORT("23LRUSvYu1M", sceAgcInit);
|
||||
int APS5_VABI sceAgcInit(uint32_t* state, uint32_t ver) {
|
||||
(void)state;
|
||||
(void)ver;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
int APS5_VABI sceAgcInit(std::uint32_t* state, std::uint32_t version) {
|
||||
Agc::Command::CheckAddress(reinterpret_cast<std::uintptr_t>(state), alignof(std::uint32_t), __func__);
|
||||
Agc::Command::Require(version < 14, __func__, "unsupported AGC initialization version");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbResetQueue(CommandBuffer* buf, std::uint32_t op, std::uint32_t state);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetFlip(CommandBuffer* buf, std::uint32_t handle, std::int32_t index, std::uint32_t mode, std::int64_t argument);
|
||||
extern "C" int APS5_VABI sceAgcSuspendPoint();
|
||||
extern "C" int APS5_VABI sceAgcInit(std::uint32_t* state, std::uint32_t version);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbDrawIndexAuto(CommandBuffer* buf, std::uint32_t indexCount, std::uint64_t modifier);
|
||||
extern "C" int APS5_VABI sceAgcWaitRegMemPatchReference(std::uint32_t* cmd, std::uint64_t reference);
|
||||
extern "C" int APS5_VABI sceAgcGetDataPacketPayloadAddressUnk(std::uint32_t** addr, std::uint32_t* cmd, int type);
|
||||
|
|
@ -252,6 +253,10 @@ void testMemory() {
|
|||
}
|
||||
|
||||
void testDefaults() {
|
||||
std::uint32_t state = 0x12345678;
|
||||
check(sceAgcInit(&state, 8) == 0 && state == 0x12345678, "AGC initialization failed or modified caller state");
|
||||
expectFailure([] { sceAgcInit(nullptr, 8); });
|
||||
expectFailure([&] { sceAgcInit(&state, 14); });
|
||||
for (std::uint32_t version = 0; version < 14; ++version) {
|
||||
for (const bool internal : {false, true}) {
|
||||
auto* first = Agc::Command::GetRegisterDefaults(version, internal, __func__);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp"
|
||||
#include <cstring>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
@ -75,4 +76,43 @@ void RunGuestAllocationTests() {
|
|||
});
|
||||
}
|
||||
Require(GuestAllocations::GuestAllocationsAcquire_nid_postfix().empty(), "unmapped fragments remain registered");
|
||||
#ifdef _WIN32
|
||||
static std::byte imageProbe{};
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.RegisterMainImage();
|
||||
mutation.RegisterMainImage();
|
||||
}
|
||||
const auto imageAddress = reinterpret_cast<std::uintptr_t>(&imageProbe);
|
||||
std::uint64_t allocationAddress = 0;
|
||||
std::size_t imageRangeCount = 0;
|
||||
{
|
||||
const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix();
|
||||
imageRangeCount = lease.size();
|
||||
const auto found = std::find_if(lease.begin(), lease.end(), [&](const auto& range) { return imageAddress >= range->address && imageAddress - range->address < range->bytes; });
|
||||
Require(found != lease.end() && (*found)->writable && !(*found)->releasable, "main image registration is missing or releasable");
|
||||
allocationAddress = (*found)->allocationAddress;
|
||||
GuestAllocations::Mutation mutation;
|
||||
bool applied = false;
|
||||
reject([&] { mutation.Protect(&imageProbe, 1, true, false, [&] { applied = true; }); });
|
||||
Require(!applied, "pinned image protection changed");
|
||||
}
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
reject([&] { mutation.Find(reinterpret_cast<void*>(allocationAddress)); });
|
||||
reject([&] { mutation.Remove(reinterpret_cast<void*>(allocationAddress)); });
|
||||
bool applied = false;
|
||||
reject([&] { mutation.Unmap(&imageProbe, 1, [&](const void*, bool) { applied = true; }); });
|
||||
Require(!applied, "image memory was unmapped");
|
||||
reject([&] { mutation.Protect(&imageProbe, 1, true, false, [] { throw std::runtime_error("host protection failure"); }); });
|
||||
}
|
||||
Require(GuestAllocations::GuestAllocationsAcquire_nid_postfix().size() == imageRangeCount, "failed image protection changed registry ranges");
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Protect(&imageProbe, 1, true, true, [] {});
|
||||
bool applied = false;
|
||||
reject([&] { mutation.Unmap(&imageProbe, 1, [&](const void*, bool) { applied = true; }); });
|
||||
Require(!applied, "split image memory became releasable");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,12 +17,16 @@ struct Range {
|
|||
bool writable;
|
||||
std::uint64_t allocationAddress;
|
||||
std::size_t allocationBytes;
|
||||
bool releasable = true;
|
||||
};
|
||||
|
||||
using Lease = std::vector<std::shared_ptr<const Range>>;
|
||||
|
||||
extern "C" {
|
||||
void* GuestAllocationsBegin_nid_postfix();
|
||||
#ifdef _WIN32
|
||||
void GuestAllocationsRegisterMainImage_nid_postfix(void* mutation);
|
||||
#endif
|
||||
void GuestAllocationsEnd_nid_postfix(void* mutation) noexcept;
|
||||
void GuestAllocationsAdd_nid_postfix(void* mutation, void* pointer, std::size_t bytes, bool readable, bool writable);
|
||||
void GuestAllocationsRequireUnpinned_nid_postfix(void* mutation, const void* pointer, std::size_t bytes);
|
||||
|
|
@ -40,6 +44,9 @@ public:
|
|||
~Mutation() { GuestAllocationsEnd_nid_postfix(handle); }
|
||||
Mutation(const Mutation&) = delete;
|
||||
Mutation& operator=(const Mutation&) = delete;
|
||||
#ifdef _WIN32
|
||||
void RegisterMainImage() { GuestAllocationsRegisterMainImage_nid_postfix(handle); }
|
||||
#endif
|
||||
void Add(void* pointer, std::size_t bytes, bool readable, bool writable) { GuestAllocationsAdd_nid_postfix(handle, pointer, bytes, readable, writable); }
|
||||
void RequireUnpinned(const void* pointer, std::size_t bytes) const { GuestAllocationsRequireUnpinned_nid_postfix(handle, pointer, bytes); }
|
||||
void RequireAvailable(const void* pointer, std::size_t bytes) const { GuestAllocationsRequireAvailable_nid_postfix(handle, pointer, bytes); }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#include <limits>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace GuestAllocations {
|
||||
namespace {
|
||||
|
|
@ -9,6 +16,7 @@ namespace {
|
|||
struct Registry {
|
||||
std::mutex mutex;
|
||||
std::map<std::uint64_t, std::shared_ptr<const Range>> ranges;
|
||||
bool mainImageRegistered = false;
|
||||
};
|
||||
|
||||
Registry& registry() {
|
||||
|
|
@ -30,6 +38,44 @@ void GuestAllocationsEnd_nid_postfix(void* mutation) noexcept {
|
|||
delete static_cast<std::unique_lock<std::mutex>*>(mutation);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
void GuestAllocationsRegisterMainImage_nid_postfix(void*) {
|
||||
auto& state = registry();
|
||||
if (state.mainImageRegistered) return;
|
||||
const auto image = GetModuleHandleW(nullptr);
|
||||
require(image != nullptr, "cannot locate the main guest image");
|
||||
auto replacement = state.ranges;
|
||||
auto cursor = reinterpret_cast<std::uintptr_t>(image);
|
||||
bool registered = false;
|
||||
for (;;) {
|
||||
MEMORY_BASIC_INFORMATION memory{};
|
||||
require(VirtualQuery(reinterpret_cast<const void*>(cursor), &memory, sizeof(memory)) == sizeof(memory), "cannot query the main guest image");
|
||||
if (memory.AllocationBase != image) break;
|
||||
require(memory.Type == MEM_IMAGE && (memory.State == MEM_COMMIT || memory.State == MEM_RESERVE), "unsupported guest image mapping");
|
||||
require(reinterpret_cast<std::uintptr_t>(memory.BaseAddress) == cursor && memory.RegionSize != 0 && memory.RegionSize <= std::numeric_limits<std::uintptr_t>::max() - cursor, "invalid guest image range");
|
||||
if (memory.State == MEM_COMMIT) {
|
||||
require((memory.Protect & PAGE_GUARD) == 0, "guarded guest image pages are not supported");
|
||||
const auto protection = memory.Protect & 0xffu;
|
||||
const bool writable = protection == PAGE_READWRITE || protection == PAGE_WRITECOPY || protection == PAGE_EXECUTE_READWRITE || protection == PAGE_EXECUTE_WRITECOPY;
|
||||
const bool readable = writable || protection == PAGE_READONLY || protection == PAGE_EXECUTE_READ;
|
||||
require(readable || protection == PAGE_NOACCESS || protection == PAGE_EXECUTE, "unsupported guest image protection");
|
||||
const auto next = replacement.lower_bound(cursor);
|
||||
require(next == replacement.end() || cursor + memory.RegionSize <= next->first, "guest image overlaps a registered allocation");
|
||||
if (next != replacement.begin()) {
|
||||
const auto& previous = *std::prev(next)->second;
|
||||
require(previous.address + previous.bytes <= cursor, "guest image overlaps a registered allocation");
|
||||
}
|
||||
replacement.emplace(cursor, std::make_shared<const Range>(Range{cursor, memory.RegionSize, readable, writable, cursor, memory.RegionSize, false}));
|
||||
registered = true;
|
||||
}
|
||||
cursor += memory.RegionSize;
|
||||
}
|
||||
require(registered, "main guest image has no committed pages");
|
||||
state.ranges.swap(replacement);
|
||||
state.mainImageRegistered = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void GuestAllocationsAdd_nid_postfix(void*, void* pointer, std::size_t bytes, bool readable, bool writable) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(pointer);
|
||||
require(address != 0 && bytes <= std::numeric_limits<std::uint64_t>::max() - address, "invalid guest allocation range");
|
||||
|
|
@ -66,7 +112,10 @@ void GuestAllocationsRequireAvailable_nid_postfix(void*, const void* pointer, st
|
|||
Range GuestAllocationsFind_nid_postfix(void*, const void* pointer) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(pointer);
|
||||
for (const auto& [base, range] : registry().ranges) {
|
||||
if (range->allocationAddress == address) return {address, range->allocationBytes, range->readable, range->writable, address, range->allocationBytes};
|
||||
if (range->allocationAddress == address) {
|
||||
require(range->releasable, "guest image memory is not a releasable allocation");
|
||||
return {address, range->allocationBytes, range->readable, range->writable, address, range->allocationBytes, range->releasable};
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("guest allocation is not registered");
|
||||
}
|
||||
|
|
@ -94,7 +143,7 @@ std::map<std::uint64_t, std::shared_ptr<const Range>> replaceRange(const void* p
|
|||
require(base <= cursor, "guest protection or unmap range has a hole");
|
||||
replacement.erase(base);
|
||||
const auto insert = [&](std::uint64_t first, std::uint64_t last, bool canRead, bool canWrite) {
|
||||
if (first < last) replacement.emplace(first, std::make_shared<const Range>(Range{first, static_cast<std::size_t>(last - first), canRead, canWrite, range.allocationAddress, range.allocationBytes}));
|
||||
if (first < last) replacement.emplace(first, std::make_shared<const Range>(Range{first, static_cast<std::size_t>(last - first), canRead, canWrite, range.allocationAddress, range.allocationBytes, range.releasable}));
|
||||
};
|
||||
insert(base, std::max(base, address), range.readable, range.writable);
|
||||
if (!remove) insert(std::max(base, address), std::min(finish, end), readable, writable);
|
||||
|
|
@ -120,6 +169,7 @@ void GuestAllocationsUnmap_nid_postfix(void* mutation, const void* pointer, std:
|
|||
const auto found = registry().ranges.upper_bound(address);
|
||||
require(found != registry().ranges.begin(), "unmap address is not registered");
|
||||
const auto& range = *std::prev(found)->second;
|
||||
require(range.releasable, "guest image memory cannot be unmapped");
|
||||
require(address >= range.address && address - range.allocationAddress <= range.allocationBytes && bytes <= range.allocationBytes - (address - range.allocationAddress), "unmap crosses allocation boundaries");
|
||||
auto replacement = replaceRange(pointer, bytes, true, false, false);
|
||||
bool last = true;
|
||||
|
|
|
|||
|
|
@ -201,11 +201,26 @@ int DoMapAnon(void** addr, size_t len, int prot, int flags) {
|
|||
}
|
||||
|
||||
int DoMprotect(const void* addr, size_t len, int prot) {
|
||||
if (len == 0 || (len & (PS5_PAGE_SIZE - 1)) != 0 || !addr) return SCE_KERNEL_ERROR_EINVAL;
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(addr);
|
||||
constexpr auto pageMask = static_cast<std::uintptr_t>(PS5_PAGE_SIZE - 1);
|
||||
const auto limit = std::numeric_limits<std::uintptr_t>::max();
|
||||
if (address == 0 || len == 0 || len > limit - address || address + len > limit - pageMask) throw std::invalid_argument("Invalid guest memory protection range");
|
||||
const auto first = address & ~pageMask;
|
||||
const auto end = (address + len + pageMask) & ~pageMask;
|
||||
const auto bytes = static_cast<std::size_t>(end - first);
|
||||
const auto* pointer = reinterpret_cast<const void*>(first);
|
||||
const auto nativeProtection = LinuxProtFromSce(prot);
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Protect(addr, len, (prot & 3) != 0, (prot & 2) != 0, [&] {
|
||||
if (mprotect(const_cast<void*>(addr), len, nativeProtection) != 0) throw std::system_error(errno, std::generic_category(), "mprotect failed");
|
||||
#ifdef _WIN32
|
||||
MEMORY_BASIC_INFORMATION memory{};
|
||||
if (VirtualQuery(pointer, &memory, sizeof(memory)) != sizeof(memory)) throw std::runtime_error("Cannot query guest memory protection range");
|
||||
if (memory.Type == MEM_IMAGE) {
|
||||
if (memory.AllocationBase != GetModuleHandleW(nullptr)) throw std::invalid_argument("Memory protection of a foreign image is not supported");
|
||||
mutation.RegisterMainImage();
|
||||
}
|
||||
#endif
|
||||
mutation.Protect(pointer, bytes, (prot & 3) != 0, (prot & 2) != 0, [&] {
|
||||
if (mprotect(const_cast<void*>(pointer), bytes, nativeProtection) != 0) throw std::system_error(errno, std::generic_category(), "mprotect failed");
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue