diff --git a/src/kernel/memory.cpp b/src/kernel/memory.cpp index 4402fd8dc..d41d30ad7 100644 --- a/src/kernel/memory.cpp +++ b/src/kernel/memory.cpp @@ -964,16 +964,6 @@ static bool IsInPrtAperture(uint64_t address, uint64_t size = 1) { } bool TryReadSparseBacking(uint64_t vaddr, void* data, uint64_t size) { - std::vector ranges; - if (!g_virtual_ranges->QuerySpan(vaddr, size, &ranges)) { - return false; - } - if (std::any_of(ranges.begin(), ranges.end(), [](const auto& range) { - return !IsReservedRangeType(range.type) && - !g_guest_address_space->BackingContains(range.start, range.size); - })) { - return false; - } return g_guest_address_space->TryReadSparseBacking(vaddr, data, size); } diff --git a/src/kernel/memoryAddressSpace.inc b/src/kernel/memoryAddressSpace.inc index 73c415830..d461c7080 100644 --- a/src/kernel/memoryAddressSpace.inc +++ b/src/kernel/memoryAddressSpace.inc @@ -220,13 +220,13 @@ public: const auto bytes = std::min(end, entry.vaddr + entry.size) - start; const auto offset = entry.backing_offset + start - entry.vaddr; if (cursor < start) { - std::memset(destination + cursor - vaddr, 0, start - cursor); + std::memset(destination + (cursor - vaddr), 0, start - cursor); } - std::memcpy(destination + start - vaddr, m_backing_base + offset, bytes); + std::memcpy(destination + (start - vaddr), m_backing_base + offset, bytes); cursor = start + bytes; } if (cursor < end) { - std::memset(destination + cursor - vaddr, 0, end - cursor); + std::memset(destination + (cursor - vaddr), 0, end - cursor); } return true; } @@ -738,6 +738,20 @@ public: } bool TryReadSparseBacking(uint64_t vaddr, void* data, uint64_t size) { + if (!Owns(vaddr, size)) { + return false; + } + std::lock_guard lock(m_mutex); + auto mapping = m_mapped.upper_bound(vaddr); + if (mapping != m_mapped.begin()) { + --mapping; + } + for (; mapping != m_mapped.end() && mapping->second.vaddr < vaddr + size; ++mapping) { + const auto& region = mapping->second; + if (region.type == MappingType::Private && region.vaddr + region.size > vaddr) { + return false; + } + } return m_backing->TryReadSparseBacking(vaddr, data, size); } diff --git a/tests/VirtualMemoryAllocationTests.cpp b/tests/VirtualMemoryAllocationTests.cpp index 4b784aab6..5bba72073 100644 --- a/tests/VirtualMemoryAllocationTests.cpp +++ b/tests/VirtualMemoryAllocationTests.cpp @@ -442,13 +442,21 @@ void TestSparseBackingReadPreservesResidency() { [](uint8_t value) { return value == 0; }), "sparse backing read did not preserve resident data between reserved gaps"); Check(test, - !Libs::LibKernel::Memory::TryReadSparseBacking( - base + SceKernelMemoryPoolReserveLen - commit_size, bytes.data(), commit_size * 2), - "sparse backing read crossed the owned reservation"); + Libs::LibKernel::Memory::TryReadSparseBacking( + base + SceKernelMemoryPoolReserveLen - commit_size, bytes.data(), commit_size * 2) && + std::all_of(bytes.begin(), bytes.begin() + commit_size * 2, + [](uint8_t value) { return value == 0; }), + "sparse backing read rejected unbacked guest address space beyond a reservation"); - constexpr uint64_t unowned = 0x5000000000ull; + constexpr uint64_t unowned = 0x10000; Check(test, !Libs::LibKernel::Memory::TryReadSparseBacking(unowned, bytes.data(), commit_size), "sparse backing read accepted an unowned virtual range"); + Check(test, + !Libs::LibKernel::Memory::TryReadSparseBacking( + Libs::LibKernel::Memory::kExtendedMemoryBase + + Libs::LibKernel::Memory::kExtendedMemorySize - commit_size, + bytes.data(), commit_size * 2), + "sparse backing read crossed the owned guest address space"); CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolDecommit(arena, commit_size, 0), "KernelMemoryPoolDecommit(first)"); @@ -476,12 +484,7 @@ void TestSparseReadDuringDirectCommit() { 0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), size, chunk_size, SceKernelMtypeC, &physical), "KernelAllocateDirectMemory"); - void* arena = reinterpret_cast(0x80180000000ull); - CheckOk(test, - Libs::LibKernel::Memory::KernelReserveVirtualRange( - &arena, size, SceKernelMapFixed | SceKernelMapNoOverwrite, size), - "KernelReserveVirtualRange"); - const auto base = reinterpret_cast(arena); + constexpr uint64_t base = 0x80180000000ull; const auto map_chunk = [&](uint64_t index) { void* address = reinterpret_cast(base + index * chunk_size); return Libs::LibKernel::Memory::KernelMapNamedDirectMemory( @@ -494,6 +497,8 @@ void TestSparseReadDuringDirectCommit() { std::memset(reinterpret_cast(base + (chunks - 1) * chunk_size), 0xa7, chunk_size); std::vector bytes(size, 0x5a); + Check(test, !Libs::LibKernel::Memory::TryReadBacking(base, bytes.data(), size), + "dense backing read accepted unbacked guest address space"); Check(test, Libs::LibKernel::Memory::TryReadSparseBacking(base, bytes.data(), size), "sparse read rejected a partly committed direct mapping"); Check(test, bytes.front() == 0x3c && bytes[chunk_size] == 0 && bytes.back() == 0xa7, @@ -501,7 +506,7 @@ void TestSparseReadDuringDirectCommit() { Libs::LibKernel::Memory::TestFailNextVirtualRangeReplacement(); CheckFailed(test, map_chunk(1), "KernelMapNamedDirectMemory(injected replacement failure)"); ExpectRange(test, Query(test, base + chunk_size), base + chunk_size, - base + (chunks - 1) * chunk_size, 0, 0, 0, 0, 0, "anon"); + base + chunk_size * 2, 0, 0, 0, 0, 0, "anon"); Check(test, !Libs::LibKernel::Memory::TryReadBacking(base + chunk_size, bytes.data(), chunk_size) && Libs::LibKernel::Memory::TryReadSparseBacking(base, bytes.data(), size) && @@ -945,6 +950,10 @@ void TestRuntimeMemoryOwnerLifecycle() { Check(test, Libs::LibKernel::Memory::TestGuestAddressRangeIsOwned(base, SceKernelPageSize * 2), "runtime allocation is outside the owner"); *reinterpret_cast(base) = 0x52554e54494d454full; // "RUNTIMEO" + uint64_t snapshot = UINT64_MAX; + Check(test, !Libs::LibKernel::Memory::TryReadSparseBacking(base, &snapshot, sizeof(snapshot)) && + snapshot == UINT64_MAX, + "sparse backing read replaced private committed data with zeros"); Check(test, Libs::LibKernel::Memory::ProtectGuestMemory(base, SceKernelPageSize, Common::VirtualMemory::Mode::Read), @@ -953,6 +962,9 @@ void TestRuntimeMemoryOwnerLifecycle() { "runtime free failed"); Check(test, Libs::LibKernel::Memory::TestPlaceholderRangeIsFree(base, SceKernelPageSize * 2), "runtime free did not restore the owner placeholder"); + Check(test, Libs::LibKernel::Memory::TryReadSparseBacking(base, &snapshot, sizeof(snapshot)) && + snapshot == 0, + "sparse backing read rejected freed guest-owned memory"); const auto reused = Libs::LibKernel::Memory::AllocateRuntimeMemory( base, SceKernelPageSize * 2, Common::VirtualMemory::Mode::ReadWrite, "runtime_reuse", true);