SaveState: Fixes from a review of the kernel savestates

- A partition memory block restored its allocator through
  BlockAllocatorFromID, whose privilege check asked the thread current
  before the load, already deleted with the kernel objects. Loading a
  kernel-partition block under a user thread left it without an allocator,
  crashing when it was freed. The saved id maps straight back now, and the
  current thread is cleared along with the objects.
- The thread LoadExec leaves waiting (with no timeout) for plugins and VSH
  modules to start wasn't saved, so a state saved while one was starting
  hung the game thread after loading. The VSH modules still to start
  weren't saved either, nor cleared.
- A state without the exit callback id left the previous session's.
- From a broken or crafted state: thread ids that GetFast trusts (current,
  idle and ready), interrupt handler numbers, FPL and TLSPL indices, and a
  MIPS call or action slot that isn't there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-20 00:20:24 +02:00
1 parent d40137a007
commit 30cab94840
8 files changed
+91 -6

No files matched your search

+17 -1
View File
@@ -117,6 +117,10 @@ void FPL::DoState(PointerWrap &p) {
FplWaitingThread dv = { 0 };
Do(p, waitingThreads, dv);
Do(p, pausedWaits);
// It's taken modulo the block count, but a negative one stays negative.
if (p.mode == p.MODE_READ && nextBlock < 0) {
p.SetError(p.ERROR_FAILURE);
}
}
void SceKernelVplHeader::Init(u32 ptr, u32 size) {
@@ -834,7 +838,14 @@ public:
if (s >= 2) {
int allocType = BlockAllocatorToID(alloc);
Do(p, allocType);
alloc = BlockAllocatorFromID(allocType);
// From BlockAllocatorToID, so not a game's partition id: map it back without
// BlockAllocatorFromID's privilege check, which asks about whatever thread is current.
switch (allocType) {
case KERNEL_PARTITION_ID: alloc = &kernelMemory; break;
case USER_PARTITION_ID: alloc = &userMemory; break;
case VSHELL_PARTITION_ID: alloc = &volatileMemory; break;
default: alloc = nullptr; break;
}
}
}
@@ -1699,6 +1710,11 @@ struct TLSPL : public KernelObject {
Do(p, waitingThreads);
Do(p, next);
Do(p, usage);
// Both index arrays.
if (p.mode == p.MODE_READ && (ntls.index < 0 || ntls.index >= TLSPL_NUM_INDEXES || usage.size() != (size_t)ntls.totalBlocks)) {
ERROR_LOG(Log::SaveState, "TLSPL: bad index %d or block count %d (%d used) in savestate", ntls.index, ntls.totalBlocks, (int)usage.size());
p.SetError(p.ERROR_FAILURE);
}
}
NativeTlspl ntls;