diff --git a/core/libs/prx/libc/include/GuestAllocations.hpp b/core/libs/prx/libc/include/GuestAllocations.hpp index 998331f60..1d4905451 100644 --- a/core/libs/prx/libc/include/GuestAllocations.hpp +++ b/core/libs/prx/libc/include/GuestAllocations.hpp @@ -27,6 +27,7 @@ void* GuestAllocationsBegin_nid_postfix(); void GuestAllocationsRegisterMainImage_nid_postfix(void* mutation); #ifdef _WIN32 void GuestAllocationsRegisterImage_nid_postfix(void* mutation, const void* image); +void GuestAllocationsUnregisterImage_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); @@ -63,6 +64,7 @@ public: void RegisterMainImage() { GuestAllocationsRegisterMainImage_nid_postfix(handle); } #ifdef _WIN32 void RegisterImage(const void* image) { GuestAllocationsRegisterImage_nid_postfix(handle, image); } + void UnregisterImage(const void* image) { GuestAllocationsUnregisterImage_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); } diff --git a/core/libs/prx/libc/src/GuestAllocations.cpp b/core/libs/prx/libc/src/GuestAllocations.cpp index de62091ac..9de0791ff 100644 --- a/core/libs/prx/libc/src/GuestAllocations.cpp +++ b/core/libs/prx/libc/src/GuestAllocations.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #ifdef _WIN32 #ifndef NOMINMAX @@ -35,7 +34,7 @@ struct Registry { std::mutex mutex; std::map> ranges; bool mainImageRegistered = false; - std::set registeredImages; + std::map registeredImages; }; Registry& registry() { @@ -168,7 +167,7 @@ void registerImage(const void* image) { } require(registered, "guest image has no committed pages"); state.ranges.swap(replacement); - state.registeredImages.insert(reinterpret_cast(image)); + state.registeredImages.emplace(reinterpret_cast(image), cursor); } } @@ -185,6 +184,17 @@ void GuestAllocationsRegisterImage_nid_postfix(void*, const void* image) { require(image != nullptr, "cannot register a guest image without a base"); registerImage(image); } + +void GuestAllocationsUnregisterImage_nid_postfix(void* mutation, const void* image) { + auto& state = registry(); + const auto found = state.registeredImages.find(reinterpret_cast(image)); + if (found == state.registeredImages.end()) return; + const auto [begin, end] = *found; + GuestAllocationsRequireUnpinned_nid_postfix(mutation, image, end - begin); + recordChange(mutation, image, end - begin); + std::erase_if(state.ranges, [&](const auto& entry) { return !entry.second->releasable && entry.second->allocationAddress >= begin && entry.second->allocationAddress < end; }); + state.registeredImages.erase(found); +} #else void GuestAllocationsRegisterMainImage_nid_postfix(void*) { auto& state = registry(); diff --git a/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp b/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp index 509194a50..2968f0c2e 100644 --- a/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp +++ b/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp @@ -1,4 +1,5 @@ #include "prx/libc/include/General.hpp" +#include "prx/libc/include/GuestAllocations.hpp" #include #include #include @@ -32,6 +33,13 @@ struct Module { ~Module() { if (owned && native) { #ifdef _WIN32 + try { + GuestAllocations::Mutation mutation; + mutation.UnregisterImage(native); + } catch (const std::exception& error) { + std::fprintf(stderr, "[dlclose] module %p stays loaded: %s\n", native, error.what()); + return; + } FreeLibrary(static_cast(native)); #else ::dlclose(native); diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index f9f1caa25..80bfe6e9c 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -7,6 +7,7 @@ #include "prx/libkernel/KernelErrors.hpp" #include "prx/libkernel/DirectMemory/DirectMemory.hpp" #include +#include #include "SceTypes.hpp" #include #include @@ -71,6 +72,9 @@ int APS5_VABI sceKernelAioDeleteRequest(std::int32_t, std::int32_t*); int APS5_VABI sceKernelMlock_nid_postfix(void*, std::uint64_t); int APS5_VABI sceKernelGetDirectMemoryType(std::int64_t, int*, std::int64_t*, std::int64_t*); int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry*, int, int*, int); +void* APS5_VABI dlopen_nid_postfix(const char*, int); +void* APS5_VABI dlsym_nid_postfix(void*, const char*); +int APS5_VABI dlclose_nid_postfix(void*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -541,15 +545,28 @@ static DWORD ImageProtection(const void* pointer) { 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")); +static unsigned char* GuestModulePage(void* module, std::size_t page) { + auto* data = static_cast(dlsym_nid_postfix(module, "guestMemoryModuleData")); Require(data != nullptr); auto* target = reinterpret_cast((reinterpret_cast(data) + page - 1) & ~(page - 1)); Require(target + page <= data + 0x10000); + return target; +} + +static bool RegisteredGuestRange(const void* pointer, std::size_t bytes) { + GuestAllocations::Mutation mutation; + return mutation.Overlaps(pointer, bytes); +} + +static void CheckGuestModuleImageProtection() { + constexpr std::size_t page = 0x4000; + const auto path = std::filesystem::relative(GUEST_MEMORY_MODULE).generic_string(); + void* module = dlopen_nid_postfix(path.c_str(), 2); + Require(module != nullptr); + auto* target = GuestModulePage(module, page); + Require(!RegisteredGuestRange(target, page)); Require(sceKernelMprotect(target, page, 1) == 0); + Require(RegisteredGuestRange(target, page)); Require(ImageProtection(target) == PAGE_READONLY); Require(sceKernelMprotect(target, page, 3) == 0); Require(ImageProtection(target) == PAGE_READWRITE || ImageProtection(target) == PAGE_WRITECOPY); @@ -557,6 +574,35 @@ static void CheckGuestModuleImageProtection() { Require(target[1] == 7); Require(sceKernelMprotect(target, page, 1) == 0); Require(ImageProtection(target) == PAGE_READONLY); + + auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix(); + std::atomic closed{false}; + std::thread closer([&] { + Require(dlclose_nid_postfix(module) == 0); + closed = true; + }); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + Require(!closed); + Require(ImageProtection(target) == PAGE_READONLY); + lease.clear(); + closer.join(); + Require(!RegisteredGuestRange(target, page)); + { + GuestAllocations::Mutation mutation; + mutation.Add(target, page, true, true); + mutation.Remove(target); + } + + module = dlopen_nid_postfix(path.c_str(), 2); + Require(module != nullptr); + target = GuestModulePage(module, page); + Require(!RegisteredGuestRange(target, page)); + Require(sceKernelMprotect(target, page, 1) == 0); + Require(ImageProtection(target) == PAGE_READONLY); + Require(RegisteredGuestRange(target, page)); + Require(dlclose_nid_postfix(module) == 0); + Require(!RegisteredGuestRange(target, page)); + bool rejected = false; try { sceKernelMprotect(GetModuleHandleA("kernel32.dll"), page, 1);