Fix a batch of missing or wrong range validation

Memory::IsValidAddress and friends tested the extended-RAM range with
(address & 0x3F000000), i.e. at 16MB granularity, so they accepted the whole 16MB
block containing the end of RAM. That's harmless at 32MB and 64MB, but the Sora no
Kiseki SC/3rd HD remasters run with 0x04C00000, so addresses from 0x0CC00000 to
0x0CFFFFFF read as valid, and MaxSizeAtAddress then underflowed to ~4GB there -
which defeats ClampValidSizeAt and IsValidRange entirely for that window. Mask
with 0x3FFFFFFF instead, in all five helpers and the copies in MemMapFunctions.cpp.

IsValidTextureAddress's extended-RAM branch repeated the first branch's whole mask
rather than just its alignment bits, so it was dead code and extended RAM was never
accepted as a texture source.

ComputeTextureHash checked IsValidAddress(addr + sizeInRAM), i.e. only the end
address, which can land in a different valid region than the start - a VRAM texture
with a large enough computed size ends exactly at the base of RAM and "passes"
while reading far past the 8MB VRAM view. Use IsValidRange.

TextureReplacer::ComputeHash's strided path had no range check at all, unlike the
contiguous path right above it. Also clamp the pack-supplied reduce-hash factor to
1.0 - it's a reduction, and the ini parser only rejects exactly 0.

ZipExtractFileToMemory read an uninitialized zip_stat when zip_stat_index failed
(it ignored the return value) and sized a host allocation directly from the zip's
declared uncompressed size. Reached just by opening an archive.

Memory::Reinit ignored Init()'s return value, and DoState fed it a memory size
taken straight from the savestate. A bogus size made the map fail to allocate and
left base null, after which DoMemoryVoid wrote RAM through it. Validate the size,
propagate the failure, and roll back to the previous size if reinit fails.

314 pspautotests pass, all unit tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCPmm7FoQUoqrbMdhfqhQ2
This commit is contained in:
Henrik Rydgård
2026-08-30 23:05:04 +02:00
co-authored by Claude Opus 5
parent e4a0f649fa
commit 016f976b1f
6 changed files with 77 additions and 23 deletions
+31 -7
View File
@@ -315,9 +315,16 @@ void MemoryMap_Shutdown() {
#endif
}
// On some 32 bit platforms (like old Android, old iOS, etc.), there are/were restrictions on memory map sizes.
// This particular size I can't find any sources for though.
static const int MAX_MMAP_SIZE = 31 * 1024 * 1024;
// Every size we actually use (32MB, 64MB, and the 76MB remasters) is a whole number of megabytes.
static bool IsPlausibleMemorySize(u32 size) {
return size != 0 && size <= (u32)MAX_MMAP_SIZE * 3 && (size & 0xFFFFF) == 0;
}
bool Init(MemMapSetupFlags flags) {
// On some 32 bit platforms (like Android, iOS, etc.), you can only map < 32 megs at a time.
const static int MAX_MMAP_SIZE = 31 * 1024 * 1024;
_dbg_assert_msg_(g_MemorySize <= MAX_MMAP_SIZE * 3, "ACK - too much memory for three mmap views.");
for (size_t i = 0; i < ARRAY_SIZE(views); i++) {
if (views[i].flags & MV_IS_PRIMARY_RAM)
@@ -339,7 +346,9 @@ bool Init(MemMapSetupFlags flags) {
return true;
}
void Reinit() {
// Returns false if the new map couldn't be set up - in which case there is no memory map at all,
// and base is null. Callers must not carry on writing to guest memory.
bool Reinit() {
_assert_msg_(PSP_GetBootState() == BootState::Complete, "Cannot reinit during startup/shutdown");
Core_NotifyLifecycle(CoreLifecycle::MEMORY_REINITING);
// Held across both halves: between Shutdown() and Init() there is no memory map at all, and a
@@ -347,8 +356,9 @@ void Reinit() {
CoreShutdownLock coreLock = Core_LockAgainstShutdown();
MemMapSetupFlags flags = g_setupFlags;
Shutdown();
Init(flags);
const bool success = Init(flags);
Core_NotifyLifecycle(CoreLifecycle::MEMORY_REINITED);
return success;
}
static void DoMemoryVoid(PointerWrap &p, uint32_t start, uint32_t size) {
@@ -397,8 +407,10 @@ void DoState(PointerWrap &p) {
p.DoMarker("PSPModel");
if (!g_RemasterMode) {
g_MemorySize = g_PSPModel == PSP_MODEL_FAT ? RAM_NORMAL_SIZE : RAM_DOUBLE_SIZE;
if (oldMemorySize < g_MemorySize) {
Reinit();
if (oldMemorySize < g_MemorySize && !Reinit()) {
ERROR_LOG(Log::MemMap, "Failed to reinit memory to %08x bytes", g_MemorySize);
p.SetError(PointerWrap::ERROR_FAILURE);
return;
}
}
} else {
@@ -408,8 +420,20 @@ void DoState(PointerWrap &p) {
Do(p, g_PSPModel);
p.DoMarker("PSPModel");
Do(p, g_MemorySize);
if (oldMemorySize != g_MemorySize) {
if (p.mode == PointerWrap::MODE_READ && !IsPlausibleMemorySize(g_MemorySize)) {
// Straight out of the file, so don't hand it to Init() - a bogus size makes the map
// fail to allocate, and we'd carry on writing RAM through a null base.
ERROR_LOG(Log::MemMap, "Savestate specifies an implausible memory size: %08x", g_MemorySize);
g_MemorySize = oldMemorySize;
p.SetError(PointerWrap::ERROR_FAILURE);
return;
}
if (oldMemorySize != g_MemorySize && !Reinit()) {
ERROR_LOG(Log::MemMap, "Failed to reinit memory to %08x bytes, restoring %08x", g_MemorySize, oldMemorySize);
g_MemorySize = oldMemorySize;
Reinit();
p.SetError(PointerWrap::ERROR_FAILURE);
return;
}
}