From b2fdad9e47d99f9e35ea80eca8b953eec3cc134c Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 13 Sep 2026 10:33:46 +0400 Subject: [PATCH] memory: preserve live POSIX mappings during partial unmap Co-authored-by: Ali Almohaya --- src/kernel/memoryAddressSpace.inc | 47 +++++++++++--------------- tests/VirtualMemoryAllocationTests.cpp | 45 ++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 28 deletions(-) diff --git a/src/kernel/memoryAddressSpace.inc b/src/kernel/memoryAddressSpace.inc index 17510688..868d8ba3 100644 --- a/src/kernel/memoryAddressSpace.inc +++ b/src/kernel/memoryAddressSpace.inc @@ -346,7 +346,9 @@ public: auto entry = std::prev(next); if (vaddr + size <= entry->second.vaddr + entry->second.size) { old = entry->second; +#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS m_maps.erase(entry); +#endif } } } @@ -355,6 +357,12 @@ public: return false; } + const uint64_t left_size = vaddr - old.vaddr; + const uint64_t right_addr = vaddr + size; + const uint64_t old_end = old.vaddr + old.size; + const uint64_t right_size = old_end - right_addr; + +#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS const bool exact_unmap = (vaddr == old.vaddr && size == old.size); if (exact_unmap) { const bool unmapped = @@ -370,12 +378,6 @@ public: return true; } - const uint64_t left_size = vaddr - old.vaddr; - const uint64_t right_addr = vaddr + size; - const uint64_t old_end = old.vaddr + old.size; - const uint64_t right_size = old_end - right_addr; - -#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS if (UnmapViewPreservePlaceholder(reinterpret_cast(old.vaddr), old.size)) { bool ok = true; if (left_size != 0 && left_size != old.size) { @@ -418,39 +420,28 @@ public: m_maps.emplace(old.vaddr, old); return false; #else - if (!UnmapViewPreservePlaceholder(reinterpret_cast(old.vaddr), old.size)) { - std::lock_guard lock(m_mutex); - m_maps.emplace(old.vaddr, old); + // Replace only the requested range so concurrent accesses and GPU watch + // protections on the surviving mappings remain intact. + if (!UnmapViewPreservePlaceholder(reinterpret_cast(vaddr), size)) { return false; } - bool ok = true; + std::lock_guard lock(m_mutex); + m_maps.erase(old.vaddr); if (left_size != 0) { - ok = MapExistingPlaceholderFixed(old.vaddr, left_size, old.backing_offset, old.mode) && - ok; + m_maps.emplace(old.vaddr, + MapEntry {old.vaddr, left_size, old.backing_offset, old.mode}); } if (right_size != 0) { - ok = MapExistingPlaceholderFixed(right_addr, right_size, - old.backing_offset + (right_addr - old.vaddr), - old.mode) && - ok; - } - if (!ok) { - if (left_size != 0 && Contains(old.vaddr, left_size)) { - EXIT_IF(!UnmapOne(old.vaddr, left_size)); - } - if (right_size != 0 && Contains(right_addr, right_size)) { - EXIT_IF(!UnmapOne(right_addr, right_size)); - } - EXIT_IF( - !MapExistingPlaceholderFixed(old.vaddr, old.size, old.backing_offset, old.mode)); - return false; + m_maps.emplace(right_addr, + MapEntry {right_addr, right_size, + old.backing_offset + right_addr - old.vaddr, old.mode}); } if (placeholder_preserved != nullptr) { *placeholder_preserved = true; } - return ok; + return true; #endif } diff --git a/tests/VirtualMemoryAllocationTests.cpp b/tests/VirtualMemoryAllocationTests.cpp index 01b61df9..05b9e05c 100644 --- a/tests/VirtualMemoryAllocationTests.cpp +++ b/tests/VirtualMemoryAllocationTests.cpp @@ -20,6 +20,11 @@ #include #include +#if defined(__linux__) +#include +#include +#endif + #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #ifndef NOMINMAX #define NOMINMAX @@ -1163,6 +1168,43 @@ void TestDirectPartialProtectUnmapPreservesNeighbors() { std::printf("[host] %-48s ok\n", test); } +#if defined(__linux__) +void TestPartialUnmapPreservesHostPermissions() { + const char* test = "PartialUnmapPreservesHostPermissions"; + const auto base = MapNamedFlexible(test, SceKernelPageSize * 3, SceKernelProtCpuRw, + "unmap_host_permissions"); + using Common::VirtualMemory::Mode; + Check(test, Libs::LibKernel::Memory::ProtectGuestHostMemory(base, SceKernelPageSize, Mode::Read), + "could not protect the left survivor from writes"); + Check(test, + Libs::LibKernel::Memory::ProtectGuestHostMemory(base + SceKernelPageSize * 2, + SceKernelPageSize, Mode::NoAccess), + "could not protect the right survivor from reads"); + CheckOk(test, + Libs::LibKernel::Memory::KernelMunmap(base + SceKernelPageSize, SceKernelPageSize), + "KernelMunmap(middle)"); + + // Probe actual host access without taking a signal or changing the page protections. + uint64_t value = 0; + iovec local {&value, sizeof(value)}; + iovec left {reinterpret_cast(base), sizeof(value)}; + iovec right {reinterpret_cast(base + SceKernelPageSize * 2), sizeof(value)}; + Check(test, process_vm_readv(getpid(), &local, 1, &left, 1, 0) == sizeof(value), + "partial unmap removed read access to the left survivor"); + Check(test, process_vm_writev(getpid(), &local, 1, &left, 1, 0) == -1, + "partial unmap removed the left survivor's write protection"); + Check(test, process_vm_readv(getpid(), &local, 1, &right, 1, 0) == -1, + "partial unmap removed the right survivor's read protection"); + + CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize), + "KernelMunmap(left cleanup)"); + CheckOk(test, + Libs::LibKernel::Memory::KernelMunmap(base + SceKernelPageSize * 2, SceKernelPageSize), + "KernelMunmap(right cleanup)"); + std::printf("[host] %-48s ok\n", test); +} +#endif + void TestDirectMapValidationBeforeOwnerMutation() { const char* test = "DirectMapValidationBeforeOwnerMutation"; int64_t invalid = -1; @@ -2529,6 +2571,9 @@ int main(int argc, char** argv) { RunTest(TestMunmapAcrossAdjacentFlexibleMappings); RunTest(TestDirectMapQueryOffsetAndPartialMunmap); RunTest(TestDirectPartialProtectUnmapPreservesNeighbors); +#if defined(__linux__) + RunTest(TestPartialUnmapPreservesHostPermissions); +#endif RunTest(TestDirectMapValidationBeforeOwnerMutation); RunTest(TestDirectReleaseRollbackRestoresOwnerMapping); RunTest(TestDirectReleaseContracts);