sceKernelModule: Bound the module import parsing

KernelImportModuleFuncs walks data straight out of the module being loaded, and
validated addresses without validating extents:

- The variable relocation list was scanned until it happened to hit a zero word,
  with no bound at all - an unterminated list runs off the end of guest memory.
  Now bounded by what's actually mapped from that address.
- nidData and varData were checked with IsValidAddress, which covers one word,
  and then indexed numFuncs (up to 65535) and numVars times. Check the whole
  array instead.
- The entry walk could read a final PspLibStubEntry that starts just short of
  libstubend and extends past it.

All of these need a corrupt or crafted module, not a normal game.
This commit is contained in:
Henrik Rydgård
2026-09-04 18:10:10 -06:00
parent c4160b4eed
commit abed1ee9eb
+31 -6
View File
@@ -868,11 +868,21 @@ static bool KernelImportModuleFuncs(PSPModule *module, u32 *firstImportStubAddr,
return false;
}
const u32_le *entryPos = (const u32_le *)Memory::GetPointerUnchecked(module->libstub);
const u32_le *entryStart = (const u32_le *)Memory::GetPointerUnchecked(module->libstub);
const u32_le *entryPos = entryStart;
const u32_le *entryEnd = (const u32_le *)Memory::GetPointerUnchecked(module->libstubend);
bool needReport = false;
while (entryPos < entryEnd) {
// The range check above only covers up to libstubend, but a final entry starting just
// short of it extends past. Make sure the whole struct is in mapped memory before reading.
const u32 entryAddr = module->libstub + (u32)(entryPos - entryStart) * sizeof(u32);
if (!Memory::IsValidRange(entryAddr, sizeof(PspLibStubEntry))) {
ERROR_LOG_REPORT(Log::Loader, "Module stub entry at %08x runs off the end of memory", entryAddr);
needReport = true;
break;
}
const PspLibStubEntry *entry = (const PspLibStubEntry *)entryPos;
entryPos += entry->size;
@@ -901,8 +911,9 @@ static bool KernelImportModuleFuncs(PSPModule *module, u32 *firstImportStubAddr,
// If nidData is 0, only variables are being imported.
if (entry->numFuncs > 0 && entry->nidData != 0) {
if (!Memory::IsValidAddress(entry->nidData)) {
ERROR_LOG_REPORT(Log::Loader, "Crazy nidData address %08x, skipping entire module", entry->nidData);
// Note: the whole array has to be valid, not just the first word - numFuncs comes from the module.
if (!Memory::IsValidRange(entry->nidData, entry->numFuncs * sizeof(u32))) {
ERROR_LOG_REPORT(Log::Loader, "Crazy nidData address %08x (%d funcs), skipping entire module", entry->nidData, entry->numFuncs);
needReport = true;
continue;
}
@@ -930,8 +941,9 @@ static bool KernelImportModuleFuncs(PSPModule *module, u32 *firstImportStubAddr,
// We skip vars when reimporting, since we might double-offset.
// We only reimport funcs, which can't be double-offset.
if (entry->numVars > 0 && entry->varData != 0 && !reimporting) {
if (!Memory::IsValidAddress(entry->varData)) {
ERROR_LOG_REPORT(Log::Loader, "Crazy varData address %08x, skipping rest of module", entry->varData);
// Note: the whole array has to be valid, not just the first word - numVars comes from the module.
if (!Memory::IsValidRange(entry->varData, entry->numVars * 8)) {
ERROR_LOG_REPORT(Log::Loader, "Crazy varData address %08x (%d vars), skipping rest of module", entry->varData, entry->numVars);
needReport = true;
continue;
}
@@ -949,13 +961,21 @@ static bool KernelImportModuleFuncs(PSPModule *module, u32 *firstImportStubAddr,
}
WriteVarSymbolState state;
// The relocation list is zero terminated, but the terminator comes from the module,
// so bound the scan by what's actually mapped from varRefsPtr on.
const u32 maxRefs = Memory::ClampValidSizeAt(varRefsPtr, 0x01000000) / sizeof(u32);
const u32_le *varRef = (const u32_le *)Memory::GetPointerUnchecked(varRefsPtr);
for (; *varRef != 0; ++varRef) {
const u32_le *varRefEnd = varRef + maxRefs;
for (; varRef < varRefEnd && *varRef != 0; ++varRef) {
var.nid = nid;
var.stubAddr = (*varRef & 0x03FFFFFF) << 2;
var.type = *varRef >> 26;
module->ImportVar(state, var);
}
if (varRef == varRefEnd) {
WARN_LOG_REPORT(Log::Loader, "Unterminated relocation list for nid %08x in %s", nid, modulename);
needReport = true;
}
}
} else if (entry->numVars > 0 && !reimporting) {
WARN_LOG_REPORT(Log::Loader, "Module entry with %d var imports but no valid address", entry->numVars);
@@ -969,6 +989,11 @@ static bool KernelImportModuleFuncs(PSPModule *module, u32 *firstImportStubAddr,
std::string debugInfo;
entryPos = (const u32_le *)Memory::GetPointerOrException(module->libstub);
while (entryPos < entryEnd) {
// Same partial-entry check as the loop above.
const u32 entryAddr = module->libstub + (u32)(entryPos - entryStart) * sizeof(u32);
if (!Memory::IsValidRange(entryAddr, sizeof(PspLibStubEntry)))
break;
const PspLibStubEntry *entry = (const PspLibStubEntry *)entryPos;
entryPos += entry->size;