mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 08:33:12 -05:00
The allocation tracker threw a runtime_error (a hole in the protected range) from inside sceKernelMprotect, which terminated the title. mprotect already turned the exception into EINVAL. sceKernelMprotect and sceKernelMtypeprotect now check that the range is registered first and return EINVAL; the retype of direct mappings is skipped on failure. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
83 lines
4.2 KiB
C++
83 lines
4.2 KiB
C++
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include "prx/libkernel/KernelErrors.hpp"
|
|
#include "SceTypes.hpp"
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <exception>
|
|
|
|
extern "C" {
|
|
void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t) noexcept;
|
|
int APS5_VABI munmap_nid_postfix(void*, std::size_t) noexcept;
|
|
int APS5_VABI mprotect_nid_postfix(void*, std::size_t, int) noexcept;
|
|
int APS5_VABI sceKernelMprotect(const void*, std::size_t, int);
|
|
int APS5_VABI sceKernelMtypeprotect(const void*, std::size_t, int, int);
|
|
int APS5_VABI sceKernelVirtualQuery(const void*, int, VirtualQueryInfo*, std::uint64_t);
|
|
int* APS5_VABI __error_nid_postfix();
|
|
}
|
|
|
|
namespace {
|
|
|
|
constexpr std::size_t Page = 0x4000;
|
|
constexpr int ErrorInvalid = 22;
|
|
constexpr int ProtectRead = 1;
|
|
constexpr int ProtectReadWrite = 3;
|
|
|
|
void Require(bool condition, const char* what) {
|
|
if (condition) return;
|
|
std::fprintf(stderr, "%s\n", what);
|
|
std::abort();
|
|
}
|
|
|
|
int Protect(const void* address, std::size_t bytes, int protection) {
|
|
try {
|
|
return sceKernelMprotect(address, bytes, protection);
|
|
} catch (const std::exception& error) {
|
|
std::fprintf(stderr, "sceKernelMprotect threw: %s\n", error.what());
|
|
std::abort();
|
|
}
|
|
}
|
|
|
|
int ProtectionAt(const unsigned char* address) {
|
|
VirtualQueryInfo info{};
|
|
Require(sceKernelVirtualQuery(address, 0, &info, sizeof(info)) == 0, "virtual query of a mapped page failed");
|
|
return info.protection;
|
|
}
|
|
|
|
unsigned char* Map(std::size_t pages) {
|
|
void* raw = mmap_nid_postfix(nullptr, Page * pages, ProtectReadWrite, 0x1002, -1, 0);
|
|
Require(raw != reinterpret_cast<void*>(static_cast<std::uintptr_t>(-1)), "mmap failed");
|
|
return static_cast<unsigned char*>(raw);
|
|
}
|
|
|
|
}
|
|
|
|
int main() {
|
|
unsigned char* memory = Map(3);
|
|
Require(munmap_nid_postfix(memory + Page, Page) == 0, "munmap of the middle page failed");
|
|
|
|
Require(Protect(memory, Page * 3, ProtectRead) == 0, "a range with a gap in the middle must succeed for its mapped parts");
|
|
Require(ProtectionAt(memory) == ProtectRead && ProtectionAt(memory + Page * 2) == ProtectRead, "the mapped parts on both sides of the gap must be protected");
|
|
Require(mprotect_nid_postfix(memory, Page * 3, ProtectReadWrite) == 0, "mprotect over a range with a gap must succeed for its mapped parts");
|
|
Require(ProtectionAt(memory) == ProtectReadWrite && ProtectionAt(memory + Page * 2) == ProtectReadWrite, "mprotect must change the mapped parts on both sides of the gap");
|
|
Require(sceKernelMtypeprotect(memory, Page * 3, 0, ProtectRead) == 0, "sceKernelMtypeprotect over a range with a gap must succeed for its mapped parts");
|
|
|
|
Require(Protect(memory + Page - 8, 16, ProtectReadWrite) == 0, "an unaligned range that reaches into the unmapped page protects the mapped page");
|
|
Require(Protect(memory + 100, Page - 200, ProtectReadWrite) == 0, "an unaligned range inside the first page must work");
|
|
|
|
Require(Protect(memory + Page, Page, ProtectRead) == SCE_KERNEL_ERROR_EINVAL, "a range that is completely unmapped must return EINVAL");
|
|
Require(Protect(memory + Page * 2, Page, ProtectReadWrite) == 0, "protecting the last page alone must work");
|
|
Require(Protect(memory + Page, Page * 2, ProtectRead) == SCE_KERNEL_ERROR_EINVAL, "a range whose start is unmapped must return EINVAL even when later pages are mapped");
|
|
Require(ProtectionAt(memory + Page * 2) == ProtectReadWrite, "a refused range must not change the mapped pages behind its start");
|
|
Require(mprotect_nid_postfix(memory + Page, Page, ProtectRead) == -1 && *__error_nid_postfix() == ErrorInvalid, "mprotect over a range that is completely unmapped must fail with EINVAL (22)");
|
|
|
|
Require(munmap_nid_postfix(memory, Page) == 0 && munmap_nid_postfix(memory + Page * 2, Page) == 0, "munmap of the remaining pages failed");
|
|
|
|
unsigned char* tail = Map(2);
|
|
Require(munmap_nid_postfix(tail + Page, Page) == 0, "munmap of the last page failed");
|
|
Require(Protect(tail, Page * 2, ProtectRead) == 0, "a range that ends in an unmapped page must succeed for its mapped part");
|
|
Require(ProtectionAt(tail) == ProtectRead, "the mapped part before a trailing gap must be protected");
|
|
Require(munmap_nid_postfix(tail, Page) == 0, "munmap of the remaining page failed");
|
|
return 0;
|
|
}
|