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

+47 -20
View File
@@ -168,24 +168,24 @@ static int sceNpTerm()
static int sceNpGetContentRatingFlag(u32 parentalControlAddr, u32 userAgeAddr)
{
if (!Memory::IsValidAddress(parentalControlAddr) || !Memory::IsValidAddress(userAgeAddr))
if (!Memory::IsValid4AlignedAddress(parentalControlAddr) || !Memory::IsValid4AlignedAddress(userAgeAddr))
return hleLogError(Log::sceNet, SCE_NP_ERROR_INVALID_ARGUMENT, "invalid arg");
INFO_LOG(Log::sceNet, "%s - Parental Control: %d", __FUNCTION__, npParentalControl);
INFO_LOG(Log::sceNet, "%s - User Age: %d", __FUNCTION__, npUserAge);
Memory::WriteOrException_U32(npParentalControl, parentalControlAddr);
Memory::WriteOrException_U32(npUserAge, userAgeAddr);
Memory::WriteUnchecked_U32(npParentalControl, parentalControlAddr);
Memory::WriteUnchecked_U32(npUserAge, userAgeAddr);
return hleLogWarning(Log::sceNet, 0, "UNTESTED");
}
static int sceNpGetChatRestrictionFlag(u32 flagAddr)
{
if (!Memory::IsValidAddress(flagAddr))
if (!Memory::IsValid4AlignedAddress(flagAddr))
return hleLogError(Log::sceNet, SCE_NP_ERROR_INVALID_ARGUMENT, "invalid arg");
Memory::WriteOrException_U32(npChatRestriction, flagAddr);
Memory::WriteUnchecked_U32(npChatRestriction, flagAddr);
return hleLogWarning(Log::sceNet, 0, "Chat restriction: %d", npChatRestriction);
}
@@ -276,7 +276,7 @@ static int sceNpGetUserProfile(u32 profilePtr)
WARN_LOG(Log::sceNet, "UNTESTED %s(%08x)", __FUNCTION__, profilePtr);
auto profile = PSPPointer<SceNpUserInformation>::Create(profilePtr);
if (!Memory::IsValidAddress(profilePtr))
if (!profile.IsValid())
return hleLogError(Log::sceNet, SCE_NP_ERROR_INVALID_ARGUMENT, "invalid arg");
profile.FillWithZero();
@@ -361,13 +361,17 @@ int sceNpAuthCreateStartRequest(u32 paramAddr) {
return hleLogError(Log::sceNet, SCE_NP_AUTH_ERROR_INVALID_ARGUMENT, "invalid arg");
}
npServiceId = Memory::GetCharPointer(params.serviceIdAddr);
// Missing fields in a short struct are zero.
auto guestString = [](u32 addr) -> const char * {
return Memory::IsValidNullTerminatedString(addr) ? Memory::GetCharPointerUnchecked(addr) : "";
};
npServiceId = guestString(params.serviceIdAddr);
INFO_LOG(Log::sceNet, "%s - Max Version: %u.%u", __FUNCTION__, params.version.major, params.version.minor);
INFO_LOG(Log::sceNet, "%s - Service ID: %s", __FUNCTION__, Memory::GetCharPointer(params.serviceIdAddr));
INFO_LOG(Log::sceNet, "%s - Entitlement ID: %s", __FUNCTION__, Memory::GetCharPointer(params.entitlementIdAddr));
INFO_LOG(Log::sceNet, "%s - Service ID: %s", __FUNCTION__, npServiceId.c_str());
INFO_LOG(Log::sceNet, "%s - Entitlement ID: %s", __FUNCTION__, guestString(params.entitlementIdAddr));
INFO_LOG(Log::sceNet, "%s - Consumed Count: %d", __FUNCTION__, params.consumedCount);
INFO_LOG(Log::sceNet, "%s - Cookie (size = %d): %s", __FUNCTION__, params.cookieSize, Memory::GetCharPointer(params.cookieAddr));
INFO_LOG(Log::sceNet, "%s - Cookie (size = %d): %s", __FUNCTION__, params.cookieSize, guestString(params.cookieAddr));
u32 retval = 0;
if (params.size >= 32 && params.ticketCbAddr != 0) {
@@ -428,12 +432,13 @@ int sceNpAuthGetTicket(u32 requestId, u32 bufferAddr, u32 length) {
}
int result = length;
Memory::Memset(bufferAddr, 0, length, "NpAuthGetTicket");
// Built here, then copied once we know it fits.
u8 staging[512]{};
SceNpTicket ticket = {};
// Dummy Login ticket returned as Login response. Dummy ticket contents were taken from https://www.psdevwiki.com/ps3/X-I-5-Ticket
ticket.header.version = TICKET_VER_2_1;
ticket.header.size = 0xF0; // size excluding the header
u8* buf = Memory::GetPointerWriteOrException(bufferAddr + sizeof(ticket));
u8 *buf = staging + sizeof(ticket);
int ofs = 0;
ofs += writeTicketParam(buf, PARAM_TYPE_STRING_ASCII, "\x4c\x47\x56\x3b\x81\x39\x4a\x22\xd8\x6b\xc1\x57\x71\x6e\xfd\xb8\xab\x63\xcc\x51", 20); // 20 random letters, token key or SceNpSignature?
ofs += writeTicketU32Param(buf + ofs, PARAM_TYPE_INT, 0x0100); // a flags?
@@ -446,7 +451,9 @@ int sceNpAuthGetTicket(u32 requestId, u32 bufferAddr, u32 length) {
ofs += writeTicketStringParam(buf + ofs, PARAM_TYPE_STRING, npOnlineId.c_str(), 32); // username (pre-cut to 16 chars)
ofs += writeTicketParam(buf + ofs, PARAM_TYPE_STRING_ASCII, npCountryCode, 4); // SceNpCountryCode ? ie. "fr" + 00 02
ofs += writeTicketStringParam(buf + ofs, PARAM_TYPE_STRING, npRegionCode, 4); // 2-char code? related to country/lang code? ie. "c9" + 00 00
ofs += writeTicketParam(buf + ofs, PARAM_TYPE_STRING_ASCII, npServiceId.c_str(), 24);
std::string serviceId = npServiceId;
serviceId.resize(24, '\0');
ofs += writeTicketParam(buf + ofs, PARAM_TYPE_STRING_ASCII, serviceId.c_str(), 24);
int status = 0;
if (npParentalControl == PARENTAL_CONTROL_ENABLED) {
status |= STATUS_ACCOUNT_PARENTAL_CONTROL_ENABLED;
@@ -457,14 +464,22 @@ int sceNpAuthGetTicket(u32 requestId, u32 bufferAddr, u32 length) {
ofs += writeTicketParam(buf + ofs, PARAM_TYPE_NULL);
ticket.section.type = SECTION_TYPE_BODY;
ticket.section.size = ofs;
Memory::Memcpy(bufferAddr, &ticket, sizeof(SceNpTicket), "NpAuthGetTicket");
memcpy(staging, &ticket, sizeof(SceNpTicket));
SceNpTicketSection footer = { SECTION_TYPE_FOOTER, 32 }; // footer section? ie. 32-bytes on version 2.1 containing 4-chars ASCII + 20-chars ASCII
Memory::Memcpy(bufferAddr + sizeof(ticket) + ofs, &footer, sizeof(SceNpTicketSection), "NpAuthGetTicket");
memcpy(buf + ofs, &footer, sizeof(SceNpTicketSection));
ofs += sizeof(footer);
ofs += writeTicketParam(buf + ofs, PARAM_TYPE_STRING_ASCII, "\x34\xcd\x3c\xa9", 4);
ofs += writeTicketParam(buf + ofs, PARAM_TYPE_STRING_ASCII, "\x3a\x4b\x42\x66\x92\xda\x6b\x7c\xb7\x4c\xe8\xd9\x4f\x2b\x77\x15\x91\xb8\xa4\xa9", 20); // 20 random letters, token key or SceNpSignature?
// includes Language list?
Memory::Memset(bufferAddr + sizeof(ticket) + ofs, 0, 36);
// includes Language list? (36 zero bytes, staging is zeroed)
const u32 total = (u32)sizeof(ticket) + ofs + 36;
_dbg_assert_(total <= sizeof(staging));
// The whole ticket gets written, even if length is shorter.
if (!Memory::IsValidRange(bufferAddr, std::max(length, total))) {
return hleLogError(Log::sceNet, SCE_NP_AUTH_ERROR_INVALID_ARGUMENT, "invalid buffer");
}
Memory::Memset(bufferAddr, 0, length, "NpAuthGetTicket");
Memory::Memcpy(bufferAddr, staging, total, "NpAuthGetTicket");
result = ticket.header.size + sizeof(ticket.header); // dummy ticket is 248 bytes
@@ -512,20 +527,32 @@ int sceNpAuthDestroyRequest(int requestId)
int sceNpAuthGetTicketParam(u32 ticketBufPtr, int ticketLen, int paramNum, u32 bufferPtr)
{
const u32 PARAM_BUFFER_MAX_SIZE = 256;
if (!Memory::IsValidRange(bufferPtr, PARAM_BUFFER_MAX_SIZE)) {
return hleLogError(Log::sceNet, SCE_NP_MANAGER_ERROR_INVALID_ARGUMENT, "invalid buffer");
}
Memory::Memset(bufferPtr, 0, PARAM_BUFFER_MAX_SIZE); // JPCSP: This clear is always done, even when an error is returned
if (paramNum < 0 || paramNum >= NUMBER_PARAMETERS) {
return hleLogError(Log::sceNet, SCE_NP_MANAGER_ERROR_INVALID_ARGUMENT);
}
if (ticketLen < (int)sizeof(SceNpTicket) || !Memory::IsValidRange(ticketBufPtr, ticketLen)) {
return hleLogError(Log::sceNet, SCE_NP_MANAGER_ERROR_INVALID_ARGUMENT, "invalid ticket");
}
SceNpTicket* ticket = (SceNpTicket*)Memory::GetPointerOrException(ticketBufPtr);
const SceNpTicket *ticket = (const SceNpTicket *)Memory::GetPointerUnchecked(ticketBufPtr);
const u32 ticketEnd = ticketBufPtr + ticketLen;
u32 inbuf = ticketBufPtr;
inbuf += sizeof(ticket->header);
inbuf += ticket->section.size + sizeof(ticket->section);
u32 outbuf = bufferPtr;
for (int i = 0; i < paramNum; i++) {
SceNpTicketParamData* ticketParam = (SceNpTicketParamData*)Memory::GetPointerOrException(inbuf);
// Only copy params that are inside the ticket and fit in the output.
if (inbuf < ticketBufPtr || inbuf + sizeof(SceNpTicketParamData) > ticketEnd)
break;
const SceNpTicketParamData *ticketParam = (const SceNpTicketParamData *)Memory::GetPointerUnchecked(inbuf);
u32 sz = (u32)sizeof(SceNpTicketParamData) + ticketParam->length;
Memory::Memcpy(outbuf, inbuf, sz);
if (sz > ticketEnd - inbuf || sz > PARAM_BUFFER_MAX_SIZE - (outbuf - bufferPtr))
break;
Memory::MemcpyUnchecked(outbuf, inbuf, sz);
DEBUG_LOG(Log::sceNet, "%s - Param #%d: Type = %04x, Length = %u", __FUNCTION__, i, static_cast<unsigned int>(ticketParam->type), static_cast<unsigned int>(ticketParam->length));
outbuf += sz;
inbuf += sz;