memory: preserve live POSIX mappings during partial unmap

Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
This commit is contained in:
nmzik
2026-09-13 10:33:46 +04:00
parent 87971a28f5
commit b2fdad9e47
2 changed files with 64 additions and 28 deletions
+19 -28
View File
@@ -346,7 +346,9 @@ public:
auto entry = std::prev(next); auto entry = std::prev(next);
if (vaddr + size <= entry->second.vaddr + entry->second.size) { if (vaddr + size <= entry->second.vaddr + entry->second.size) {
old = entry->second; old = entry->second;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
m_maps.erase(entry); m_maps.erase(entry);
#endif
} }
} }
} }
@@ -355,6 +357,12 @@ public:
return false; 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); const bool exact_unmap = (vaddr == old.vaddr && size == old.size);
if (exact_unmap) { if (exact_unmap) {
const bool unmapped = const bool unmapped =
@@ -370,12 +378,6 @@ public:
return true; 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<void*>(old.vaddr), old.size)) { if (UnmapViewPreservePlaceholder(reinterpret_cast<void*>(old.vaddr), old.size)) {
bool ok = true; bool ok = true;
if (left_size != 0 && left_size != old.size) { if (left_size != 0 && left_size != old.size) {
@@ -418,39 +420,28 @@ public:
m_maps.emplace(old.vaddr, old); m_maps.emplace(old.vaddr, old);
return false; return false;
#else #else
if (!UnmapViewPreservePlaceholder(reinterpret_cast<void*>(old.vaddr), old.size)) { // Replace only the requested range so concurrent accesses and GPU watch
std::lock_guard lock(m_mutex); // protections on the surviving mappings remain intact.
m_maps.emplace(old.vaddr, old); if (!UnmapViewPreservePlaceholder(reinterpret_cast<void*>(vaddr), size)) {
return false; return false;
} }
bool ok = true; std::lock_guard lock(m_mutex);
m_maps.erase(old.vaddr);
if (left_size != 0) { if (left_size != 0) {
ok = MapExistingPlaceholderFixed(old.vaddr, left_size, old.backing_offset, old.mode) && m_maps.emplace(old.vaddr,
ok; MapEntry {old.vaddr, left_size, old.backing_offset, old.mode});
} }
if (right_size != 0) { if (right_size != 0) {
ok = MapExistingPlaceholderFixed(right_addr, right_size, m_maps.emplace(right_addr,
old.backing_offset + (right_addr - old.vaddr), MapEntry {right_addr, right_size,
old.mode) && 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;
} }
if (placeholder_preserved != nullptr) { if (placeholder_preserved != nullptr) {
*placeholder_preserved = true; *placeholder_preserved = true;
} }
return ok; return true;
#endif #endif
} }
+45
View File
@@ -20,6 +20,11 @@
#include <string> #include <string>
#include <vector> #include <vector>
#if defined(__linux__)
#include <sys/uio.h>
#include <unistd.h>
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#ifndef NOMINMAX #ifndef NOMINMAX
#define NOMINMAX #define NOMINMAX
@@ -1163,6 +1168,43 @@ void TestDirectPartialProtectUnmapPreservesNeighbors() {
std::printf("[host] %-48s ok\n", test); 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<void*>(base), sizeof(value)};
iovec right {reinterpret_cast<void*>(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() { void TestDirectMapValidationBeforeOwnerMutation() {
const char* test = "DirectMapValidationBeforeOwnerMutation"; const char* test = "DirectMapValidationBeforeOwnerMutation";
int64_t invalid = -1; int64_t invalid = -1;
@@ -2529,6 +2571,9 @@ int main(int argc, char** argv) {
RunTest(TestMunmapAcrossAdjacentFlexibleMappings); RunTest(TestMunmapAcrossAdjacentFlexibleMappings);
RunTest(TestDirectMapQueryOffsetAndPartialMunmap); RunTest(TestDirectMapQueryOffsetAndPartialMunmap);
RunTest(TestDirectPartialProtectUnmapPreservesNeighbors); RunTest(TestDirectPartialProtectUnmapPreservesNeighbors);
#if defined(__linux__)
RunTest(TestPartialUnmapPreservesHostPermissions);
#endif
RunTest(TestDirectMapValidationBeforeOwnerMutation); RunTest(TestDirectMapValidationBeforeOwnerMutation);
RunTest(TestDirectReleaseRollbackRestoresOwnerMapping); RunTest(TestDirectReleaseRollbackRestoresOwnerMapping);
RunTest(TestDirectReleaseContracts); RunTest(TestDirectReleaseContracts);