From 100896c1dc00d337bdba4aa0be3f9511610db6ab Mon Sep 17 00:00:00 2001 From: ArjenS-LA Date: Mon, 12 Oct 2026 00:01:49 -0700 Subject: [PATCH] fix(relinker): convert guest modules with GNU hash tables --- core/relinker/CMakeLists.txt | 3 + .../relinker/src/guest/GuestImageReader.cpp | 32 ++++- .../relinker/tests/test_guest_gnu_hash.py | 111 ++++++++++++++++++ 3 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 core/relinker/relinker/tests/test_guest_gnu_hash.py diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index d3bdf0051..bc0255ad6 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -459,6 +459,9 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_init_array_roots.py $) add_test(NAME gnu_hash_analysis COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_gnu_hash_analysis.py $) + add_test(NAME guest_gnu_hash COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_gnu_hash.py $) + set_tests_properties(guest_gnu_hash PROPERTIES TIMEOUT 20) add_test(NAME to_rosetta COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_to_rosetta.py $) add_test(NAME syscall_number COMMAND ${Python3_EXECUTABLE} diff --git a/core/relinker/relinker/src/guest/GuestImageReader.cpp b/core/relinker/relinker/src/guest/GuestImageReader.cpp index e6c3545a7..4ebc792f4 100644 --- a/core/relinker/relinker/src/guest/GuestImageReader.cpp +++ b/core/relinker/relinker/src/guest/GuestImageReader.cpp @@ -148,7 +148,7 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector if (tag == 1) needed.push_back(value); else if (tag == 0x61000045) moduleImports.push_back(value); else if (tag == 0x6100000d || tag == 0x61000043) moduleExports.push_back(value); - else if (tag < 0x60000000 || tag == 0x6100003f || (tag >= 0x61000027 && tag <= 0x6100003b)) { + else if (tag < 0x60000000 || tag == 0x6ffffef5 || tag == 0x6100003f || (tag >= 0x61000027 && tag <= 0x6100003b)) { if (!tags.emplace(tag, value).second) fail("Duplicate dynamic tag " + std::to_string(tag)); } } @@ -194,7 +194,35 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector std::uint64_t symSize = 0; if (tags.contains(0x6100003f)) symSize = tags.at(0x6100003f); else if (tags.contains(4)) symSize = static_cast(Io::ReadU32(bytes, translate(tags.at(4), 8) + 4)) * 24; - else fail("Missing dynamic symbol count"); + else if (tags.contains(0x6ffffef5)) { + const auto address = tags.at(0x6ffffef5); + const auto header = translate(address, 16); + const auto bucketCount = Io::ReadU32(bytes, header); + const auto firstSymbol = Io::ReadU32(bytes, header + 4); + const auto bloomCount = Io::ReadU32(bytes, header + 8); + if (bucketCount == 0 || bloomCount == 0 || firstSymbol == 0) fail("Invalid GNU hash header"); + const std::uint64_t bucketOffset = 16 + static_cast(bloomCount) * 8; + const auto chainOffset = bucketOffset + static_cast(bucketCount) * 4; + const auto start = translate(address, chainOffset); + std::uint64_t lastBucket = 0; + for (std::uint64_t index = 0; index < bucketCount; ++index) { + const auto bucket = Io::ReadU32(bytes, start + bucketOffset + index * 4); + if (bucket != 0 && bucket < firstSymbol) fail("Invalid GNU hash bucket"); + lastBucket = std::max(lastBucket, static_cast(bucket)); + } + std::uint64_t symbolCount = firstSymbol; + if (lastBucket != 0) { + symbolCount = lastBucket; + for (;;) { + const auto offset = chainOffset + (symbolCount - firstSymbol) * 4; + if (offset > std::numeric_limits::max() - address) fail("Invalid GNU hash chain"); + const auto chain = Io::ReadU32(bytes, translate(address + offset, 4)); + ++symbolCount; + if (chain & 1) break; + } + } + symSize = symbolCount * 24; + } else fail("Missing dynamic symbol count"); if (symSize == 0 || symSize % 24 != 0 || symSize / 24 > std::numeric_limits::max()) fail("Invalid dynamic symbol count"); const auto symOffset = table(6, 0x61000039, symSize); image.Dynamic.DynSymData.assign(bytes.begin() + symOffset, bytes.begin() + symOffset + symSize); diff --git a/core/relinker/relinker/tests/test_guest_gnu_hash.py b/core/relinker/relinker/tests/test_guest_gnu_hash.py new file mode 100644 index 000000000..7b03aca66 --- /dev/null +++ b/core/relinker/relinker/tests/test_guest_gnu_hash.py @@ -0,0 +1,111 @@ +from pathlib import Path +import os +import struct +import subprocess +import sys +import tempfile + +from test_gnu_hash_analysis import name_hash +from test_guest_intel_trampolines import main_fixture +from test_guest_symbol_names import dynamic_tags, file_offset +from test_windows_import_modules import dynamic, executable, provider + + +HASH = 0xA00 +SYMBOLS = 0xC00 +STRINGS = 0xD00 + + +def module(names=("alpha", "bravo", "charlie"), bucket_count=3): + image = provider(42) + image.extend(bytes(0x1000 - len(image))) + struct.pack_into("> 5) % 64)) + if chains: + chains[-1] |= 1 + struct.pack_into("