From bfa1ac0751d6cec02b4bc13d26ca64cd0320be1b Mon Sep 17 00:00:00 2001 From: boykopovar Date: Sun, 27 Sep 2026 09:00:11 +0300 Subject: [PATCH] fix(relinker): scan complete function ranges for windows tls accesses --- core/relinker/CMakeLists.txt | 4 +- .../src/analysis/CodeInstructionCollector.cpp | 49 +++++++- .../tests/test_tls_function_coverage.py | 116 ++++++++++++++++++ 3 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 core/relinker/relinker/tests/test_tls_function_coverage.py diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 0e95e87f9..606259877 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -86,8 +86,10 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_optional_plt.py $) add_test(NAME empty_tls COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_empty_tls.py $) + add_test(NAME tls_function_coverage COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_tls_function_coverage.py $) else() - message(STATUS "Python3 interpreter not found; skipping optional_plt and empty_tls tests") + message(STATUS "Python3 interpreter not found; skipping Python relinker tests") endif() add_executable(strict_nid_filter_tests ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/StrictNidFilterTests.cpp diff --git a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp index 061c35378..0b009c39f 100644 --- a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp +++ b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp @@ -2,8 +2,11 @@ #include #include #include +#include #include #include +#include +#include namespace Relinker { @@ -73,7 +76,27 @@ std::set CodeInstructionCollector::Collect(const std::ve return offset; }; std::set roots; + std::map functions; const auto addRoot = [&](std::uint64_t address) { if (isCode(address)) roots.insert(address); }; + const auto addFunction = [&](std::uint64_t begin, std::uint64_t size) { + if (size == 0) return; + if (!isCode(begin) || size > std::numeric_limits::max() - begin) + throw Domain::RelinkerException("Code analysis: invalid function range", begin); + bool mapped = false; + for (const auto& header : headers) { + if (header.Type != 1 || (header.Flags & 1) == 0 || begin < header.MappedAddress || begin - header.MappedAddress >= header.FileSize) continue; + if (size > header.FileSize - (begin - header.MappedAddress)) + throw Domain::RelinkerException("Code analysis: function exceeds executable segment", begin); + range(header.Offset + begin - header.MappedAddress, size); + mapped = true; + break; + } + if (!mapped) throw Domain::RelinkerException("Code analysis: function is not file-backed", begin); + const auto [position, inserted] = functions.emplace(begin, begin + size); + if (!inserted && position->second != begin + size) + throw Domain::RelinkerException("Code analysis: conflicting function ranges", begin); + roots.insert(begin); + }; range(0, 64); if (const auto entry = Io::ReadU64(bytes, 24); entry != 0) addRoot(entry); for (const auto tag : {12u, 13u}) if (const auto address = value(tag, 0x60000000 + tag); address && *address != 0) addRoot(*address); @@ -89,7 +112,11 @@ std::set CodeInstructionCollector::Collect(const std::ve symbols = table(6, 0x61000039, symbolCount * 24); for (std::uint64_t index = 0; index < symbolCount; ++index) { const auto offset = symbols + index * 24; - if ((bytes[offset + 4] & 15) == 2 && Io::ReadU16(bytes, offset + 6) != 0) addRoot(Io::ReadU64(bytes, offset + 8)); + if ((bytes[offset + 4] & 15) == 2 && Io::ReadU16(bytes, offset + 6) != 0) { + const auto address = Io::ReadU64(bytes, offset + 8); + addRoot(address); + addFunction(address, Io::ReadU64(bytes, offset + 16)); + } } } CodePointers pointers; @@ -139,12 +166,30 @@ std::set CodeInstructionCollector::Collect(const std::ve } } for (const auto& function : UnusedNidFilter::ReadExceptionFunctions(bytes, headers, pointers.Values, importSlots)) { - addRoot(function.Begin); + if (function.End <= function.Begin) throw Domain::RelinkerException("Code analysis: invalid unwind function range", function.Begin); + addFunction(function.Begin, function.End - function.Begin); for (const auto target : function.ExtraTargets) addRoot(target); } if (roots.empty()) throw Domain::RelinkerException("Code analysis: no code entry points"); std::set instructions; const Codegen::X64InstructionDecoder decoder; + for (const auto& [begin, end] : functions) { + const auto offset = fileOffset(begin, end - begin); + for (auto address = begin; address < end;) { + Codegen::DecodedInstructionInfo info; + try { + info = decoder.DecodeInstruction(bytes.data() + offset + address - begin, end - address); + } catch (const Codegen::CodegenException& error) { + throw Domain::RelinkerException(std::string("Code analysis: ") + error.what(), address); + } + if (info.Length == 0 || info.Length > end - address) + throw Domain::RelinkerException("Code analysis: instruction crosses function boundary", address); + instructions.insert(address); + if (info.HasBranchTarget && !info.HasRipRelativeDisp) + addRoot(address + info.Length + static_cast(info.BranchDisp)); + address += info.Length; + } + } std::size_t previousRoots = 0; do { previousRoots = roots.size(); diff --git a/core/relinker/relinker/tests/test_tls_function_coverage.py b/core/relinker/relinker/tests/test_tls_function_coverage.py new file mode 100644 index 000000000..c0bc8145b --- /dev/null +++ b/core/relinker/relinker/tests/test_tls_function_coverage.py @@ -0,0 +1,116 @@ +import os +from pathlib import Path +import struct +import subprocess +import sys +import tempfile + +from test_optional_plt import fixture + + +TLS_LOAD = bytes.fromhex("66 66 66 64 48 8b 04 25 00 00 00 00") + + +def make_image(transfer, metadata, extent=None): + image = fixture() + image.extend(b"\x90" * 0x1000) + struct.pack_into("