From d0502b449d076428b63d2d78606a6af87ea40b1b Mon Sep 17 00:00:00 2001 From: saxopa <99569665+saxopa@users.noreply.github.com> Date: Mon, 12 Oct 2026 16:54:13 +0200 Subject: [PATCH] fix(relinker): load a PT_SCE_RELRO segment that no PT_LOAD covers PS4 executables keep their relocated read-only data, GOT and init/fini arrays in a PT_SCE_RELRO segment outside every PT_LOAD. The segment filter dropped it, so Windows and macOS output stopped at the first relocation into it, and Linux output placed the relinker's extra block over its addresses without an error. The ELF reader now reports such a segment as a read-write PT_LOAD. A PT_SCE_RELRO inside a PT_LOAD keeps being skipped, and one that partially overlaps a PT_LOAD throws. Co-Authored-By: Claude Opus 5.5 --- core/relinker/CMakeLists.txt | 2 + .../relinker/src/parsing/ElfReader.cpp | 17 +++++ .../relinker/relinker/tests/test_sce_relro.py | 74 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 94 insertions(+) create mode 100644 core/relinker/relinker/tests/test_sce_relro.py diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 89709dc94..74a23a8a0 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -465,6 +465,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_string_table_bounds.py $) add_test(NAME sce_code_extent COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_sce_code_extent.py $) + add_test(NAME sce_relro COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_sce_relro.py $) add_test(NAME call_registry COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_call_registry.py $) add_test(NAME jmprel_table_bounds COMMAND ${Python3_EXECUTABLE} diff --git a/core/relinker/relinker/src/parsing/ElfReader.cpp b/core/relinker/relinker/src/parsing/ElfReader.cpp index 4b0e8560d..7e5ab3134 100644 --- a/core/relinker/relinker/src/parsing/ElfReader.cpp +++ b/core/relinker/relinker/src/parsing/ElfReader.cpp @@ -130,6 +130,23 @@ std::vector ElfReader::ReadProgramHeaders() const { offset += header.ProgramHeaderEntrySize; } + for (auto& relro : headers) { + if (relro.Type != PT_OS_RELRO || relro.MemorySize == 0) continue; + bool covered = false; + for (const auto& load : headers) { + if (load.Type != PT_LOAD || load.MemorySize == 0) continue; + const bool overlaps = relro.MappedAddress < load.MappedAddress ? load.MappedAddress - relro.MappedAddress < relro.MemorySize : relro.MappedAddress - load.MappedAddress < load.MemorySize; + if (!overlaps) continue; + if (relro.MappedAddress < load.MappedAddress || relro.MemorySize > load.MemorySize - (relro.MappedAddress - load.MappedAddress)) + throw RelinkerException("PT_SCE_RELRO partially overlaps a PT_LOAD segment", relro.MappedAddress); + covered = true; + } + if (!covered) { + relro.Type = PT_LOAD; + relro.Flags = PF_R | PF_W; + } + } + return headers; } diff --git a/core/relinker/relinker/tests/test_sce_relro.py b/core/relinker/relinker/tests/test_sce_relro.py new file mode 100644 index 000000000..bcb878337 --- /dev/null +++ b/core/relinker/relinker/tests/test_sce_relro.py @@ -0,0 +1,74 @@ +import os +from pathlib import Path +import struct +import subprocess +import sys +import tempfile + +from test_linux_dynamic_strings import dynamic_strings, file_offset +from test_linux_load_alignment import loads + +PT_LOAD = 1 +PT_DYNAMIC = 2 +PT_SCE_RELRO = 0x61000010 +PT_SCE_VERSION = 0x6FFFFF01 + + +def fixture(extra): + image = bytearray(0x3000) + image[:16] = b"\x7fELF\x02\x01\x01" + bytes(9) + headers = [(PT_LOAD, 5, 0, 0, 0x1000, 0x1000, 0x1000), *extra] + tags = [(5, 0x600), (10, 1), (6, 0x620), (11, 24), (7, 0x700), (8, 24), (9, 24), (0, 0)] + headers.append((PT_DYNAMIC, 6, 0x400, 0x400, len(tags) * 16, len(tags) * 16, 8)) + headers += [(PT_SCE_VERSION, 0, 0, 0, 0, 0, 1)] * (7 - len(headers)) + struct.pack_into("