feat(libkernel): guest memory protection and VirtualQuery on macOS hosts

- mprotect: the guest rounds a range to its 16 KiB pages. A Mach-O image
  is loaded at a multiple of the host page size only (4 KiB under
  Rosetta), so the pages are counted from where the ELF address 0 of the
  image holding the range was loaded, and a rounded edge smaller than a
  guest page that lies in the gap between two segments, where no host page
  exists, is left out. The write watch is told the new protection.
- sceKernelVirtualQuery reads the region with mach_vm_region.
- sceKernelMunlockall unlocks the ranges libkernel locked, one by one:
  macOS has no munlockall, which fails there with ENOSYS and would
  overwrite errno.
- The stack protect test reads page protections with mach_vm_region.

This is mugurc's f66a4df741 from his macOS series, without the mlock
check that #10889 adds.

Co-authored-by: mugurc <[email protected]>
This commit is contained in:
Emanueleandmugurc authored and Adrià Franch committed 2026-10-27 08:16:35 +01:00
1 parent 81267b2204
commit 190ed6c8fb
6 files changed
+99 -6

No files matched your search

@@ -13,6 +13,10 @@
#define NOMINMAX
#endif
#include <windows.h>
#elif defined(__APPLE__)
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <sys/mman.h>
#else
#include <sys/mman.h>
#include <sys/resource.h>
@@ -39,6 +43,16 @@ static PageState QueryPage(std::uintptr_t address) {
Require(VirtualQuery(reinterpret_cast<const void*>(address), &memory, sizeof(memory)) == sizeof(memory));
const bool mapped = memory.State == MEM_COMMIT;
return {mapped, mapped && (memory.Protect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE)) != 0, (static_cast<std::uint64_t>(memory.State) << 32) | memory.Protect};
#elif defined(__APPLE__)
mach_vm_address_t region = address;
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;
if (mach_vm_region(mach_task_self(), &region, &size, VM_REGION_BASIC_INFO_64, reinterpret_cast<vm_region_info_t>(&info), &count, &object) != KERN_SUCCESS || region > address) return {false, false, 0};
const bool readable = (info.protection & VM_PROT_READ) != 0;
const bool writable = (info.protection & VM_PROT_WRITE) != 0;
return {true, readable && writable, 1ull | (readable ? 2ull : 0) | (writable ? 4ull : 0) | ((info.protection & VM_PROT_EXECUTE) != 0 ? 8ull : 0)};
#else
std::FILE* maps = std::fopen("/proc/self/maps", "r");
Require(maps != nullptr);