Path: blob/master/src/util-tests/elf_parser_tests.cpp
4802 views
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <[email protected]>1// SPDX-License-Identifier: CC-BY-NC-ND-4.023#include "util/elf_file.h"45#include "common/error.h"67#include <gtest/gtest.h>89namespace {1011// Helper to create minimal valid ELF file data12ELFFile::DataArray CreateValidELFData()13{14// Create a minimal valid ELF file (32-bit MIPS)15constexpr size_t elf_size = 768;16ELFFile::DataArray data(elf_size);17std::memset(data.data(), 0, data.size());1819// ELF Header20auto* ehdr = reinterpret_cast<ELFFile::Elf32_Ehdr*>(data.data());21ehdr->e_ident[0] = 0x7F; // Magic bytes22ehdr->e_ident[1] = 'E';23ehdr->e_ident[2] = 'L';24ehdr->e_ident[3] = 'F';25ehdr->e_type = ELFFile::ET_EXEC;26ehdr->e_machine = ELFFile::EM_MIPS;27ehdr->e_version = 1;28ehdr->e_entry = 0x80010000; // Entry point29ehdr->e_phoff = sizeof(ELFFile::Elf32_Ehdr); // Program header table right after ELF header30ehdr->e_shoff =31sizeof(ELFFile::Elf32_Ehdr) + 2 * sizeof(ELFFile::Elf32_Phdr); // Section header table after program headers32ehdr->e_flags = 0;33ehdr->e_ehsize = sizeof(ELFFile::Elf32_Ehdr);34ehdr->e_phentsize = sizeof(ELFFile::Elf32_Phdr);35ehdr->e_phnum = 2; // Two program headers36ehdr->e_shentsize = sizeof(ELFFile::Elf32_Shdr);37ehdr->e_shnum = 3; // Three section headers (null + .text + .shstrtab)38ehdr->e_shstrndx = 2; // String table is section 23940// Program Headers41auto* phdr = reinterpret_cast<ELFFile::Elf32_Phdr*>(data.data() + ehdr->e_phoff);4243// First program header - loadable segment with code44phdr[0].p_type = ELFFile::PT_LOAD;45phdr[0].p_offset = 0x100; // Start of section data46phdr[0].p_vaddr = 0x80010000; // Virtual address matching entry point47phdr[0].p_filesz = 0x100; // Size in file48phdr[0].p_memsz = 0x100; // Size in memory49phdr[0].p_flags = 5; // Read + execute50phdr[0].p_align = 0x1000; // Page alignment5152// Second program header - non-loadable segment53phdr[1].p_type = ELFFile::PT_NOTE;5455// Section Headers56auto* shdr = reinterpret_cast<ELFFile::Elf32_Shdr*>(data.data() + ehdr->e_shoff);5758// Section 0 - NULL section59shdr[0].sh_name = 0;60shdr[0].sh_type = ELFFile::SHT_NULL;6162// Section 1 - .text63shdr[1].sh_name = 1; // Offset in string table64shdr[1].sh_type = ELFFile::SHT_PROGBITS;65shdr[1].sh_flags = 6; // Executable, allocated66shdr[1].sh_addr = 0x80010000;67shdr[1].sh_offset = 0x100; // Start of section data68shdr[1].sh_size = 0x100; // Size69shdr[1].sh_link = 0;70shdr[1].sh_info = 0;71shdr[1].sh_addralign = 4;72shdr[1].sh_entsize = 0;7374// Section 2 - .shstrtab (string table)75shdr[2].sh_name = 7; // Offset in string table76shdr[2].sh_type = ELFFile::SHT_STRTAB;77shdr[2].sh_flags = 0;78shdr[2].sh_addr = 0;79shdr[2].sh_offset = 0x200; // String table location80shdr[2].sh_size = 0x20; // String table size81shdr[2].sh_link = 0;82shdr[2].sh_info = 0;83shdr[2].sh_addralign = 1;84shdr[2].sh_entsize = 0;8586// String table section87char* strtab = reinterpret_cast<char*>(data.data() + shdr[2].sh_offset);88strtab[0] = '\0'; // Empty string at index 089strcpy(strtab + 1, ".text"); // Section name at index 190strcpy(strtab + 7, ".shstrtab"); // Section name at index 79192// Add some fake code to the .text section93u8* text = data.data() + shdr[1].sh_offset;94memset(text, 0xAA, shdr[1].sh_size);9596return data;97}9899// Helper to create invalid ELF data (wrong magic)100ELFFile::DataArray CreateInvalidELFData()101{102auto data = CreateValidELFData();103data[1] = 'X'; // Corrupt magic number104return data;105}106107// Helper to create ELF with wrong machine type108ELFFile::DataArray CreateWrongMachineELFData()109{110auto data = CreateValidELFData();111auto* ehdr = reinterpret_cast<ELFFile::Elf32_Ehdr*>(data.data());112ehdr->e_machine = 0x42; // Not MIPS113return data;114}115116// Helper to create ELF with missing entry point117ELFFile::DataArray CreateMissingEntryELFData()118{119auto data = CreateValidELFData();120auto* ehdr = reinterpret_cast<ELFFile::Elf32_Ehdr*>(data.data());121// auto* phdr = reinterpret_cast<ELFFile::Elf32_Phdr*>(data.data() + ehdr->e_phoff);122123// Set entry point outside of loadable segment124ehdr->e_entry = 0x90000000;125126return data;127}128129// Helper to create ELF with out-of-range program header130ELFFile::DataArray CreateOutOfRangeProgramHeaderELFData()131{132auto data = CreateValidELFData();133auto* ehdr = reinterpret_cast<ELFFile::Elf32_Ehdr*>(data.data());134auto* phdr = reinterpret_cast<ELFFile::Elf32_Phdr*>(data.data() + ehdr->e_phoff);135136// Set offset and size to be out of file range137phdr[0].p_offset = static_cast<u32>(data.size()) - 10;138phdr[0].p_filesz = 100;139140return data;141}142143class ELFParserTest : public ::testing::Test144{145protected:146void SetUp() override147{148valid_elf_data = CreateValidELFData();149invalid_elf_data = CreateInvalidELFData();150wrong_machine_elf_data = CreateWrongMachineELFData();151missing_entry_elf_data = CreateMissingEntryELFData();152out_of_range_program_header_elf_data = CreateOutOfRangeProgramHeaderELFData();153}154155ELFFile::DataArray valid_elf_data;156ELFFile::DataArray invalid_elf_data;157ELFFile::DataArray wrong_machine_elf_data;158ELFFile::DataArray missing_entry_elf_data;159ELFFile::DataArray out_of_range_program_header_elf_data;160};161162} // namespace163164TEST_F(ELFParserTest, ValidELFHeader)165{166Error error;167EXPECT_TRUE(ELFFile::IsValidElfHeader(valid_elf_data.cspan(), &error));168EXPECT_FALSE(error.IsValid());169170// Test the static header checking method171const auto& header = *reinterpret_cast<const ELFFile::Elf32_Ehdr*>(valid_elf_data.data());172EXPECT_TRUE(ELFFile::IsValidElfHeader(header, &error));173}174175TEST_F(ELFParserTest, InvalidELFHeader)176{177Error error;178EXPECT_FALSE(ELFFile::IsValidElfHeader(invalid_elf_data.cspan(), &error));179EXPECT_TRUE(error.IsValid());180}181182TEST_F(ELFParserTest, WrongMachineType)183{184Error error;185EXPECT_FALSE(ELFFile::IsValidElfHeader(wrong_machine_elf_data.cspan(), &error));186EXPECT_TRUE(error.IsValid());187}188189TEST_F(ELFParserTest, TooSmallBuffer)190{191Error error;192std::span<const u8> small_span = valid_elf_data.cspan(10); // Too small for header193EXPECT_FALSE(ELFFile::IsValidElfHeader(small_span, &error));194EXPECT_TRUE(error.IsValid());195}196197TEST_F(ELFParserTest, OpenValidELF)198{199ELFFile elf;200Error error;201EXPECT_TRUE(elf.Open(std::move(valid_elf_data), &error));202EXPECT_FALSE(error.IsValid());203}204205TEST_F(ELFParserTest, OpenInvalidELF)206{207ELFFile elf;208Error error;209EXPECT_FALSE(elf.Open(invalid_elf_data, &error));210EXPECT_TRUE(error.IsValid());211}212213TEST_F(ELFParserTest, OpenWrongMachineELF)214{215ELFFile elf;216Error error;217EXPECT_FALSE(elf.Open(wrong_machine_elf_data, &error));218EXPECT_TRUE(error.IsValid());219}220221TEST_F(ELFParserTest, GetELFHeader)222{223ELFFile elf;224Error error;225ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));226227const auto& header = elf.GetELFHeader();228EXPECT_EQ(header.e_type, ELFFile::ET_EXEC);229EXPECT_EQ(header.e_machine, ELFFile::EM_MIPS);230EXPECT_EQ(header.e_entry, 0x80010000);231}232233TEST_F(ELFParserTest, GetEntryPoint)234{235ELFFile elf;236Error error;237ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));238239EXPECT_EQ(elf.GetEntryPoint(), 0x80010000);240}241242TEST_F(ELFParserTest, GetSectionCount)243{244ELFFile elf;245Error error;246ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));247248EXPECT_EQ(elf.GetSectionCount(), 3u);249}250251TEST_F(ELFParserTest, GetValidSectionHeader)252{253ELFFile elf;254Error error;255ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));256257// Get .text section258const auto* section = elf.GetSectionHeader(1);259ASSERT_NE(section, nullptr);260EXPECT_EQ(section->sh_type, ELFFile::SHT_PROGBITS);261EXPECT_EQ(section->sh_addr, 0x80010000);262}263264TEST_F(ELFParserTest, GetInvalidSectionHeader)265{266ELFFile elf;267Error error;268ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));269270// Try to get a section with an out-of-bounds index271EXPECT_EQ(elf.GetSectionHeader(99), nullptr);272273// Try the special "undefined" section274EXPECT_EQ(elf.GetSectionHeader(ELFFile::SHN_UNDEF), nullptr);275}276277TEST_F(ELFParserTest, GetSectionName)278{279ELFFile elf;280Error error;281ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));282283// Get .text section name284const auto* section = elf.GetSectionHeader(1);285ASSERT_NE(section, nullptr);286EXPECT_EQ(elf.GetSectionName(*section), ".text");287288// Get .shstrtab section name289const auto* strtab_section = elf.GetSectionHeader(2);290ASSERT_NE(strtab_section, nullptr);291EXPECT_EQ(elf.GetSectionName(*strtab_section), ".shstrtab");292}293294TEST_F(ELFParserTest, GetProgramHeaderCount)295{296ELFFile elf;297Error error;298ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));299300EXPECT_EQ(elf.GetProgramHeaderCount(), 2u);301}302303TEST_F(ELFParserTest, GetValidProgramHeader)304{305ELFFile elf;306Error error;307ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));308309// Get loadable segment310const auto* phdr = elf.GetProgramHeader(0);311ASSERT_NE(phdr, nullptr);312EXPECT_EQ(phdr->p_type, ELFFile::PT_LOAD);313EXPECT_EQ(phdr->p_vaddr, 0x80010000u);314315// Get note segment316const auto* phdr2 = elf.GetProgramHeader(1);317ASSERT_NE(phdr2, nullptr);318EXPECT_EQ(phdr2->p_type, ELFFile::PT_NOTE);319}320321TEST_F(ELFParserTest, GetInvalidProgramHeader)322{323ELFFile elf;324Error error;325ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));326327// Try to get a program header with an out-of-bounds index328EXPECT_EQ(elf.GetProgramHeader(99), nullptr);329}330331TEST_F(ELFParserTest, LoadExecutableSections)332{333ELFFile elf;334Error error;335ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));336337// Track loaded sections338struct LoadedSection339{340std::vector<u8> data;341u32 vaddr;342u32 size;343};344std::vector<LoadedSection> loaded_sections;345346bool result = elf.LoadExecutableSections(347[&loaded_sections](std::span<const u8> data, u32 dest_vaddr, u32 dest_size, Error* error) {348LoadedSection section;349section.data.assign(data.begin(), data.end());350section.vaddr = dest_vaddr;351section.size = dest_size;352loaded_sections.push_back(std::move(section));353return true;354},355&error);356357EXPECT_TRUE(result);358EXPECT_FALSE(error.IsValid());359360// We should have loaded one section (the loadable segment)361ASSERT_EQ(loaded_sections.size(), 1u);362EXPECT_EQ(loaded_sections[0].vaddr, 0x80010000u);363EXPECT_EQ(loaded_sections[0].size, 0x100u);364EXPECT_EQ(loaded_sections[0].data[0], 0xAAu); // Check first byte of our fake code365}366367TEST_F(ELFParserTest, MissingEntryPoint)368{369ELFFile elf;370Error error;371ASSERT_TRUE(elf.Open(std::move(missing_entry_elf_data), &error));372373bool result = elf.LoadExecutableSections(374[](std::span<const u8> data, u32 dest_vaddr, u32 dest_size, Error* error) { return true; }, &error);375376EXPECT_FALSE(result);377EXPECT_TRUE(error.IsValid());378}379380TEST_F(ELFParserTest, OutOfRangeProgramHeader)381{382ELFFile elf;383Error error;384ASSERT_TRUE(elf.Open(std::move(out_of_range_program_header_elf_data), &error));385386bool result = elf.LoadExecutableSections(387[](std::span<const u8> data, u32 dest_vaddr, u32 dest_size, Error* error) { return true; }, &error);388389EXPECT_FALSE(result);390EXPECT_TRUE(error.IsValid());391}392393394