mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-28 12:05:06 +00:00
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. - sceKernelMlock fails with ENOMEM when the range holds an inaccessible page: macOS wires such pages, which FreeBSD and Linux refuse. - 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 commit is contained in:
1 parent
47a266cf97
commit
f66a4df741
6 files changed
+122
-6
No files matched your search
@@ -60,6 +60,8 @@ void GuestAllocationsAdd_nid_postfix(void* mutation, void* pointer, std::size_t
|
||||
void GuestAllocationsRequireUnpinned_nid_postfix(void* mutation, const void* pointer, std::size_t bytes);
|
||||
void GuestAllocationsRequireAvailable_nid_postfix(void* mutation, const void* pointer, std::size_t bytes);
|
||||
bool GuestAllocationsCovers_nid_postfix(void* mutation, const void* pointer, std::size_t bytes);
|
||||
void GuestAllocationsTrimToRegistered_nid_postfix(void* mutation, const void** pointer, std::size_t* bytes);
|
||||
void GuestAllocationsGuestPages_nid_postfix(std::uintptr_t address, std::size_t bytes, std::size_t page, std::uintptr_t* first, std::uintptr_t* end);
|
||||
bool GuestAllocationsOverlaps_nid_postfix(void* mutation, const void* pointer, std::size_t bytes);
|
||||
bool GuestAllocationsForRegistered_nid_postfix(void* mutation, const void* pointer, std::size_t bytes, const std::function<void(const void*, std::size_t)>& visit);
|
||||
Range GuestAllocationsFind_nid_postfix(void* mutation, const void* pointer);
|
||||
@@ -99,6 +101,7 @@ public:
|
||||
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); }
|
||||
bool Covers(const void* pointer, std::size_t bytes) const { return GuestAllocationsCovers_nid_postfix(handle, pointer, bytes); }
|
||||
void TrimToRegistered(const void*& pointer, std::size_t& bytes) const { GuestAllocationsTrimToRegistered_nid_postfix(handle, &pointer, &bytes); }
|
||||
bool Overlaps(const void* pointer, std::size_t bytes) const { return GuestAllocationsOverlaps_nid_postfix(handle, pointer, bytes); }
|
||||
bool ForRegistered(const void* pointer, std::size_t bytes, const std::function<void(const void*, std::size_t)>& visit) const { return GuestAllocationsForRegistered_nid_postfix(handle, pointer, bytes, visit); }
|
||||
Range Find(const void* pointer) const { return GuestAllocationsFind_nid_postfix(handle, pointer); }
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include "prx/libc/include/specifics/linux/ElfTypes.hpp"
|
||||
#include <mach-o/dyld.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
@@ -439,6 +444,54 @@ bool GuestAllocationsForRegistered_nid_postfix(void*, const void* pointer, std::
|
||||
return any;
|
||||
}
|
||||
|
||||
void GuestAllocationsGuestPages_nid_postfix(std::uintptr_t address, std::size_t bytes, std::size_t page, std::uintptr_t* first, std::uintptr_t* end) {
|
||||
std::uintptr_t bias = 0;
|
||||
#ifdef __APPLE__
|
||||
struct Search {
|
||||
std::uintptr_t address;
|
||||
std::uintptr_t bias;
|
||||
} search{address, 0};
|
||||
dl_iterate_phdr([](dl_phdr_info* image, std::size_t, void* data) {
|
||||
auto& found = *static_cast<Search*>(data);
|
||||
for (unsigned index = 0; index < image->dlpi_phnum; ++index) {
|
||||
const auto& header = image->dlpi_phdr[index];
|
||||
const auto start = static_cast<std::uintptr_t>(image->dlpi_addr + header.p_vaddr);
|
||||
if (header.p_type == PT_LOAD && found.address >= start && found.address - start < header.p_memsz) {
|
||||
found.bias = static_cast<std::uintptr_t>(image->dlpi_addr);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}, &search);
|
||||
bias = search.bias;
|
||||
#endif
|
||||
const auto mask = static_cast<std::uintptr_t>(page - 1);
|
||||
*first = bias + ((address - bias) & ~mask);
|
||||
*end = bias + ((address + bytes - bias + mask) & ~mask);
|
||||
}
|
||||
|
||||
void GuestAllocationsTrimToRegistered_nid_postfix(void*, const void** pointer, std::size_t* bytes) {
|
||||
#ifdef __APPLE__
|
||||
constexpr std::uint64_t guestPage = 0x4000;
|
||||
auto start = reinterpret_cast<std::uintptr_t>(*pointer);
|
||||
auto end = start + *bytes;
|
||||
const auto& ranges = registry().ranges;
|
||||
const auto following = ranges.upper_bound(start);
|
||||
const bool startCovered = following != ranges.begin() && std::prev(following)->first + std::prev(following)->second->bytes > start;
|
||||
if (!startCovered && following != ranges.end() && following->first < end && following->first - start < guestPage) start = following->first;
|
||||
const auto beyond = ranges.lower_bound(end);
|
||||
if (beyond != ranges.begin()) {
|
||||
const auto finish = std::prev(beyond)->first + std::prev(beyond)->second->bytes;
|
||||
if (finish < end && finish > start && end - finish < guestPage) end = finish;
|
||||
}
|
||||
*pointer = reinterpret_cast<const void*>(start);
|
||||
*bytes = end - start;
|
||||
#else
|
||||
static_cast<void>(pointer);
|
||||
static_cast<void>(bytes);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool GuestAllocationsCovers_nid_postfix(void*, const void* pointer, std::size_t bytes) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(pointer);
|
||||
require(address != 0 && bytes != 0 && bytes <= std::numeric_limits<std::uint64_t>::max() - address, "invalid guest allocation range");
|
||||
|
||||
@@ -1079,10 +1079,13 @@ int DoMprotect(const void* addr, size_t len, int prot) {
|
||||
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);
|
||||
auto first = address & ~pageMask;
|
||||
auto end = (address + len + pageMask) & ~pageMask;
|
||||
#ifdef __APPLE__
|
||||
GuestAllocations::GuestAllocationsGuestPages_nid_postfix(address, len, PS5_PAGE_SIZE, &first, &end);
|
||||
#endif
|
||||
auto bytes = static_cast<std::size_t>(end - first);
|
||||
const void* pointer = reinterpret_cast<const void*>(first);
|
||||
const auto nativeProtection = LinuxProtFromSce(prot);
|
||||
if (GuestThreadStackHostRangeOverlaps(address, address + len))
|
||||
throw std::system_error(EACCES, std::generic_category(), "mprotect overlaps host-only guest thread stack pages");
|
||||
@@ -1120,14 +1123,18 @@ int DoMprotect(const void* addr, size_t len, int prot) {
|
||||
if (!image.guestModule) throw std::invalid_argument("Memory protection of a foreign image is not supported");
|
||||
mutation.RegisterImage(reinterpret_cast<const void*>(image.base));
|
||||
}
|
||||
mutation.TrimToRegistered(pointer, bytes);
|
||||
#endif
|
||||
std::vector<std::pair<const void*, std::size_t>> spans;
|
||||
mutation.ForRegistered(pointer, bytes, [&](const void* span, std::size_t spanBytes) { spans.emplace_back(span, spanBytes); });
|
||||
if (spans.empty() || reinterpret_cast<std::uintptr_t>(spans.front().first) != first) return SCE_KERNEL_ERROR_EINVAL;
|
||||
if (spans.empty() || spans.front().first != pointer) return SCE_KERNEL_ERROR_EINVAL;
|
||||
for (const auto& [span, spanBytes] : spans) mutation.RequireUnpinned(span, spanBytes);
|
||||
for (const auto& [span, spanBytes] : spans) {
|
||||
mutation.Protect(span, spanBytes, (prot & 3) != 0, (prot & 2) != 0, (prot & GuestProtGpuReadWrite) != 0, [&] {
|
||||
if (mprotect(const_cast<void*>(span), spanBytes, nativeProtection) != 0) throw std::system_error(errno, std::generic_category(), "mprotect failed");
|
||||
#ifdef __APPLE__
|
||||
GuestWriteWatch::GuestWriteWatchProtectionChanged_nid_postfix(span, spanBytes, nativeProtection);
|
||||
#endif
|
||||
});
|
||||
RecordProtection(span, spanBytes, prot);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
#include <sstream>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/resource.h>
|
||||
#ifdef __APPLE__
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_vm.h>
|
||||
#endif
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
@@ -342,6 +346,17 @@ int APS5_VABI sceKernelVirtualQuery(const void* addr, int flags, VirtualQueryInf
|
||||
info->end = info->start + host.RegionSize;
|
||||
const bool writable = (host.Protect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_WRITECOPY)) != 0;
|
||||
const bool executable = (host.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) != 0;
|
||||
#elif defined(__APPLE__)
|
||||
mach_vm_address_t region = address;
|
||||
mach_vm_size_t regionSize = 0;
|
||||
vm_region_basic_info_data_64_t host{};
|
||||
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(), ®ion, ®ionSize, VM_REGION_BASIC_INFO_64, reinterpret_cast<vm_region_info_t>(&host), &count, &object) != KERN_SUCCESS || region > address || (host.protection & VM_PROT_READ) == 0) return SCE_KERNEL_ERROR_EACCES;
|
||||
info->start = region;
|
||||
info->end = region + regionSize;
|
||||
const bool writable = (host.protection & VM_PROT_WRITE) != 0;
|
||||
const bool executable = (host.protection & VM_PROT_EXECUTE) != 0;
|
||||
#else
|
||||
std::ifstream maps("/proc/self/maps");
|
||||
std::string line;
|
||||
@@ -629,9 +644,28 @@ int UnlockHostPages(std::uintptr_t start, std::uintptr_t end) {
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#ifdef __APPLE__
|
||||
bool HasInaccessiblePages(std::uintptr_t start, std::uintptr_t end) {
|
||||
for (auto address = static_cast<mach_vm_address_t>(start); address < end;) {
|
||||
auto 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(), ®ion, &size, VM_REGION_BASIC_INFO_64, reinterpret_cast<vm_region_info_t>(&info), &count, &object) != KERN_SUCCESS) return true;
|
||||
if (region > address || info.protection == VM_PROT_NONE) return true;
|
||||
address = region + size;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
int LockHostPages(std::uintptr_t start, std::uintptr_t end) {
|
||||
auto* const address = reinterpret_cast<void*>(start);
|
||||
const std::size_t bytes = end - start;
|
||||
#ifdef __APPLE__
|
||||
if (HasInaccessiblePages(start, end)) return SCE_KERNEL_ERROR_ENOMEM;
|
||||
#endif
|
||||
if (::mlock(address, bytes) == 0) {
|
||||
RecordLockedRange(start, end);
|
||||
return 0;
|
||||
@@ -689,7 +723,7 @@ int APS5_VABI sceKernelMunlock_nid_postfix(void* address, std::uint64_t length)
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelMunlockall_nid_postfix() {
|
||||
#ifndef _WIN32
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
::munlockall();
|
||||
#endif
|
||||
std::map<uintptr_t, size_t> ranges;
|
||||
@@ -715,6 +749,10 @@ int APS5_VABI sceKernelMunlockall_nid_postfix() {
|
||||
cursor = next;
|
||||
}
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
const int saved = errno;
|
||||
for (const auto& [start, len] : ranges) ::munlock(reinterpret_cast<void*>(start), len);
|
||||
errno = saved;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,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 <cstdio>
|
||||
#include <sys/mman.h>
|
||||
@@ -38,6 +42,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(), ®ion, &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);
|
||||
|
||||
@@ -176,6 +176,7 @@
|
||||
- [getsockopt, setsockopt](../../../core/libs/prx/libkernel/Socket/src/SocketOptions.cpp) (libkernel) on a guest socket handle the SOL_SOCKET level and `TCP_NODELAY`, after FreeBSD 11.4's [`sosetopt` and `sogetopt`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c): `SO_REUSEADDR`, `SO_KEEPALIVE`, `SO_DONTROUTE`, `SO_BROADCAST`, `SO_OOBINLINE` and `SO_LINGER` act on the host socket, and `SO_ERROR`, `SO_TYPE`, `SO_ACCEPTCONN`, `SO_SNDBUF` and `SO_RCVBUF` are read. A boolean option reads back as its own bit, as FreeBSD returns `so_options & option`. Winsock does not clear a socket's pending error when it is read, so on Windows the library reports a pending error once and shows it again only after a new `connect`; a second failure with the same code on the same socket without a `connect` in between would be hidden. `SO_SNDBUF` and `SO_RCVBUF` read the host's value, which is not the size FreeBSD reports (Linux doubles it). `SO_NOSIGPIPE` is kept per socket and reads back as its bit. On Linux a `send`, `sendto`, `sendmsg` or `write` on that socket passes `MSG_NOSIGNAL` to the host, as [`kern_sendit`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_syscalls.c#L890-L895) and [`soo_write`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/sys_socket.c#L147-L151) leave out the signal. An accepted socket takes it from its listener, as [`sonewconn`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_socket.c#L603) copies the options. `SO_NBIO` (`0x1200`) is the console's own option, not FreeBSD's. It switches the socket between blocking and non-blocking as `FIONBIO` does and reads back as 0 or 1, as `sceNetSetsockopt` and `sceNetGetsockopt` treat it here; what the console reads back for it is not measured. `TCP_NODELAY` (`IPPROTO_TCP`, 1) is set on the host socket and reads back as `TF_NODELAY` (4), as [`tcp_default_ctloutput`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/netinet/tcp_usrreq.c#L1860-L1861) returns it. The `IPPROTO_TCP` level on a datagram or `AF_UNIX` socket fails with `EINVAL`, as [`ip_ctloutput`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/netinet/ip_output.c#L943-L944) and [`uipc_ctloutput`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_usrreq.c#L1228-L1229) answer it, and an accepted socket takes `TCP_NODELAY` through the host. `SO_SNDTIMEO` and `SO_RCVTIMEO` follow [`sosetopt`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_socket.c#L2657-L2689) and [`sogetopt`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_socket.c#L2846-L2850). A 16-byte `timeval` with a negative field or 1000000 microseconds or more fails with `EDOM`, and more than `INT32_MAX` seconds becomes the longest wait. The value is kept and read back through FreeBSD's 32.32 fixed-point conversion ([`tvtosbt`, `sbttotv`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/sys/time.h#L178-L286)), so half a second reads back as 499999 microseconds and one microsecond as 0. A `recv`, `recvfrom`, `recvmsg`, `read` or `readv`, and a `send`, `sendto`, `sendmsg`, `write` or `writev`, on a blocking socket then waits that long for the host to report the socket readable or writable, and fails with `EWOULDBLOCK`; a call with `MSG_DONTWAIT` does not wait. An accepted socket takes both from its listener ([`sonewconn`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_socket.c#L629-L630)). The wait covers only the first transfer: once the socket is writable the host send runs without a limit, where FreeBSD stops a send that cannot finish and returns the bytes sent so far, and a receive whose data another thread takes between the wait and the host call blocks again. `sendfile` ignores the send timeout. The host wait has millisecond steps, so a shorter timeout lasts up to a millisecond longer. A null value fails with `EINVAL`, where FreeBSD's [`kern_setsockopt`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_syscalls.c#L1414-L1415) returns `EFAULT`. Every other SOL_SOCKET option (`SO_DEBUG`, `SO_USELOOPBACK`, `SO_REUSEPORT` and `SO_TIMESTAMP`, which FreeBSD stores and no title has been seen to set, the low-water marks among them), every other `IPPROTO_TCP` option (`TCP_MAXSEG`, `TCP_KEEPIDLE`, `TCP_KEEPINTVL` and `TCP_KEEPCNT` among them) and every other level (`IPPROTO_IP`, `IPPROTO_IPV6`) fail with `ENOPROTOOPT`. None of this was measured on a console.
|
||||
- [connect to AF_UNSPEC](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) dissolves a datagram association as FreeBSD 11.4's `udp_disconnect()` does, which resets the local address to the wildcard address and keeps the port. On Linux a socket bound to an explicit port keeps its local address as well (`127.0.0.1:<port>` stays `127.0.0.1:<port>`), because a bound socket cannot be bound again; one bound to port 0 is bound again to the wildcard address with the port it had
|
||||
- [msync](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) follows FreeBSD 11's `kern_msync` and `vm_map_sync`, not measured on the console: the address is rounded down to the page, a range that wraps is `EINVAL`, as is `MS_ASYNC` with `MS_INVALIDATE`, and a range whose start or any page is not mapped (as `sceKernelVirtualQuery` reports it, reservations included) is `ENOMEM`; a page unmapped from inside a larger mapping still counts as mapped there, since `sceKernelVirtualQuery` keeps reporting the whole mapping. The guest has no file mappings, so a successful call writes nothing back. `MS_INVALIDATE` on a mapped range throws: FreeBSD returns `EBUSY` when the range holds `mlock`ed pages, and locked pages are not tracked
|
||||
- Guest memory protection on macOS ([mprotect](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp), [sceKernelVirtualQuery, sceKernelMlock](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp)): the guest rounds a range to its 16 KiB pages, counted from where the ELF address 0 of the image holding it was loaded, which is a multiple of the host page size only (4 KiB under Rosetta). A rounded edge smaller than a guest page that lies in the gap between two segments of an image, where no host page exists, is left out of the request. `sceKernelVirtualQuery` reports the region `mach_vm_region` returns, so its extent is the host's. `sceKernelMlock` fails with `ENOMEM` when the range holds an inaccessible page, which macOS wires where FreeBSD and Linux refuse. After a protection change the write watch is told the protection, which the host reports for a watched page as its own
|
||||
- [madvise](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) accepts `MADV_PROTECT` (10) as a no-op hint without modifying process protection. FreeBSD 11's `kern_madvise` (`sys/vm/vm_mmap.c`) handles it before the address check, ignores the address and length, and calls `kern_procctl(..., PROC_SPROTECT, PPROT_SET)`, which requires `PRIV_VM_MADV_PROTECT` and fails with `EPERM` without it. Returning 0 unconditionally assumes the calling process is privileged. Console behaviour is unverified
|
||||
- [mincore](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) follows FreeBSD 11's `sys_mincore` (`sys/vm/vm_mmap.c`), not measured on the console: the address is rounded down to the page, length 0 returns 0, a null vector over a mapped range returns `EFAULT`, an overflowing range or any unmapped page in the range (as reported by `sceKernelVirtualQuery`) returns `ENOMEM`. For valid ranges, a page reports `MINCORE_INCORE` (0x1) when `sceKernelVirtualQuery` reports it committed and 0 when it is only reserved; actual residency, referenced and modified bits are not modelled. Length 0 returns 0 when the start page is mapped and `ENOMEM` otherwise, and the range and mapping checks come before the null vector `EFAULT`, as in `kern_mincore` Java 25 LAN / DNS (PPSA99869) imports it
|
||||
- [mlockall, munlockall](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) - `mlockall` returns `EINVAL` for zero or unknown flags. Valid `MCL_CURRENT` throws because it would lock host stacks, heaps, modules and driver mappings along with guest memory; `MCL_FUTURE` throws because later mappings are not tracked for locking. FreeBSD 11.4's `sys_munlockall` (`sys/vm/vm_mmap.c`:1133-1165) checks `priv_check(td, PRIV_VM_MUNLOCK)` first (returning `EPERM` without it) and unwires the user map (`vm_map_unwire` over `vm_map_min`...`vm_map_max` with `VM_MAP_WIRE_HOLESOK`). Returning 0 unconditionally assumes the calling process is privileged. `munlockall` unlocks memory locked by `mlock` (`VirtualUnlock` on Windows, tolerating `ERROR_NOT_LOCKED` for pages never locked, and `munlockall` on Linux). On main, XPSemu (PPSA97358) fails to load at these imports (mlockall #4386, munlockall); their calls in QEMU `os-posix.c` execute only when `-overcommit mem-lock=on` is enabled. The call has not been observed in a run with this implementation.
|
||||
|
||||
Reference in new issue
Block a user