From 91311f1bf4e076ca211174f2de4deea308d9bb97 Mon Sep 17 00:00:00 2001 From: XatiX Date: Sun, 1 Nov 2026 07:43:01 +0000 Subject: [PATCH] fix(libkernel): forward socket calls on sceNetSocket descriptors to libSceNet A title can create a socket with sceNetSocket and then call bind, getsockname, sendto or recvfrom from libkernel, which does not hold that descriptor, so the call failed with EBADF. PPSA06866's RakNet does this, its local server fails to start and the load after difficulty selection never ends. libSceNet now registers these four calls with libkernel, as it does for setsockopt and getsockopt. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceNet/CMakeLists.txt | 11 +++ core/libs/prx/libSceNet/Export.cpp | 36 ++++++++ .../libkernel/Socket/include/SocketPoll.hpp | 7 ++ core/libs/prx/libkernel/Socket/src/Udp.cpp | 18 ++++ core/libs/tests/GuestNetKernelForwards.cpp | 82 +++++++++++++++++++ 5 files changed, 154 insertions(+) create mode 100644 core/libs/tests/GuestNetKernelForwards.cpp diff --git a/core/libs/prx/libSceNet/CMakeLists.txt b/core/libs/prx/libSceNet/CMakeLists.txt index 54c026e47c..6076a98df3 100644 --- a/core/libs/prx/libSceNet/CMakeLists.txt +++ b/core/libs/prx/libSceNet/CMakeLists.txt @@ -124,6 +124,17 @@ if(BUILD_TESTING) set_tests_properties(guest_sce_net_keepalive PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_net_kernel_forwards_tests ${LIBS_INCLUDE_DIR}/tests/GuestNetKernelForwards.cpp) + target_include_directories(guest_net_kernel_forwards_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_net_kernel_forwards_tests PRIVATE libSceNet libkernel libc) + configure_windows_unwind(guest_net_kernel_forwards_tests) + add_test(NAME guest_sce_net_kernel_forwards COMMAND guest_net_kernel_forwards_tests) + set_tests_properties(guest_sce_net_kernel_forwards PROPERTIES TIMEOUT 20) + if(WIN32) + target_link_libraries(guest_net_kernel_forwards_tests PRIVATE ws2_32) + set_tests_properties(guest_sce_net_kernel_forwards PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_datagram_receive_tests ${LIBS_INCLUDE_DIR}/tests/GuestDatagramReceive.cpp) target_include_directories(guest_datagram_receive_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_datagram_receive_tests PRIVATE libSceNet libkernel libc) diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 958a4cfaf1..389b3652ca 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -928,6 +928,42 @@ bool GetSocketOptionForKernel(int descriptor, int level, int option, void* value const bool g_socketOptionGetterRegistered = (KernelSetSocketOptionGetter_nid_no_patch(&GetSocketOptionForKernel), true); +bool OwnsSocket(int descriptor) { + std::lock_guard lk(g_mutex); + return g_socks.count(descriptor) != 0; +} + +int ErrorOf(std::int64_t result) { + return result < 0 ? *errno_slot() : 0; +} + +const KernelSocketPoll::Forwards g_socketForwards{ + [](int descriptor, const void* address, std::uint32_t length, int* error) { + if (!OwnsSocket(descriptor)) return false; + *error = ErrorOf(sceNetBind_nid_postfix(descriptor, address, length)); + return true; + }, + [](int descriptor, void* address, std::uint32_t* length, int* error) { + if (!OwnsSocket(descriptor)) return false; + *error = ErrorOf(sceNetGetsockname(descriptor, address, length)); + return true; + }, + [](int descriptor, const void* buffer, std::uint64_t length, int flags, const void* address, std::uint32_t addressLength, std::int64_t* result, int* error) { + if (!OwnsSocket(descriptor)) return false; + *result = sceNetSendto(descriptor, buffer, static_cast(length), flags, address, addressLength); + *error = ErrorOf(*result); + return true; + }, + [](int descriptor, void* buffer, std::uint64_t length, int flags, void* address, std::uint32_t* addressLength, std::int64_t* result, int* error) { + if (!OwnsSocket(descriptor)) return false; + *result = sceNetRecvfrom(descriptor, buffer, static_cast(length), flags, address, addressLength); + *error = ErrorOf(*result); + return true; + }, +}; + +const bool g_socketForwardsRegistered = (KernelSetSocketForwards_nid_no_patch(&g_socketForwards), true); + constexpr int GuestFcntlGetStatus = 3; constexpr int GuestFcntlSetStatus = 4; constexpr int GuestReadWrite = 2; diff --git a/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp b/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp index 55956c9294..15b5fdbfd9 100644 --- a/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp +++ b/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp @@ -21,6 +21,12 @@ using Closer = bool (*)(int descriptor, int* error); using OptionSetter = bool (*)(int descriptor, int level, int option, const void* value, std::uint32_t length, int* error); using OptionGetter = bool (*)(int descriptor, int level, int option, void* value, std::uint32_t* length, int* error); using StatusControl = bool (*)(int descriptor, int command, int flags, int* result, int* error); +struct Forwards { + bool (*bind)(int descriptor, const void* address, std::uint32_t length, int* error); + bool (*getsockname)(int descriptor, void* address, std::uint32_t* length, int* error); + bool (*sendto)(int descriptor, const void* buffer, std::uint64_t length, int flags, const void* address, std::uint32_t addressLength, std::int64_t* result, int* error); + bool (*recvfrom)(int descriptor, void* buffer, std::uint64_t length, int flags, void* address, std::uint32_t* addressLength, std::int64_t* result, int* error); +}; struct NativeOwner { int (*transfer)(NativeOwner* owner, bool send, void* buffer, int length) = nullptr; }; @@ -28,6 +34,7 @@ struct NativeOwner { using NativeLookup = bool (*)(int descriptor, std::shared_ptr* owner, std::uintptr_t* native); } +extern "C" void KernelSetSocketForwards_nid_no_patch(const KernelSocketPoll::Forwards* forwards); extern "C" void KernelSetSocketPoller_nid_no_patch(KernelSocketPoll::Poller poller); extern "C" void KernelSetSocketCloser_nid_no_patch(KernelSocketPoll::Closer closer); extern "C" bool KernelCloseSocket_nid_no_patch(int descriptor, int* error); diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index d652759f0e..9b62ca5f82 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -270,6 +270,10 @@ int Install(std::shared_ptr socket) { } return number; } +std::atomic socketForwards{nullptr}; +const KernelSocketPoll::Forwards* ForwardsFor(int descriptor) { + return GuestSockets::IsOpen(descriptor) ? nullptr : socketForwards.load(std::memory_order_acquire); +} std::shared_ptr Lookup(int descriptor) { std::lock_guard lock(socketsMutex); const auto found = sockets.find(descriptor); @@ -652,6 +656,10 @@ extern "C" void KernelReleaseSocketNumber_nid_no_patch(int number) { ReleaseNumber(number); } +extern "C" void KernelSetSocketForwards_nid_no_patch(const KernelSocketPoll::Forwards* forwards) { + socketForwards.store(forwards, std::memory_order_release); +} + namespace { #ifdef _WIN32 using NativePollDescriptor = WSAPOLLFD; @@ -1545,6 +1553,8 @@ std::int64_t APS5_VABI recv_nid_postfix(int descriptor, void* buffer, std::uint6 return result < 0 ? Fail(NativeError()) : result; } int APS5_VABI bind_nid_postfix(int descriptor, const void* address, std::uint32_t length) { + int netError = 0; + if (const auto* forwards = ForwardsFor(descriptor); forwards != nullptr && forwards->bind(descriptor, address, length, &netError)) return netError == 0 ? 0 : Fail(netError); if (const int error = AddressLengthError(address, length)) return Fail(error); const auto socket = Lookup(descriptor); if (!socket) return -1; @@ -1608,6 +1618,8 @@ int CopyAddressOut(const sockaddr_storage& native, bool unnamed, void* address, int APS5_VABI getsockname_nid_postfix(int descriptor, void* address, std::uint32_t* length) { std::uint32_t capacity = 0; if (!GuestCopyIn_nid_no_patch(&capacity, length, sizeof(capacity))) return Fail(14); + int netError = 0; + if (const auto* forwards = ForwardsFor(descriptor); forwards != nullptr && forwards->getsockname(descriptor, address, length, &netError)) return netError == 0 ? 0 : Fail(netError); const auto socket = Lookup(descriptor); if (!socket) return -1; if (socket->family == GuestUnix) return CopyAddressOut({}, true, address, length, capacity); @@ -1674,6 +1686,9 @@ int APS5_VABI _ioctl_nid_postfix(int descriptor, std::uint64_t request, void* ar } std::int64_t APS5_VABI sendto_nid_postfix(int descriptor, const void* buffer, std::uint64_t length, int flags, const void* address, std::uint32_t addressLength) { + std::int64_t netResult = 0; + int netError = 0; + if (const auto* forwards = ForwardsFor(descriptor); forwards != nullptr && forwards->sendto(descriptor, buffer, length, flags, address, addressLength, &netResult, &netError)) return netError == 0 ? netResult : Fail(netError); std::array destinationCopy; if (address) { if (const int error = AddressLengthError(address, addressLength)) return Fail(error); @@ -1711,6 +1726,9 @@ std::int64_t APS5_VABI sendto_nid_postfix(int descriptor, const void* buffer, st } std::int64_t APS5_VABI recvfrom_nid_postfix(int descriptor, void* buffer, std::uint64_t length, int flags, void* address, std::uint32_t* addressLength) { + std::int64_t netResult = 0; + int netError = 0; + if (const auto* forwards = ForwardsFor(descriptor); forwards != nullptr && forwards->recvfrom(descriptor, buffer, length, flags, address, addressLength, &netResult, &netError)) return netError == 0 ? netResult : Fail(netError); std::uint32_t capacity = 0; if (addressLength && !GuestCopyIn_nid_no_patch(&capacity, addressLength, sizeof(capacity))) return Fail(14); const auto socket = Lookup(descriptor); diff --git a/core/libs/tests/GuestNetKernelForwards.cpp b/core/libs/tests/GuestNetKernelForwards.cpp new file mode 100644 index 0000000000..9c24837fe8 --- /dev/null +++ b/core/libs/tests/GuestNetKernelForwards.cpp @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include "prx/libSceNet/libSceNet.hpp" +#include "prx/libkernel/libkernel.hpp" + +static void Require(bool condition) { + if (!condition) std::abort(); +} + +static bool Failed(std::int64_t result, int error) { + return result == -1 && *__error_nid_postfix() == error; +} + +static void ClearError() { + *__error_nid_postfix() = 0; +} + +static void CheckForwardedDatagram() { + const int receiver = sceNetSocket("forward-receiver", 2, 2, 17); + const int sender = sceNetSocket("forward-sender", 2, 2, 17); + Require(receiver >= 0 && sender >= 0); + std::array local{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(receiver, local.data(), local.size()) == 0); + Require(bind_nid_postfix(sender, local.data(), local.size()) == 0); + std::array receiverAddress{}; + std::array senderAddress{}; + std::uint32_t size = receiverAddress.size(); + Require(getsockname_nid_postfix(receiver, receiverAddress.data(), &size) == 0 && size == receiverAddress.size()); + Require((receiverAddress[2] | receiverAddress[3]) != 0); + size = senderAddress.size(); + Require(getsockname_nid_postfix(sender, senderAddress.data(), &size) == 0 && size == senderAddress.size()); + const char payload[] = "ping"; + Require(sendto_nid_postfix(sender, payload, sizeof(payload), 0, receiverAddress.data(), receiverAddress.size()) == sizeof(payload)); + char received[8]{}; + std::array from{}; + size = from.size(); + Require(recvfrom_nid_postfix(receiver, received, sizeof(received), 0, from.data(), &size) == sizeof(payload)); + Require(std::strcmp(received, payload) == 0 && size == from.size()); + Require(from[2] == senderAddress[2] && from[3] == senderAddress[3]); + Require(sceNetSocketClose(receiver) == 0 && sceNetSocketClose(sender) == 0); +} + +static void CheckForwardedErrors() { + const int socket = sceNetSocket("forward-errors", 2, 2, 17); + Require(socket >= 0); + std::array local{16, 2, 0, 0, 127, 0, 0, 1}; + ClearError(); + Require(Failed(bind_nid_postfix(socket, local.data(), 1), 22)); + Require(bind_nid_postfix(socket, local.data(), local.size()) == 0); + char received[8]{}; + ClearError(); + Require(Failed(recvfrom_nid_postfix(socket, received, sizeof(received), 0x80, nullptr, nullptr), 35)); + ClearError(); + Require(Failed(getsockname_nid_postfix(socket, nullptr, nullptr), 22)); + ClearError(); + Require(Failed(sendto_nid_postfix(socket, "x", 1, 0, local.data(), 1), 22)); + Require(sceNetSocketClose(socket) == 0); +} + +static void CheckUnknownDescriptor() { + constexpr int unknown = 0x7ffffff0; + std::array local{16, 2, 0, 0, 127, 0, 0, 1}; + std::uint32_t size = local.size(); + char byte = 0; + ClearError(); + Require(Failed(bind_nid_postfix(unknown, local.data(), local.size()), 9)); + ClearError(); + Require(Failed(getsockname_nid_postfix(unknown, local.data(), &size), 9)); + ClearError(); + Require(Failed(sendto_nid_postfix(unknown, &byte, 1, 0, local.data(), local.size()), 9)); + ClearError(); + Require(Failed(recvfrom_nid_postfix(unknown, &byte, 1, 0, nullptr, nullptr), 9)); +} + +int main() { + CheckForwardedDatagram(); + CheckForwardedErrors(); + CheckUnknownDescriptor(); + return 0; +}