diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 2d05bbf7d..47cc710e8 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -293,6 +293,8 @@ if(BUILD_TESTING) endif() add_test(NAME entry_point_bounds COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_entry_point_bounds.py $) + add_test(NAME init_array_roots COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_init_array_roots.py $) else() message(STATUS "Python3 interpreter not found; skipping Python relinker tests") endif() diff --git a/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp b/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp index 51936f5dc..b0b6e5f25 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp @@ -97,6 +97,8 @@ inline constexpr std::int64_t DT_INIT_ARRAYSZ = 27; inline constexpr std::int64_t DT_FINI_ARRAYSZ = 28; inline constexpr std::int64_t DT_RUNPATH = 29; inline constexpr std::int64_t DT_FLAGS = 30; +inline constexpr std::int64_t DT_PREINIT_ARRAY = 32; +inline constexpr std::int64_t DT_PREINIT_ARRAYSZ = 33; inline constexpr std::uint64_t DF_BIND_NOW = 0x8; inline constexpr std::int64_t DT_OS_INIT = 0x6000000c; @@ -105,6 +107,8 @@ inline constexpr std::int64_t DT_OS_INIT_ARRAY = 0x60000019; inline constexpr std::int64_t DT_OS_FINI_ARRAY = 0x6000001a; inline constexpr std::int64_t DT_OS_INIT_ARRAYSZ = 0x6000001b; inline constexpr std::int64_t DT_OS_FINI_ARRAYSZ = 0x6000001c; +inline constexpr std::int64_t DT_OS_PREINIT_ARRAY = 0x60000020; +inline constexpr std::int64_t DT_OS_PREINIT_ARRAYSZ = 0x60000021; inline constexpr std::int64_t DT_OS_PLTGOT = 0x61000027; inline constexpr std::int64_t DT_OS_JMPREL = 0x61000029; inline constexpr std::int64_t DT_OS_PLTREL = 0x6100002b; diff --git a/core/relinker/relinker/include/relinker/analysis/UnusedNidFilter/IEntryPointCollector.hpp b/core/relinker/relinker/include/relinker/analysis/UnusedNidFilter/IEntryPointCollector.hpp index 0241329ee..fc9127d18 100644 --- a/core/relinker/relinker/include/relinker/analysis/UnusedNidFilter/IEntryPointCollector.hpp +++ b/core/relinker/relinker/include/relinker/analysis/UnusedNidFilter/IEntryPointCollector.hpp @@ -2,6 +2,7 @@ #define RELINKER_ANALYSIS_UNUSED_NID_FILTER_IENTRYPOINTCOLLECTOR_HPP #include +#include #include #include @@ -13,7 +14,8 @@ public: virtual std::vector Collect( const std::vector& elfBytes, VirtualAddress textVaddr, - std::size_t textSize + std::size_t textSize, + const IRelativeRelocationIndex& relativeRelocations ) const = 0; }; diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter.cpp index e6b796348..e3396f536 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter.cpp @@ -16,15 +16,15 @@ public: ) override { if (textSection.empty()) throw RelinkerException("Cannot filter NIDs: text section is empty"); + auto relativeRelocations = UnusedNidFilter::BuildRelativeRelocationIndex(elfBytes); auto collector = UnusedNidFilter::MakeEntryPointCollector(); - auto entries = collector->Collect(elfBytes, textVAddr, textSection.size()); + auto entries = collector->Collect(elfBytes, textVAddr, textSection.size(), *relativeRelocations); if (entries.empty()) throw RelinkerException("Cannot filter NIDs: no entry points found"); VirtualAddress primary = entries[0]; std::vector extra(entries.begin() + 1, entries.end()); - auto relativeRelocations = UnusedNidFilter::BuildRelativeRelocationIndex(elfBytes); auto cfg = UnusedNidFilter::BuildControlFlowGraph(textSection, textVAddr, primary, extra, *relativeRelocations); auto index = UnusedNidFilter::BuildGotAccessIndex(*cfg, textSection, textVAddr); diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/EntryPointCollector.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/EntryPointCollector.cpp index 4840a3bad..8e87a10d8 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/EntryPointCollector.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/EntryPointCollector.cpp @@ -1,6 +1,7 @@ #include #include #include +#include namespace Relinker::UnusedNidFilter { @@ -30,6 +31,12 @@ bool fitsAt(std::uint64_t base, std::uint64_t offset, std::uint64_t length, std: return base <= size && offset <= size - base && length <= size - base - offset; } +struct LoadSegment { + VirtualAddress vaddr; + std::uint64_t fileOffset; + std::uint64_t fileSize; +}; + bool inText(VirtualAddress va, VirtualAddress textVaddr, std::size_t textSize) { return va >= textVaddr && va < textVaddr + static_cast(textSize); } @@ -41,7 +48,8 @@ public: std::vector Collect( const std::vector& elfBytes, VirtualAddress textVaddr, - std::size_t textSize + std::size_t textSize, + const IRelativeRelocationIndex& relativeRelocations ) const override { if (elfBytes.size() < 64) throw RelinkerException("ELF too small for header"); if (elfBytes[0] != 0x7f || elfBytes[1] != 'E' || @@ -66,6 +74,14 @@ public: if (phEntSize < 56) throw RelinkerException("ELF program header entry too small"); + std::vector loads; + for (std::uint16_t i = 0; i < phCount; ++i) { + std::size_t phPos = static_cast(phOff) + i * phEntSize; + if (phPos + 56 > elfBytes.size()) throw RelinkerException("Program header out of bounds"); + if (read32(elfBytes, phPos) != PT_LOAD) continue; + loads.push_back({read64(elfBytes, phPos + 16), read64(elfBytes, phPos + 8), read64(elfBytes, phPos + 32)}); + } + for (std::uint16_t i = 0; i < phCount; ++i) { const std::uint64_t phIndexOff = static_cast(i) * phEntSize; if (!fitsAt(phOff, phIndexOff, 56, elfBytes.size())) throw RelinkerException("Program header out of bounds"); @@ -84,6 +100,8 @@ public: std::uint64_t initArraySz = 0; VirtualAddress finiArrayVa = 0; std::uint64_t finiArraySz = 0; + VirtualAddress preinitArrayVa = 0; + std::uint64_t preinitArraySz = 0; for (std::uint64_t off = 0; off + 16 <= segSz; off += 16) { std::size_t pos = static_cast(segOff + off); @@ -97,24 +115,37 @@ public: if (tag == DT_INIT_ARRAYSZ || tag == DT_OS_INIT_ARRAYSZ) initArraySz = val; if (tag == DT_FINI_ARRAY || tag == DT_OS_FINI_ARRAY) finiArrayVa = val; if (tag == DT_FINI_ARRAYSZ || tag == DT_OS_FINI_ARRAYSZ) finiArraySz = val; + if (tag == DT_PREINIT_ARRAY || tag == DT_OS_PREINIT_ARRAY) preinitArrayVa = val; + if (tag == DT_PREINIT_ARRAYSZ || tag == DT_OS_PREINIT_ARRAYSZ) preinitArraySz = val; } auto collectArray = [&](VirtualAddress arrayVa, std::uint64_t arraySz) { if (arrayVa == 0 || arraySz == 0) return; - std::uint64_t phFileOff = read64(elfBytes, phPos + 8); - std::uint64_t phVaddr = read64(elfBytes, phPos + 16); - if (arrayVa < phVaddr || !fitsAt(phFileOff, arrayVa - phVaddr, 0, elfBytes.size())) return; - std::uint64_t arrayFileOff = phFileOff + (arrayVa - phVaddr); - std::uint64_t count = arraySz / 8; - for (std::uint64_t k = 0; k < count; ++k) { - if (!fitsAt(arrayFileOff, k * 8, 8, elfBytes.size())) break; - std::size_t entPos = static_cast(arrayFileOff + k * 8); - addIfInText(read64(elfBytes, entPos)); + if (arraySz % 8 != 0) throw RelinkerException("Init/fini array size is not a multiple of 8", arrayVa); + const LoadSegment* container = nullptr; + for (const auto& load : loads) { + if (arrayVa >= load.vaddr && arrayVa - load.vaddr <= load.fileSize && + arraySz <= load.fileSize - (arrayVa - load.vaddr)) { + container = &load; + break; + } + } + if (!container) throw RelinkerException("Init/fini array is not file-backed", arrayVa); + std::uint64_t arrayFileOff = container->fileOffset + (arrayVa - container->vaddr); + if (arrayFileOff > elfBytes.size() || arraySz > elfBytes.size() - arrayFileOff) + throw RelinkerException("Init/fini array is out of bounds", arrayVa); + for (std::uint64_t k = 0; k < arraySz / 8; ++k) { + VirtualAddress slot = arrayVa + k * 8; + auto relocated = relativeRelocations.TargetOfSlot(slot); + std::uint64_t target = relocated ? *relocated : read64(elfBytes, static_cast(arrayFileOff + k * 8)); + if (target == 0 || target == std::numeric_limits::max()) continue; + addIfInText(target); } }; collectArray(initArrayVa, initArraySz); collectArray(finiArrayVa, finiArraySz); + collectArray(preinitArrayVa, preinitArraySz); break; } diff --git a/core/relinker/relinker/tests/test_init_array_roots.py b/core/relinker/relinker/tests/test_init_array_roots.py new file mode 100644 index 000000000..5f847a8eb --- /dev/null +++ b/core/relinker/relinker/tests/test_init_array_roots.py @@ -0,0 +1,110 @@ +from pathlib import Path +import struct +import subprocess +import sys +import tempfile + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from test_jmprel_table_bounds import fixture, DT_RELA, DT_RELASZ + + +DT_INIT_ARRAY = 25 +DT_FINI_ARRAY = 26 +DT_INIT_ARRAYSZ = 27 +DT_FINI_ARRAYSZ = 28 +DT_PREINIT_ARRAY = 32 +DT_PREINIT_ARRAYSZ = 33 +DT_OS_INIT_ARRAY = 0x60000019 +DT_OS_INIT_ARRAYSZ = 0x6000001B +DT_OS_PREINIT_ARRAY = 0x60000020 +DT_OS_PREINIT_ARRAYSZ = 0x60000021 +R_X86_64_GLOB_DAT = 6 +R_X86_64_RELATIVE = 8 +DYNAMIC = 0x400 +RELA = 0x7A0 +GOT_SLOT = 0x308 +FUNCTION = 0x280 +BEFORE_DYNAMIC = 0x380 +AFTER_DYNAMIC = 0x540 +LOAD_END = 0x800 +KEPT = "CFG/GOT filtering: 1 -> 1; filtered=0" +DROPPED = "CFG/GOT filtering: 1 -> 0; filtered=1" + + +def image(array_tag=None, array_size_tag=None, array_va=0, slots=(), relative=(), load_size=None): + elf = fixture() + for index in range(16): + tag, _ = struct.unpack_from("