@ 261107.7
current
/* Copyright 2017 - 2026 R. Thomas
 * Copyright 2017 - 2026 Quarkslab
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "LIEF/ELF/Binary.hpp"
#include "LIEF/ELF/Header.hpp"
#include "LIEF/ELF/Segment.hpp"
#include "LIEF/ELF/Section.hpp"
#include "LIEF/ELF/Symbol.hpp"
#include "LIEF/ELF/DynamicEntry.hpp"
#include "LIEF/ELF/Note.hpp"
#include "LIEF/ELF/utils.hpp"
#include "LIEF/ELF/SysvHash.hpp"
#include "ELF/Structures.hpp"

#include <spdlog/fmt/fmt.h>
#include <algorithm>


namespace LIEF::ELF {

class LayoutChecker {
  public:
  static constexpr uint32_t DEFAULT_PAGE_SIZE = 0x1000;

  LayoutChecker() = delete;
  LayoutChecker(const Binary& bin) :
    elf(bin),
    filesz(elf.original_size()) {}

  bool check_header();
  bool check_segments();
  bool check_sections();
  bool check_dynamic();
  bool check_notes();
  bool check_tls();
  bool check_mips_rld_map();

  bool check() {
    if (!check_header()) {
      return false;
    }
    if (!check_segments()) {
      return false;
    }
    if (!check_sections()) {
      return false;
    }
    if (!check_dynamic()) {
      return false;
    }
    if (!check_mips_rld_map()) {
      return false;
    }
    if (!check_notes()) {
      return false;
    }
    if (!check_tls()) {
      return false;
    }
    return true;
  }

  bool error(std::string msg) {
    error_msg = std::move(msg);
    return false;
  }

  template<typename... Args>
  bool error(const char* fmt, const Args&... args) {
    error_msg = fmt::format(fmt::runtime(fmt), args...);
    return false;
  }

  const std::string& get_error() const {
    return error_msg;
  }

  bool is64() const {
    return elf.header().identity_class() == Header::CLASS::ELF64;
  }

  private:
  std::string error_msg;
  const Binary& elf;
  uint64_t filesz;
};

bool LayoutChecker::check_header() {
  const Header& hdr = elf.header();

  const uint32_t ehdr_size =
      is64() ? sizeof(details::Elf64_Ehdr) : sizeof(details::Elf32_Ehdr);
  if (filesz < ehdr_size) {
    return error("File size ({:#x}) is too small for ELF header ({:#x})", filesz,
                 ehdr_size);
  }

  if (hdr.identity_version() != Header::VERSION::CURRENT) {
    return error("Invalid ELF version: {}", (int)hdr.identity_version());
  }

  if (hdr.identity_class() == Header::CLASS::NONE) {
    return error("Invalid ELF class");
  }

  if (hdr.file_type() == Header::FILE_TYPE::NONE) {
    return error("Invalid ELF type (ET_NONE)");
  }

  const uint32_t phdr_size =
      is64() ? sizeof(details::Elf64_Phdr) : sizeof(details::Elf32_Phdr);
  const uint32_t phnum = hdr.numberof_segments();
  if (phnum > 0) {
    const uint64_t phoff = hdr.program_headers_offset();
    const uint64_t pht_size = (uint64_t)phnum * phdr_size;

    if (phoff + pht_size > filesz) {
      return error(
          "Program header table (offset: {:#x}, size: {:#x}) is beyond file size",
          phoff, pht_size
      );
    }

    // Required by Android
    if (phoff % elf.ptr_size() != 0) {
      return error("phoff offset ({:#010x}) misaligned", phoff);
    }

    if (phnum > (std::numeric_limits<uint16_t>::max() / phdr_size)) {
      return error("Too many program headers: {}", phnum);
    }
  }

  const uint32_t shdr_size =
      is64() ? sizeof(details::Elf64_Shdr) : sizeof(details::Elf32_Shdr);
  const uint32_t shnum = hdr.numberof_sections();
  if (shnum > 0) {
    const uint64_t shoff = hdr.section_headers_offset();
    const uint64_t sht_size = (uint64_t)shnum * shdr_size;
    if (shoff + sht_size > filesz) {
      return error(
          "Section header table (offset: {:#x}, size: {:#x}) is beyond file size",
          shoff, sht_size
      );
    }

    // Required by Android
    if (shoff % elf.ptr_size() != 0) {
      return error("shdr offset ({:#010x}) misaligned", shoff);
    }

    if (hdr.section_name_table_idx() >= shnum) {
      return error("Invalid section name table index: {}",
                   hdr.section_name_table_idx());
    }
  }

  // Android specific checks if targeting Android
  if (elf.is_targeting_android()) {
    if (hdr.identity_data() != Header::ELF_DATA::LSB) {
      return error("Android ELF must be little-endian");
    }
    if (hdr.file_type() != Header::FILE_TYPE::DYN) {
      return error("Android ELF must be ET_DYN (PIE or shared library)");
    }
  }

  return true;
}

bool LayoutChecker::check_segments() {
  bool has_pt_load = false;
  auto segments = elf.segments();

  const Segment* pt_phdr = nullptr;

  for (size_t i = 0; i < segments.size(); ++i) {
    const Segment& seg = segments[i];

    if (seg.file_offset() + seg.physical_size() > filesz) {
      return error("Segment[{}] offset ({:#x}) + filesz ({:#x}) is beyond file "
                   "size ({:#x})",
                   i, seg.file_offset(), seg.physical_size(), filesz);
    }

    if (seg.virtual_size() < seg.physical_size()) {
      return error("Segment[{}] memsz ({:#x}) is smaller than filesz ({:#x})", i,
                   seg.virtual_size(), seg.physical_size());
    }

    const uint64_t alignment = seg.alignment();
    if (alignment > 1 && (alignment & (alignment - 1)) != 0) {
      return error("Segment[{}] alignment ({:#x}) is not a power of 2", i,
                   alignment);
    }

    if (seg.type() == Segment::TYPE::PHDR) {
      pt_phdr = &seg;
    }

    if (seg.type() == Segment::TYPE::LOAD) {
      has_pt_load = true;
      if (alignment > 1) {
        if ((seg.virtual_address() % alignment) != (seg.file_offset() % alignment))
        {
          return error(
              "Segment[{}] (PT_LOAD) p_vaddr ({:#x}) and p_offset ({:#x}) "
              "are not congruent modulo p_align ({:#x})",
              i, seg.virtual_address(), seg.file_offset(), alignment
          );
        }
      }

      // Android W+E check
      if (elf.is_targeting_android()) {
        if (seg.has(Segment::FLAGS::W) && seg.has(Segment::FLAGS::X)) {
          return error("Segment[{}] (PT_LOAD) is both writable and executable", i);
        }
      }

      // Overlap check for PT_LOAD
      for (size_t j = i + 1; j < segments.size(); ++j) {
        const Segment& other = segments[j];
        if (other.type() != Segment::TYPE::LOAD) {
          continue;
        }

        const uint64_t start1 = seg.virtual_address();
        const uint64_t end1 = seg.virtual_address() + seg.virtual_size();
        const uint64_t start2 = other.virtual_address();
        const uint64_t end2 = other.virtual_address() + other.virtual_size();

        if (std::max(start1, start2) < std::min(end1, end2)) {
          return error("Segment[{}] and Segment[{}] (PT_LOAD) overlap in memory",
                       i, j);
        }
      }
    }
  }

  if (!has_pt_load) {
    return error("No loadable segments (PT_LOAD)");
  }

  // PT_PHDR must be wrapped by a PT_LOAD
  if (pt_phdr != nullptr) {
    bool wrapped = false;
    for (const Segment& seg : segments) {
      if (seg.type() != Segment::TYPE::LOAD) {
        continue;
      }
      if (seg.virtual_address() <= pt_phdr->virtual_address() &&
          (pt_phdr->virtual_address() + pt_phdr->virtual_size()) <=
              (seg.virtual_address() + seg.physical_size()))
      {
        wrapped = true;
        break;
      }
    }
    if (!wrapped) {
      return error("PT_PHDR segment is not wrapped by a PT_LOAD segment");
    }
  } else {
    // If no PT_PHDR, program headers are usually at the beginning of the first
    // PT_LOAD (offset 0) Linker checks this in FindPhdr()
    bool found_pht_in_load = false;
    const uint64_t pht_offset = elf.header().program_headers_offset();
    const uint32_t phdr_size =
        is64() ? sizeof(details::Elf64_Phdr) : sizeof(details::Elf32_Phdr);
    const uint64_t pht_size =
        (uint64_t)elf.header().numberof_segments() * phdr_size;

    for (const Segment& seg : segments) {
      if (seg.type() != Segment::TYPE::LOAD) {
        continue;
      }
      if (seg.file_offset() <= pht_offset &&
          (pht_offset + pht_size) <= (seg.file_offset() + seg.physical_size()))
      {
        found_pht_in_load = true;
        break;
      }
    }
    if (!found_pht_in_load) {
      return error("Program header table is not wrapped by a PT_LOAD segment");
    }
  }

  return true;
}

bool LayoutChecker::check_sections() {
  const uint64_t filesz = elf.original_size();
  auto sections = elf.sections();
  const Section* dynamic_sec = nullptr;
  const Section* dynsym_sec = nullptr;

  for (size_t i = 0; i < sections.size(); ++i) {
    const Section& sec = sections[i];
    if (sec.type() == Section::TYPE::DYNAMIC) {
      dynamic_sec = &sec;
    }

    if (sec.type() == Section::TYPE::DYNSYM) {
      if (dynsym_sec != nullptr) {
        return error("Multiple SHT_DYNSYM sections found");
      }
      dynsym_sec = &sec;
    }

    if (sec.type() == Section::TYPE::NOBITS) {
      continue;
    }

    if (sec.offset() + sec.size() > filesz) {
      return error("Section[{}] ('{}') offset ({:#x}) + size ({:#x}) is beyond "
                   "file size ({:#x})",
                   i, sec.name(), sec.offset(), sec.size(), filesz);
    }

    // sh_entsize checks
    uint64_t expected_entsize = 0;
    switch (sec.type()) {
      case Section::TYPE::SYMTAB:
      case Section::TYPE::DYNSYM:
        expected_entsize =
            is64() ? sizeof(details::Elf64_Sym) : sizeof(details::Elf32_Sym);
        break;
      case Section::TYPE::DYNAMIC:
        expected_entsize =
            is64() ? sizeof(details::Elf64_Dyn) : sizeof(details::Elf32_Dyn);
        break;
      case Section::TYPE::REL:
        expected_entsize =
            is64() ? sizeof(details::Elf64_Rel) : sizeof(details::Elf32_Rel);
        break;
      case Section::TYPE::RELA:
        expected_entsize =
            is64() ? sizeof(details::Elf64_Rela) : sizeof(details::Elf32_Rela);
        break;
      case Section::TYPE::GROUP:
      case Section::TYPE::SYMTAB_SHNDX: expected_entsize = sizeof(uint32_t); break;
      default: break;
    }

    if (expected_entsize > 0 && sec.entry_size() != expected_entsize) {
      return error(
          "Section[{}] ('{}') has invalid sh_entsize: {:#x} (expected: {:#x})", i,
          sec.name(), sec.entry_size(), expected_entsize
      );
    }

    if (sec.entry_size() > 0 && (sec.size() % sec.entry_size()) != 0) {
      return error("Section[{}] ('{}') size ({:#x}) is not a multiple of "
                   "sh_entsize ({:#x})",
                   i, sec.name(), sec.size(), sec.entry_size());
    }

    // sh_link checks
    switch (sec.type()) {
      case Section::TYPE::DYNAMIC:
      case Section::TYPE::SYMTAB:
      case Section::TYPE::DYNSYM:
        if (sec.link() >= sections.size()) {
          return error("Section[{}] ('{}') has invalid sh_link: {}", i, sec.name(),
                       sec.link());
        }
        {
          const Section& strtab = sections[sec.link()];
          if (strtab.type() != Section::TYPE::STRTAB) {
            return error("Section[{}] ('{}') has invalid sh_link: {} (expected "
                         "SHT_STRTAB, but got {})",
                         i, sec.name(), sec.link(), to_string(strtab.type()));
          }
        }
        break;
      case Section::TYPE::REL:
      case Section::TYPE::RELA:
      case Section::TYPE::HASH:
      case Section::TYPE::GNU_HASH:
        if (sec.link() >= sections.size()) {
          return error("Section[{}] ('{}') has invalid sh_link: {}", i, sec.name(),
                       sec.link());
        }
        break;
      default: break;
    }

    // sh_info checks
    switch (sec.type()) {
      case Section::TYPE::SYMTAB:
      case Section::TYPE::DYNSYM:
        if (sec.entry_size() > 0 &&
            sec.information() > sec.size() / sec.entry_size())
        {
          return error("Section[{}] ('{}') has invalid sh_info: {} (larger than "
                       "symbol count {})",
                       i, sec.name(), sec.information(),
                       sec.size() / sec.entry_size());
        }
        break;
      default: break;
    }
  }

  if (const Segment* pt_dynamic = elf.get(Segment::TYPE::DYNAMIC)) {
    if (dynamic_sec != nullptr) {
      if (dynamic_sec->offset() != pt_dynamic->file_offset()) {
        return error("SHT_DYNAMIC section header and PT_DYNAMIC program header "
                     "disagree about "
                     "the location of the dynamic table: section offset {:#x} vs "
                     "segment offset {:#x}",
                     dynamic_sec->offset(), pt_dynamic->file_offset());
      }
      if (dynamic_sec->size() != pt_dynamic->physical_size()) {
        return error("SHT_DYNAMIC section header and PT_DYNAMIC program header "
                     "disagree about "
                     "the size of the dynamic table: section size {:#x} vs "
                     "segment size {:#x}",
                     dynamic_sec->size(), pt_dynamic->physical_size());
      }
    }

    // Android specific: .dynamic section header must exist if PT_DYNAMIC exists
    if (elf.is_targeting_android()) {
      if (dynamic_sec == nullptr) {
        return error(".dynamic section header was not found");
      }
      if (dynamic_sec->link() >= sections.size()) {
        return error(".dynamic section has invalid sh_link: {}",
                     dynamic_sec->link());
      }
      const Section& strtab = sections[dynamic_sec->link()];
      if (strtab.type() != Section::TYPE::STRTAB) {
        return error(".dynamic section has invalid link({}) sh_type: {} (expected "
                     "SHT_STRTAB)",
                     dynamic_sec->link(), (uint32_t)strtab.type());
      }
    }
  }

  return true;
}

bool LayoutChecker::check_dynamic() {
  const bool is64 = this->is64();
  uint64_t relaent = 0;
  uint64_t relent = 0;
  uint64_t relasz = 0;
  uint64_t relsz = 0;
  uint64_t syment = 0;
  uint64_t strsz = 0;
  uint64_t strtab = 0;
  uint64_t symtab = 0;

  for (const DynamicEntry& entry : elf.dynamic_entries()) {
    switch (entry.tag()) {
      case DynamicEntry::TAG::RELAENT: relaent = entry.value(); break;
      case DynamicEntry::TAG::RELENT: relent = entry.value(); break;
      case DynamicEntry::TAG::RELASZ: relasz = entry.value(); break;
      case DynamicEntry::TAG::RELSZ: relsz = entry.value(); break;
      case DynamicEntry::TAG::SYMENT: syment = entry.value(); break;
      case DynamicEntry::TAG::STRSZ: strsz = entry.value(); break;
      case DynamicEntry::TAG::STRTAB: strtab = entry.value(); break;
      case DynamicEntry::TAG::SYMTAB: symtab = entry.value(); break;
      case DynamicEntry::TAG::PLTREL:
      {
        uint64_t val = entry.value();
        if (val != (uint64_t)DynamicEntry::TAG::REL &&
            val != (uint64_t)DynamicEntry::TAG::RELA)
        {
          return error("Invalid DT_PLTREL value: {}", val);
        }
        break;
      }
      default: break;
    }
  }

  if (relaent > 0) {
    const uint64_t expected =
        is64 ? sizeof(details::Elf64_Rela) : sizeof(details::Elf32_Rela);
    if (relaent != expected) {
      return error("DT_RELAENT ({:#x}) does not match expected size ({:#x})",
                   relaent, expected);
    }
  }
  if (relent > 0) {
    const uint64_t expected =
        is64 ? sizeof(details::Elf64_Rel) : sizeof(details::Elf32_Rel);
    if (relent != expected) {
      return error("DT_RELENT ({:#x}) does not match expected size ({:#x})",
                   relent, expected);
    }
  }

  if (relasz > 0 && relaent > 0 && (relasz % relaent) != 0) {
    return error("DT_RELASZ ({:#x}) is not a multiple of DT_RELAENT ({:#x})",
                 relasz, relaent);
  }

  if (relsz > 0 && relent > 0 && (relsz % relent) != 0) {
    return error("DT_RELSZ ({:#x}) is not a multiple of DT_RELENT ({:#x})", relsz,
                 relent);
  }

  if (syment > 0) {
    const uint64_t expected =
        is64 ? sizeof(details::Elf64_Sym) : sizeof(details::Elf32_Sym);
    if (syment != expected) {
      return error("DT_SYMENT ({:#x}) does not match expected size ({:#x})",
                   syment, expected);
    }
  }

  if (strtab > 0 && strsz > 0) {
    if (result<uint64_t> res = elf.virtual_address_to_offset(strtab)) {
      uint64_t offset = *res;
      if (offset + strsz > filesz) {
        return error("Dynamic string table (offset: {:#x}, size: {:#x}) goes "
                     "past the end of the file (size: {:#x})",
                     offset, strsz, filesz);
      }
    }
  }

  if (symtab > 0) {
    const Section* dynsym = elf.get(Section::TYPE::DYNSYM);
    if (dynsym != nullptr && dynsym->virtual_address() != symtab) {
      return error("SHT_DYNSYM section header and DT_SYMTAB disagree about "
                   "the location of the dynamic symbol table: section VA {:#x} "
                   "vs dynamic VA {:#x}",
                   dynsym->virtual_address(), symtab);
    }
  }

  if (elf.use_sysv_hash()) {
    const SysvHash* hash = elf.sysv_hash();
    const Section* dynsym = elf.get(Section::TYPE::DYNSYM);
    if (dynsym != nullptr && dynsym->entry_size() > 0) {
      uint64_t dynsym_count = dynsym->size() / dynsym->entry_size();
      if (hash->nchain() != dynsym_count) {
        return error("hash table nchain ({}) differs from symbol count derived "
                     "from SHT_DYNSYM section header ({})",
                     hash->nchain(), dynsym_count);
      }
    }
  }

  return true;
}

bool LayoutChecker::check_mips_rld_map() {
  // DT_MIPS_RLD_MAP_REL stores the offset of the run-time loader map
  // **relative** to the address of its own dynamic entry.
  // When `DT_MIPS_RLD_MAP` is also present (absolute version)
  // both tags must reference the same address
  const bool is64 = this->is64();

  const DynamicEntry* rld_map_rel = elf.get(DynamicEntry::TAG::MIPS_RLD_MAP_REL);
  if (rld_map_rel == nullptr) {
    return true;
  }
  const DynamicEntry* rld_map = elf.get(DynamicEntry::TAG::MIPS_RLD_MAP);
  const Segment* pt_dynamic = elf.get(Segment::TYPE::DYNAMIC);

  if (rld_map == nullptr || pt_dynamic == nullptr) {
    return true;
  }

  const uint64_t dynent_size =
      is64 ? sizeof(details::Elf64_Dyn) : sizeof(details::Elf32_Dyn);

  uint64_t index = 0;
  for (const DynamicEntry& entry : elf.dynamic_entries()) {
    if (entry.tag() == DynamicEntry::TAG::MIPS_RLD_MAP_REL) {
      break;
    }
    ++index;
  }

  const uint64_t entry_va = pt_dynamic->virtual_address() + index * dynent_size;
  uint64_t resolved = entry_va + rld_map_rel->value();
  if (!is64) {
    resolved &= 0xffffffff;
  }

  if (resolved != rld_map->value()) {
    return error("DT_MIPS_RLD_MAP_REL resolves to {:#x} but DT_MIPS_RLD_MAP "
                 "points to {:#x}",
                 resolved, rld_map->value());
  }
  return true;
}

bool LayoutChecker::check_notes() {
  const uint64_t filesz = elf.original_size();
  for (const Segment& seg : elf.segments()) {
    if (seg.type() != Segment::TYPE::NOTE) {
      continue;
    }
    if (seg.physical_size() == 0) {
      continue;
    }

    if (seg.file_offset() + seg.physical_size() > filesz) {
      return error("PT_NOTE segment runs off end of file");
    }
    if (seg.physical_size() != seg.virtual_size()) {
      return error("PT_NOTE segment p_filesz ({:#x}) != p_memsz ({:#x})",
                   seg.physical_size(), seg.virtual_size());
    }
  }
  return true;
}

bool LayoutChecker::check_tls() {
  const Segment* tls_seg = nullptr;
  for (const Segment& seg : elf.segments()) {
    if (seg.type() == Segment::TYPE::TLS) {
      if (tls_seg != nullptr) {
        return error("Multiple PT_TLS segments");
      }
      tls_seg = &seg;
    }
  }

  if (tls_seg == nullptr) {
    return true;
  }

  for (const Symbol& sym : elf.symbols()) {
    if (sym.type() != Symbol::TYPE::TLS) {
      continue;
    }

    uint64_t addr = sym.value();
    if (addr > tls_seg->virtual_size() ||
        (addr + sym.size()) > tls_seg->virtual_size())
    {
      return error("TLS symbol {:#010}: {} out of PT_TLS segment range"
                   "([{:#010x}, {:#010x}])",
                   sym.value(), sym.name(), 0, tls_seg->virtual_size());
    }
  }

  return true;
}

bool check_layout(const Binary& bin, std::string* error_info) {
  LayoutChecker checker(bin);
  if (!checker.check()) {
    if (error_info != nullptr) {
      *error_info = checker.get_error();
    }
    return false;
  }
  return true;
}

}