From 5cf013a2994039138b0f0c686a8b9c898bccde3f Mon Sep 17 00:00:00 2001 From: Gjon Gojcevic Date: Wed, 7 Oct 2026 20:31:08 -0400 Subject: [PATCH] fix(libkernel): replace partly overlapped allocations with a fixed mapping A fixed mapping without no-overwrite unmaps the registered pieces it overlaps before mapping, as it already did when the range lay inside registered allocations. --- .../libkernel/DirectMemory/DirectMemory.cpp | 43 ++++++++++++------- core/libs/tests/GuestMemory.cpp | 22 ++++++++++ 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 297dd9d94..cd7bf920b 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -615,6 +615,30 @@ bool Reserved(const void* addr, size_t len) { return false; } +void UnmapRegistered(GuestAllocations::Mutation& mutation, void* addr, size_t len) { + mutation.Unmap(addr, len, [&](const void* piece, std::size_t pieceBytes, const void* allocation, bool last) { + auto* pieceAddress = const_cast(piece); + std::lock_guard lock(g_directLock); + EraseMappings(reinterpret_cast(piece), reinterpret_cast(piece) + pieceBytes); +#if defined(__linux__) + Unmap(pieceAddress, pieceBytes); +#else + if (KernelArena::Get().Contains(pieceAddress, pieceBytes)) munmap(pieceAddress, pieceBytes); + else if (last) munmap_release(const_cast(allocation)); + else munmap(pieceAddress, pieceBytes); +#endif + }); + EraseReservations(addr, len); + RecordProtection(addr, len, -1); +} + +void ReplaceFixedOverlap(GuestAllocations::Mutation& mutation, void* addr, size_t len, int flags) { + constexpr int GuestMapNoOverwrite = 0x80; + if (addr == nullptr || (flags & GuestMapFixedFlag) == 0) return; + if ((flags & GuestMapNoOverwrite) == 0 && mutation.Overlaps(addr, len)) UnmapRegistered(mutation, addr, len); + mutation.RequireAvailable(addr, len); +} + bool FixedNoOverwriteConflict(const GuestAllocations::Mutation& mutation, void* addr, size_t len, int flags) { constexpr int GuestMapNoOverwrite = 0x80; if (addr == nullptr || (flags & GuestMapFixedFlag) == 0 || (flags & GuestMapNoOverwrite) == 0) return false; @@ -634,7 +658,7 @@ int DoMapDirect(void** addr, size_t len, int prot, int flags, int64_t physStart, RecordProtection(*addr, len, prot); return 0; } - if (*addr != nullptr && (flags & GuestMapFixedFlag) != 0) mutation.RequireAvailable(*addr, len); + ReplaceFixedOverlap(mutation, *addr, len, flags); std::lock_guard lock(g_directLock); ValidatePhysicalRange(static_cast(physStart), len); void* mapped = MapAligned(*addr, len, PROT_NONE, flags, alignment); @@ -663,7 +687,7 @@ int DoMapAnon(void** addr, size_t len, int prot, int flags) { RecordProtection(*addr, len, prot); return 0; } - if (*addr != nullptr && (flags & GuestMapFixedFlag) != 0) mutation.RequireAvailable(*addr, len); + ReplaceFixedOverlap(mutation, *addr, len, flags); void* mapped = MapAligned(*addr, len, LinuxProtFromSce(prot), flags, PS5_PAGE_SIZE); try { mutation.Add(mapped, len, (prot & 3) != 0, (prot & 2) != 0); @@ -738,20 +762,7 @@ int DoMunmap(void* addr, size_t len) { Trace("unmap %p+0x%zx", addr, len); if (len == 0 || (len & (PS5_PAGE_SIZE - 1)) != 0 || !addr) return SCE_KERNEL_ERROR_EINVAL; GuestAllocations::Mutation mutation; - mutation.Unmap(addr, len, [&](const void* piece, std::size_t pieceBytes, const void* allocation, bool last) { - auto* pieceAddress = const_cast(piece); - std::lock_guard lock(g_directLock); - EraseMappings(reinterpret_cast(piece), reinterpret_cast(piece) + pieceBytes); -#if defined(__linux__) - Unmap(pieceAddress, pieceBytes); -#else - if (KernelArena::Get().Contains(pieceAddress, pieceBytes)) munmap(pieceAddress, pieceBytes); - else if (last) munmap_release(const_cast(allocation)); - else munmap(pieceAddress, pieceBytes); -#endif - }); - EraseReservations(addr, len); - RecordProtection(addr, len, -1); + UnmapRegistered(mutation, addr, len); return 0; } diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index ff2cd1b95..fb508b265 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -280,6 +280,27 @@ static void CheckReleaseDirectMemoryClearsMappings() { Require(sceKernelMunmap(mapped, page * 2) == 0); } +static void CheckFixedMappingReplacesPartialOverlap() { + constexpr std::size_t page = 0x4000; + std::int64_t phys = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 3, 0, 0, &phys) == 0); + void* reserved = nullptr; + Require(sceKernelMapDirectMemory(&reserved, page * 3, 3, 0, phys, 0) == 0); + Require(sceKernelMunmap(reserved, page * 3) == 0); + void* head = reserved; + Require(sceKernelMapDirectMemory(&head, page, 3, 0x10, phys, 0) == 0 && head == reserved); + void* fixed = reserved; + Require(sceKernelMapDirectMemory(&fixed, page * 3, 3, 0x10, phys, 0) == 0); + Require(fixed == reserved); + static_cast(fixed)[page * 2 + 7] = 0x5c; + VirtualQueryInfo info{}; + Require(sceKernelVirtualQuery(fixed, 0, &info, sizeof(info)) == 0); + Require(info.is_direct && info.offset == static_cast(phys)); + Require(static_cast(fixed)[page * 2 + 7] == 0x5c); + Require(sceKernelMunmap(fixed, page * 3) == 0); + Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); +} + static void CheckGetDirectMemoryType() { constexpr std::size_t page = 0x4000; std::int64_t first = 0; @@ -1043,6 +1064,7 @@ int main() { CheckAudioCoprocessorProtection(); CheckDirectMemoryFollowsPhysicalPages(); CheckReleaseDirectMemoryClearsMappings(); + CheckFixedMappingReplacesPartialOverlap(); CheckDirectMemoryGpuProtBits(); CheckFixedVirtualReservation(); CheckReservedRangeIsNotCommitted();