diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index e670c9980..0a19391d9 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -428,6 +428,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_executable_segments.py $) add_test(NAME linux_executable_exports COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_executable_exports.py $) + add_test(NAME executable_tls_relocations COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_executable_tls_relocations.py $) add_test(NAME linux_entry_argv COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_entry_argv.py $) add_test(NAME linux_program_header_order COMMAND ${Python3_EXECUTABLE} diff --git a/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp b/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp index dc0099f1d..e0b0ee4ba 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp @@ -63,6 +63,7 @@ inline constexpr std::uint32_t PT_DYNAMIC = 2; inline constexpr std::uint32_t PT_INTERP = 3; inline constexpr std::uint32_t PT_NOTE = 4; inline constexpr std::uint32_t PT_PHDR = 6; +inline constexpr std::uint32_t PT_TLS = 7; inline constexpr std::uint32_t PT_GNU_EH_FRAME = 0x6474e550; inline constexpr std::uint32_t PT_GNU_STACK = 0x6474e551; inline constexpr std::uint32_t PT_GNU_RELRO = 0x6474e552; @@ -137,6 +138,9 @@ inline constexpr std::uint32_t R_X86_64_RELATIVE = 8; inline constexpr std::uint32_t R_X86_64_GOTPCREL = 9; inline constexpr std::uint32_t R_X86_64_32 = 10; inline constexpr std::uint32_t R_X86_64_32S = 11; +inline constexpr std::uint32_t R_X86_64_DTPMOD64 = 16; +inline constexpr std::uint32_t R_X86_64_DTPOFF64 = 17; +inline constexpr std::uint32_t R_X86_64_TPOFF64 = 18; inline constexpr std::uint32_t R_X86_64_GOTPCRELX = 41; inline constexpr std::uint32_t R_X86_64_REX_GOTPCRELX = 42; @@ -144,6 +148,7 @@ inline constexpr std::uint8_t STB_GLOBAL = 1; inline constexpr std::uint8_t STB_WEAK = 2; inline constexpr std::uint8_t STT_NOTYPE = 0; inline constexpr std::uint8_t STT_FUNC = 2; +inline constexpr std::uint8_t STT_TLS = 6; inline constexpr std::uint8_t STV_DEFAULT = 0; inline constexpr std::uint16_t SHN_UNDEF = 0; diff --git a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsRelocationBuilder.hpp b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsRelocationBuilder.hpp index 0d641e8fe..c9d0c656e 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsRelocationBuilder.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsRelocationBuilder.hpp @@ -7,7 +7,8 @@ namespace Elfpatcher::Windows { class WindowsRelocationBuilder { public: - PeRelocations Apply(WindowsLoadImage& image, const Domain::SysVDynamicSection& dynamicSection) const; + PeRelocations Apply(WindowsLoadImage& image, const Domain::SysVDynamicSection& dynamicSection, + const std::vector& headers) const; std::vector BuildBaseRelocations(const std::vector& targets) const; }; diff --git a/core/relinker/elfpatcher/src/macos/MacOsMachOPatcher.cpp b/core/relinker/elfpatcher/src/macos/MacOsMachOPatcher.cpp index 8e7603773..2a56daf6d 100644 --- a/core/relinker/elfpatcher/src/macos/MacOsMachOPatcher.cpp +++ b/core/relinker/elfpatcher/src/macos/MacOsMachOPatcher.cpp @@ -46,7 +46,7 @@ std::vector MacOsMachOPatcher::Patch(const std::vector WindowsPePatcher::Patch(const std::vector #include #include +#include +#include #include +#include namespace Elfpatcher::Windows { -PeRelocations WindowsRelocationBuilder::Apply(WindowsLoadImage& image, const Domain::SysVDynamicSection& dynamicSection) const { +PeRelocations WindowsRelocationBuilder::Apply(WindowsLoadImage& image, const Domain::SysVDynamicSection& dynamicSection, + const std::vector& headers) const { if (dynamicSection.DynSymData.empty() || dynamicSection.DynSymData.size() % 24 != 0) throw Domain::RelinkerException("Invalid ELF dynamic symbol table size"); PeRelocations result; @@ -19,7 +23,7 @@ PeRelocations WindowsRelocationBuilder::Apply(WindowsLoadImage& image, const Dom const auto addend = Io::ReadU64(table, offset + 16); const auto type = static_cast(info); const auto symbol = info >> 32; - if ((plt && type != 7) || (type != 1 && type != 6 && type != 7 && type != 8)) + if ((plt && type != 7) || (type != 1 && type != 6 && type != 7 && type != 8 && type != 16 && type != 17 && type != 18)) throw Domain::RelinkerException("Unsupported Windows relocation type " + std::to_string(type), target); const auto rva = image.GetRva(target, 8); const auto next = ranges.lower_bound(target); @@ -33,6 +37,21 @@ PeRelocations WindowsRelocationBuilder::Apply(WindowsLoadImage& image, const Dom result.BaseRelocations.push_back(rva); continue; } + if (type == 16 || type == 17 || type == 18) { + if (symbol != 0) + throw Domain::RelinkerException("Imported TLS relocation " + std::to_string(type) + " is not supported for this target", target); + if (type == 16) + throw Domain::RelinkerException("Executable DTPMOD64 relocation is not supported for this target", target); + const auto tls = std::find_if(headers.begin(), headers.end(), [](const auto& header) { return header.Type == 7; }); + if (tls == headers.end() || tls->MemorySize == 0) + throw Domain::RelinkerException("TLS relocation without a non-empty PT_TLS segment", target); + const auto alignment = std::max(tls->Alignment, 1); + if (!std::has_single_bit(alignment) || tls->MemorySize > 0x7fff0000u) + throw Domain::RelinkerException("Invalid or unsupported ELF TLS layout", tls->Offset); + image.RequireWritable(target, 8); + image.WritePointer(target, type == 17 ? addend : addend - Io::AlignUp64(tls->MemorySize, alignment)); + continue; + } if (symbol == 0 || symbol >= dynamicSection.DynSymData.size() / 24) throw Domain::RelinkerException("Invalid import symbol index", target); const auto symbolOffset = static_cast(symbol) * 24; diff --git a/core/relinker/relinker/src/analysis/ValidationPolicy.cpp b/core/relinker/relinker/src/analysis/ValidationPolicy.cpp index 2f2f4f91b..6cf1c7606 100644 --- a/core/relinker/relinker/src/analysis/ValidationPolicy.cpp +++ b/core/relinker/relinker/src/analysis/ValidationPolicy.cpp @@ -29,6 +29,9 @@ void ValidationPolicy::_initializeSupportedRelocationTypes() { R_X86_64_GOTPCREL, R_X86_64_32, R_X86_64_32S, + R_X86_64_DTPMOD64, + R_X86_64_DTPOFF64, + R_X86_64_TPOFF64, R_X86_64_GOTPCRELX, R_X86_64_REX_GOTPCRELX, }; diff --git a/core/relinker/relinker/src/output/SysVDynamicSectionBuilder.cpp b/core/relinker/relinker/src/output/SysVDynamicSectionBuilder.cpp index 65441ef8c..32cb6523c 100644 --- a/core/relinker/relinker/src/output/SysVDynamicSectionBuilder.cpp +++ b/core/relinker/relinker/src/output/SysVDynamicSectionBuilder.cpp @@ -84,7 +84,9 @@ SysVDynamicSection SysVDynamicSectionBuilder::BuildDynamicSection( auto symbolInfo = [](const NidReference& ref) -> std::uint8_t { if (ref.Nid.empty()) return static_cast((STB_WEAK << 4) | STT_NOTYPE); - return static_cast(((ref.Weak ? STB_WEAK : STB_GLOBAL) << 4) | STT_FUNC); + const bool tls = ref.RelocationTypeValue == R_X86_64_DTPMOD64 || ref.RelocationTypeValue == R_X86_64_DTPOFF64 + || ref.RelocationTypeValue == R_X86_64_TPOFF64; + return static_cast(((ref.Weak ? STB_WEAK : STB_GLOBAL) << 4) | (tls ? STT_TLS : STT_FUNC)); }; static constexpr std::size_t kRelaEntSize = 24; diff --git a/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp b/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp index bd9808804..f6a3206e6 100644 --- a/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp +++ b/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp @@ -36,6 +36,9 @@ std::string RelinkerPipeline::relocationTypeName(std::uint32_t type) { case 6: return "R_X86_64_GLOB_DAT"; case 7: return "R_X86_64_JUMP_SLOT"; case 10: return "R_X86_64_32"; + case 16: return "R_X86_64_DTPMOD64"; + case 17: return "R_X86_64_DTPOFF64"; + case 18: return "R_X86_64_TPOFF64"; default: { std::ostringstream oss; oss << "UNKNOWN(" << type << ")"; @@ -230,6 +233,31 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& sourceElf return (raw[symOff + 4] & 0xf) == 3 && value == 0; }; + const auto isTlsRelocation = [](const std::uint32_t type) { + return type == R_X86_64_DTPMOD64 || type == R_X86_64_DTPOFF64 || type == R_X86_64_TPOFF64; + }; + + const bool hasTlsSegment = std::any_of(programHeaders.begin(), programHeaders.end(), + [](const ProgramHeader& ph) { return ph.Type == PT_TLS; }); + + const auto definedTlsSymbolValue = [&](const std::uint32_t symIdx, const FileByteOffset pos) -> std::optional { + const FileByteOffset symOff = dynSymTabOffset + static_cast(symIdx) * symEntSize; + if (symOff < dynSymTabOffset || symOff > raw.size() || raw.size() - symOff < symEntSize) + throw RelinkerException("Symbol table entry out of bounds", symOff); + std::uint16_t section = 0; + std::memcpy(§ion, raw.data() + symOff + 6, sizeof(section)); + if (section == SHN_UNDEF) + return std::nullopt; + if ((raw[symOff + 4] & 15) != STT_TLS) + throw RelinkerException("TLS relocation names a defined symbol that is not STT_TLS", pos); + std::uint64_t value = 0; + std::memcpy(&value, raw.data() + symOff + 8, sizeof(value)); + return value; + }; + const auto definedTlsSymbol = [&](const std::uint32_t symIdx, const FileByteOffset pos) { + return definedTlsSymbolValue(symIdx, pos).has_value(); + }; + auto extractRela = [&](const FileByteOffset relaOff, const ByteCount relaSize) { for (ByteCount off = 0; relaSize >= relaEntSize && off <= relaSize - relaEntSize; off += relaEntSize) { const FileByteOffset pos = relaEntryPos(relaOff, off); @@ -252,6 +280,9 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& sourceElf continue; } + if (isTlsRelocation(relType) && (symIdx == 0 || definedTlsSymbol(symIdx, pos))) + continue; + const FileByteOffset symOff = dynSymTabOffset + static_cast(symIdx) * symEntSize; if (symOff < dynSymTabOffset || symOff > raw.size() || raw.size() - symOff < 5) throw RelinkerException("Symbol table entry out of bounds", symOff); @@ -415,7 +446,7 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& sourceElf std::memcpy(buf.data() + pos + 16, &addend, 8); }; - auto extractRelative = [&](const FileByteOffset relaOff, const ByteCount relaSize) { + auto extractLocal = [&](const FileByteOffset relaOff, const ByteCount relaSize) { for (ByteCount off = 0; relaSize >= relaEntSize && off <= relaSize - relaEntSize; off += relaEntSize) { const FileByteOffset pos = relaEntryPos(relaOff, off); std::uint64_t rOffset = 0, rInfo = 0; @@ -423,14 +454,32 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& sourceElf std::memcpy(&rOffset, raw.data() + pos, 8); std::memcpy(&rInfo, raw.data() + pos + 8, 8); std::memcpy(&rAddend, raw.data() + pos + 16, 8); + const std::uint32_t symIdx = static_cast(rInfo >> 32); const std::uint32_t relType = static_cast(rInfo & 0xffffffff); - if (relType == R_X86_64_RELATIVE) + if (relType == R_X86_64_RELATIVE) { appendRela(dynSection.RelaData, rOffset, static_cast(R_X86_64_RELATIVE), rAddend); + continue; + } + if (!isTlsRelocation(relType)) + continue; + std::uint64_t offset = 0; + if (symIdx != 0) { + const auto value = definedTlsSymbolValue(symIdx, pos); + if (!value) + continue; + offset = *value; + } + if (!hasTlsSegment) + throw RelinkerException("TLS relocation in an executable without a PT_TLS segment", pos); + if (relType == R_X86_64_DTPMOD64 && rAddend != 0) + throw RelinkerException("DTPMOD64 relocation has a nonzero addend", pos); + const auto addend = relType == R_X86_64_DTPMOD64 ? 0 : static_cast(offset + static_cast(rAddend)); + appendRela(dynSection.RelaData, rOffset, static_cast(relType), addend); } }; - extractRelative(dynRelaOffset, dynRelaSize); - extractRelative(dynJmpRelOffset, dynJmpRelSize); + extractLocal(dynRelaOffset, dynRelaSize); + extractLocal(dynJmpRelOffset, dynJmpRelSize); std::vector entries; entries.reserve(nidRefs.size()); diff --git a/core/relinker/relinker/tests/test_executable_tls_relocations.py b/core/relinker/relinker/tests/test_executable_tls_relocations.py new file mode 100644 index 000000000..074767719 --- /dev/null +++ b/core/relinker/relinker/tests/test_executable_tls_relocations.py @@ -0,0 +1,143 @@ +"""Initial-exec TLS relocations in the executable (R_X86_64_TPOFF64, DTPOFF64, DTPMOD64), as a PS5 eboot +has them in its GOT. The Linux ELF keeps them dynamic and symbolless, with the variable's offset in the +TLS block as the addend, for the dynamic loader to resolve against where it puts the block; a named one +against a defined STT_TLS symbol is folded the same way, and one against an import stays named, typed +STT_TLS. The Windows and macOS images resolve TPOFF64 and DTPOFF64 statically against their block, +AlignUp(p_memsz, p_align) below the thread pointer, and reject what they cannot express.""" + +from pathlib import Path +import struct +import subprocess +import sys +import tempfile + +from test_linux_load_alignment import fixture +from test_macos_output import MachO +from test_tls_function_coverage import pe_bytes_at + +PT_LOAD = 1 +PT_TLS = 7 +DTPMOD64, DTPOFF64, TPOFF64, TPOFF32 = 16, 17, 18, 23 +TLS_MEMSZ, TLS_ALIGN = 0x24, 16 +TLS_BLOCK = 0x30 +STRINGS = b"\0Tls\0Data\0ImportedTls\0" +TLS_NAME, DATA_NAME, IMPORT_NAME = 1, 5, 10 + +NULL, TLS_SYMBOL, DATA_SYMBOL, IMPORTED = 0, 1, 2, 3 + + +def image(relocations, tls=True): + """The load-alignment fixture with a writable segment (vaddr 0x1000) holding a GOT and an 8-byte + TLS template, a symbol table and the given (offset, type, symbol, addend) relocations.""" + data = fixture() + struct.pack_into("> 32) * 24) + end = elf.index(b"\0", strings + name) + result.append((target, info & 0xffffffff, elf[strings + name:end].decode(), symbol_info & 15, addend)) + return result + + +def macho_bytes_at(data, address, size): + for segment in MachO(data).segments: + if segment["vmaddr"] <= address and address + size <= segment["vmaddr"] + segment["filesize"]: + offset = segment["fileoff"] + address - segment["vmaddr"] + return data[offset:offset + size] + raise AssertionError(f"unmapped address {address:#x}") + + +def main(): + relinker = Path(sys.argv[1]).resolve() + with tempfile.TemporaryDirectory(prefix="anyps5-exec-tls-") as directory: + local = [(0x1000, TPOFF64, NULL, 4), (0x1008, DTPOFF64, NULL, 8), (0x1010, TPOFF64, TLS_SYMBOL, 2)] + result, output = relink(relinker, directory, "linux", image(local + [(0x1018, DTPMOD64, NULL, 0), + (0x1020, TPOFF64, IMPORTED, 0)])) + assert result.returncode == 0, (result.stdout, result.stderr) + elf = output.read_bytes() + relocations = dynamic_relocations(elf) + tls = sorted(relocation for relocation in relocations if relocation[1] in (DTPMOD64, DTPOFF64, TPOFF64)) + assert tls == [(0x1000, TPOFF64, "", 0, 4), (0x1008, DTPOFF64, "", 0, 8), (0x1010, TPOFF64, "", 0, 0x12), + (0x1018, DTPMOD64, "", 0, 0), (0x1020, TPOFF64, "ImportedTls", 6, 0)], relocations + assert any(struct.unpack_from("