From bb443667ec4e53c1ce6a3b8aec04960634649596 Mon Sep 17 00:00:00 2001 From: MiniRML <134226498+MiniRML@users.noreply.github.com> Date: Mon, 12 Oct 2026 09:39:28 -0700 Subject: [PATCH] fix(nid): validate GNU hash geometry before rewriting --- core/libs/CMakeLists.txt | 2 + core/libs/nid/src/ElfNidPatcher.cpp | 17 ++++++- core/libs/nid/tests/test_gnu_hash.py | 75 ++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 core/libs/nid/tests/test_gnu_hash.py diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index d094f39cf..7d1d29857 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -163,6 +163,8 @@ if(BUILD_TESTING) endif() find_package(Python3 QUIET COMPONENTS Interpreter) if(Python3_Interpreter_FOUND) + add_test(NAME nid_gnu_hash COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_gnu_hash.py $) add_test(NAME nid_patcher_macho COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_macho_nids.py $) endif() diff --git a/core/libs/nid/src/ElfNidPatcher.cpp b/core/libs/nid/src/ElfNidPatcher.cpp index 4f341bbd2..972af0d23 100644 --- a/core/libs/nid/src/ElfNidPatcher.cpp +++ b/core/libs/nid/src/ElfNidPatcher.cpp @@ -36,6 +36,18 @@ GnuHashLayout ReadGnuHashLayout(const std::vector& elf, std::size_ return layout; } +void ValidateGnuHash(const std::vector& elf, std::size_t offset, std::size_t size, std::size_t symCount) { + if (offset > elf.size() || size > elf.size() - offset || size < 16u) + throw std::runtime_error("invalid gnu.hash section range"); + const auto layout = ReadGnuHashLayout(elf, offset); + if (layout.SymOffset > symCount || !layout.BloomSize || (layout.BloomSize & (layout.BloomSize - 1u)) || + layout.BloomShift >= 32u || (!layout.NBuckets && layout.SymOffset != symCount)) + throw std::runtime_error("invalid gnu.hash geometry"); + const std::uint64_t required = 16u + std::uint64_t{layout.BloomSize} * 8u + + std::uint64_t{layout.NBuckets} * 4u + (symCount - layout.SymOffset) * 4u; + if (required > size) throw std::runtime_error("gnu.hash section too small to rebuild in place"); +} + void ReorderDynSymForGnuHash( std::vector& elf, std::size_t dynSymOffset, @@ -333,7 +345,6 @@ void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& if (shdr.sh_type == kShtGnuHash) { gnuHashOffset = static_cast(shdr.sh_offset); gnuHashSize = static_cast(shdr.sh_size); - gnuHashSymOffset = Read(elf, gnuHashOffset + 4u); } if (shdr.sh_type == kShtGnuVersym) versymOffset = static_cast(shdr.sh_offset); @@ -345,6 +356,10 @@ void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& const std::size_t symCount = dynSymSize / sizeof(Elf64_Sym); if (symCount == 0u) throw std::runtime_error(".dynsym is empty"); + if (gnuHashOffset != 0u) { + ValidateGnuHash(elf, gnuHashOffset, gnuHashSize, symCount); + gnuHashSymOffset = Read(elf, gnuHashOffset + 4u); + } const std::size_t dynStrSectionLink = [&]() -> std::size_t { for (std::uint16_t i = 0u; i < ehdr.e_shnum; ++i) { diff --git a/core/libs/nid/tests/test_gnu_hash.py b/core/libs/nid/tests/test_gnu_hash.py new file mode 100644 index 000000000..3b2d27bbc --- /dev/null +++ b/core/libs/nid/tests/test_gnu_hash.py @@ -0,0 +1,75 @@ +import struct +import sys +import unittest +from pathlib import Path + +from test_symbol_versions import build_image, symbols, patch, HASH_OFFSET, SHOFF, read_dynsym, gnu_hash + + +class GnuHashTests(unittest.TestCase): + def image(self, buckets=4, bloom=1, shift=6, symoffset=3): + image = build_image(symbols(False), False) + count = len(symbols(False)) + struct.pack_into("> shift) % 64))) + start = struct.unpack_from("