From 12d785db6ad84d490ed7093461b2289fd397631b Mon Sep 17 00:00:00 2001 From: Hamza Shakir <60213466+hamzashakir99@users.noreply.github.com> Date: Thu, 24 Sep 2026 23:18:27 +0500 Subject: [PATCH] Resolve red-zone patcher jump tables outside the code segment Jump tables usually sit in a read-only data segment, but the resolver only accepted tables inside the executable segment being patched. Every function with a switch was treated as having an unknown indirect branch, which disables neighbour relocation, so its short memory instructions stayed unprotected: 178406 of 520371 in PPSA10595's eboot.bin, including the zstd decoder stores whose handled GPU-tracking faults wiped its red zone. Accept tables anywhere in the loaded module once readable. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/loader/redZonePatcher.cpp | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/loader/redZonePatcher.cpp b/src/loader/redZonePatcher.cpp index 43f3de5c0..5375f0107 100644 --- a/src/loader/redZonePatcher.cpp +++ b/src/loader/redZonePatcher.cpp @@ -23,6 +23,7 @@ #include #include #if defined(_WIN32) +#include // IWYU pragma: keep #include #include #endif @@ -306,10 +307,22 @@ bool WritesRegister(const DecodedCodeInstruction& decoded, ZydisRegister reg) { }); } +bool IsReadableRange(uintptr_t address, size_t size) { + for (uintptr_t current = address; current < address + size;) { + MEMORY_BASIC_INFORMATION info {}; + if (VirtualQuery(reinterpret_cast(current), &info, sizeof(info)) == 0 || + info.State != MEM_COMMIT || (info.Protect & (PAGE_NOACCESS | PAGE_GUARD)) != 0) { + return false; + } + current = reinterpret_cast(info.BaseAddress) + info.RegionSize; + } + return true; +} + std::optional> ResolveBoundedJumpTable(const DecodedFunction& function, uintptr_t branch_address, - uintptr_t function_start, uintptr_t function_end, uintptr_t segment_start, - uintptr_t segment_end) { + uintptr_t function_start, uintptr_t function_end, uintptr_t data_start, + uintptr_t data_end) { const auto branch = function.instructions.find(branch_address); if (branch == function.instructions.end() || branch->second.instruction.mnemonic != ZYDIS_MNEMONIC_JMP || @@ -463,8 +476,9 @@ ResolveBoundedJumpTable(const DecodedFunction& function, uintptr_t branch_addres std::optional> resolved_targets; for (const uintptr_t table_address: table_candidates) { - if (table_address < segment_start || table_address > segment_end || - *table_size > (segment_end - table_address) / sizeof(s32)) { + if (table_address < data_start || table_address > data_end || + *table_size > (data_end - table_address) / sizeof(s32) || + !IsReadableRange(table_address, *table_size * sizeof(s32))) { continue; } @@ -498,7 +512,7 @@ ResolveBoundedJumpTable(const DecodedFunction& function, uintptr_t branch_addres } DecodedFunction DecodeFunction(uintptr_t function_start, uintptr_t function_end, - uintptr_t segment_start, uintptr_t segment_end) { + uintptr_t data_start, uintptr_t data_end) { DecodedFunction function; std::vector blocks {function_start}; std::unordered_set visited; @@ -549,7 +563,7 @@ DecodedFunction DecodeFunction(uintptr_t function_start, uintptr_t function_end, continue; } const auto targets = ResolveBoundedJumpTable(function, branch_address, function_start, - function_end, segment_start, segment_end); + function_end, data_start, data_end); if (!targets) { continue; } @@ -1461,7 +1475,10 @@ RedZonePatchResult PatchGuestInstructions(u64 segment_addr, u64 segment_size, } ++result.function_count; - auto function = DecodeFunction(function_start, function_end, segment_addr, segment_end); + // Jump tables usually live in a read-only data segment, not beside the code. + auto function = + DecodeFunction(function_start, function_end, reinterpret_cast(module->start), + reinterpret_cast(module->end)); AnalyzeFramePointerRedZone(function, function_start); AnalyzeRedZoneLiveness(function); result.instruction_count += function.instructions.size();