fix(libkernel): reprotect bundled guest module images on Windows
On Windows, sceKernelMprotect accepted only the executable's image and threw "Memory protection of a foreign image is not supported" for every other image. Bundled title modules are relinked into DLLs named <module>.guest.prx, so a title that reprotects its own module pages failed. Dying Light 2's bundled libNRD does this at start-up (#1794). DoMprotect now also accepts images whose file name ends in .guest.prx, and registers their committed pages like the executable's. Other images are still refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
85d16bcc23
commit
9f1d7b0516
|
|
@ -25,6 +25,9 @@ using Lease = std::vector<std::shared_ptr<const Range>>;
|
|||
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); }
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <limits>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
|
|
@ -34,6 +35,7 @@ struct Registry {
|
|||
std::mutex mutex;
|
||||
std::map<std::uint64_t, std::shared_ptr<const Range>> ranges;
|
||||
bool mainImageRegistered = false;
|
||||
std::set<std::uintptr_t> 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<std::uintptr_t>(image))) return;
|
||||
auto replacement = state.ranges;
|
||||
auto cursor = reinterpret_cast<std::uintptr_t>(image);
|
||||
bool registered = false;
|
||||
for (;;) {
|
||||
MEMORY_BASIC_INFORMATION memory{};
|
||||
require(VirtualQuery(reinterpret_cast<const void*>(cursor), &memory, sizeof(memory)) == sizeof(memory), "cannot query the main guest image");
|
||||
require(VirtualQuery(reinterpret_cast<const void*>(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<std::uintptr_t>(memory.BaseAddress) == cursor && memory.RegionSize != 0 && memory.RegionSize <= std::numeric_limits<std::uintptr_t>::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<std::uintptr_t>(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();
|
||||
|
|
|
|||
|
|
@ -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="$<TARGET_FILE:guest_memory_module>")
|
||||
set_tests_properties(guest_memory PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -11,9 +11,12 @@
|
|||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cwctype>
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -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<HMODULE>(const_cast<void*>(base)), path.data(), static_cast<DWORD>(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<std::ptrdiff_t>(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<std::uintptr_t>(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();
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <source_location>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
|
@ -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<unsigned char*>(GetProcAddress(module, "guestMemoryModuleData"));
|
||||
Require(data != nullptr);
|
||||
auto* target = reinterpret_cast<unsigned char*>((reinterpret_cast<std::uintptr_t>(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();
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
extern "C" unsigned char guestMemoryModuleData[0x10000] = {1};
|
||||
Loading…
Reference in New Issue