From e838a2d9df6c290dc0f2e9dd95f22d999418739b Mon Sep 17 00:00:00 2001 From: boykopovar Date: Sat, 22 Aug 2026 11:47:41 +0300 Subject: [PATCH] fix(nid): rebuild ghu hash for patched nid --- core/libs/nid/src/ElfNidPatcher.cpp | 194 ++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) diff --git a/core/libs/nid/src/ElfNidPatcher.cpp b/core/libs/nid/src/ElfNidPatcher.cpp index dbd5fa8d5..12995de9a 100644 --- a/core/libs/nid/src/ElfNidPatcher.cpp +++ b/core/libs/nid/src/ElfNidPatcher.cpp @@ -51,10 +51,185 @@ struct Elf64_Sym { std::uint64_t st_size; }; +struct Elf64_Rela { + std::uint64_t r_offset; + std::uint64_t r_info; + std::int64_t r_addend; +}; + constexpr std::uint32_t kShtDynsym = 11u; +constexpr std::uint32_t kShtGnuHash = 0x6ffffff6u; +constexpr std::uint32_t kShtRela = 4u; constexpr std::uint8_t kStbLocal = 0u; constexpr std::uint16_t kShnUndef = 0u; +std::uint32_t GnuHash(const std::string& name) { + std::uint32_t h = 5381u; + for (const unsigned char c : name) + h = h * 33u + c; + return h; +} + +struct GnuHashLayout { + std::uint32_t NBuckets; + std::uint32_t SymOffset; + std::uint32_t BloomSize; + std::uint32_t BloomShift; + std::size_t BloomOffset; + std::size_t BucketsOffset; + std::size_t ChainOffset; +}; + +GnuHashLayout ReadGnuHashLayout(const std::vector& elf, std::size_t sectionOffset) { + using namespace Internal; + + GnuHashLayout layout{}; + layout.NBuckets = Read(elf, sectionOffset + 0u); + layout.SymOffset = Read(elf, sectionOffset + 4u); + layout.BloomSize = Read(elf, sectionOffset + 8u); + layout.BloomShift = Read(elf, sectionOffset + 12u); + layout.BloomOffset = sectionOffset + 16u; + layout.BucketsOffset = layout.BloomOffset + static_cast(layout.BloomSize) * 8u; + layout.ChainOffset = layout.BucketsOffset + static_cast(layout.NBuckets) * 4u; + return layout; +} + +void ReorderDynSymForGnuHash( + std::vector& elf, + std::size_t dynSymOffset, + std::size_t symCount, + std::uint32_t symOffset, + std::uint32_t bucketCount, + std::vector& symbolNames, + std::vector& oldToNewIndex +) { + using namespace Internal; + + std::vector movableIndices; + for (std::uint32_t i = symOffset; i < symCount; ++i) + movableIndices.push_back(i); + + std::stable_sort( + movableIndices.begin(), movableIndices.end(), + [&](const std::uint32_t lhs, const std::uint32_t rhs) { + return GnuHash(symbolNames[lhs]) % bucketCount < GnuHash(symbolNames[rhs]) % bucketCount; + } + ); + + std::vector reorderedSyms(symCount); + std::vector reorderedNames(symCount); + + for (std::uint32_t i = 0u; i < symOffset; ++i) { + reorderedSyms[i] = Read(elf, dynSymOffset + i * sizeof(Elf64_Sym)); + reorderedNames[i] = symbolNames[i]; + oldToNewIndex[i] = i; + } + + for (std::size_t position = 0u; position < movableIndices.size(); ++position) { + const std::uint32_t oldIndex = movableIndices[position]; + const std::uint32_t newIndex = symOffset + static_cast(position); + reorderedSyms[newIndex] = Read(elf, dynSymOffset + oldIndex * sizeof(Elf64_Sym)); + reorderedNames[newIndex] = symbolNames[oldIndex]; + oldToNewIndex[oldIndex] = newIndex; + } + + for (std::size_t i = 0u; i < symCount; ++i) + Write(elf, dynSymOffset + i * sizeof(Elf64_Sym), reorderedSyms[i]); + + symbolNames = std::move(reorderedNames); +} + +void FixupRelocationSymbolIndices( + std::vector& elf, + const Elf64_Ehdr& ehdr, + std::uint32_t dynSymSectionIndex, + const std::vector& oldToNewIndex +) { + using namespace Internal; + + for (std::uint16_t i = 0u; i < ehdr.e_shnum; ++i) { + const std::size_t shOffset = static_cast(ehdr.e_shoff) + i * sizeof(Elf64_Shdr); + const auto shdr = Read(elf, shOffset); + if (shdr.sh_type != kShtRela) continue; + if (shdr.sh_link != dynSymSectionIndex) continue; + + const std::size_t relaOffset = static_cast(shdr.sh_offset); + const std::size_t relaCount = static_cast(shdr.sh_size) / sizeof(Elf64_Rela); + + for (std::size_t r = 0u; r < relaCount; ++r) { + const std::size_t entryOffset = relaOffset + r * sizeof(Elf64_Rela); + const auto rela = Read(elf, entryOffset); + + const auto oldSymIndex = static_cast(rela.r_info >> 32u); + if (oldSymIndex >= oldToNewIndex.size()) continue; + + const std::uint32_t newSymIndex = oldToNewIndex[oldSymIndex]; + const auto relType = static_cast(rela.r_info & 0xffffffffu); + const std::uint64_t newInfo = (static_cast(newSymIndex) << 32u) | relType; + + Write(elf, entryOffset + offsetof(Elf64_Rela, r_info), newInfo); + } + } +} + +void RebuildGnuHash( + std::vector& elf, + std::size_t sectionOffset, + std::size_t sectionSize, + std::size_t symCount, + const std::vector& symbolNames +) { + using namespace Internal; + + const GnuHashLayout layout = ReadGnuHashLayout(elf, sectionOffset); + + const std::size_t chainCount = symCount - layout.SymOffset; + const std::size_t requiredSize = layout.ChainOffset - sectionOffset + chainCount * 4u; + if (requiredSize > sectionSize) + throw std::runtime_error("gnu.hash section too small to rebuild in place"); + + std::vector hashes(symCount, 0u); + for (std::size_t i = layout.SymOffset; i < symCount; ++i) + hashes[i] = GnuHash(symbolNames[i]); + + std::vector bloom(layout.BloomSize, 0u); + for (std::size_t i = layout.SymOffset; i < symCount; ++i) { + const std::uint32_t h = hashes[i]; + const std::uint32_t wordIndex = (h / 64u) % layout.BloomSize; + const std::uint32_t bit1 = h % 64u; + const std::uint32_t bit2 = (h >> layout.BloomShift) % 64u; + bloom[wordIndex] |= (std::uint64_t{1} << bit1); + bloom[wordIndex] |= (std::uint64_t{1} << bit2); + } + + std::vector buckets(layout.NBuckets, 0u); + std::vector chain(chainCount, 0u); + + for (std::size_t i = layout.SymOffset; i < symCount; ++i) { + const std::uint32_t bucketIndex = hashes[i] % layout.NBuckets; + if (buckets[bucketIndex] == 0u) + buckets[bucketIndex] = static_cast(i); + } + + for (std::size_t i = layout.SymOffset; i < symCount; ++i) { + const std::uint32_t bucketIndex = hashes[i] % layout.NBuckets; + const bool isLastInBucket = (i + 1u == symCount) || (hashes[i + 1u] % layout.NBuckets != bucketIndex); + chain[i - layout.SymOffset] = isLastInBucket ? (hashes[i] | 1u) : (hashes[i] & ~std::uint32_t{1}); + } + + for (std::uint32_t bloomIndex = 0u; bloomIndex < layout.BloomSize; ++bloomIndex) + Write(elf, layout.BloomOffset + bloomIndex * 8u, bloom[bloomIndex]); + + for (std::uint32_t bucketIndex = 0u; bucketIndex < layout.NBuckets; ++bucketIndex) + Write(elf, layout.BucketsOffset + bucketIndex * 4u, buckets[bucketIndex]); + + for (std::size_t i = 0u; i < chainCount; ++i) + Write(elf, layout.ChainOffset + i * 4u, chain[i]); + + if (requiredSize < sectionSize) + std::memset(elf.data() + sectionOffset + requiredSize, 0, sectionSize - requiredSize); +} + } void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& libraryName) const { @@ -66,12 +241,21 @@ void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& const auto ehdr = Read(elf, 0u); std::size_t dynSymOffset = 0u, dynSymSize = 0u; + std::size_t gnuHashOffset = 0u, gnuHashSize = 0u; + std::uint32_t dynSymSectionIndex = 0u; + std::uint32_t gnuHashSymOffset = 0u; for (std::uint16_t i = 0u; i < ehdr.e_shnum; ++i) { const std::size_t shOffset = static_cast(ehdr.e_shoff) + i * sizeof(Elf64_Shdr); const auto shdr = Read(elf, shOffset); if (shdr.sh_type == kShtDynsym) { dynSymOffset = static_cast(shdr.sh_offset); dynSymSize = static_cast(shdr.sh_size); + dynSymSectionIndex = i; + } + if (shdr.sh_type == kShtGnuHash) { + gnuHashOffset = static_cast(shdr.sh_offset); + gnuHashSize = static_cast(shdr.sh_size); + gnuHashSymOffset = Read(elf, gnuHashOffset + 4u); } } @@ -154,6 +338,7 @@ void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& } newDynStr.resize(dynStrSize, 0u); + std::vector newSymbolNames(symCount); for (std::size_t i = 1u; i < symCount; ++i) { const std::size_t symOffset = dynSymOffset + i * sizeof(Elf64_Sym); const auto sym = Read(elf, symOffset); @@ -165,10 +350,19 @@ void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& ); if (mapped == oldToNewOffset.end()) continue; + newSymbolNames[i] = ReadCStr(newDynStr, mapped->second); Write(elf, symOffset + offsetof(Elf64_Sym, st_name), mapped->second); } std::memcpy(elf.data() + dynStrOffset, newDynStr.data(), newDynStr.size()); + + if (gnuHashOffset != 0u) { + const auto bucketCount = Read(elf, gnuHashOffset); + std::vector oldToNewIndex(symCount); + ReorderDynSymForGnuHash(elf, dynSymOffset, symCount, gnuHashSymOffset, bucketCount, newSymbolNames, oldToNewIndex); + FixupRelocationSymbolIndices(elf, ehdr, dynSymSectionIndex, oldToNewIndex); + RebuildGnuHash(elf, gnuHashOffset, gnuHashSize, symCount, newSymbolNames); + } } }