From 6dffcc91d20d09e2c01f19c075828a0781f324e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 24 Sep 2026 11:07:57 -0600 Subject: [PATCH] Utility: Check request sizes like the firmware InitStart sizes: Netconf and NpSignin accepted any size, then wrote common.size bytes back from a 64-68 byte host struct, copying host memory into PSP RAM. GamedataInstall looked for install files before checking the size, and the HtmlViewer read options before checking the whole request was in memory. All the dialogs now check the address, then the sizes sceUtility_Driver accepts (utility/dialog/sizes), before anything else, as the firmware does (a bad address is INVALID_ADDRESS), and write back no more than the struct. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/Dialog/PSPDialog.cpp | 14 ++++++++++ Core/Dialog/PSPDialog.h | 5 ++++ Core/Dialog/PSPGamedataInstallDialog.cpp | 13 +++------ Core/Dialog/PSPHtmlViewerDialog.cpp | 34 ++++++------------------ Core/Dialog/PSPMsgDialog.cpp | 16 +++++------ Core/Dialog/PSPNetconfDialog.cpp | 17 +++++++----- Core/Dialog/PSPNpSigninDialog.cpp | 13 ++++++--- Core/Dialog/PSPPlaceholderDialog.cpp | 11 ++++---- Core/Dialog/PSPSaveDialog.cpp | 10 +++---- Core/Dialog/PSPScreenshotDialog.cpp | 20 ++++---------- test.py | 1 + 11 files changed, 74 insertions(+), 80 deletions(-) diff --git a/Core/Dialog/PSPDialog.cpp b/Core/Dialog/PSPDialog.cpp index ac554c6e4f..c87a35dc1a 100644 --- a/Core/Dialog/PSPDialog.cpp +++ b/Core/Dialog/PSPDialog.cpp @@ -394,6 +394,20 @@ void PSPDialog::DisplayButtons(int flags, std::string_view caption) { } } +int PSPDialog::CheckRequest(u32 addr, std::initializer_list sizes) { + if (!Memory::IsValidRange(addr, sizeof(pspUtilityDialogCommon))) { + return SCE_ERROR_UTILITY_INVALID_ADDRESS; + } + const u32 size = Memory::ReadUnchecked_U32(addr); + if (std::find(sizes.begin(), sizes.end(), size) == sizes.end()) { + return SCE_ERROR_UTILITY_INVALID_PARAM_SIZE; + } + if (!Memory::IsValidRange(addr, size)) { + return SCE_ERROR_UTILITY_INVALID_ADDRESS; + } + return 0; +} + int PSPDialog::GetConfirmButton() { if (PSP_CoreParameter().compat.flags().ForceCircleButtonConfirm) { return CTRL_CIRCLE; diff --git a/Core/Dialog/PSPDialog.h b/Core/Dialog/PSPDialog.h index 9f0320305e..7272225533 100644 --- a/Core/Dialog/PSPDialog.h +++ b/Core/Dialog/PSPDialog.h @@ -17,6 +17,8 @@ #pragma once +#include + #include "Common/CommonTypes.h" #include "Common/Render/TextureAtlas.h" #include "Common/Swap.h" @@ -114,6 +116,9 @@ protected: } static int GetConfirmButton(); + // What every InitStart checks first in sceUtility_Driver: the address, then the size against + // the ones that type accepts (utility/dialog/sizes), then the rest of the range. + static int CheckRequest(u32 addr, std::initializer_list sizes); static int GetCancelButton(); void StartFade(bool fadeIn_); diff --git a/Core/Dialog/PSPGamedataInstallDialog.cpp b/Core/Dialog/PSPGamedataInstallDialog.cpp index 8985cfc462..3939d61714 100644 --- a/Core/Dialog/PSPGamedataInstallDialog.cpp +++ b/Core/Dialog/PSPGamedataInstallDialog.cpp @@ -61,10 +61,10 @@ int PSPGamedataInstallDialog::Init(u32 paramAddr) { return SCE_ERROR_UTILITY_INVALID_STATUS; } - if (!Memory::IsValidRange(paramAddr, sizeof(SceUtilityGamedataInstallParam))) { - // This should probably crash - ERROR_LOG(Log::sceUtility, "sceGamedataInstallInitStart: invalid param address 0x%08X", paramAddr); - return SCE_KERNEL_ERROR_INVALID_POINTER; + const int check = CheckRequest(paramAddr, { 1424, 1432 }); + if (check < 0) { + ERROR_LOG(Log::sceUtility, "sceGamedataInstallInitStart: bad request at %08x: %08x", paramAddr, check); + return check; } param.ptr = paramAddr; @@ -88,11 +88,6 @@ int PSPGamedataInstallDialog::Init(u32 paramAddr) { } const int size = Memory::ReadUnchecked_U32(paramAddr); - if (size != 1424 && size != 1432) { - ERROR_LOG_REPORT(Log::sceUtility, "sceGamedataInstallInitStart: invalid param size %d", size); - return SCE_ERROR_UTILITY_INVALID_PARAM_SIZE; - } - memset(&request, 0, sizeof(request)); // Only copy the right size to support different request format Memory::Memcpy(&request, paramAddr, size, "sceGamedataInstallInitStart"); diff --git a/Core/Dialog/PSPHtmlViewerDialog.cpp b/Core/Dialog/PSPHtmlViewerDialog.cpp index 7f83874e54..391a4d5185 100644 --- a/Core/Dialog/PSPHtmlViewerDialog.cpp +++ b/Core/Dialog/PSPHtmlViewerDialog.cpp @@ -52,23 +52,10 @@ struct SceUtilityHtmlViewerParam { }; // From sceUtility_Driver's InitStart for the HtmlViewer, firmware 6.61: the request size says which -// firmware's layout it is, and the working memory it allocates for the browser depends on that and -// on bit 0x400 of options. Any other size is INVALID_PARAM_SIZE. -static bool HtmlViewerWorkSize(u32 size, u32 options, u32 *workSize) { - switch (size) { - case 0x70: // 2.00 - case 0x78: // 2.50 - case 0x80: // 2.60 - *workSize = 0x380000; - return true; - case 0x98: // 2.70 - case 0xA4: // 2.80 - case 0xA8: // 3.00 - *workSize = (options & 0x400) ? 0x480000 : 0x380000; - return true; - default: - return false; - } +// firmware's layout it is: 0x70, 0x78 and 0x80 are 2.00 to 2.60, 0x98, 0xA4 and 0xA8 2.70 to 3.00. +// The working memory it allocates for the browser depends on that and on bit 0x400 of options. +static u32 HtmlViewerWorkSize(u32 size, u32 options) { + return size >= 0x98 && (options & 0x400) ? 0x480000 : 0x380000; } static std::string ReadUrl(u32 addr) { @@ -99,18 +86,13 @@ int PSPHtmlViewerDialog::Init(u32 paramAddr) { if (GetStatus() != SCE_UTILITY_STATUS_NONE) { return SCE_ERROR_UTILITY_INVALID_STATUS; } - if (!Memory::IsValidRange(paramAddr, sizeof(pspUtilityDialogCommon))) { - return SCE_ERROR_UTILITY_INVALID_ADDRESS; + const int check = CheckRequest(paramAddr, { 0x70, 0x78, 0x80, 0x98, 0xA4, 0xA8 }); + if (check < 0) { + return check; } const u32 size = Memory::ReadUnchecked_U32(paramAddr); const SceUtilityHtmlViewerParam *param = (const SceUtilityHtmlViewerParam *)Memory::GetPointerUnchecked(paramAddr); - u32 workSize = 0; - if (!HtmlViewerWorkSize(size, param->options, &workSize)) { - return SCE_ERROR_UTILITY_INVALID_PARAM_SIZE; - } - if (!Memory::IsValidRange(paramAddr, size)) { - return SCE_ERROR_UTILITY_INVALID_ADDRESS; - } + u32 workSize = HtmlViewerWorkSize(size, param->options); u32 addr = userMemory.Alloc(workSize, false, "HtmlViewer"); if (addr == (u32)-1) { diff --git a/Core/Dialog/PSPMsgDialog.cpp b/Core/Dialog/PSPMsgDialog.cpp index 965695e38b..ff6064f78a 100755 --- a/Core/Dialog/PSPMsgDialog.cpp +++ b/Core/Dialog/PSPMsgDialog.cpp @@ -56,18 +56,16 @@ int PSPMsgDialog::Init(unsigned int paramAddr) { return SCE_ERROR_UTILITY_INVALID_STATUS; } + const int check = CheckRequest(paramAddr, { SCE_UTILITY_MSGDIALOG_SIZE_V1, SCE_UTILITY_MSGDIALOG_SIZE_V2, SCE_UTILITY_MSGDIALOG_SIZE_V3 }); + if (check < 0) { + return check; + } + if (!Memory::IsValid4AlignedAddress(paramAddr)) { + return SCE_KERNEL_ERROR_BAD_ARGUMENT; // untested + } messageDialogAddr = paramAddr; - if (!Memory::IsValid4AlignedAddress(paramAddr)) { - // What to do? - return SCE_KERNEL_ERROR_BAD_ARGUMENT; - } - int size = Memory::ReadUnchecked_U32(paramAddr); - // The only sizes sceUtility_Driver accepts (utility/dialog/sizes). - if (size != SCE_UTILITY_MSGDIALOG_SIZE_V1 && size != SCE_UTILITY_MSGDIALOG_SIZE_V2 && size != SCE_UTILITY_MSGDIALOG_SIZE_V3) { - return SCE_ERROR_UTILITY_INVALID_PARAM_SIZE; - } memset(&messageDialog, 0, sizeof(messageDialog)); // Only copy the right size to support different request format Memory::Memcpy(&messageDialog,paramAddr,size); diff --git a/Core/Dialog/PSPNetconfDialog.cpp b/Core/Dialog/PSPNetconfDialog.cpp index 08572b9d34..b361a0e886 100644 --- a/Core/Dialog/PSPNetconfDialog.cpp +++ b/Core/Dialog/PSPNetconfDialog.cpp @@ -60,16 +60,21 @@ int PSPNetconfDialog::Init(u32 paramAddr) { if (ReadStatus() != SCE_UTILITY_STATUS_NONE) return SCE_ERROR_UTILITY_INVALID_STATUS; + const int check = CheckRequest(paramAddr, { 0x38, 0x40, 0x44 }); + if (check < 0) { + return check; + } + + if (!ReadVariableSizedStruct(paramAddr, &request)) { + return SCE_KERNEL_ERROR_BAD_ARGUMENT; // untested, it's misaligned + } + requestAddr = paramAddr; + NOTICE_LOG(Log::sceUtility, "PSPNetConfDialog Init"); jsonReady_ = false; // Kick off a request to the infra-dns.json since we'll need it later. StartInfraJsonDownload(); - requestAddr = paramAddr; - if (!ReadVariableSizedStruct(paramAddr, &request)) { - return SCE_KERNEL_ERROR_BAD_ARGUMENT; // untested - } - ChangeStatusInit(NET_INIT_DELAY_US); // Eat any keys pressed before the dialog inited. @@ -340,7 +345,7 @@ int PSPNetconfDialog::Update(int animSpeed) { } if (ReadStatus() == SCE_UTILITY_STATUS_FINISHED || pendingStatus == SCE_UTILITY_STATUS_FINISHED) - Memory::Memcpy(requestAddr, &request, request.common.size, "NetConfDialogParam"); + Memory::Memcpy(requestAddr, &request, std::min((u32)request.common.size, (u32)sizeof(request)), "NetConfDialogParam"); return 0; } diff --git a/Core/Dialog/PSPNpSigninDialog.cpp b/Core/Dialog/PSPNpSigninDialog.cpp index ecc2feb4ca..064c54fb11 100644 --- a/Core/Dialog/PSPNpSigninDialog.cpp +++ b/Core/Dialog/PSPNpSigninDialog.cpp @@ -43,11 +43,16 @@ int PSPNpSigninDialog::Init(u32 paramAddr) { if (ReadStatus() != SCE_UTILITY_STATUS_NONE) return SCE_ERROR_UTILITY_INVALID_STATUS; - requestAddr = paramAddr; - if (!ReadVariableSizedStruct(paramAddr, &request)) { - return SCE_KERNEL_ERROR_BAD_ARGUMENT; // untested + const int check = CheckRequest(paramAddr, { 0x40 }); + if (check < 0) { + return check; } + if (!ReadVariableSizedStruct(paramAddr, &request)) { + return SCE_KERNEL_ERROR_BAD_ARGUMENT; // untested, it's misaligned + } + requestAddr = paramAddr; + WARN_LOG_REPORT_ONCE(PSPNpSigninDialogInit, Log::sceNet, "NpSignin Init Params: %08x, %08x, %08x, %08x", request.npSigninStatus, request.unknown1, request.unknown2, request.unknown3); ChangeStatusInit(NP_INIT_DELAY_US); @@ -172,7 +177,7 @@ int PSPNpSigninDialog::Shutdown(bool force) { // FIXME: This should probably be done within FinishShutdown to prevent some games (ie. UNO) from progressing further while the Dialog is still being faded-out, since we can't override non-virtual method... so here is the closes one to FinishShutdown. if (Memory::IsValidAddress(requestAddr)) // Need to validate first to prevent Invalid address when the game is being Shutdown/Exited to menu - Memory::Memcpy(requestAddr, &request, request.common.size, "NpSigninDialogParam"); + Memory::Memcpy(requestAddr, &request, std::min((u32)request.common.size, (u32)sizeof(request)), "NpSigninDialogParam"); return 0; } diff --git a/Core/Dialog/PSPPlaceholderDialog.cpp b/Core/Dialog/PSPPlaceholderDialog.cpp index 85c9415b0c..3f0a7d9565 100644 --- a/Core/Dialog/PSPPlaceholderDialog.cpp +++ b/Core/Dialog/PSPPlaceholderDialog.cpp @@ -31,16 +31,15 @@ int PSPPlaceholderDialog::Init(u32 paramAddr) { if (ReadStatus() != SCE_UTILITY_STATUS_NONE) { return SCE_ERROR_UTILITY_INVALID_STATUS; } + // The request sizes a PSP accepts for GameSharing. + const int check = DialogType() == UtilityDialogType::GAMESHARING ? CheckRequest(paramAddr, { 0x50, 0x54, 0x64 }) : 0; + if (check < 0) { + return check; + } if (!Memory::IsValidRange(paramAddr, sizeof(pspUtilityDialogCommon))) { return SCE_KERNEL_ERROR_BAD_ARGUMENT; } params_ = paramAddr; - // The request sizes a PSP accepts for GameSharing. - const u32 size = params_->size; - if (DialogType() == UtilityDialogType::GAMESHARING && size != 0x50 && size != 0x54 && size != 0x64) { - params_ = 0; - return SCE_ERROR_UTILITY_INVALID_PARAM_SIZE; - } ChangeStatusInit(PLACEHOLDER_INIT_DELAY_US); InitCommon(); diff --git a/Core/Dialog/PSPSaveDialog.cpp b/Core/Dialog/PSPSaveDialog.cpp index 271f3baae9..08a9b88078 100755 --- a/Core/Dialog/PSPSaveDialog.cpp +++ b/Core/Dialog/PSPSaveDialog.cpp @@ -127,6 +127,11 @@ int PSPSaveDialog::Init(int paramAddr) { ioThreadStatus = SAVEIO_NONE; requestAddr = 0; + const int check = CheckRequest(paramAddr, { SAVEDATA_DIALOG_SIZE_V1, SAVEDATA_DIALOG_SIZE_V2, SAVEDATA_DIALOG_SIZE_V3 }); + if (check < 0) { + ERROR_LOG(Log::sceUtility, "sceUtilitySavedataInitStart: bad request at %08x: %08x", paramAddr, check); + return check; + } if (!Memory::IsValid4AlignedAddress(paramAddr)) { return SCE_KERNEL_ERROR_BAD_ARGUMENT; // untested } @@ -134,11 +139,6 @@ int PSPSaveDialog::Init(int paramAddr) { int size = Memory::ReadUnchecked_U32(requestAddr); memset(&request, 0, sizeof(request)); - // Only copy the right size to support different save request format - if (size != SAVEDATA_DIALOG_SIZE_V1 && size != SAVEDATA_DIALOG_SIZE_V2 && size != SAVEDATA_DIALOG_SIZE_V3) { - ERROR_LOG_REPORT(Log::sceUtility, "sceUtilitySavedataInitStart: invalid size %d", size); - return SCE_ERROR_UTILITY_INVALID_PARAM_SIZE; - } Memory::Memcpy(&request, requestAddr, size); Memory::Memcpy(&originalRequest, requestAddr, size); diff --git a/Core/Dialog/PSPScreenshotDialog.cpp b/Core/Dialog/PSPScreenshotDialog.cpp index 83f0d87b21..dcef0c30c5 100644 --- a/Core/Dialog/PSPScreenshotDialog.cpp +++ b/Core/Dialog/PSPScreenshotDialog.cpp @@ -66,22 +66,12 @@ int PSPScreenshotDialog::Init(u32 paramAddr) { return SCE_ERROR_UTILITY_INVALID_STATUS; } + const int check = CheckRequest(paramAddr, { SCE_UTILITY_SCREENSHOTDIALOG_SIZE_V1, SCE_UTILITY_SCREENSHOTDIALOG_SIZE_V2, SCE_UTILITY_SCREENSHOTDIALOG_SIZE_V3 }); + if (check < 0) { + ERROR_LOG(Log::HLE, "sceUtilityScreenshotInitStart(%08x): bad request: %08x", paramAddr, check); + return check; + } params_ = PSPPointer::Create(paramAddr); - if (!params_.IsValid()) { - ERROR_LOG_REPORT(Log::HLE, "sceUtilityScreenshotInitStart(%08x): invalid pointer", paramAddr); - return SCE_KERNEL_ERROR_INVALID_POINTER; - } - - switch ((u32)params_->base.size) { - case SCE_UTILITY_SCREENSHOTDIALOG_SIZE_V1: - case SCE_UTILITY_SCREENSHOTDIALOG_SIZE_V2: - case SCE_UTILITY_SCREENSHOTDIALOG_SIZE_V3: - break; - - default: - ERROR_LOG_REPORT(Log::HLE, "sceUtilityScreenshotInitStart(%08x): invalid size %d", paramAddr, (u32)params_->base.size); - return SCE_ERROR_UTILITY_INVALID_PARAM_SIZE; - } mode = params_->mode; ChangeStatus(SCE_UTILITY_STATUS_INITIALIZE, 0); diff --git a/test.py b/test.py index 51b13df6a1..0900273693 100755 --- a/test.py +++ b/test.py @@ -428,6 +428,7 @@ tests_good = [ "utility/dialog/abort", "utility/dialog/htmlviewer", "utility/dialog/priority", + "utility/dialog/sizes", "utility/dialog/status", "utility/msgdialog/abort", "utility/savedata/autosave",