diff --git a/core/libs/prx/libc/include/GuestArena.hpp b/core/libs/prx/libc/include/GuestArena.hpp index 8d7972549..2eab958d6 100644 --- a/core/libs/prx/libc/include/GuestArena.hpp +++ b/core/libs/prx/libc/include/GuestArena.hpp @@ -29,9 +29,27 @@ void GuestArenaMap_nid_postfix(void* pointer, std::size_t bytes, void* section, void* GuestArenaMapAlias_nid_postfix(std::uintptr_t address, std::size_t bytes); void GuestArenaUnmapAlias_nid_postfix(void* alias); #endif +bool GuestArenaBeginHostWrite_nid_postfix(void* pointer, std::size_t bytes); +void GuestArenaEndHostWrite_nid_postfix(void* pointer, std::size_t bytes); } +class HostWrite { +public: + HostWrite(void* pointer, std::size_t bytes) : pointer(pointer), bytes(bytes), open(GuestArenaBeginHostWrite_nid_postfix(pointer, bytes)) {} + ~HostWrite() { + if (open) GuestArenaEndHostWrite_nid_postfix(pointer, bytes); + } + HostWrite(const HostWrite&) = delete; + HostWrite& operator=(const HostWrite&) = delete; + bool Open() const { return open; } + +private: + void* pointer; + std::size_t bytes; + bool open; +}; + } #endif diff --git a/core/libs/prx/libc/include/WindowsMappings.hpp b/core/libs/prx/libc/include/WindowsMappings.hpp index d2bf6c862..6fb6018c7 100644 --- a/core/libs/prx/libc/include/WindowsMappings.hpp +++ b/core/libs/prx/libc/include/WindowsMappings.hpp @@ -88,7 +88,7 @@ public: } const auto base = cursor + done; shared->aliases.push_back(base); - views.emplace(base, View{shared, protection, 0, false, owned, offset + done}); + views.emplace(base, View{shared, protection, 0, false, owned, offset + done, 0}); invalidate(*shared); } } @@ -115,6 +115,34 @@ public: return true; } + bool BeginHostWrite(std::uintptr_t address, std::size_t bytes) { + std::lock_guard lock(mutex); + const auto first = views.lower_bound(address & ~(pageBytes - 1)); + const auto end = address + bytes; + for (auto it = first; it != views.end() && it->first < end; ++it) { + if (!writable(it->second.protection)) return false; + } + for (auto it = first; it != views.end() && it->first < end; ++it) { + auto& view = it->second; + ++view.hostWrites; + invalidate(*view.page); + if (!view.armed) continue; + DWORD previous; + if (!VirtualProtect(reinterpret_cast(it->first), pageBytes, view.protection, &previous)) fail("open shared memory to a host write"); + view.armed = false; + } + return true; + } + + void EndHostWrite(std::uintptr_t address, std::size_t bytes) { + std::lock_guard lock(mutex); + const auto end = address + bytes; + for (auto it = views.lower_bound(address & ~(pageBytes - 1)); it != views.end() && it->first < end; ++it) { + --it->second.hostWrites; + invalidate(*it->second.page); + } + } + void* MapAlias(std::uintptr_t address, std::size_t bytes) { std::lock_guard lock(mutex); const auto refuse = [&](const char* reason) { @@ -190,7 +218,7 @@ public: if (clear) { for (const auto alias : view.page->aliases) { auto& other = views.at(alias); - if (!writable(other.protection) || other.armed) continue; + if (!writable(other.protection) || other.armed || other.hostWrites != 0) continue; DWORD previous; const DWORD protection = other.protection == PAGE_EXECUTE_READWRITE ? PAGE_EXECUTE_READ : PAGE_READONLY; if (!VirtualProtect(reinterpret_cast(alias), pageBytes, protection, &previous)) fail("arm shared memory write tracking"); @@ -236,6 +264,7 @@ private: bool armed; std::shared_ptr
section; std::uint64_t offset; + std::uint32_t hostWrites; }; void forgetClean(std::uintptr_t start, std::uintptr_t end) { auto it = cleanRanges.lower_bound(start); diff --git a/core/libs/prx/libc/src/GuestArena.cpp b/core/libs/prx/libc/src/GuestArena.cpp index dc87e282e..48f4709dd 100644 --- a/core/libs/prx/libc/src/GuestArena.cpp +++ b/core/libs/prx/libc/src/GuestArena.cpp @@ -191,4 +191,23 @@ bool GuestArenaWriteWatched_nid_postfix() { return Arena::Get().WriteWatched(); } +bool GuestArenaBeginHostWrite_nid_postfix(void* pointer, std::size_t bytes) { +#ifdef _WIN32 + return WindowsMappings::Get().BeginHostWrite(reinterpret_cast(pointer), bytes); +#else + (void)pointer; + (void)bytes; + return true; +#endif +} + +void GuestArenaEndHostWrite_nid_postfix(void* pointer, std::size_t bytes) { +#ifdef _WIN32 + WindowsMappings::Get().EndHostWrite(reinterpret_cast(pointer), bytes); +#else + (void)pointer; + (void)bytes; +#endif +} + } diff --git a/core/libs/prx/libkernel/Aio/src/Aio.cpp b/core/libs/prx/libkernel/Aio/src/Aio.cpp index baff62d79..b592c1643 100644 --- a/core/libs/prx/libkernel/Aio/src/Aio.cpp +++ b/core/libs/prx/libkernel/Aio/src/Aio.cpp @@ -8,6 +8,7 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/GuestArena.hpp" #include "prx/libkernel/KernelErrors.hpp" #ifdef _WIN32 @@ -49,6 +50,11 @@ void SetState(std::int32_t id, std::int32_t state) { } std::int64_t NativePread(std::int32_t fd, void* buf, std::size_t nbyte, std::int64_t offset) { + const GuestArena::HostWrite destination(buf, nbyte); + if (!destination.Open()) { + errno = EFAULT; + return -1; + } #ifdef _WIN32 if (nbyte > static_cast(std::numeric_limits::max())) { throw std::runtime_error("sceKernelAioSubmitReadCommands: nbytes exceeds platform limit"); @@ -110,6 +116,7 @@ int SubmitCommands(KernelAioRwRequest* req, std::int32_t size, bool write, std:: const int error = errno; req[i].result->return_value = static_cast(SCE_KERNEL_ERROR_EIO); if (error == EBADF) req[i].result->return_value = static_cast(SCE_KERNEL_ERROR_EBADF); + if (error == EFAULT) req[i].result->return_value = static_cast(SCE_KERNEL_ERROR_EFAULT); req[i].result->state = AioAborted; aborted = true; } else { diff --git a/core/libs/prx/libkernel/Apr/src/Apr.cpp b/core/libs/prx/libkernel/Apr/src/Apr.cpp index 0c19fdbb0..e2f11ae0e 100644 --- a/core/libs/prx/libkernel/Apr/src/Apr.cpp +++ b/core/libs/prx/libkernel/Apr/src/Apr.cpp @@ -2,6 +2,7 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/GuestArena.hpp" #include "prx/libkernel/Apr/include/AprCommandBuffer.hpp" #include "prx/libkernel/File/include/NativeStat.hpp" #include "prx/libkernel/Equeue/Equeue.hpp" @@ -128,8 +129,12 @@ void _readFile(const Apr::ReadFileCommand& command) { std::ifstream stream(file.path, std::ios::binary); if (!stream) throw std::runtime_error("APR: cannot open " + file.path.string()); stream.seekg(static_cast(command.offset)); + const GuestArena::HostWrite destination(reinterpret_cast(command.destination), command.size); + if (!destination.Open()) throw std::runtime_error("APR: the read destination of " + file.path.string() + " is not writable guest memory"); stream.read(reinterpret_cast(command.destination), static_cast(command.size)); if (stream.bad()) throw std::runtime_error("APR: read failed for " + file.path.string()); + const auto read = static_cast(stream.gcount()); + if (read != command.size) throw std::runtime_error("APR: read of " + file.path.string() + " at offset " + std::to_string(command.offset) + " returned " + std::to_string(read) + " of " + std::to_string(command.size) + " bytes"); } void _writeAddress(const Apr::WriteAddressCommand& command) { diff --git a/core/libs/prx/libkernel/File/src/Open.cpp b/core/libs/prx/libkernel/File/src/Open.cpp index 469c4ad12..49d2b44cb 100644 --- a/core/libs/prx/libkernel/File/src/Open.cpp +++ b/core/libs/prx/libkernel/File/src/Open.cpp @@ -1,6 +1,7 @@ #include "prx/libkernel/File/include/FileFlags.hpp" #include "prx/libkernel/File/include/NativeStat.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/GuestArena.hpp" #include "prx/libkernel/File/include/File.hpp" #include "prx/libkernel/File/include/DirectoryDescriptor.hpp" #include "prx/libkernel/KernelErrors.hpp" @@ -125,7 +126,9 @@ std::int64_t APS5_VABI sceKernelRead(int d, void* buf, std::size_t nbytes) { if (buf == nullptr) { throw std::invalid_argument(std::string(__func__) + ": buf is null"); } - auto n = NativeRead(d, buf, nbytes); + const GuestArena::HostWrite destination(buf, nbytes); + if (!destination.Open()) errno = EFAULT; + auto n = destination.Open() ? NativeRead(d, buf, nbytes) : -1; if (n < 0) { throw std::runtime_error(std::string(__func__) + ": read failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno)); } diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index 0846da2ae..75696033a 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -3,6 +3,7 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/GuestArena.hpp" #include "prx/libkernel/File/include/File.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" #include "prx/libkernel/File/include/NativeStat.hpp" @@ -241,7 +242,9 @@ int64_t APS5_VABI pread_nid_postfix(int d, void* buf, size_t nbytes, int64_t off if (offset < 0) { APS5_INVALID_ARG_EX; } - auto n = NativePread(d, buf, nbytes, offset); + const GuestArena::HostWrite destination(buf, nbytes); + if (!destination.Open()) errno = EFAULT; + auto n = destination.Open() ? NativePread(d, buf, nbytes, offset) : -1; if (n < 0) { throw std::runtime_error(std::string(__func__) + ": pread failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno)); } @@ -398,6 +401,8 @@ int64_t APS5_VABI sceKernelPwrite(int d, const void* buf, size_t nbytes, int64_t int64_t APS5_VABI sceKernelPread(int d, void* buf, size_t nbytes, int64_t offset) { if (buf == nullptr && nbytes != 0) return SceErrorFromErrno(GUEST_EFAULT); if (offset < 0) return SceErrorFromErrno(GUEST_EINVAL); + const GuestArena::HostWrite destination(buf, nbytes); + if (!destination.Open()) return SceErrorFromErrno(GUEST_EFAULT); const auto result = NativePread(d, buf, nbytes, offset); return result < 0 ? SceErrorFromErrno(errno) : result; } diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index 94fd6eb02..c5bcc5d5a 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -3,10 +3,16 @@ #include "prx/libc/include/GuestHeap.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" +#include "prx/libkernel/File/include/FileFlags.hpp" +#include "prx/libkernel/KernelErrors.hpp" #include #include "SceTypes.hpp" +#include #include #include +#include +#include +#include #include #include #include @@ -38,6 +44,14 @@ int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64 int APS5_VABI sceKernelReleaseDirectMemory(std::int64_t, std::size_t); int APS5_VABI sceKernelReserveVirtualRange(void**, std::size_t, int, std::size_t); int APS5_VABI sceKernelMemoryPoolReserve(void*, std::size_t, std::size_t, int, void**); +int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); +int APS5_VABI sceKernelClose(int); +std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t); +std::int64_t APS5_VABI sceKernelPread(int, void*, std::size_t, std::int64_t); +int APS5_VABI sceKernelAioInitializeImpl(void*, std::int32_t); +int APS5_VABI sceKernelAioSubmitReadCommands(KernelAioRwRequest*, std::int32_t, std::int32_t, std::int32_t*); +int APS5_VABI sceKernelAioWaitRequest(std::int32_t, std::int32_t*, std::uint32_t*); +int APS5_VABI sceKernelAioDeleteRequest(std::int32_t, std::int32_t*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -274,6 +288,73 @@ static void CheckSharedWriteTracking() { #endif } +static void CheckReadsIntoSharedWriteTracking() { +#ifdef _WIN32 + constexpr std::size_t page = 0x4000; + const auto path = std::filesystem::path("anyps5-tracked-read-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".bin"); + std::vector seed(page * 2); + for (std::size_t i = 0; i < seed.size(); ++i) seed[i] = static_cast(i * 7 + 1); + { + std::ofstream stream(path, std::ios::binary); + stream.write(seed.data(), static_cast(seed.size())); + } + std::int64_t phys = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0); + void* mapped = nullptr; + Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0, phys, 0) == 0); + auto* bytes = static_cast(mapped); + const auto collect = [&] { + std::array pages{}; + std::size_t count = pages.size(); + Require(GuestArena::GuestArenaCollectWrites_nid_postfix(reinterpret_cast(mapped), page * 2, pages.data(), &count, true)); + return count; + }; + const int fd = sceKernelOpen(path.string().c_str(), SCE_KERNEL_O_RDONLY, 0); + Require(fd >= 0); + collect(); + Require(collect() == 0); + Require(sceKernelPread(fd, mapped, page * 2, 0) == static_cast(page * 2)); + Require(std::memcmp(bytes, seed.data(), page * 2) == 0); + Require(collect() == 8); + const int sequential = sceKernelOpen(path.string().c_str(), SCE_KERNEL_O_RDONLY, 0); + Require(sequential >= 0); + Require(sceKernelRead(sequential, bytes + page, page) == static_cast(page)); + Require(sceKernelClose(sequential) == 0); + Require(std::memcmp(bytes + page, seed.data(), page) == 0); + Require(collect() == 4); + Require(sceKernelAioInitializeImpl(nullptr, 0) == 0); + KernelAioResult result{-1, 0}; + KernelAioRwRequest request{static_cast(page), page, bytes, &result, fd}; + std::int32_t id = 0; + std::int32_t state = 0; + Require(sceKernelAioSubmitReadCommands(&request, 1, 0, &id) == 0); + Require(sceKernelAioWaitRequest(id, &state, nullptr) == 0); + Require(result.state == 3 && result.return_value == static_cast(page)); + Require(std::memcmp(bytes, seed.data() + page, page) == 0); + Require(collect() == 4); + std::int32_t deleted = -1; + Require(sceKernelAioDeleteRequest(id, &deleted) == 0); + Require(sceKernelMprotect(mapped, page * 2, 1) == 0); + bool refused = false; + try { + sceKernelPread(fd, mapped, page, 0); + } catch (const std::exception&) { + refused = true; + } + Require(refused); + KernelAioResult refusedResult{-1, 0}; + KernelAioRwRequest refusedRequest{0, page, bytes, &refusedResult, fd}; + Require(sceKernelAioSubmitReadCommands(&refusedRequest, 1, 0, &id) == 0); + Require(sceKernelAioWaitRequest(id, &state, nullptr) == 0); + Require(refusedResult.return_value == static_cast(SCE_KERNEL_ERROR_EFAULT)); + Require(sceKernelAioDeleteRequest(id, &deleted) == 0); + Require(sceKernelClose(fd) == 0); + Require(sceKernelMunmap(mapped, page * 2) == 0); + Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); + std::filesystem::remove(path); +#endif +} + #if defined(__linux__) using PageRuns = std::vector>; @@ -457,6 +538,7 @@ int main() { CheckSharedDirectMemoryLifecycle(); CheckHeapAfterMappingReuse(); CheckSharedWriteTracking(); + CheckReadsIntoSharedWriteTracking(); #if defined(__linux__) CheckWriteWatch(); CheckDirectMemoryWriteWatch();