From abe8fb81938394c3fe1d7e30f7549a96f8ce3473 Mon Sep 17 00:00:00 2001 From: MiniRML <134226498+MiniRML@users.noreply.github.com> Date: Mon, 12 Oct 2026 09:45:30 -0700 Subject: [PATCH 1/2] fix(nid): validate ELF table layouts before patching --- core/libs/CMakeLists.txt | 2 ++ core/libs/nid/src/ElfNidPatcher.cpp | 13 +++++++ core/libs/nid/tests/test_elf_layout.py | 48 ++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 core/libs/nid/tests/test_elf_layout.py diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index d094f39cf..06b1f3c30 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_elf_layout COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_elf_layout.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..602037b84 100644 --- a/core/libs/nid/src/ElfNidPatcher.cpp +++ b/core/libs/nid/src/ElfNidPatcher.cpp @@ -15,6 +15,11 @@ namespace Nid { namespace { +void RequireElfRange(const std::vector& elf, std::uint64_t offset, std::uint64_t size, const char* table) { + if (offset > elf.size() || size > elf.size() - offset) + throw std::runtime_error(std::string("invalid ELF ") + table + " range"); +} + std::uint32_t GnuHash(const std::string& name) { std::uint32_t h = 5381u; for (const unsigned char c : name) @@ -316,6 +321,9 @@ void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& if (elf[4] != 2) throw std::runtime_error("not ELF64"); const auto ehdr = Read(elf, 0u); + if (ehdr.e_ident[5] != 1 || ehdr.e_shentsize != sizeof(Elf64_Shdr)) + throw std::runtime_error("unsupported ELF byte order or section header size"); + RequireElfRange(elf, ehdr.e_shoff, std::uint64_t{ehdr.e_shnum} * sizeof(Elf64_Shdr), "section table"); std::size_t dynSymOffset = 0u, dynSymSize = 0u; std::size_t gnuHashOffset = 0u, gnuHashSize = 0u; @@ -326,6 +334,9 @@ void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& 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) { + if (shdr.sh_entsize != sizeof(Elf64_Sym) || shdr.sh_size % sizeof(Elf64_Sym) || shdr.sh_link >= ehdr.e_shnum) + throw std::runtime_error("invalid ELF dynsym layout"); + RequireElfRange(elf, shdr.sh_offset, shdr.sh_size, "dynsym"); dynSymOffset = static_cast(shdr.sh_offset); dynSymSize = static_cast(shdr.sh_size); dynSymSectionIndex = i; @@ -359,6 +370,8 @@ void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& { const std::size_t shOffset = static_cast(ehdr.e_shoff) + dynStrSectionLink * sizeof(Elf64_Shdr); const auto shdr = Read(elf, shOffset); + if (shdr.sh_type != 3) throw std::runtime_error("invalid ELF dynstr section type"); + RequireElfRange(elf, shdr.sh_offset, shdr.sh_size, "dynstr"); dynStrOffset = static_cast(shdr.sh_offset); dynStrSize = static_cast(shdr.sh_size); } diff --git a/core/libs/nid/tests/test_elf_layout.py b/core/libs/nid/tests/test_elf_layout.py new file mode 100644 index 000000000..703ac46db --- /dev/null +++ b/core/libs/nid/tests/test_elf_layout.py @@ -0,0 +1,48 @@ +import struct +import sys +import unittest +from pathlib import Path + +from test_symbol_versions import build_image, symbols, patch, SHOFF + + +class ElfLayoutTests(unittest.TestCase): + def test_declared_layout_must_match_supported_tables(self): + changes = ((5, "B", 2), (0x3a, "H", 32), + (SHOFF + 64 + 56, "Q", 8), + (SHOFF + 64 + 32, "Q", len(symbols(False)) * 24 + 1), + (SHOFF + 128 + 4, "I", 1)) + for offset, kind, value in changes: + with self.subTest(offset=offset, value=value): + image = build_image(symbols(False), False) + struct.pack_into("<" + kind, image, offset, value) + self.reject(image) + + def test_table_ranges_and_links_are_checked(self): + changes = ((0x28, "Q", 0x10000), + (SHOFF + 64 + 24, "Q", 0xff0), + (SHOFF + 64 + 40, "I", 100), + (SHOFF + 128 + 24, "Q", 0xff0), + (SHOFF + 128 + 32, "Q", 0xffffffffffffffff)) + for offset, kind, value in changes: + with self.subTest(offset=offset, value=value): + image = build_image(symbols(False), False) + struct.pack_into("<" + kind, image, offset, value) + self.reject(image) + + def reject(self, image): + result, after = patch(PATCHER, bytes(image), "layout.so") + self.assertEqual(result.returncode, 2, (result.returncode, result.stderr)) + self.assertRegex(result.stderr.decode(), "ELF|dynsym|dynstr") + self.assertEqual(after, image) + + def test_valid_supported_layout_still_patches(self): + image = build_image(symbols(False), False) + result, after = patch(PATCHER, bytes(image), "valid-layout.so") + self.assertEqual(result.returncode, 0, result.stderr) + self.assertNotEqual(after, image) + + +if __name__ == "__main__": + PATCHER = Path(sys.argv.pop(1)).resolve() + unittest.main() From 617e7acfa895b1d575dfddadfebfe36c8e175e5e Mon Sep 17 00:00:00 2001 From: MiniRML <134226498+MiniRML@users.noreply.github.com> Date: Mon, 12 Oct 2026 09:46:59 -0700 Subject: [PATCH 2/2] test(nid): avoid competing hash test registration --- core/libs/CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 06b1f3c30..29c712034 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -163,8 +163,6 @@ if(BUILD_TESTING) endif() find_package(Python3 QUIET COMPONENTS Interpreter) if(Python3_Interpreter_FOUND) - add_test(NAME nid_elf_layout COMMAND ${Python3_EXECUTABLE} - ${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_elf_layout.py $) add_test(NAME nid_patcher_macho COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_macho_nids.py $) endif() @@ -175,6 +173,10 @@ if(BUILD_TESTING) target_link_options(patched_libraries_tests PRIVATE -static -static-libgcc -static-libstdc++) endif() add_test(NAME patched_libraries COMMAND patched_libraries_tests ${PATCHED_DIR}) + if(Python3_Interpreter_FOUND) + add_test(NAME nid_elf_layout COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_elf_layout.py $) + endif() add_test_executable(guest_kernel_sys_tests tests/GuestKernelSys.cpp) target_include_directories(guest_kernel_sys_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_kernel_sys_tests PRIVATE libkernel_sys libkernel libc)