fix(libc): keep resident mappings to direct memory backings on Windows

Map made every singly mapped section page private resident memory. A JIT
or shm_open section is file backed and committed: its bytes live in the
file, and its alias descriptors map duplicated handles, which the physical
page slots do not match, so a resident copy diverged from the file, and
committing its view on a demote failed with access denied
(guest_jit_shared_memory). Only direct memory backings, mapped through the
new GuestArenaMapDirect, now become resident; other sections keep main's
shared views. Shared pages are committed only while their section page is
reserved, as on main.

The socket host write tests map their direct memory a second time, so the
write-protected shared path they check is still the one they exercise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1W4jdNSKTV7xwuS6xQPJV
This commit is contained in:
thibaut 2026-10-14 00:24:09 +02:00
parent d9852df2a2
commit 71da166a18
6 changed files with 23 additions and 10 deletions

View File

@ -41,6 +41,7 @@ bool GuestArenaHostRegionOverlaps_nid_postfix(std::uintptr_t address, std::size_
std::uint64_t GuestArenaCommitGeneration_nid_postfix();
void GuestArenaSetPrivateMappingObserver_nid_postfix(void (*callback)(std::uintptr_t address, std::size_t bytes, std::uint64_t generation));
void GuestArenaMap_nid_postfix(void* pointer, std::size_t bytes, void* section, std::uint64_t offset, std::uint32_t protection);
void GuestArenaMapDirect_nid_postfix(void* pointer, std::size_t bytes, void* section, std::uint64_t offset, std::uint32_t protection);
void* GuestArenaMapAlias_nid_postfix(std::uintptr_t address, std::size_t bytes);
void GuestArenaUnmapAlias_nid_postfix(void* alias);
bool GuestArenaResidentSpan_nid_postfix(std::uintptr_t address, std::size_t bytes, std::uintptr_t* start, std::uintptr_t* end);

View File

@ -75,11 +75,11 @@ public:
reset(reinterpret_cast<std::uintptr_t>(address), bytes);
}
void Map(void* address, std::size_t bytes, HANDLE section, std::uint64_t offset, DWORD protection, bool watched) {
void Map(void* address, std::size_t bytes, HANDLE section, std::uint64_t offset, DWORD protection, bool watched = false, bool resident = false) {
std::lock_guard lock(mutex);
auto cursor = reinterpret_cast<std::uintptr_t>(address);
reset(cursor, bytes);
watchResident = watched;
if (resident) watchResident = watched;
HANDLE duplicate = nullptr;
if (!DuplicateHandle(GetCurrentProcess(), section, GetCurrentProcess(), &duplicate, 0, FALSE, DUPLICATE_SAME_ACCESS)) fail("keep shared guest section");
const auto owned = std::make_shared<Section>(duplicate);
@ -95,7 +95,7 @@ public:
const auto single = [&](std::size_t done) {
const auto& slot = slotOf(done);
const auto shared = slot.page.lock();
return !slot.stored && (!shared || shared->aliases.empty());
return resident && !slot.stored && (!shared || shared->aliases.empty());
};
for (std::size_t done = 0; done < bytes;) {
const auto base = cursor + done;
@ -589,7 +589,7 @@ private:
void mapShared(std::uintptr_t base, HANDLE section, std::uint64_t offset, DWORD protection) {
void* page = reinterpret_cast<void*>(base);
if (!map(section, GetCurrentProcess(), page, offset, pageBytes, MEM_REPLACE_PLACEHOLDER, PAGE_EXECUTE_READWRITE, nullptr, 0)) fail("map shared guest page");
if (!VirtualAlloc(page, pageBytes, MEM_COMMIT, PAGE_EXECUTE_READWRITE)) fail("commit shared guest page");
if (query(base).State == MEM_RESERVE && !VirtualAlloc(page, pageBytes, MEM_COMMIT, PAGE_EXECUTE_READWRITE)) fail("commit shared guest page");
DWORD previous;
if (!VirtualProtect(page, pageBytes, protection, &previous)) fail("protect shared guest page");
}
@ -610,7 +610,7 @@ private:
if (!VirtualProtect(reinterpret_cast<void*>(start), stop - start, PAGE_READONLY, &previous)) fail("fence resident guest pages");
const auto section = views.at(start).section;
auto window = open(section->handle, views.at(start).offset, stop - start);
if (!VirtualAlloc(window.bytes, stop - start, MEM_COMMIT, PAGE_READWRITE)) fail("commit direct memory pages");
if (query(reinterpret_cast<std::uintptr_t>(window.bytes)).State == MEM_RESERVE && !VirtualAlloc(window.bytes, stop - start, MEM_COMMIT, PAGE_READWRITE)) fail("commit direct memory pages");
std::memcpy(window.bytes, reinterpret_cast<const void*>(start), stop - start);
close(window);
if (!VirtualFree(reinterpret_cast<void*>(start), stop - start, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) fail("release resident guest pages");

View File

@ -378,7 +378,12 @@ void GuestArenaReset_nid_postfix(void* pointer, std::size_t bytes) {
void GuestArenaMap_nid_postfix(void* pointer, std::size_t bytes, void* section, std::uint64_t offset, std::uint32_t protection) {
if (!Arena::Get().Contains(pointer, bytes)) throw OutsideArena("shared mapping", pointer, bytes);
WindowsMappings::Get().Map(pointer, bytes, section, offset, protection, Arena::Get().WriteWatched());
WindowsMappings::Get().Map(pointer, bytes, section, offset, protection, Arena::Get().WriteWatched(), false);
}
void GuestArenaMapDirect_nid_postfix(void* pointer, std::size_t bytes, void* section, std::uint64_t offset, std::uint32_t protection) {
if (!Arena::Get().Contains(pointer, bytes)) throw OutsideArena("direct memory mapping", pointer, bytes);
WindowsMappings::Get().Map(pointer, bytes, section, offset, protection, Arena::Get().WriteWatched(), true);
}
void* GuestArenaMapAlias_nid_postfix(std::uintptr_t address, std::size_t bytes) {

View File

@ -314,7 +314,7 @@ public:
void Map(std::uintptr_t address, std::size_t bytes, std::uint64_t offset, int protection) const {
#ifdef _WIN32
GuestArena::GuestArenaMap_nid_postfix(reinterpret_cast<void*>(address), bytes, section, offset, WinProtFromPosix(protection));
GuestArena::GuestArenaMapDirect_nid_postfix(reinterpret_cast<void*>(address), bytes, section, offset, WinProtFromPosix(protection));
#else
if (::mmap(reinterpret_cast<void*>(address), bytes, protection, MAP_SHARED | MAP_FIXED, file, static_cast<off_t>(fileOffset + offset)) == MAP_FAILED) throw std::system_error(errno, std::generic_category(), "map direct memory backing");
#endif

View File

@ -141,6 +141,8 @@ int main(int argc, char** argv) {
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &physical) == 0, "allocate direct memory");
void* mapping = nullptr;
Require(sceKernelMapDirectMemory(&mapping, page * 2, 3, 0, physical, 0) == 0, "map read/write memory");
void* twin = nullptr;
Require(sceKernelMapDirectMemory(&twin, page * 2, 3, 0, physical, 0) == 0, "map read/write memory a second time");
auto* buffer = static_cast<char*>(mapping) + page - 7;
const auto collect = [&] {
std::array<void*, 16> pages{};
@ -199,6 +201,9 @@ int main(int argc, char** argv) {
void* outputMapping = nullptr;
Require(sceKernelMapDirectMemory(&outputMapping, page * 2, 3, 0, outputPhysical, 0) == 0,
"map sender output memory");
void* outputTwin = nullptr;
Require(sceKernelMapDirectMemory(&outputTwin, page * 2, 3, 0, outputPhysical, 0) == 0,
"map sender output memory a second time");
const auto collectOutputs = [&] {
std::array<void*, 16> pages{};
std::size_t count = pages.size();
@ -238,6 +243,7 @@ int main(int argc, char** argv) {
if (mask & 1) Require(Protection(address) == PAGE_READONLY, "sender address rearmed");
if (mask & 2) Require(Protection(length) == PAGE_READONLY, "sender length rearmed");
}
Require(sceKernelMunmap(outputTwin, page * 2) == 0, "unmap the second sender output mapping");
Require(sceKernelMunmap(outputMapping, page * 2) == 0, "unmap sender outputs");
Require(sceKernelReleaseDirectMemory(outputPhysical, page * 2) == 0, "release sender output memory");
}
@ -300,6 +306,7 @@ int main(int argc, char** argv) {
Require(Protection(buffer) == PAGE_READONLY, "completed receive can be rearmed");
Require((net ? sceNetSocketClose(sender) : close_nid_postfix(sender)) == 0, "close sender");
Require((net ? sceNetSocketClose(receiver) : close_nid_postfix(receiver)) == 0, "close receiver");
Require(sceKernelMunmap(twin, page * 2) == 0, "unmap the second direct memory mapping");
Require(sceKernelMunmap(mapping, page * 2) == 0, "unmap direct memory");
Require(sceKernelReleaseDirectMemory(physical, page * 2) == 0, "release direct memory");
}

View File

@ -32,8 +32,8 @@ struct Region {
Region() {
Require(section && first && alias, "cannot allocate shared test memory");
Mappings::Get().Map(first, Bytes, section, 0, PAGE_READWRITE, true);
Mappings::Get().Map(alias, Bytes, section, 0, PAGE_READWRITE, true);
Mappings::Get().Map(first, Bytes, section, 0, PAGE_READWRITE);
Mappings::Get().Map(alias, Bytes, section, 0, PAGE_READWRITE);
}
~Region() {
Mappings::Get().Reset(first, Bytes);
@ -113,7 +113,7 @@ void CheckAliasesAndCapacity() {
Require(region.Collect(Collection::Armed) == Pages(0, 1), "alias write was missed or fresh neighbors were consumed");
Require(region.Collect(Collection::Fresh) == Pages(1, 16), "alias collection changed another view's history");
Mappings::Get().Reset(region.first, Bytes);
Mappings::Get().Map(region.first, Bytes, region.section, 0, PAGE_READWRITE, true);
Mappings::Get().Map(region.first, Bytes, region.section, 0, PAGE_READWRITE);
Require(region.Collect(Collection::Armed).empty(), "remapped address kept old collection history");
Require(region.Collect(Collection::Fresh) == Pages(0, 16), "remapped memory was not fresh");
}