HLE: Check more guest buffers by their full length, and an empty insert

Calls that checked one byte of a game's buffer and then wrote or read the
whole thing, or trusted a negative length:
- sceNetEtherNtostr/Strton, the apctl BSS descriptor calls, GetState and
  sceNetGetDropRate.
- sceNpAuthGetTicket wrote its whole ticket whatever the length; it's
  now built locally and written once the buffer is known to hold it.
  sceNpAuthGetTicketParam checked its limits after copying, and walked
  ticket params past the ticket's end.
- sceNp2's communication ID and passphrase, a negative maxServerIds, and
  an unchecked unknown1Ptr.
- sceParseUri's work area and scheme string, sceParseHttp's negative
  header length and value address, and ReadVariableSizedStruct's size.
- sceKernelCreateLwMutex initialized the work area without checking it
  at all, and the other LwMutex calls checked one byte of it.
- sceNetAdhocctlGetScanInfo cleared the length the game gave, of a
  buffer checked by one byte (or any length, negative ones included).

Also: a game mode join inserted at begin() + 1 into a possibly empty
list of players.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-31 11:26:03 +01:00
1 parent c0e750726f
commit 0a778a13ff
9 files changed
+127 -67

No files matched your search

+26 -12
View File
@@ -1145,12 +1145,13 @@ static u32 sceWlanGetSwitchState() {
static void sceNetEtherNtostr(MIPSState *mips, u32 macPtr, u32 bufferPtr) {
DEBUG_LOG(Log::sceNet, "sceNetEtherNtostr(%08x, %08x) at %08x", macPtr, bufferPtr, mips->pc);
if (Memory::IsValidAddress(bufferPtr) && Memory::IsValidAddress(macPtr)) {
// "xx:xx:xx:xx:xx:xx" and a null.
if (Memory::IsValidRange(bufferPtr, 18) && Memory::IsValidRange(macPtr, ETHER_ADDR_LEN)) {
char *buffer = (char *)Memory::GetPointerWriteUnchecked(bufferPtr);
const u8 *mac = Memory::GetPointerUnchecked(macPtr);
// MAC address is always 6 bytes / 48 bits.
sprintf(buffer, "%02x:%02x:%02x:%02x:%02x:%02x",
snprintf(buffer, 18, "%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
VERBOSE_LOG(Log::sceNet, "sceNetEtherNtostr - [%s]", buffer);
@@ -1173,9 +1174,9 @@ static int hex_to_digit(int c) {
static void sceNetEtherStrton(u32 bufferPtr, u32 macPtr) {
DEBUG_LOG(Log::sceNet, "sceNetEtherStrton(%08x, %08x)", bufferPtr, macPtr);
if (Memory::IsValidAddress(bufferPtr) && Memory::IsValidAddress(macPtr)) {
if (Memory::IsValidNullTerminatedString(bufferPtr) && Memory::IsValidRange(macPtr, ETHER_ADDR_LEN)) {
const char *buffer = (const char *)Memory::GetPointerUnchecked(bufferPtr);
u8 *mac = Memory::GetPointerWriteOrException(macPtr);
u8 *mac = Memory::GetPointerWriteUnchecked(macPtr);
// MAC address is always 6 pairs of hex digits.
// TODO: Funny stuff happens if it's too short.
@@ -1201,7 +1202,7 @@ static void sceNetEtherStrton(u32 bufferPtr, u32 macPtr) {
}
}
VERBOSE_LOG(Log::sceNet, "sceNetEtherStrton - [%s]", mac2str((SceNetEtherAddr*)Memory::GetPointerOrException(macPtr)).c_str());
VERBOSE_LOG(Log::sceNet, "sceNetEtherStrton - [%s]", mac2str((const SceNetEtherAddr *)Memory::GetPointerUnchecked(macPtr)).c_str());
// Seems to maybe kinda return the last value. Probably returns void.
//return value;
}
@@ -1299,7 +1300,7 @@ static int sceNetApctlInit(MIPSState *mips, int stackSize, int initPriority) {
userMemory.Free(apctlProdCodeAddr);
}
apctlProdCodeAddr = userMemory.Alloc(structsz, false, "ApctlAdhocId");
SceNetAdhocctlAdhocId* prodCode = (SceNetAdhocctlAdhocId*)Memory::GetCharPointer(apctlProdCodeAddr);
SceNetAdhocctlAdhocId* prodCode = Memory::IsValidRange(apctlProdCodeAddr, structsz) ? (SceNetAdhocctlAdhocId *)Memory::GetPointerWriteUnchecked(apctlProdCodeAddr) : nullptr;
if (prodCode) {
memset(prodCode, 0, structsz);
// TODO: Use a 9-characters product id instead of disc id (ie. not null-terminated(VT_UTF8_SPE) and shouldn't be variable size?)
@@ -1611,9 +1612,9 @@ static int sceNetApctlGetState(u32 pStateAddr) {
}
// Valid Arguments
if (Memory::IsValidAddress(pStateAddr)) {
if (Memory::IsValid4AlignedAddress(pStateAddr)) {
// Return Thread Status
Memory::WriteOrException_U32(NetApctl_GetState(), pStateAddr);
Memory::WriteUnchecked_U32(NetApctl_GetState(), pStateAddr);
// Return Success
return hleLogDebug(Log::sceNet, 0);
}
@@ -1652,7 +1653,10 @@ int NetApctl_GetBSSDescIDListUser(u32 sizeAddr, u32 bufAddr) {
// Return size required
Memory::WriteUnchecked_U32(entries * userInfoSize, sizeAddr);
if (bufAddr != 0 && Memory::IsValidAddress(sizeAddr)) {
if (bufAddr != 0) {
// Only write entries that fit in valid memory.
if (size > 0)
size = (int)Memory::ClampValidSizeAt(bufAddr, size);
int offset = 0;
for (int i = 0; i < entries; i++) {
// Check if enough space available to write the next structure
@@ -1683,7 +1687,15 @@ static int sceNetApctlGetBSSDescIDListUser(u32 sizeAddr, u32 bufAddr) {
}
int NetApctl_GetBSSDescEntryUser(int entryId, int infoId, u32 resultAddr) {
if (!Memory::IsValidAddress(resultAddr))
u32 resultSize = 4;
switch (infoId) {
case PSP_NET_APCTL_DESC_IBSS: resultSize = ETHER_ADDR_LEN; break;
case PSP_NET_APCTL_DESC_SSID_NAME: resultSize = APCTL_SSID_MAXLEN; break;
case PSP_NET_APCTL_DESC_CHANNEL:
case PSP_NET_APCTL_DESC_SIGNAL_STRENGTH: resultSize = 1; break;
default: break;
}
if (!Memory::IsValidRange(resultAddr, resultSize))
return hleLogError(Log::sceNet, -1, "apctl invalid arg"); // 0x8002013A or ERROR_NET_WLAN_INVALID_ARG ?
// Generate an SSID name
@@ -1853,8 +1865,10 @@ static int sceNetUpnpGetNatInfo() {
}
static int sceNetGetDropRate(u32 dropRateAddr, u32 dropDurationAddr) {
Memory::WriteOrException_U32(netDropRate, dropRateAddr);
Memory::WriteOrException_U32(netDropDuration, dropDurationAddr);
if (Memory::IsValid4AlignedAddress(dropRateAddr))
Memory::WriteUnchecked_U32(netDropRate, dropRateAddr);
if (Memory::IsValid4AlignedAddress(dropDurationAddr))
Memory::WriteUnchecked_U32(netDropDuration, dropDurationAddr);
return hleLogInfo(Log::sceNet, 0);
}