From 9673868d5f19a876834b2b60184e149208e09c06 Mon Sep 17 00:00:00 2001 From: p3nicillin Date: Mon, 26 Oct 2026 00:48:02 +0000 Subject: [PATCH] fix(relinker): accept an identical duplicate relocation in Windows executables A RELA entry repeated with the same target, type and addend writes the same value twice, so applying it once is equivalent. The Windows relocation builder refused it as overlapping and code analysis as an invalid relative relocation; the Linux output already passed it through. Forever Launcher's game-child.bin (PPSA99030) repeats one RELATIVE entry at 0x2df70 and could not be converted. Distinct entries at one target are still refused. Co-Authored-By: Claude Opus 5.5 --- core/relinker/CMakeLists.txt | 3 + .../src/windows/WindowsRelocationBuilder.cpp | 5 ++ .../src/analysis/CodeInstructionCollector.cpp | 6 +- .../tests/test_duplicate_relocations.py | 55 +++++++++++++++++++ 4 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 core/relinker/relinker/tests/test_duplicate_relocations.py diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index a33e0cd96c..186a21cc98 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -538,6 +538,9 @@ if(BUILD_TESTING) add_test(NAME overlapping_relocations COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_overlapping_relocations.py $) set_tests_properties(overlapping_relocations PROPERTIES TIMEOUT 30) + add_test(NAME duplicate_relocations COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_duplicate_relocations.py $) + set_tests_properties(duplicate_relocations PROPERTIES TIMEOUT 30) add_test(NAME guest_own_tls_relocations COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_own_tls_relocations.py $) set_tests_properties(guest_own_tls_relocations PROPERTIES TIMEOUT 30) diff --git a/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp index 7bd368ac26..5987e995cf 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace Elfpatcher::Windows { @@ -15,6 +16,7 @@ PeRelocations WindowsRelocationBuilder::Apply(WindowsLoadImage& image, const Dom throw Domain::RelinkerException("Invalid ELF dynamic symbol table size"); PeRelocations result; std::map ranges; + std::map> entries; const auto applyTable = [&](const std::vector& table, const bool plt) { if (table.size() % 24 != 0) throw Domain::RelinkerException("Invalid ELF RELA table size"); @@ -29,10 +31,13 @@ PeRelocations WindowsRelocationBuilder::Apply(WindowsLoadImage& image, const Dom if (target > std::numeric_limits::max() - 8) throw Domain::RelinkerException("Relocation target address overflow", target); const auto rva = image.GetRva(target, 8); + const auto applied = entries.find(target); + if (applied != entries.end() && applied->second == std::pair{info, addend}) continue; const auto next = ranges.lower_bound(target); if ((next != ranges.end() && next->first < target + 8) || (next != ranges.begin() && std::prev(next)->second > target)) throw Domain::RelinkerException("Overlapping ELF relocation targets", target); ranges.emplace(target, target + 8); + entries.emplace(target, std::pair{info, addend}); if (type == 8) { if (symbol != 0) throw Domain::RelinkerException("RELATIVE relocation has a symbol index", target); diff --git a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp index 14fd038f7a..0225956f22 100644 --- a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp +++ b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp @@ -310,7 +310,8 @@ std::set CodeInstructionCollector::Collect(const std::ve const auto matchingDefinedSymbol = hasSymbol && Io::ReadU16(bytes, symbol + 6) != 0 && addend <= static_cast(std::numeric_limits::max()) && Io::ReadU64(bytes, symbol + 8) == addend; - if ((index != 0 && !zeroSectionSymbol && !matchingDefinedSymbol) || !pointers.Values.emplace(slot, addend).second) throw Domain::RelinkerException("Code analysis: invalid relative relocation", slot); + const auto [recorded, inserted] = pointers.Values.emplace(slot, addend); + if ((index != 0 && !zeroSectionSymbol && !matchingDefinedSymbol) || (!inserted && recorded->second != addend)) throw Domain::RelinkerException("Code analysis: invalid relative relocation", slot); addPointerRoot(addend); } else if (type == 1 || type == 6 || type == 7) { if (index != 0 && index >= symbolCount) throw Domain::RelinkerException("Code analysis: invalid relocation symbol", slot); @@ -318,7 +319,8 @@ std::set CodeInstructionCollector::Collect(const std::ve if (index == 0 || Io::ReadU16(bytes, symbol + 6) == 0) importSlots.insert(slot); else { const auto target = Io::ReadU64(bytes, symbol + 8) + addend; - if (!pointers.Values.emplace(slot, target).second) throw Domain::RelinkerException("Code analysis: duplicate pointer relocation", slot); + const auto [recorded, inserted] = pointers.Values.emplace(slot, target); + if (!inserted && recorded->second != target) throw Domain::RelinkerException("Code analysis: duplicate pointer relocation", slot); addPointerRoot(target); } } diff --git a/core/relinker/relinker/tests/test_duplicate_relocations.py b/core/relinker/relinker/tests/test_duplicate_relocations.py new file mode 100644 index 0000000000..b0920bfe16 --- /dev/null +++ b/core/relinker/relinker/tests/test_duplicate_relocations.py @@ -0,0 +1,55 @@ +import struct +import subprocess +import sys +import tempfile +from pathlib import Path + +from test_guest_none_relocations import module +from test_overlapping_relocations import TARGETS, check_output, fixture + + +def with_second_entry(entry): + image, local, plt = fixture("disjoint", False, False) + struct.pack_into("