Files
Emanuele f7f88a4886 fix(libkernel): wire a remapped locked range again on macOS
- mlock on macOS skipped every page the locked ranges recorded, but a
  mapping that replaces a locked range in place leaves the record behind
  without wires, so a later mlock returned 0 and wired nothing. Within a
  recorded range it now wires again the host regions whose wire count is
  0. The mlock test locks, remaps the range fixed and locks again.
- munlock and munlockall on macOS hold the locked-range lock across the
  host unwire and the record update, so a concurrent mlock cannot see a
  range as wired while it is being unwired.
- sceKernelVirtualQuery limits the host region it reports to the write
  watch run that holds the address, so the permissions it reports hold
  for the whole extent.
- The libc debt entry says what the mapping paths do: they register the
  range again, and GuestWriteWatchProtectionChanged has no caller yet.
2026-10-27 08:16:36 +01:00

189 lines
6.5 KiB
C++

#include "prx/libkernel/include/Api.hpp"
#include "prx/libc/include/Api.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <limits>
#include <stdexcept>
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif
#if defined(__linux__)
#include <fstream>
#include <string>
#endif
#ifdef __APPLE__
#include <mach/mach.h>
#include <mach/mach_vm.h>
#endif
static void Require(bool value) { if (!value) std::abort(); }
static void RequireLockAllInvalid(int flags) {
*__error_nid_postfix() = 0;
Require(mlockall_nid_postfix(flags) == -1 && *__error_nid_postfix() == 22);
}
static void RequireLockAllUnsupported(int flags) {
bool rejected = false;
try {
mlockall_nid_postfix(flags);
} catch (const std::runtime_error&) {
rejected = true;
}
Require(rejected);
}
#if defined(__linux__) && !defined(ANYPS5_ENABLE_ASAN) && !defined(ANYPS5_ENABLE_TSAN)
#define HAS_LOCKED_KILOBYTES
static std::size_t LockedKilobytes() {
std::ifstream status("/proc/self/status");
std::string line;
while (std::getline(status, line)) {
if (line.rfind("VmLck:", 0) == 0) return std::strtoull(line.c_str() + 6, nullptr, 10);
}
return 0;
}
#endif
#ifdef __APPLE__
static unsigned WiredCount(const void* page) {
mach_vm_address_t address = reinterpret_cast<mach_vm_address_t>(page);
mach_vm_size_t size = 0;
vm_region_basic_info_data_64_t info{};
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
mach_port_t object = MACH_PORT_NULL;
Require(mach_vm_region(mach_task_self(), &address, &size, VM_REGION_BASIC_INFO_64, reinterpret_cast<vm_region_info_t>(&info), &count, &object) == KERN_SUCCESS);
Require(address <= reinterpret_cast<mach_vm_address_t>(page));
return info.user_wired_count;
}
#endif
int main() {
constexpr int untouched = 12345;
constexpr std::size_t page = 0x4000;
#ifdef _WIN32
constexpr std::size_t length = 0x400000;
#else
constexpr std::size_t length = 0x10000;
#endif
void* mapped = nullptr;
Require(sceKernelMapFlexibleMemory(&mapped, length, 3, 0) == 0);
auto* bytes = static_cast<unsigned char*>(mapped);
*__error_nid_postfix() = untouched;
Require(mlock_nid_postfix(mapped, 0) == 0);
Require(*__error_nid_postfix() == untouched);
#ifdef HAS_LOCKED_KILOBYTES
const auto lockedBefore = LockedKilobytes();
#endif
Require(mlock_nid_postfix(bytes + 1, length - page) == 0);
#ifdef HAS_LOCKED_KILOBYTES
Require(LockedKilobytes() - lockedBefore == length / 1024);
#endif
Require(mlock_nid_postfix(mapped, length) == 0);
Require(mlock_nid_postfix(mapped, length) == 0);
#ifdef __APPLE__
Require(WiredCount(mapped) == 1);
void* remapped = mapped;
Require(sceKernelMapFlexibleMemory(&remapped, length, 3, 0x10) == 0 && remapped == mapped);
Require(WiredCount(mapped) == 0);
Require(mlock_nid_postfix(mapped, length) == 0);
Require(WiredCount(mapped) == 1);
#endif
bytes[length - 1] = 7;
*__error_nid_postfix() = untouched;
Require(munlock_nid_postfix(mapped, 0) == 0);
Require(*__error_nid_postfix() == untouched);
Require(munlock_nid_postfix(bytes + 1, length - page) == 0);
#ifdef HAS_LOCKED_KILOBYTES
Require(LockedKilobytes() == lockedBefore);
#elif defined(_WIN32)
Require(!VirtualUnlock(mapped, length) && GetLastError() == ERROR_NOT_LOCKED);
#endif
*__error_nid_postfix() = untouched;
Require(munlock_nid_postfix(mapped, length) == 0);
Require(*__error_nid_postfix() == untouched);
#ifdef __APPLE__
Require(WiredCount(mapped) == 0);
#endif
bytes[0] = 7;
Require(mlock_nid_postfix(mapped, page) == 0);
#ifdef HAS_LOCKED_KILOBYTES
Require(LockedKilobytes() - lockedBefore == page / 1024);
#endif
Require(munlock_nid_postfix(mapped, length) == 0);
#ifdef HAS_LOCKED_KILOBYTES
Require(LockedKilobytes() == lockedBefore);
#elif defined(_WIN32)
Require(!VirtualUnlock(mapped, page) && GetLastError() == ERROR_NOT_LOCKED);
#endif
Require(mlock_nid_postfix(mapped, page) == 0);
Require(munlock_nid_postfix(mapped, page) == 0);
#ifdef HAS_LOCKED_KILOBYTES
Require(LockedKilobytes() == lockedBefore);
#elif defined(_WIN32)
Require(!VirtualUnlock(mapped, page) && GetLastError() == ERROR_NOT_LOCKED);
#endif
Require(mlock_nid_postfix(mapped, page) == 0);
#ifdef HAS_LOCKED_KILOBYTES
Require(LockedKilobytes() - lockedBefore == page / 1024);
#endif
Require(mlock_nid_postfix(mapped, page) == 0);
*__error_nid_postfix() = untouched;
Require(munlockall_nid_postfix() == 0);
Require(*__error_nid_postfix() == untouched);
#ifdef HAS_LOCKED_KILOBYTES
Require(LockedKilobytes() == lockedBefore);
#elif defined(__APPLE__)
Require(WiredCount(mapped) == 0);
#elif defined(_WIN32)
Require(!VirtualUnlock(mapped, page) && GetLastError() == ERROR_NOT_LOCKED);
#endif
*__error_nid_postfix() = 0;
Require(mlock_nid_postfix(reinterpret_cast<void*>(std::numeric_limits<std::uintptr_t>::max() - page + 1), page * 2) == -1);
Require(*__error_nid_postfix() == 22);
*__error_nid_postfix() = 0;
Require(munlock_nid_postfix(reinterpret_cast<void*>(std::numeric_limits<std::uintptr_t>::max() - page + 1), page * 2) == -1);
Require(*__error_nid_postfix() == 22);
void* reserved = nullptr;
Require(sceKernelReserveVirtualRange(&reserved, page, 0, 0) == 0);
#if !defined(ANYPS5_ENABLE_ASAN) && !defined(ANYPS5_ENABLE_TSAN)
*__error_nid_postfix() = 0;
Require(mlock_nid_postfix(reserved, page) == -1 && *__error_nid_postfix() == 12);
#endif
*__error_nid_postfix() = untouched;
Require(munlock_nid_postfix(reserved, page) == 0);
Require(*__error_nid_postfix() == untouched);
Require(sceKernelMunmap(reinterpret_cast<std::uint64_t>(reserved), page) == 0);
Require(sceKernelMunmap(reinterpret_cast<std::uint64_t>(mapped), length) == 0);
#if !defined(ANYPS5_ENABLE_ASAN) && !defined(ANYPS5_ENABLE_TSAN)
*__error_nid_postfix() = 0;
Require(mlock_nid_postfix(mapped, page) == -1 && *__error_nid_postfix() == 12);
*__error_nid_postfix() = 0;
Require(munlock_nid_postfix(mapped, page) == -1 && *__error_nid_postfix() == 12);
#endif
RequireLockAllInvalid(0);
RequireLockAllInvalid(4);
RequireLockAllInvalid(1 | 4);
RequireLockAllInvalid(-1);
RequireLockAllUnsupported(1);
RequireLockAllUnsupported(2);
RequireLockAllUnsupported(1 | 2);
*__error_nid_postfix() = untouched;
Require(munlockall_nid_postfix() == 0);
Require(*__error_nid_postfix() == untouched);
return 0;
}