From 68f5474263b6b69b95ee94eb09d582742efc8a4e Mon Sep 17 00:00:00 2001 From: Vingelvatten <143954555+vingelvatten@users.noreply.github.com> Date: Sat, 24 Oct 2026 11:59:08 +0200 Subject: [PATCH] fix(relinker): accept PT_DYNAMIC data after DT_NULL The dynamic-segment parsers refused a PT_DYNAMIC whose p_filesz is not a multiple of 16 before reading it. elfldr v0.27 from ps5-payload-dev declares 0x1468 bytes, the tail of its PT_LOAD, with DT_NULL at entry 21; FreeBSD rtld and glibc stop at DT_NULL and never read p_filesz. Read whole 16-byte entries within the declared size, stop at DT_NULL, and keep refusing a segment that ends without one, in the executable reader, the code analysis, the relative relocation index, the strict NID filter and the guest module reader. Co-Authored-By: Claude Fable 5.1 --- .../src/analysis/CodeInstructionCollector.cpp | 4 +- .../RelativeRelocationIndex.cpp | 1 - .../UnusedNidFilter/StrictNidFilter.cpp | 3 +- .../relinker/src/guest/GuestImageReader.cpp | 4 +- .../relinker/src/parsing/ElfReader.cpp | 6 +- .../tests/test_conversion_diagnostics.py | 2 +- .../relinker/tests/test_dynamic_segment.py | 56 +++++++++++++++++-- 7 files changed, 57 insertions(+), 19 deletions(-) diff --git a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp index 2e7d216a7f..b03fcecae1 100644 --- a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp +++ b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp @@ -71,11 +71,11 @@ std::set CodeInstructionCollector::Collect(const std::ve for (const auto& header : headers) { if (header.Type == 0x61000000) sceBase = header.Offset; if (header.Type != 2) continue; - if (hasDynamic || header.FileSize % 16 != 0) throw Domain::RelinkerException("Code analysis: invalid dynamic segment"); + if (hasDynamic) throw Domain::RelinkerException("Code analysis: invalid dynamic segment"); hasDynamic = true; range(header.Offset, header.FileSize); bool terminated = false; - for (std::uint64_t offset = header.Offset; offset < header.Offset + header.FileSize; offset += 16) { + for (std::uint64_t offset = header.Offset; header.Offset + header.FileSize - offset >= 16; offset += 16) { const auto tag = Io::ReadU64(bytes, offset); if (tag == 0) { terminated = true; break; } const auto value = Io::ReadU64(bytes, offset + 8); diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp index d6a5eb9ffe..9108445515 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp @@ -175,7 +175,6 @@ std::unique_ptr BuildRelativeRelocationIndex( std::uint64_t segOff = read64(elfBytes, phPos + 8); std::uint64_t segSz = read64(elfBytes, phPos + 32); if (segOff > elfBytes.size() || segSz > elfBytes.size() - segOff) throw RelinkerException("PT_DYNAMIC segment out of bounds"); - if (segSz % 16 != 0) throw RelinkerException("Invalid dynamic segment size", segOff); std::optional relaVa; std::optional relaSz; diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp index 92b5f148e1..75d0da66f9 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp @@ -34,9 +34,8 @@ public: throw RelinkerException("Strict filter: invalid load segment size", segment.Offset); if (segment.Type != 2) continue; if (!tags.empty()) throw RelinkerException("Strict filter: multiple dynamic segments"); - if (segment.FileSize % 16 != 0) throw RelinkerException("Strict filter: truncated dynamic segment", segment.Offset); bool terminated = false; - for (std::uint64_t offset = 0; offset < segment.FileSize; offset += 16) { + for (std::uint64_t offset = 0; segment.FileSize - offset >= 16; offset += 16) { const auto tag = read(segment.Offset + offset); if (tag == 0) { terminated = true; diff --git a/core/relinker/relinker/src/guest/GuestImageReader.cpp b/core/relinker/relinker/src/guest/GuestImageReader.cpp index 6010508de0..4d2da5edac 100644 --- a/core/relinker/relinker/src/guest/GuestImageReader.cpp +++ b/core/relinker/relinker/src/guest/GuestImageReader.cpp @@ -61,14 +61,14 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector tls = &header; } } - if (loads.empty() || dynamic == nullptr || dynamic->FileSize % 16 != 0) fail("Missing or invalid ELF load/dynamic segments"); + if (loads.empty() || dynamic == nullptr) fail("Missing or invalid ELF load/dynamic segments"); std::map tags; std::vector needed; std::vector moduleImports; std::vector ps4ModuleImports; std::vector moduleExports; bool terminated = false; - for (std::uint64_t offset = dynamic->Offset; offset < dynamic->Offset + dynamic->FileSize; offset += 16) { + for (std::uint64_t offset = dynamic->Offset; dynamic->Offset + dynamic->FileSize - offset >= 16; offset += 16) { const auto tag = Io::ReadU64(bytes, offset); const auto value = Io::ReadU64(bytes, offset + 8); if (tag == 0) { terminated = true; break; } diff --git a/core/relinker/relinker/src/parsing/ElfReader.cpp b/core/relinker/relinker/src/parsing/ElfReader.cpp index 82f5e304ae..f112da1bea 100644 --- a/core/relinker/relinker/src/parsing/ElfReader.cpp +++ b/core/relinker/relinker/src/parsing/ElfReader.cpp @@ -270,15 +270,11 @@ std::vector ElfReader::ReadDynamicTags(const ProgramHeader& dynamicH if (!_rangeFits(dynamicHeader.Offset, dynamicHeader.FileSize, _fileBuffer.size())) { throw RelinkerException("Dynamic segment out of bounds", dynamicHeader.Offset); } - if (dynamicHeader.FileSize % 16 != 0) { - throw RelinkerException("Invalid dynamic segment size", dynamicHeader.Offset); - } - std::vector tags; FileByteOffset offset = dynamicHeader.Offset; const FileByteOffset end = dynamicHeader.Offset + dynamicHeader.FileSize; - while (offset < end) { + while (end - offset >= 16) { DynamicTag tag; tag.Tag = static_cast(_readU64At(offset)); tag.Value = _readU64At(offset + 0x08); diff --git a/core/relinker/relinker/tests/test_conversion_diagnostics.py b/core/relinker/relinker/tests/test_conversion_diagnostics.py index 9e9daf8bdf..2bbc2b9dcd 100644 --- a/core/relinker/relinker/tests/test_conversion_diagnostics.py +++ b/core/relinker/relinker/tests/test_conversion_diagnostics.py @@ -75,7 +75,7 @@ def main(): convert(f"{index}-module-name", None, "Unsupported Windows guest filename", flags, module=guest_fixture(PLAIN_SITE)) module = guest_fixture(PLAIN_SITE) struct.pack_into("