From 6ce6850d12a8b55d80381095ca23736f9dd7116c Mon Sep 17 00:00:00 2001 From: simonbronk Date: Sat, 17 Oct 2026 17:23:32 +0200 Subject: [PATCH] fix(libSceNet): guard recvfrom sender outputs before receiving AI-assisted: OpenAI Codex. Windows native checks: 17 related CTests and 10 regression repetitions pass. No affected title verified. --- core/libs/prx/libSceNet/Export.cpp | 6 ++++ core/libs/tests/GuestSocketHostWrite.cpp | 45 ++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 8fd85787623..e5537bf33df 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -987,6 +987,8 @@ int64_t APS5_VABI sceNetRecvfrom(int s, void* buf, size_t len, int flags, void* if (it == g_socks.end()) return fail(NET_EBADF); socket = it->second; } + const GuestArena::HostWrite lengthOutput(from == nullptr ? nullptr : fromlen, from == nullptr ? 0 : sizeof(*fromlen)); + if (!lengthOutput.Open()) return fail(NET_EFAULT); #ifdef _WIN32 if (const int wait = wait_for_local_address(s, socket, len == 0)) { if (wait != RECEIVE_NOTHING) return fail(wait); @@ -994,6 +996,10 @@ int64_t APS5_VABI sceNetRecvfrom(int s, void* buf, size_t len, int flags, void* return 0; } #endif + const auto addressBytes = from != nullptr && socket.type == NET_SOCK_DGRAM ? + std::min(*fromlen, socket.family == NET_AF_INET ? 16u : 28u) : 0u; + const GuestArena::HostWrite addressOutput(addressBytes == 0 ? nullptr : from, addressBytes); + if (!addressOutput.Open()) return fail(NET_EFAULT); const GuestArena::HostWrite destination(len == 0 ? nullptr : buf, len); if (!destination.Open()) return fail(NET_EFAULT); sockaddr_storage peer{}; diff --git a/core/libs/tests/GuestSocketHostWrite.cpp b/core/libs/tests/GuestSocketHostWrite.cpp index 06e23b986e8..982eb625d70 100644 --- a/core/libs/tests/GuestSocketHostWrite.cpp +++ b/core/libs/tests/GuestSocketHostWrite.cpp @@ -243,6 +243,51 @@ 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"); } + if (api == 5) { + for (const bool readOnlyAddress : {true, false}) { + auto* address = static_cast(outputMapping) + 16; + auto* length = reinterpret_cast(static_cast(outputMapping) + page + 16); + { + const GuestArena::HostWrite addressDestination(address, 32); + const GuestArena::HostWrite lengthDestination(length, sizeof(*length)); + Require(addressDestination.Open() && lengthDestination.Open(), "initialize refused sender outputs"); + std::memset(address, 0xa5, 32); + *length = 32; + } + void* refused = static_cast(outputMapping) + (readOnlyAddress ? 0 : page); + Require(sceKernelMprotect(refused, page, 1) == 0, "make one sender output read-only"); + send(); + waitForPacket(); + std::array recovery{}; + Require(receive(recovery.data(), recovery.size(), 0, address, length) == static_cast(0x8041010eu) && *error == 14, + "read-only sender output fails with EFAULT"); + Require(*length == 32 && std::all_of(address, address + 32, [](unsigned char value) { return value == 0xa5; }), + "refused sender outputs remain unchanged"); + Require(receive(recovery.data(), recovery.size(), 0, nullptr, nullptr) == sizeof(payload) && + std::memcmp(recovery.data(), payload, sizeof(payload)) == 0, "refused sender output preserves queued data"); + Require(sceKernelMprotect(refused, page, 3) == 0, "restore sender output permission"); + } + auto* address = static_cast(outputMapping) + 16; + Require(sceKernelMprotect(outputMapping, page, 1) == 0, "make an unused sender address read-only"); + std::uint32_t zero = 0; + std::array recovery{}; + send(); + waitForPacket(); + Require(receive(recovery.data(), recovery.size(), 0, address, &zero) == sizeof(payload) && zero == 0 && + std::memcmp(recovery.data(), payload, sizeof(payload)) == 0, "zero-capacity sender address needs no write access"); + Require(sceKernelMprotect(outputMapping, page, 3) == 0, "restore unused sender address permission"); + address = static_cast(outputMapping) + page - senderAddress.size(); + Require(sceKernelMprotect(static_cast(outputMapping) + page, page, 1) == 0, + "make bytes beyond the sender address read-only"); + std::uint32_t oversized = 32; + send(); + waitForPacket(); + Require(receive(recovery.data(), recovery.size(), 0, address, &oversized) == sizeof(payload) && + oversized == senderAddress.size() && std::memcmp(address, senderAddress.data(), oversized) == 0, + "guard only the sender address bytes the receive writes"); + Require(sceKernelMprotect(static_cast(outputMapping) + page, page, 3) == 0, + "restore bytes beyond the sender address"); + } 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");