From fdbce5fbf2eed80189e3f23a722ccbd568fcdb00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 28 Oct 2026 09:21:42 +0100 Subject: [PATCH] sceNetInet: The firmware's stack check, and GetPspError as it computes it Every export of pspnet_inet.prx but the address conversions starts with sceKernelCheckThreadStack(), and fails without doing anything if the thread has less than 0x1000 bytes of stack free (0xFE0 for socket()): the socket calls with -1, without setting errno, and Init, Term, GetErrno, GetPspError and SocketAbort with 0x80410005. Each function's limit plus its own stack frame comes to that. pspautotests net/inet/errnothread shows it on a thread with a 4 KB stack. GetPspError is 0x80010000 | errno, and 0 for no error, rather than a table of guesses, so 0x80010001 on a thread with no errno record yet (net/inet/errnothread). Co-Authored-By: Claude Opus 5.5 --- Core/HLE/NetInetConstants.cpp | 86 ----------------------------------- Core/HLE/NetInetConstants.h | 2 +- Core/HLE/sceNetInet.cpp | 53 +++++++++++++++++++-- pspautotests | 2 +- test.py | 2 +- 5 files changed, 51 insertions(+), 94 deletions(-) diff --git a/Core/HLE/NetInetConstants.cpp b/Core/HLE/NetInetConstants.cpp index 4d2beb5e4e..399857f3f0 100644 --- a/Core/HLE/NetInetConstants.cpp +++ b/Core/HLE/NetInetConstants.cpp @@ -945,92 +945,6 @@ const char *convertInetErrno2str(int error) { } } -// FIXME: Some of this might be wrong -int convertInetErrno2PSPError(int error) { - switch (error) { - case ERROR_INET_EINTR: - return SCE_KERNEL_ERROR_ERRNO_DEVICE_BUSY; - case ERROR_INET_EACCES: - return SCE_KERNEL_ERROR_ERRNO_READ_ONLY; - case ERROR_INET_EFAULT: - return SCE_KERNEL_ERROR_ERRNO_ADDR_OUT_OF_MAIN_MEM; - case ERROR_INET_EINVAL: - return SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT; - case ERROR_INET_ENOSPC: - return SCE_KERNEL_ERROR_ERRNO_NO_MEMORY; - case ERROR_INET_EPIPE: - return SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND; - case ERROR_INET_ENOMSG: - return SCE_KERNEL_ERROR_ERRNO_NO_MEDIA; - case ERROR_INET_ENOLINK: - return SCE_KERNEL_ERROR_ERRNO_DEVICE_NOT_FOUND; - case ERROR_INET_EPROTO: - return SCE_KERNEL_ERROR_ERRNO_FILE_PROTOCOL; - case ERROR_INET_EBADMSG: - return SCE_KERNEL_ERROR_ERRNO_INVALID_MEDIUM; - case ERROR_INET_EOPNOTSUPP: - return SCE_KERNEL_ERROR_ERRNO_FUNCTION_NOT_SUPPORTED; - case ERROR_INET_EPFNOSUPPORT: - return SCE_KERNEL_ERROR_ERRNO_FUNCTION_NOT_SUPPORTED; - case ERROR_INET_ECONNRESET: - return SCE_KERNEL_ERROR_ERRNO_CONNECTION_RESET; - case ERROR_INET_ENOBUFS: - return SCE_KERNEL_ERROR_ERRNO_NO_FREE_BUF_SPACE; - case ERROR_INET_EAFNOSUPPORT: - return SCE_KERNEL_ERROR_ERRNO_FUNCTION_NOT_SUPPORTED; - case ERROR_INET_EPROTOTYPE: - return SCE_KERNEL_ERROR_ERRNO_FILE_PROTOCOL; - case ERROR_INET_ENOTSOCK: - return SCE_KERNEL_ERROR_ERRNO_INVALID_FILE_DESCRIPTOR; - case ERROR_INET_ENOPROTOOPT: - return SCE_KERNEL_ERROR_ERRNO_FILE_PROTOCOL; - case ERROR_INET_ESHUTDOWN: - return SCE_KERNEL_ERROR_ERRNO_CLOSED; - case ERROR_INET_ECONNREFUSED: - return SCE_KERNEL_ERROR_ERRNO_FILE_ALREADY_EXISTS; - case ERROR_INET_EADDRINUSE: - return SCE_KERNEL_ERROR_ERRNO_FILE_ADDR_IN_USE; - case ERROR_INET_ECONNABORTED: - return SCE_KERNEL_ERROR_ERRNO_CONNECTION_ABORTED; - case ERROR_INET_ENETUNREACH: - return SCE_KERNEL_ERROR_ERRNO_DEVICE_NOT_FOUND; - case ERROR_INET_ENETDOWN: - return SCE_KERNEL_ERROR_ERRNO_CLOSED; - case ERROR_INET_ETIMEDOUT: - return SCE_KERNEL_ERROR_ERRNO_FILE_TIMEOUT; - case ERROR_INET_EHOSTDOWN: - return SCE_KERNEL_ERROR_ERRNO_CLOSED; - case ERROR_INET_EHOSTUNREACH: - return SCE_KERNEL_ERROR_ERRNO_DEVICE_NOT_FOUND; - case ERROR_INET_EALREADY: - return SCE_KERNEL_ERROR_ERRNO_ALREADY; - case ERROR_INET_EMSGSIZE: - return SCE_KERNEL_ERROR_ERRNO_FILE_IS_TOO_BIG; - case ERROR_INET_EPROTONOSUPPORT: - return SCE_KERNEL_ERROR_ERRNO_FUNCTION_NOT_SUPPORTED; - case ERROR_INET_ESOCKTNOSUPPORT: - return SCE_KERNEL_ERROR_ERRNO_FUNCTION_NOT_SUPPORTED; - case ERROR_INET_EADDRNOTAVAIL: - return SCE_KERNEL_ERROR_ERRNO_ADDRESS_NOT_AVAILABLE; - case ERROR_INET_ENETRESET: - return SCE_KERNEL_ERROR_ERRNO_CONNECTION_RESET; - case ERROR_INET_EISCONN: - return SCE_KERNEL_ERROR_ERRNO_ALREADY; // SCE_KERNEL_ERROR_ERRNO_IS_ALREADY_CONNECTED; // UNO only check for 0x80010077 and 0x80010078 - case ERROR_INET_ENOTCONN: - return SCE_KERNEL_ERROR_ERRNO_NOT_CONNECTED; - case ERROR_INET_EAGAIN: - return SCE_KERNEL_ERROR_ERRNO_RESOURCE_UNAVAILABLE; // SCE_ERROR_ERRNO_EAGAIN; -#if !defined(_WIN32) - case ERROR_INET_EINPROGRESS: - return SCE_KERNEL_ERROR_ERRNO_IN_PROGRESS; -#endif - } - if (error != 0) - return hleLogError(Log::sceNet, error, "Unknown PSP Error Number (%d)", error); - return error; -} - - template std::string num2hex(I w, size_t hex_len = sizeof(I) << 1) { static const char* digits = "0123456789ABCDEF"; diff --git a/Core/HLE/NetInetConstants.h b/Core/HLE/NetInetConstants.h index 13497bbb3b..7342edb849 100644 --- a/Core/HLE/NetInetConstants.h +++ b/Core/HLE/NetInetConstants.h @@ -9,6 +9,7 @@ enum { ERROR_NET_INTERNAL = 0x80410002, ERROR_NET_INVALID_ARG = 0x80410003, ERROR_NET_NO_ENTRY = 0x80410004, + ERROR_NET_NOT_ENOUGH_STACK = 0x80410005, // pspnet_core ERROR_NET_CORE_NOT_TERMINATED = 0x80410101, @@ -325,6 +326,5 @@ int convertSockoptNamePSP2Host(int optname, int level); int convertSockoptNameHost2PSP(int optname, int level); std::string inetSockoptName2str(int optname, int level); int convertInetErrnoHost2PSP(int error); -int convertInetErrno2PSPError(int error); const char *convertInetErrno2str(int error); std::string convertNetError2str(uint32_t errorCode); diff --git a/Core/HLE/sceNetInet.cpp b/Core/HLE/sceNetInet.cpp index 4d6a021ebd..19d17d4d7a 100644 --- a/Core/HLE/sceNetInet.cpp +++ b/Core/HLE/sceNetInet.cpp @@ -94,6 +94,23 @@ static bool GetCallSocket(int socket, InetSocket **inetSock) { return g_socketManager.GetInetSocket(socket, inetSock); } +// Every call but the address conversions first checks that the thread has 0x1000 bytes of stack free +// (0xFE0 for socket()), and fails without doing anything if not: the socket calls with -1, without +// setting errno, and the others with 0x80410005 (pspautotests net/inet/errnothread). +static bool InetStackOk(u32 needed = 0x1000) { + const u32 stackStart = __KernelGetCurThreadStackStart(); + return stackStart == 0 || currentMIPS->r[MIPS_REG_SP] - stackStart >= needed; +} + +#define INET_CHECK_STACK_SOCKET(...) \ + if (!InetStackOk(__VA_ARGS__)) { \ + return hleLogError(Log::sceNet, -1, "not enough stack"); \ + } +#define INET_CHECK_STACK() \ + if (!InetStackOk()) { \ + return hleLogError(Log::sceNet, ERROR_NET_NOT_ENOUGH_STACK, "not enough stack"); \ + } + // Game memory that a host call will read or write directly. Fails with EFAULT rather than letting // the host run past the end of it. static bool InetValidRange(u32 ptr, u32 size, const char *func, SceUID threadID = __KernelGetCurThread()) { @@ -205,6 +222,7 @@ void __NetInetShutdown() { } static int sceNetInetInit() { + INET_CHECK_STACK(); if (netInetInited) return hleLogError(Log::sceNet, SCE_NET_INET_ERROR_ALREADY_INITIALIZED); g_inetLastErrno.clear(); @@ -213,12 +231,14 @@ static int sceNetInetInit() { } static int sceNetInetTerm() { + INET_CHECK_STACK(); INFO_LOG(Log::sceNet, "sceNetInetTerm()"); __NetInetShutdown(); return hleLogDebug(Log::sceNet, 0); } static int sceNetInetGetErrno(MIPSState *mips) { + INET_CHECK_STACK(); const auto it = g_inetLastErrno.find(__KernelGetCurThread()); if (it == g_inetLastErrno.end()) { return hleLogDebug(Log::sceNet, ERROR_NET_NO_SPACE, "no call on this thread yet"); @@ -233,11 +253,13 @@ static int sceNetInetGetErrno(MIPSState *mips) { } static int sceNetInetGetPspError(MIPSState *mips) { - int threadID = __KernelGetCurThread(); - const int lastErrno = g_inetLastErrno[threadID]; - - uint32_t error = convertInetErrno2PSPError(lastErrno); - return hleLogInfo(Log::sceNet, error, "returning %s converted to %08x at %08x", convertInetErrno2str(lastErrno), error, mips->pc); + INET_CHECK_STACK(); + // errno as an SCE error code, 0 staying 0. So a thread with no record yet gets 0x80010001 + // (pspautotests net/inet/errnothread). + const auto it = g_inetLastErrno.find(__KernelGetCurThread()); + const u32 lastErrno = it == g_inetLastErrno.end() ? (u32)ERROR_NET_NO_SPACE : (u32)it->second; + const u32 error = lastErrno == 0 ? 0 : (0x80010000 | (lastErrno & 0xFFFF)); + return hleLogDebug(Log::sceNet, error, "errno %s at %08x", convertInetErrno2str(lastErrno), mips->pc); } // The address parsers work like 4.4BSD's, which the host's may not (pspautotests net/inet/addr). @@ -427,6 +449,7 @@ static void WriteSockaddrToPSP(const SockAddrIN4 &saddr, u32 namePtr, u32 namele } static int sceNetInetGetpeername(int socket, u32 namePtr, u32 namelenPtr) { + INET_CHECK_STACK_SOCKET(); if (!Memory::IsValidAddress(namePtr) || !Memory::IsValidRange(namelenPtr, 4)) { UpdateErrnoFromHost(__KernelGetCurThread(), EFAULT, __FUNCTION__); return hleLogError(Log::sceNet, -1, "invalid arg"); @@ -454,6 +477,7 @@ static int sceNetInetGetpeername(int socket, u32 namePtr, u32 namelenPtr) { } static int sceNetInetGetsockname(int socket, u32 namePtr, u32 namelenPtr) { + INET_CHECK_STACK_SOCKET(); if (!Memory::IsValidAddress(namePtr) || !Memory::IsValidRange(namelenPtr, 4)) { UpdateErrnoFromHost(__KernelGetCurThread(), EFAULT, __FUNCTION__); return hleLogError(Log::sceNet, -1, "invalid arg"); @@ -899,6 +923,7 @@ static void __NetInetWaitPoll(u64 userdata, int cyclesLate) { // FIXME: nfds is number of fd(s) as in posix poll, or was it maximum fd value as in posix select? Star Wars Battlefront Renegade seems to set the nfds to 64, while Coded Arms Contagion is using 256 int sceNetInetSelect(int nfds, u32 readfdsPtr, u32 writefdsPtr, u32 exceptfdsPtr, u32 timeoutPtr) { + INET_CHECK_STACK_SOCKET(); InetThreadSeen(); for (u32 ptr : { readfdsPtr, writefdsPtr, exceptfdsPtr }) { if (ptr && !InetValidRange(ptr, sizeof(SceNetInetFdSet), __FUNCTION__)) { @@ -936,6 +961,7 @@ int sceNetInetSelect(int nfds, u32 readfdsPtr, u32 writefdsPtr, u32 exceptfdsPtr } int sceNetInetPoll(MIPSState *mips, u32 fdsPtr, u32 nfds, int timeout) { + INET_CHECK_STACK_SOCKET(); DEBUG_LOG(Log::sceNet, "UNTESTED sceNetInetPoll(%08x, %d, %i) at %08x", fdsPtr, nfds, timeout, mips->pc); InetThreadSeen(); if (nfds > PSP_NET_INET_FD_SETSIZE) @@ -960,6 +986,7 @@ int sceNetInetPoll(MIPSState *mips, u32 fdsPtr, u32 nfds, int timeout) { } static int sceNetInetRecv(int socket, u32 bufPtr, u32 bufLen, u32 flags) { + INET_CHECK_STACK_SOCKET(); InetSocket *inetSock; if (!GetCallSocket(socket, &inetSock)) { return InetBadSocket(socket, __FUNCTION__); @@ -985,6 +1012,7 @@ static int sceNetInetRecv(int socket, u32 bufPtr, u32 bufLen, u32 flags) { } static int sceNetInetSend(int socket, u32 bufPtr, u32 bufLen, u32 flags) { + INET_CHECK_STACK_SOCKET(); InetSocket *inetSock; if (!GetCallSocket(socket, &inetSock)) { return InetBadSocket(socket, __FUNCTION__); @@ -1012,6 +1040,7 @@ static int sceNetInetSend(int socket, u32 bufPtr, u32 bufLen, u32 flags) { } static int sceNetInetSocket(MIPSState *mips, int domain, int type, int protocol) { + INET_CHECK_STACK_SOCKET(0xFE0); INFO_LOG(Log::sceNet, "sceNetInetSocket(%d, %d, %d) at %08x - Socket: Domain = %s, Type = %s, Protocol = %s", domain, type, protocol, mips->pc, inetSocketDomain2str(domain).c_str(), inetSocketType2str(type).c_str(), inetSocketProto2str(protocol).c_str()); @@ -1202,6 +1231,7 @@ static int InetSetsockopt(MIPSState *mips, int socket, int level, int optname, u } static int sceNetInetSetsockopt(MIPSState *mips, int socket, int level, int optname, u32 optvalPtr, int optlen) { + INET_CHECK_STACK_SOCKET(); const int result = InetSetsockopt(mips, socket, level, optname, optvalPtr, optlen); if (result >= 0) { ClearInetErrno(); @@ -1210,6 +1240,7 @@ static int sceNetInetSetsockopt(MIPSState *mips, int socket, int level, int optn } static int sceNetInetGetsockopt(MIPSState *mips, int socket, int level, int optname, u32 optvalPtr, u32 optlenPtr) { + INET_CHECK_STACK_SOCKET(); DEBUG_LOG(Log::sceNet, "sceNetInetGetsockopt(%i, %i, %i, %08x, %08x) at %08x: Level = %s, OptName = %s", socket, level, optname, optvalPtr, optlenPtr, mips->pc, inetSockoptLevel2str(level).c_str(), inetSockoptName2str(optname, level).c_str()); @@ -1298,6 +1329,7 @@ static int sceNetInetGetsockopt(MIPSState *mips, int socket, int level, int optn } static int sceNetInetBind(MIPSState *mips, int socket, u32 namePtr, int namelen) { + INET_CHECK_STACK_SOCKET(); INFO_LOG(Log::sceNet, "sceNetInetBind(%i, %08x, %i) at %08x", socket, namePtr, namelen, mips->pc); InetSocket *inetSock; @@ -1421,6 +1453,7 @@ static InetTry TryConnectDone(SceUID threadID, const u32 *args) { } static int sceNetInetConnect(int socket, u32 sockAddrPtr, int sockAddrLen) { + INET_CHECK_STACK_SOCKET(); InetSocket *inetSock; if (!GetCallSocket(socket, &inetSock)) { return InetBadSocket(socket, __FUNCTION__); @@ -1483,6 +1516,7 @@ static int sceNetInetConnect(int socket, u32 sockAddrPtr, int sockAddrLen) { } static int sceNetInetListen(int socket, int backlog) { + INET_CHECK_STACK_SOCKET(); InetSocket *inetSock; if (!GetCallSocket(socket, &inetSock)) { return InetBadSocket(socket, __FUNCTION__); @@ -1558,6 +1592,7 @@ static InetTry TryAccept(SceUID threadID, const u32 *args) { } static int sceNetInetAccept(int socket, u32 addrPtr, u32 addrLenPtr) { + INET_CHECK_STACK_SOCKET(); InetSocket *inetSock; if (!GetCallSocket(socket, &inetSock)) { return InetBadSocket(socket, __FUNCTION__); @@ -1582,6 +1617,7 @@ static int sceNetInetAccept(int socket, u32 addrPtr, u32 addrLenPtr) { } static int sceNetInetShutdown(int socket, int how) { + INET_CHECK_STACK_SOCKET(); InetSocket *inetSock; if (!GetCallSocket(socket, &inetSock)) { return InetBadSocket(socket, __FUNCTION__); @@ -1616,6 +1652,7 @@ static int sceNetInetShutdown(int socket, int how) { } static int sceNetInetSocketAbort(int socket) { + INET_CHECK_STACK(); InetSocket *inetSock; if (!GetCallSocket(socket, &inetSock)) { return InetBadSocket(socket, __FUNCTION__); @@ -1627,6 +1664,7 @@ static int sceNetInetSocketAbort(int socket) { } static int sceNetInetClose(int socket) { + INET_CHECK_STACK_SOCKET(); InetSocket *inetSock; if (!GetCallSocket(socket, &inetSock)) { return InetBadSocket(socket, __FUNCTION__); @@ -1639,6 +1677,7 @@ static int sceNetInetClose(int socket) { // TODO: How is this different than just sceNetInetClose? static int sceNetInetCloseWithRST(int socket) { + INET_CHECK_STACK_SOCKET(); InetSocket *inetSock; if (!GetCallSocket(socket, &inetSock)) { return InetBadSocket(socket, __FUNCTION__); @@ -1689,6 +1728,7 @@ static InetTry TryRecvFrom(SceUID threadID, const u32 *args) { } static int sceNetInetRecvfrom(int socket, u32 bufferPtr, int len, int flags, u32 fromPtr, u32 fromlenPtr) { + INET_CHECK_STACK_SOCKET(); InetSocket *inetSock; if (!GetCallSocket(socket, &inetSock)) { return InetBadSocket(socket, __FUNCTION__); @@ -1782,6 +1822,7 @@ static InetTry TrySendTo(SceUID threadID, const u32 *args) { } static int sceNetInetSendto(int socket, u32 bufferPtr, int len, int flags, u32 toPtr, int tolen) { + INET_CHECK_STACK_SOCKET(); InetSocket *inetSock; if (!GetCallSocket(socket, &inetSock)) { return InetBadSocket(socket, __FUNCTION__); @@ -1865,6 +1906,7 @@ static bool ReadInetMsghdr(u32 msghdrPtr, InetMsghdr *msg, std::vector