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.
This commit is contained in:
Gjon Gojcevic 2026-10-07 20:31:08 -04:00
parent df16c4c256
commit 5cf013a299
2 changed files with 49 additions and 16 deletions

View File

@ -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<void*>(piece);
std::lock_guard lock(g_directLock);
EraseMappings(reinterpret_cast<std::uintptr_t>(piece), reinterpret_cast<std::uintptr_t>(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<void*>(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<std::uint64_t>(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<void*>(piece);
std::lock_guard lock(g_directLock);
EraseMappings(reinterpret_cast<std::uintptr_t>(piece), reinterpret_cast<std::uintptr_t>(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<void*>(allocation));
else munmap(pieceAddress, pieceBytes);
#endif
});
EraseReservations(addr, len);
RecordProtection(addr, len, -1);
UnmapRegistered(mutation, addr, len);
return 0;
}

View File

@ -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<unsigned char*>(fixed)[page * 2 + 7] = 0x5c;
VirtualQueryInfo info{};
Require(sceKernelVirtualQuery(fixed, 0, &info, sizeof(info)) == 0);
Require(info.is_direct && info.offset == static_cast<std::uint64_t>(phys));
Require(static_cast<unsigned char*>(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();