diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp index 112c4fe78..5a9ba4cd2 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp @@ -423,6 +423,17 @@ struct PageStates { arena.forget(address, bytes); image.forget(address, bytes); } + + void record(std::uintptr_t base, std::uintptr_t regionEnd, bool readable, bool writable, std::uint64_t generation) { + for (PageSpan* span : {&arena, &image}) { + if (!readable || span->size == 0 || regionEnd <= span->base || base >= span->base + span->size) continue; + const std::uint8_t value = PageReadable | (writable ? PageWritable : 0u); + const auto first = std::max(base, span->base); + const auto last = std::min(regionEnd, span->base + span->size); + for (auto at = first; at < last; at += PageBytes) span->store(at, value); + if (GuestAllocations::GuestAllocationsGeneration_nid_postfix() != generation) span->forget(first, static_cast(last - first)); + } + } }; PageStates& Pages() { @@ -533,18 +544,16 @@ bool describePages(std::uintptr_t address, std::size_t bytes, Emit&& emit) { const bool committed = memory.State == MEM_COMMIT && (memory.Protect & (PAGE_GUARD | PAGE_NOACCESS)) == 0; const bool readable = committed && readableProtection(protection); const bool writable = readable && writableProtection(protection); - for (PageSpan* span : {&pages.arena, &pages.image}) { - if (!readable || span->size == 0 || regionEnd <= span->base || base >= span->base + span->size) continue; - const std::uint8_t value = PageReadable | (writable ? PageWritable : 0u); - const auto first = std::max(base, span->base); - const auto last = std::min(regionEnd, span->base + span->size); - for (auto at = first; at < last; at += PageBytes) span->store(at, value); - if (GuestAllocations::GuestAllocationsGeneration_nid_postfix() != generation) span->forget(first, static_cast(last - first)); - } + pages.record(base, regionEnd, readable, writable, generation); const auto next = std::min(end, regionEnd); if (!emit(PageRun{cursor, next, readable, writable})) return true; cursor = next; #else + const auto generation = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + const auto recordRun = [&](std::uintptr_t next, bool readable, bool writable) { + constexpr auto mask = static_cast(PageBytes - 1); + pages.record(cursor & ~mask, (next + mask) & ~mask, readable, writable, generation); + }; if (const int fd = HostMapping::QueryFd(); fd >= 0) { procmap_query query{}; query.size = sizeof(query); @@ -559,8 +568,10 @@ bool describePages(std::uintptr_t address, std::size_t bytes, Emit&& emit) { continue; } const bool readable = (query.vma_flags & PROCMAP_QUERY_VMA_READABLE) != 0; + const bool writable = readable && (query.vma_flags & PROCMAP_QUERY_VMA_WRITABLE) != 0; const auto next = std::min(end, query.vma_end); - if (!emit(PageRun{cursor, next, readable, readable && (query.vma_flags & PROCMAP_QUERY_VMA_WRITABLE) != 0})) return true; + recordRun(next, readable, writable); + if (!emit(PageRun{cursor, next, readable, writable})) return true; cursor = next; continue; } @@ -589,8 +600,11 @@ bool describePages(std::uintptr_t address, std::size_t bytes, Emit&& emit) { found = true; break; } + const bool readable = permissions[0] == 'r'; + const bool writable = readable && permissions[1] == 'w'; const auto next = std::min(end, last); - if (!emit(PageRun{cursor, next, permissions[0] == 'r', permissions[0] == 'r' && permissions[1] == 'w'})) return true; + recordRun(next, readable, writable); + if (!emit(PageRun{cursor, next, readable, writable})) return true; cursor = next; found = true; break; diff --git a/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp b/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp index f4f02db6a..ad2314a04 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp @@ -1,5 +1,6 @@ #include "BdaTests.hpp" #include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/GuestHeap.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" @@ -187,5 +188,36 @@ void RunUnmappedGapTests() { Require(!GuestMemory::Accessible(reinterpret_cast(std::uintptr_t{0x18}), 4), "a near-null address counts as accessible"); munmap(block, page); munmap(block + 2 * page, page); + if (!GuestArena::GuestArenaAvailable_nid_postfix()) return; + auto* guest = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(2 * page, page)); + Require(mmap(guest, 2 * page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) == guest, "cannot map the arena page test pages"); + { + GuestAllocations::Mutation mutation; + mutation.Add(guest, 2 * page, true, true, true); + } + Require(GuestMemory::Accessible(guest, page, true), "a mapped arena page is not writable"); + Require(mprotect(guest, page, PROT_NONE) == 0, "cannot revoke the arena test page behind the registry"); + Require(GuestMemory::Accessible(guest, page, true), "a checked arena page was queried again instead of read from the page table"); + Require(mprotect(guest, page, PROT_READ | PROT_WRITE) == 0, "cannot restore the arena test page behind the registry"); + Require(mprotect(guest + page, page, PROT_READ) == 0, "cannot protect the arena test's second page"); + Require(!GuestMemory::Accessible(guest + page, page, true), "a check stored arena pages outside its range"); + { + GuestAllocations::Mutation mutation; + mutation.Protect(guest + page, page, true, true, true, [&] { Require(mprotect(guest + page, page, PROT_READ | PROT_WRITE) == 0, "cannot restore the arena test's second page"); }); + } + Require(GuestMemory::Accessible(guest, 2 * page, true), "mapped arena pages are not writable"); + { + GuestAllocations::Mutation mutation; + mutation.Protect(guest, page, true, false, true, [&] { Require(mprotect(guest, page, PROT_READ) == 0, "cannot protect the arena test page"); }); + } + Require(!GuestMemory::Accessible(guest, page, true) && GuestMemory::Accessible(guest, page) && GuestMemory::Accessible(guest + page, page, true), "an arena page protected through the registry kept its old access"); + { + GuestAllocations::Mutation mutation; + const auto release = [](const void* pointer, std::size_t bytes, const void*, bool) { Require(munmap(const_cast(pointer), bytes) == 0, "cannot unmap an arena test page"); }; + mutation.Unmap(guest, page, release); + mutation.Unmap(guest + page, page, release); + } + Require(!GuestMemory::Accessible(guest, page) && !GuestMemory::Accessible(guest + page, page), "unmapped arena pages are still accessible"); + GuestArena::GuestArenaRelease_nid_postfix(guest, 2 * page); #endif }