diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 165d204af..0093e1ffe 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -214,6 +214,10 @@ if(BUILD_TESTING) target_link_libraries(guest_kernel_sys_tests PRIVATE libkernel_sys libkernel libc) configure_windows_unwind(guest_kernel_sys_tests) add_test(NAME guest_kernel_sys COMMAND guest_kernel_sys_tests) + if(Python3_Interpreter_FOUND) + add_test(NAME nid_verneed COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_verneed.py $) + endif() if(WIN32) set_tests_properties(guest_kernel_sys PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") diff --git a/core/libs/nid/src/ElfNidPatcher.cpp b/core/libs/nid/src/ElfNidPatcher.cpp index 806e166fc..e97b8bfcb 100644 --- a/core/libs/nid/src/ElfNidPatcher.cpp +++ b/core/libs/nid/src/ElfNidPatcher.cpp @@ -216,49 +216,65 @@ void RemapDynamicNeededOffsets( } } +struct VersionNameField { + std::size_t Offset; + std::uint32_t Name; +}; + +std::vector ReadVersionNameFields(const std::vector& elf, const Elf64_Ehdr& ehdr) { + using namespace Internal; + std::vector fields; + for (std::uint16_t i = 0u; i < ehdr.e_shnum; ++i) { + const auto section = Read(elf, static_cast(ehdr.e_shoff) + i * sizeof(Elf64_Shdr)); + if (section.sh_type != kShtGnuVerneed || section.sh_size == 0u) continue; + if (section.sh_offset > elf.size() || section.sh_size > elf.size() - section.sh_offset) + throw std::runtime_error("invalid .gnu.version_r section range"); + const auto location = [&](std::uint64_t relative, std::size_t size) { + if (relative > section.sh_size || size > section.sh_size - relative) + throw std::runtime_error(".gnu.version_r record outside section"); + return static_cast(section.sh_offset + relative); + }; + const auto advance = [&](std::uint64_t current, std::uint32_t step, std::size_t size) { + if (step < size || step > section.sh_size - current) + throw std::runtime_error("invalid .gnu.version_r record link"); + return current + step; + }; + std::uint64_t current = 0u; + while (true) { + const auto offset = location(current, sizeof(Elf64_Verneed)); + const auto need = Read(elf, offset); + if (need.vn_version != 1u) throw std::runtime_error("unsupported .gnu.version_r structure version"); + fields.push_back({offset + offsetof(Elf64_Verneed, vn_file), need.vn_file}); + if (need.vn_cnt != 0u) { + auto auxiliary = advance(current, need.vn_aux, sizeof(Elf64_Verneed)); + for (std::uint16_t index = 0u; index < need.vn_cnt; ++index) { + const auto auxOffset = location(auxiliary, sizeof(Elf64_Vernaux)); + const auto aux = Read(elf, auxOffset); + fields.push_back({auxOffset + offsetof(Elf64_Vernaux, vna_name), aux.vna_name}); + if (index + 1u == need.vn_cnt) { + if (aux.vna_next != 0u) throw std::runtime_error(".gnu.version_r auxiliary count mismatch"); + } else { + auxiliary = advance(auxiliary, aux.vna_next, sizeof(Elf64_Vernaux)); + } + } + } + if (need.vn_next == 0u) break; + current = advance(current, need.vn_next, sizeof(Elf64_Verneed)); + } + } + return fields; +} + void RemapVerneedOffsets( std::vector& elf, const Elf64_Ehdr& ehdr, const std::vector>& oldToNewOffset ) { 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 != kShtGnuVerneed) continue; - - const std::size_t verneedOffset = static_cast(shdr.sh_offset); - std::size_t entryOffset = verneedOffset; - - while (true) { - const auto verneed = Read(elf, entryOffset); - - const auto mappedFile = std::find_if( - oldToNewOffset.begin(), oldToNewOffset.end(), - [&](const auto& entry) { return entry.first == verneed.vn_file; } - ); - if (mappedFile != oldToNewOffset.end()) - Write(elf, entryOffset + offsetof(Elf64_Verneed, vn_file), mappedFile->second); - - std::size_t auxOffset = entryOffset + verneed.vn_aux; - for (std::uint16_t a = 0u; a < verneed.vn_cnt; ++a) { - const auto vernaux = Read(elf, auxOffset); - - const auto mappedName = std::find_if( - oldToNewOffset.begin(), oldToNewOffset.end(), - [&](const auto& entry) { return entry.first == vernaux.vna_name; } - ); - if (mappedName != oldToNewOffset.end()) - Write(elf, auxOffset + offsetof(Elf64_Vernaux, vna_name), mappedName->second); - - if (vernaux.vna_next == 0u) break; - auxOffset += vernaux.vna_next; - } - - if (verneed.vn_next == 0u) break; - entryOffset += verneed.vn_next; - } + for (const auto& field : ReadVersionNameFields(elf, ehdr)) { + const auto mapped = std::find_if(oldToNewOffset.begin(), oldToNewOffset.end(), + [&](const auto& entry) { return entry.first == field.Name; }); + if (mapped != oldToNewOffset.end()) Write(elf, field.Offset, mapped->second); } } @@ -287,35 +303,8 @@ std::vector CollectDynamicNameOffsets(const std::vector CollectVerneedNameOffsets(const std::vector& elf, const Elf64_Ehdr& ehdr) { - using namespace Internal; - std::vector offsets; - - 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 != kShtGnuVerneed) continue; - - std::size_t entryOffset = static_cast(shdr.sh_offset); - - while (true) { - const auto verneed = Read(elf, entryOffset); - offsets.push_back(verneed.vn_file); - - std::size_t auxOffset = entryOffset + verneed.vn_aux; - for (std::uint16_t a = 0u; a < verneed.vn_cnt; ++a) { - const auto vernaux = Read(elf, auxOffset); - offsets.push_back(vernaux.vna_name); - - if (vernaux.vna_next == 0u) break; - auxOffset += vernaux.vna_next; - } - - if (verneed.vn_next == 0u) break; - entryOffset += verneed.vn_next; - } - } - + for (const auto& field : ReadVersionNameFields(elf, ehdr)) offsets.push_back(field.Name); return offsets; } diff --git a/core/libs/nid/tests/test_verneed.py b/core/libs/nid/tests/test_verneed.py new file mode 100644 index 000000000..58ebc02c6 --- /dev/null +++ b/core/libs/nid/tests/test_verneed.py @@ -0,0 +1,76 @@ +import struct +import sys +import unittest +from pathlib import Path + +from test_symbol_versions import build_image, symbols, patch, SHOFF, DYNSTR_OFFSET, sections + +VERNEED = 0x580 + + +def image(*, auxiliary=16, count=1, size=32, version=1, auxnext=0, nextrecord=0): + data = build_image(symbols(False), False) + strings = sections(data)[2] + filename = strings[5] + names = b"libfixture.so\0FIXTURE_1\0" + data[DYNSTR_OFFSET + filename:DYNSTR_OFFSET + filename + len(names)] = names + struct.pack_into("