diff --git a/core/libs/prx/libc/include/GuestAllocations.hpp b/core/libs/prx/libc/include/GuestAllocations.hpp index 22f0fa290..998331f60 100644 --- a/core/libs/prx/libc/include/GuestAllocations.hpp +++ b/core/libs/prx/libc/include/GuestAllocations.hpp @@ -25,6 +25,9 @@ using Lease = std::vector>; extern "C" { void* GuestAllocationsBegin_nid_postfix(); void GuestAllocationsRegisterMainImage_nid_postfix(void* mutation); +#ifdef _WIN32 +void GuestAllocationsRegisterImage_nid_postfix(void* mutation, const void* image); +#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); @@ -58,6 +61,9 @@ public: Mutation(const Mutation&) = delete; Mutation& operator=(const Mutation&) = delete; void RegisterMainImage() { GuestAllocationsRegisterMainImage_nid_postfix(handle); } +#ifdef _WIN32 + void RegisterImage(const void* image) { GuestAllocationsRegisterImage_nid_postfix(handle, image); } +#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); } diff --git a/core/libs/prx/libc/src/GuestAllocations.cpp b/core/libs/prx/libc/src/GuestAllocations.cpp index a8afbe1aa..de62091ac 100644 --- a/core/libs/prx/libc/src/GuestAllocations.cpp +++ b/core/libs/prx/libc/src/GuestAllocations.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #ifdef _WIN32 #ifndef NOMINMAX @@ -34,6 +35,7 @@ struct Registry { std::mutex mutex; std::map> ranges; bool mainImageRegistered = false; + std::set registeredImages; }; Registry& registry() { @@ -134,17 +136,16 @@ void GuestAllocationsSetPinWaiter_nid_postfix(bool (*callback)(std::uintptr_t, s } #ifdef _WIN32 -void GuestAllocationsRegisterMainImage_nid_postfix(void*) { +namespace { +void registerImage(const void* image) { auto& state = registry(); - if (state.mainImageRegistered) return; - const auto image = GetModuleHandleW(nullptr); - require(image != nullptr, "cannot locate the main guest image"); + if (state.registeredImages.contains(reinterpret_cast(image))) return; auto replacement = state.ranges; auto cursor = reinterpret_cast(image); bool registered = false; for (;;) { MEMORY_BASIC_INFORMATION memory{}; - require(VirtualQuery(reinterpret_cast(cursor), &memory, sizeof(memory)) == sizeof(memory), "cannot query the main guest image"); + require(VirtualQuery(reinterpret_cast(cursor), &memory, sizeof(memory)) == sizeof(memory), "cannot query a 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(memory.BaseAddress) == cursor && memory.RegionSize != 0 && memory.RegionSize <= std::numeric_limits::max() - cursor, "invalid guest image range"); @@ -165,10 +166,25 @@ void GuestAllocationsRegisterMainImage_nid_postfix(void*) { } cursor += memory.RegionSize; } - require(registered, "main guest image has no committed pages"); + require(registered, "guest image has no committed pages"); state.ranges.swap(replacement); + state.registeredImages.insert(reinterpret_cast(image)); +} +} + +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"); + registerImage(image); state.mainImageRegistered = true; } + +void GuestAllocationsRegisterImage_nid_postfix(void*, const void* image) { + require(image != nullptr, "cannot register a guest image without a base"); + registerImage(image); +} #else void GuestAllocationsRegisterMainImage_nid_postfix(void*) { auto& state = registry(); diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index b139729e2..5146ed74c 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -691,6 +691,10 @@ if(BUILD_TESTING) configure_windows_unwind(guest_memory_tests) add_test(NAME guest_memory COMMAND guest_memory_tests) if(WIN32) + add_library(guest_memory_module SHARED ${LIBS_INCLUDE_DIR}/tests/GuestMemoryModule.cpp) + set_target_properties(guest_memory_module PROPERTIES PREFIX "" SUFFIX ".guest.prx") + add_dependencies(guest_memory_tests guest_memory_module) + target_compile_definitions(guest_memory_tests PRIVATE GUEST_MEMORY_MODULE="$") set_tests_properties(guest_memory PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 7fe75c74f..054080eb6 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -11,9 +11,12 @@ #include #include #include +#include #include #include #include +#include +#include #include #include @@ -777,6 +780,24 @@ int DoMapAnon(void** addr, size_t len, int prot, int flags) { return 0; } +#ifdef _WIN32 +static bool IsGuestModuleImage(const void* base) { + std::wstring path(MAX_PATH, L'\0'); + for (;;) { + const auto length = GetModuleFileNameW(static_cast(const_cast(base)), path.data(), static_cast(path.size())); + if (length == 0) return false; + if (length < path.size()) { + path.resize(length); + break; + } + path.resize(path.size() * 2); + } + constexpr std::wstring_view suffix = L".guest.prx"; + if (path.size() < suffix.size()) return false; + return std::equal(suffix.begin(), suffix.end(), path.end() - static_cast(suffix.size()), [](wchar_t expected, wchar_t actual) { return std::towlower(actual) == expected; }); +} +#endif + int DoMprotect(const void* addr, size_t len, int prot) { Trace("protect %p+0x%zx prot=0x%x", addr, len, prot); const auto address = reinterpret_cast(addr); @@ -793,8 +814,9 @@ int DoMprotect(const void* addr, size_t len, int prot) { 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(); + if (memory.AllocationBase == GetModuleHandleW(nullptr)) mutation.RegisterMainImage(); + else if (IsGuestModuleImage(memory.AllocationBase)) mutation.RegisterImage(memory.AllocationBase); + else throw std::invalid_argument("Memory protection of a foreign image is not supported"); } #else mutation.RegisterMainImage(); diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index 863278120..f9f1caa25 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -534,6 +535,37 @@ static void CheckNoOverwriteRejectsHostOccupiedMapping() { Require(sceKernelReleaseDirectMemory(phys, page) == 0); } +static DWORD ImageProtection(const void* pointer) { + MEMORY_BASIC_INFORMATION memory{}; + Require(VirtualQuery(pointer, &memory, sizeof(memory)) == sizeof(memory)); + return memory.Protect & 0xffu; +} + +static void CheckGuestModuleImageProtection() { + constexpr std::size_t page = 0x4000; + const auto module = LoadLibraryA(GUEST_MEMORY_MODULE); + Require(module != nullptr); + auto* data = reinterpret_cast(GetProcAddress(module, "guestMemoryModuleData")); + Require(data != nullptr); + auto* target = reinterpret_cast((reinterpret_cast(data) + page - 1) & ~(page - 1)); + Require(target + page <= data + 0x10000); + Require(sceKernelMprotect(target, page, 1) == 0); + Require(ImageProtection(target) == PAGE_READONLY); + Require(sceKernelMprotect(target, page, 3) == 0); + Require(ImageProtection(target) == PAGE_READWRITE || ImageProtection(target) == PAGE_WRITECOPY); + target[1] = 7; + Require(target[1] == 7); + Require(sceKernelMprotect(target, page, 1) == 0); + Require(ImageProtection(target) == PAGE_READONLY); + bool rejected = false; + try { + sceKernelMprotect(GetModuleHandleA("kernel32.dll"), page, 1); + } catch (const std::invalid_argument&) { + rejected = true; + } + Require(rejected); +} + static void CheckFixedMappingsReachTheApplicationAreaEnd() { constexpr std::size_t page = 0x4000; constexpr std::uintptr_t applicationAreaEnd = 0xFC00000000ull; @@ -1267,6 +1299,7 @@ int main() { #ifdef _WIN32 CheckNoOverwriteRejectsHostOccupiedMapping(); CheckFixedMappingsReachTheApplicationAreaEnd(); + CheckGuestModuleImageProtection(); #endif CheckSharedWriteTracking(); CheckReadsIntoSharedWriteTracking(); diff --git a/core/libs/tests/GuestMemoryModule.cpp b/core/libs/tests/GuestMemoryModule.cpp new file mode 100644 index 000000000..ed8642ed6 --- /dev/null +++ b/core/libs/tests/GuestMemoryModule.cpp @@ -0,0 +1 @@ +extern "C" unsigned char guestMemoryModuleData[0x10000] = {1};