From 74178a81069cedb8a9f9bc7cc5e9f337a3a5ef8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 31 Oct 2026 11:50:17 +0100 Subject: [PATCH] sceNetAdhoc: Check the game's ad hoc buffers by their full length Calls that checked one byte of a buffer, then used the game's length: - GetPeerList and GetAddrByName cleared that many bytes; GetPdpStat and GetPtpStat filled that many entries. Clamped to valid memory. - PollSocket read and wrote count entries. - GetNameByAddr, GetPeerInfo, Join, GetGameModeInfo and UpdateReplica wrote or read whole structs. - PtpSend and PtpRecv used the whole data length. - PdpSend and PdpRecv took host pointers from generic wrappers. Their syscalls now check the game's buffers and pass NULL for bad ones, which the calls already reject, since game mode calls the functions with host buffers. - Matching SelectTarget, CancelTargetWithOpt and SendData copied the whole opt or data length. GetMembers cleared a NULL buffer when given a bad one. Also: the matching thread list is savestated without the contexts, so a context's index may be past its end after a load. Co-Authored-By: Claude Opus 5.5 --- Core/HLE/FunctionWrappers.h | 13 -------- Core/HLE/sceNetAdhoc.cpp | 57 +++++++++++++++++++++++--------- Core/HLE/sceNetAdhocMatching.cpp | 26 ++++++++++----- 3 files changed, 59 insertions(+), 37 deletions(-) diff --git a/Core/HLE/FunctionWrappers.h b/Core/HLE/FunctionWrappers.h index a29af02f21..cc6abb39d7 100644 --- a/Core/HLE/FunctionWrappers.h +++ b/Core/HLE/FunctionWrappers.h @@ -921,19 +921,6 @@ template void WrapI_ICI(MIPSState *mips) { RETURN(retval); } -template void WrapI_IVVVVUI(MIPSState *mips) { - // Any of the pointers may be NULL (sceNetAdhocPdpRecv takes NULL for the sender), so don't fault on it. - auto ptr = [](u32 addr) -> void * { return addr ? Memory::GetPointerWriteOrException(addr) : nullptr; }; - u32 retval = HLECall(mips, PARAM(0), ptr(PARAM(1)), ptr(PARAM(2)), ptr(PARAM(3)), ptr(PARAM(4)), PARAM(5), PARAM(6) ); - RETURN(retval); -} - -template void WrapI_ICUVIII(MIPSState *mips) { - // The buffer may be NULL (sceNetAdhocPdpSend with a length of 0 is fine with that), so don't fault on it. - u32 retval = HLECall(mips, PARAM(0), Memory::GetCharPointer(PARAM(1)), PARAM(2), PARAM(3) ? Memory::GetPointerWriteOrException(PARAM(3)) : nullptr, PARAM(4), PARAM(5), PARAM(6)); - RETURN(retval); -} - template void WrapI_VUI(MIPSState *mips) { u32 retval = HLECall(mips, Memory::GetPointerWriteOrException(PARAM(0)), PARAM(1), PARAM(2)); RETURN(retval); diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index 9fd16d1292..9a06cbd400 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -2551,7 +2551,8 @@ int sceNetAdhocPollSocket(MIPSState *mips, u32 socketStructAddr, int count, int if (netAdhocInited) { SceNetAdhocPollSd * sds = NULL; - if (Memory::IsValidAddress(socketStructAddr)) sds = (SceNetAdhocPollSd *)Memory::GetPointerOrException(socketStructAddr); + const u64 sdsSize = (u64)std::max(count, 0) * sizeof(SceNetAdhocPollSd); + if (sdsSize <= 0x10000000 && Memory::IsValidRange(socketStructAddr, (u32)sdsSize)) sds = (SceNetAdhocPollSd *)Memory::GetPointerOrException(socketStructAddr); // Valid Arguments if (sds != NULL && count > 0) @@ -3062,7 +3063,7 @@ static int sceNetAdhocctlGetNameByAddr(MIPSState *mips, const char *mac, u32 nam if (netAdhocctlInited) { // Valid Arguments - if (mac != NULL && Memory::IsValidAddress(nameAddr)) + if (mac != NULL && Memory::IsValidRange(nameAddr, sizeof(SceNetAdhocctlNickname))) { SceNetAdhocctlNickname * nickname = (SceNetAdhocctlNickname *)Memory::GetPointerOrException(nameAddr); // Get Local MAC Address @@ -3124,7 +3125,7 @@ int sceNetAdhocctlGetPeerInfo(MIPSState *mips, const char *mac, int size, u32 pe SceNetEtherAddr * maddr = (SceNetEtherAddr *)mac; SceNetAdhocctlPeerInfoEmu * buf = NULL; - if (Memory::IsValidAddress(peerInfoAddr)) { + if (Memory::IsValidRange(peerInfoAddr, sizeof(SceNetAdhocctlPeerInfoEmu))) { buf = (SceNetAdhocctlPeerInfoEmu *)Memory::GetPointerOrException(peerInfoAddr); } // Library initialized @@ -3283,7 +3284,7 @@ int sceNetAdhocctlJoin(MIPSState *mips, u32 scanInfoAddr) { if (netAdhocctlInited) { // Valid Argument - if (Memory::IsValidAddress(scanInfoAddr)) + if (Memory::IsValidRange(scanInfoAddr, sizeof(SceNetAdhocctlScanInfoEmu))) { SceNetAdhocctlScanInfoEmu* sinfo = (SceNetAdhocctlScanInfoEmu*)Memory::GetPointerOrException(scanInfoAddr); char grpName[ADHOCCTL_GROUPNAME_LEN + 1] = { 0 }; @@ -3524,7 +3525,7 @@ static int sceNetAdhocGetPdpStat(MIPSState *mips, u32 structSize, u32 structAddr else if (buflen != NULL && buf != NULL) { // Figure out how many Sockets we will return - int count = *buflen / sizeof(SceNetAdhocPdpStat); + int count = Memory::ClampValidSizeAt(structAddr, std::max((s32)*buflen, 0)) / sizeof(SceNetAdhocPdpStat); if (count > socketcount) count = socketcount; // Copy Counter @@ -3611,7 +3612,7 @@ static int sceNetAdhocGetPtpStat(MIPSState *mips, u32 structSize, u32 structAddr // Status Returner Mode else if (buflen != NULL && buf != NULL) { // Figure out how many Sockets we will return - int count = *buflen / sizeof(SceNetAdhocPtpStat); + int count = Memory::ClampValidSizeAt(structAddr, std::max((s32)*buflen, 0)) / sizeof(SceNetAdhocPtpStat); if (count > socketcount) count = socketcount; // Copy Counter @@ -4318,7 +4319,7 @@ static int sceNetAdhocPtpSend(MIPSState *mips, int id, u32 dataAddr, u32 dataSiz if (*len == 0) { return hleLogDebug(Log::sceNet, 0, "nothing to send"); } - if (*len < 0 || data == NULL) { + if (*len < 0 || data == NULL || !Memory::IsValidRange(dataAddr, *len)) { return hleLogError(Log::sceNet, SCE_NET_ADHOC_ERROR_INVALID_DATALEN, "invalid data length"); } // Valid Arguments @@ -4374,7 +4375,7 @@ static int sceNetAdhocPtpRecv(MIPSState *mips, int id, u32 dataAddr, u32 dataSiz // Library is initialized if (netAdhocInited) { // Valid Arguments - if (buf != NULL && len != NULL && *len > 0) { + if (buf != NULL && len != NULL && *len > 0 && Memory::IsValidRange(dataAddr, *len)) { // Valid Socket if (id > 0 && id <= MAX_SOCKET && adhocSockets[id - 1] != NULL) { // Cast Socket @@ -4678,7 +4679,7 @@ static int sceNetAdhocGameModeUpdateReplica(MIPSState *mips, int id, u32 infoAdd // Bomberman Panic Bomber is using 0/null on infoAddr, so i guess it's optional. GameModeUpdateInfo* gmuinfo = NULL; - if (Memory::IsValidAddress(infoAddr)) { + if (Memory::IsValidRange(infoAddr, sizeof(GameModeUpdateInfo))) { gmuinfo = (GameModeUpdateInfo*)Memory::GetPointerOrException(infoAddr); } @@ -4836,13 +4837,31 @@ void __NetTriggerCallbacks() hleNoLogVoid(); } +// The game's buffer, if all of it is valid memory, else NULL, which the calls treat as missing. +static void *GuestBuffer(u32 addr, int size) { + return Memory::IsValidRange(addr, std::max(size, 1)) ? Memory::GetPointerWriteUnchecked(addr) : nullptr; +} + +// The PDP syscalls check the game's buffers here, since game mode calls the functions with host ones. +static int sceNetAdhocPdpSendSyscall(MIPSState *mips, int id, u32 macAddr, u32 port, u32 dataAddr, u32 len, u32 timeout, u32 flag) { + void *data = dataAddr ? GuestBuffer(dataAddr, (int)len) : nullptr; + return sceNetAdhocPdpSend(mips, id, (const char *)GuestBuffer(macAddr, ETHER_ADDR_LEN), port, data, (int)len, (int)timeout, (int)flag); +} + +static int sceNetAdhocPdpRecvSyscall(MIPSState *mips, int id, u32 macAddr, u32 portAddr, u32 bufAddr, u32 lenAddr, u32 timeout, u32 flag) { + void *len = GuestBuffer(lenAddr, 4); + void *buf = GuestBuffer(bufAddr, len ? *(s32_le *)len : 0); + // NULL for the sender's MAC and port is fine. + return sceNetAdhocPdpRecv(mips, id, macAddr ? GuestBuffer(macAddr, ETHER_ADDR_LEN) : nullptr, portAddr ? GuestBuffer(portAddr, 2) : nullptr, buf, len, timeout, (int)flag); +} + const HLEFunction sceNetAdhoc[] = { {0XE1D621D7, &WrapU_V, "sceNetAdhocInit", 'x', "" }, {0XA62C6F57, &WrapI_V, "sceNetAdhocTerm", 'i', "" }, {0X0AD043ED, &WrapI_C, "sceNetAdhocctlConnect", 'i', "s" }, {0X6F92741B, &WrapI_CIIU, "sceNetAdhocPdpCreate", 'i', "siix" }, - {0XABED3790, &WrapI_ICUVIII, "sceNetAdhocPdpSend", 'i', "isxpiii" }, - {0XDFE53E03, &WrapI_IVVVVUI, "sceNetAdhocPdpRecv", 'i', "ippppxi" }, + {0XABED3790, &WrapI_IUUUUUU, "sceNetAdhocPdpSend", 'i', "ixxxiii" }, + {0XDFE53E03, &WrapI_IUUUUUU, "sceNetAdhocPdpRecv", 'i', "ixxxxxi" }, {0X7F27BB5E, &WrapI_II, "sceNetAdhocPdpDelete", 'i', "ii" }, {0XC7C1FC57, &WrapI_UU, "sceNetAdhocGetPdpStat", 'i', "xx" }, {0X157E6225, &WrapI_II, "sceNetAdhocPtpClose", 'i', "ii" }, @@ -4897,7 +4916,7 @@ static int sceNetAdhocctlGetGameModeInfo(u32 infoAddr) { if (!netAdhocctlInited) return hleLogError(Log::sceNet, SCE_NET_ADHOCCTL_ERROR_NOT_INITIALIZED, "not initialized"); - if (!Memory::IsValidAddress(infoAddr)) + if (!Memory::IsValidRange(infoAddr, sizeof(SceNetAdhocctlGameModeInfo))) return hleLogError(Log::sceNet, SCE_NET_ADHOCCTL_ERROR_INVALID_ARG, "invalid arg"); // pspautotests net/adhoc/gamemode @@ -4961,13 +4980,16 @@ static int sceNetAdhocctlGetPeerList(MIPSState *mips, u32 sizeAddr, u32 bufAddr) // Discovery Counter int discovered = 0; + // Only the part of the buffer that's valid memory. + const u32 validLen = Memory::ClampValidSizeAt(bufAddr, std::max((s32)*buflen, 0)); + // Calculate Request Count - int requestcount = *buflen / sizeof(SceNetAdhocctlPeerInfoEmu); + int requestcount = validLen / sizeof(SceNetAdhocctlPeerInfoEmu); // FIXME: When bufAddr is not null but buffer size is smaller than activePeers * sizeof(SceNetAdhocctlPeerInfoEmu), simply return buffer size = 0 without filling the buffer? // Clear Memory - memset(buf, 0, *buflen); + memset(buf, 0, validLen); // Minimum Arguments if (requestcount > 0) { @@ -5056,14 +5078,17 @@ static int sceNetAdhocctlGetAddrByName(MIPSState *mips, const char *nickName, u3 // Normal Information Mode else { + // Only the part of the buffer that's valid memory. + const u32 validLen = Memory::ClampValidSizeAt(bufAddr, std::max((s32)*buflen, 0)); + // Clear Memory - memset(buf, 0, *buflen); + memset(buf, 0, validLen); // Discovered Player Count int discovered = 0; // Calculate Requested Elements - int requestcount = *buflen / sizeof(SceNetAdhocctlPeerInfoEmu); + int requestcount = validLen / sizeof(SceNetAdhocctlPeerInfoEmu); // Minimum Space available if (requestcount > 0) diff --git a/Core/HLE/sceNetAdhocMatching.cpp b/Core/HLE/sceNetAdhocMatching.cpp index aa597fcc3c..fdce9166c2 100644 --- a/Core/HLE/sceNetAdhocMatching.cpp +++ b/Core/HLE/sceNetAdhocMatching.cpp @@ -1382,11 +1382,14 @@ int NetAdhocMatching_Stop(int matchingId) { // Stop fake PSP Thread. // kernelObjects may already been cleared early during a Shutdown, thus trying to access it may generates Warning/Error in the log - if (matchingThreads[item->matching_thid] > 0 && strcmp(__KernelGetThreadName(matchingThreads[item->matching_thid]), "ERROR") != 0) { - __KernelStopThread(matchingThreads[item->matching_thid], SCE_KERNEL_ERROR_THREAD_TERMINATED, "AdhocMatching stopped"); - __KernelDeleteThread(matchingThreads[item->matching_thid], SCE_KERNEL_ERROR_THREAD_TERMINATED, "AdhocMatching deleted"); + // A savestate load replaces the thread list but not the contexts, so the index may be past its end. + if (item->matching_thid >= 0 && item->matching_thid < (int)matchingThreads.size()) { + if (matchingThreads[item->matching_thid] > 0 && strcmp(__KernelGetThreadName(matchingThreads[item->matching_thid]), "ERROR") != 0) { + __KernelStopThread(matchingThreads[item->matching_thid], SCE_KERNEL_ERROR_THREAD_TERMINATED, "AdhocMatching stopped"); + __KernelDeleteThread(matchingThreads[item->matching_thid], SCE_KERNEL_ERROR_THREAD_TERMINATED, "AdhocMatching deleted"); + } + matchingThreads[item->matching_thid] = 0; } - matchingThreads[item->matching_thid] = 0; // Make sure nobody locking/using the socket item->socketlock->lock(); @@ -1721,6 +1724,9 @@ int NetAdhocMatching_Start(int matchingId, int evthPri, int evthPartitionId, int // Created and started without rescheduling, like adhocctl's thread: starting it through // sceKernelStartThread switched threads inside this call, and the game got the thread's ID back // instead of 0 (pspautotests net/adhoc/matching). + if (item->matching_thid >= (int)matchingThreads.size()) { + matchingThreads.resize(item->matching_thid + 1); + } matchingThreads[item->matching_thid] = __KernelCreateThread(thrname.c_str(), __KernelGetCurThreadModuleId(), matchingThreadHackAddr, evthPri, evthStack, PSP_THREAD_ATTR_USER, 0, true); //item->matchingThread = new HLEHelperThread(thrname.c_str(), "sceNetAdhocMatching", "__NetMatchingCallbacks", inthPri, inthStack); if (matchingThreads[item->matching_thid] > 0) { @@ -1823,7 +1829,7 @@ static int sceNetAdhocMatchingSelectTarget(MIPSState *mips, int matchingId, cons } void * opt = NULL; - if (Memory::IsValidAddress(optDataPtr)) opt = Memory::GetPointerWriteUnchecked(optDataPtr); + if (Memory::IsValidRange(optDataPtr, std::max(optLen, 1))) opt = Memory::GetPointerWriteUnchecked(optDataPtr); // Host Mode if (context->mode == PSP_ADHOC_MATCHING_MODE_PARENT) { // Already Connected. Right after accepting, it's still in progress (pspautotests net/adhoc/pair/matching). @@ -1921,7 +1927,7 @@ int NetAdhocMatching_CancelTargetWithOpt(int matchingId, const char* macAddress, SceNetEtherAddr* target = (SceNetEtherAddr*)macAddress; void* opt = NULL; - if (Memory::IsValidAddress(optDataPtr)) opt = Memory::GetPointerWriteUnchecked(optDataPtr); + if (Memory::IsValidRange(optDataPtr, std::max(optLen, 1))) opt = Memory::GetPointerWriteUnchecked(optDataPtr); if (target == NULL || ((optLen != 0) && (optLen <= 0 || opt == NULL))) { // Invalid Arguments @@ -2183,8 +2189,12 @@ static int sceNetAdhocMatchingGetMembers(int matchingId, u32 sizeAddr, u32 buf) // Fix Oversize Request if ((*buflen) > available) *buflen = available; + // Only the part of the buffer that's valid memory (none if buf2 is NULL). + *buflen = Memory::ClampValidSizeAt(buf, *buflen); + // Clear Memory - memset(buf2, 0, *buflen); + if (buf2) + memset(buf2, 0, *buflen); // Calculate Requested Peer Count int requestedpeers = (*buflen) / sizeof(SceNetAdhocMatchingMemberInfoEmu); @@ -2354,7 +2364,7 @@ int sceNetAdhocMatchingSendData(MIPSState *mips, int matchingId, const char *mac } void* data = NULL; - if (Memory::IsValidAddress(dataAddr)) data = Memory::GetPointerWriteUnchecked(dataAddr); + if (Memory::IsValidRange(dataAddr, dataLen)) data = Memory::GetPointerWriteUnchecked(dataAddr); // Lock the peer std::lock_guard peer_guard(peerlock);