Merge pull request #4615 from MiniRML/fix/nid-elf-table-layout
fix(nid): validate ELF table layouts before patching
This commit is contained in:
commit
75ceccd5db
|
|
@ -209,6 +209,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 $<TARGET_FILE:nid_patcher>)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@ namespace Nid {
|
|||
namespace {
|
||||
|
||||
|
||||
void RequireElfRange(const std::vector<std::uint8_t>& 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)
|
||||
|
|
@ -317,6 +322,9 @@ void ElfNidPatcher::PatchNids(std::vector<std::uint8_t>& elf, const std::string&
|
|||
if (elf[4] != 2) throw std::runtime_error("not ELF64");
|
||||
|
||||
const auto ehdr = Read<Elf64_Ehdr>(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;
|
||||
|
|
@ -327,6 +335,9 @@ void ElfNidPatcher::PatchNids(std::vector<std::uint8_t>& elf, const std::string&
|
|||
const std::size_t shOffset = static_cast<std::size_t>(ehdr.e_shoff) + i * sizeof(Elf64_Shdr);
|
||||
const auto shdr = Read<Elf64_Shdr>(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<std::size_t>(shdr.sh_offset);
|
||||
dynSymSize = static_cast<std::size_t>(shdr.sh_size);
|
||||
dynSymSectionIndex = i;
|
||||
|
|
@ -363,6 +374,8 @@ void ElfNidPatcher::PatchNids(std::vector<std::uint8_t>& elf, const std::string&
|
|||
{
|
||||
const std::size_t shOffset = static_cast<std::size_t>(ehdr.e_shoff) + dynStrSectionLink * sizeof(Elf64_Shdr);
|
||||
const auto shdr = Read<Elf64_Shdr>(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<std::size_t>(shdr.sh_offset);
|
||||
dynStrSize = static_cast<std::size_t>(shdr.sh_size);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
Loading…
Reference in New Issue