current commit
260929.11 · deps: update lief to 1.0.0
/* 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/PE/Binary.hpp"
#include "LIEF/PE/Section.hpp"
#include "LIEF/PE/Relocation.hpp"
#include "LIEF/PE/RelocationEntry.hpp"
#include "LIEF/PE/LoadConfigurations/LoadConfiguration.hpp"
#include "LIEF/PE/TLS.hpp"
#include "LIEF/utils.hpp"
#include "LIEF/PE/utils.hpp"

#include "PE/Structures.hpp"

#include <spdlog/fmt/fmt.h>

#include <cstddef>


namespace LIEF::PE {

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

  LayoutChecker() = delete;
  LayoutChecker(const Binary& bin) :
    pe(bin),
    arch(bin.header().machine()),
    e_lfanew(bin.dos_header().addressof_new_exeheader()),
    filesz(bin.original_size()),
    section_alignment(bin.optional_header().section_alignment()),
    file_alignment(bin.optional_header().file_alignment()),
    sizeof_headers(bin.optional_header().sizeof_headers()),
    vaddr_start(bin.imagebase()),
    vaddr_end(bin.imagebase() + bin.virtual_size()) {}

  bool check_dos_header();
  bool check_header();
  bool check_opt_header();
  bool check_sections();
  bool check_code_integrity();
  bool check_load_config();
  bool check_imports();
  bool check_tls();
  bool check_relocations();

  bool is_legacy_arch() const {
    return arch == Header::MACHINE_TYPES::I386 ||
           arch == Header::MACHINE_TYPES::AMD64;
  }

  bool check() {
    if (filesz == 0) {
      return error("Invalid filesz");
    }

    if (!check_dos_header()) {
      return false;
    }

    if (!check_header()) {
      return false;
    }

    if (!check_opt_header()) {
      return false;
    }

    if (!check_sections()) {
      return false;
    }

    if (!check_code_integrity()) {
      return false;
    }

    if (!check_load_config()) {
      return false;
    }

    if (!check_imports()) {
      return false;
    }

    if (!check_tls()) {
      return false;
    }

    if (!check_relocations()) {
      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;
  }

  uint32_t ptr_size() const {
    return pe.optional_header().magic() == PE_TYPE::PE32 ? sizeof(uint32_t) :
                                                           sizeof(uint64_t);
  }

  bool is_pe64() const {
    return pe.optional_header().magic() == PE_TYPE::PE32_PLUS;
  }

  bool is_pe32() const {
    return pe.optional_header().magic() == PE_TYPE::PE32;
  }

  bool contains(uint64_t va) const {
    if (va == 0) {
      return true;
    }
    return vaddr_start <= va && va < vaddr_end;
  }

  bool contains(optional<uint64_t> va) const {
    return contains(va.value_or(0));
  }

  private:
  std::string error_msg;
  const Binary& pe;
  Header::MACHINE_TYPES arch;
  uint32_t e_lfanew = 0;
  uint64_t filesz = 0;
  uint32_t section_alignment = 0;
  uint32_t file_alignment = 0;
  uint64_t sizeof_headers = 0;
  uint64_t vaddr_start = 0;
  uint64_t vaddr_end = 0;
};


bool LayoutChecker::check_dos_header() {
  const DosHeader& dos = pe.dos_header();
  if (dos.magic() != /* MZ */ 0x5A4D) {
    return error("Invalid DOS Magic");
  }

  if (dos.addressof_new_exeheader() & 3) {
    return error("DOS.e_lfanew is not correctly aligned");
  }

  if (e_lfanew > filesz) {
    return error("DOS.e_lfanew is beyond the file size");
  }

  if (e_lfanew < sizeof(details::pe_dos_header)) {
    return error("DOS.e_lfanew is less than the size of the DOS header");
  }

  if (e_lfanew > section_alignment) {
    return error("DOS.e_lfanew is larger than the section's alignment");
  }
  return true;
}

bool LayoutChecker::check_header() {
  const Header& hdr = pe.header();
  if (arch == Header::MACHINE_TYPES::UNKNOWN && hdr.sizeof_optional_header() == 0)
  {
    return error("Header error: invalid machine type and size of optional header");
  }

  if (!hdr.has_characteristic(Header::CHARACTERISTICS::EXECUTABLE_IMAGE)) {
    return error("Header does not have the EXECUTABLE_IMAGE characteristic");
  }

  // This check is not really enforced by Windows
  if (hdr.numberof_sections() > 50) {
    return error("Invalid number of sections");
  }

  if (pe.type() == PE_TYPE::PE32) {
    if (arch != Header::MACHINE_TYPES::ARMNT &&
        arch != Header::MACHINE_TYPES::I386)
    {
      return error("Invalid Architecture");
    }
  }

  if (pe.type() == PE_TYPE::PE32_PLUS) {
    if (arch != Header::MACHINE_TYPES::IA64 &&
        arch != Header::MACHINE_TYPES::AMD64 &&
        arch != Header::MACHINE_TYPES::ARM64)
    {
      return error("Invalid Architecture");
    }
  }
  if ((hdr.sizeof_optional_header() & 0x07) != 0) {
    return error("Size of OptionalHeader is wrongly aligned");
  }

  return true;
}


bool LayoutChecker::check_opt_header() {
  const OptionalHeader& opt_hdr = pe.optional_header();
  if (is_legacy_arch()) {
    if (!pe.header().has_characteristic(Header::CHARACTERISTICS::RELOCS_STRIPPED))
    {
      if (opt_hdr.has(OptionalHeader::DLL_CHARACTERISTICS::APPCONTAINER)) {
        return error("AppContainer with stripped relocations");
      }
    }
  } else {
    if (!pe.header().has_characteristic(Header::CHARACTERISTICS::RELOCS_STRIPPED))
    {
      if (!opt_hdr.has(OptionalHeader::DLL_CHARACTERISTICS::DYNAMIC_BASE)) {
        return error("Missing DYNAMIC_BASE characteristic");
      }

      if (!opt_hdr.has(OptionalHeader::DLL_CHARACTERISTICS::NX_COMPAT)) {
        return error("Missing NX_COMPAT characteristic");
      }
    }
  }

  if (section_alignment >= DEFAULT_PAGE_SIZE && sizeof_headers == 0) {
    return error("OptionalHeader.sizeof_headers is null");
  }

  if (file_alignment == 0) {
    return error("File alignment can't be null");
  }

  if (const auto fa = file_alignment; (fa & (fa - 1)) != 0) {
    return error("File alignment must be a power of 2");
  }

  if (section_alignment == 0) {
    return error("Section alignment can't be null");
  }

  if (const auto sa = section_alignment; (sa & (sa - 1)) != 0) {
    return error("Section alignment must be a power of 2");
  }

  if (section_alignment < file_alignment) {
    return error("Section alignment must be larger than file alignment");
  }

  // This check is not accurate (c.f. PE64_x86-64_binary_winhello64-mingw.exe)
  if (false) {
    if (opt_hdr.major_linker_version() < 3 && opt_hdr.major_linker_version() < 5) {
      return error("Bad linker version");
    }
  }

  if ((file_alignment & 0x1ff) != 0 && file_alignment != section_alignment) {
    return error("Bad consistency between file/section alignment");
  }

  if (opt_hdr.sizeof_image() > MAX_SIZEOF_IMAGE) {
    return error("Image is too big");
  }

  if (sizeof_headers > opt_hdr.sizeof_image()) {
    return error("Size of Headers is larger than the image size");
  }

  if ((opt_hdr.imagebase() & 0xffff) != 0) {
    return error("Image base is not correctly aligned");
  }

  return true;
}

bool LayoutChecker::check_sections() {
  const auto sections = pe.sections();
  if (sections.empty()) {
    return error("No sections");
  }

  const uint64_t sizeof_image = pe.optional_header().sizeof_image();
  const uint32_t file_alignment_mask = file_alignment - 1;
  uint32_t nb_pages =
      (sizeof_image >> 12) + ((sizeof_image & (DEFAULT_PAGE_SIZE - 1)) != 0);
  uint32_t nb_sec_pages =
      align(sizeof_headers, section_alignment) / DEFAULT_PAGE_SIZE;
  uint64_t next_va = 0;

  if (section_alignment >= DEFAULT_PAGE_SIZE) {
    if ((sizeof_headers + (section_alignment - 1)) < sizeof_headers) {
      return error("Size of header is wrong");
    }

    if (nb_sec_pages > nb_pages) {
      return error("Invalid number of pages");
    }

    next_va += static_cast<uint64_t>(nb_sec_pages * DEFAULT_PAGE_SIZE);
  } else {
    nb_sec_pages = align(sizeof_image, DEFAULT_PAGE_SIZE) / DEFAULT_PAGE_SIZE;
  }

  nb_pages -= nb_sec_pages;

  for (size_t i = 0; i < sections.size(); ++i) {
    const Section& section = sections[i];

    uint32_t offset =
        section.sizeof_raw_data() != 0 ? section.pointerto_raw_data() : 0;
    [[maybe_unused]] uint32_t end_offset = offset + section.sizeof_raw_data();
    uint32_t vsize = section.virtual_size() != 0 ? section.virtual_size() :
                                                   section.sizeof_raw_data();

    if (offset + section.size() < offset) {
      return error("Section overflow");
    }

    if (section_alignment < DEFAULT_PAGE_SIZE) {
      if (section.virtual_address() != section.pointerto_raw_data() ||
          section.sizeof_raw_data() < vsize)
      {
        return error("Section size mismatch");
      }
    } else {
      if (next_va != section.virtual_address()) {
        return error("Invalid section virtual address");
      }

      if ((next_va + vsize) <= next_va) {
        return error("Invalid section vsize");
      }

      if ((vsize + (DEFAULT_PAGE_SIZE - 1)) <= vsize) {
        return error("Invalid section vsize");
      }

      nb_sec_pages = align(vsize, section_alignment) / DEFAULT_PAGE_SIZE;
      if (nb_sec_pages > nb_pages) {
        return error("Invalid section vsize");
      }

      nb_pages -= nb_sec_pages;

      if (((offset + section.sizeof_raw_data() + file_alignment_mask) &
           ~file_alignment_mask) < section.pointerto_raw_data())
      {
        return error("Invalid section raw size");
      }

      const bool is_last_section = i == (sections.size() - 1);

      if (is_last_section && section.sizeof_raw_data() != 0) {
        if (section.pointerto_raw_data() + section.sizeof_raw_data() >
            pe.original_size())
        {
          return error("File is cut");
        }
      }

      next_va += static_cast<uint64_t>(nb_sec_pages * DEFAULT_PAGE_SIZE);
    }
  }

  return true;
}

bool LayoutChecker::check_code_integrity() {
  if (section_alignment - e_lfanew <= e_lfanew) {
    return error("{}:{}", __FUNCTION__, __LINE__);
  }

  const size_t sizeof_nt_headers =
      sizeof(details::pe_header) + sizeof(details::pe32_optional_header);
  if ((e_lfanew + sizeof_nt_headers) > section_alignment) {
    return error("{}:{}", __FUNCTION__, __LINE__);
  }

  if (pe.optional_header().sizeof_headers() == 0 ||
      pe.optional_header().sizeof_headers() > pe.original_size())
  {
    return error("{}:{}", __FUNCTION__, __LINE__);
  }

  if (file_alignment == 0 || (file_alignment & (file_alignment - 1)) != 0) {
    return error("{}:{}", __FUNCTION__, __LINE__);
  }

  if ((section_alignment & (section_alignment - 1)) != 0) {
    return error("{}:{}", __FUNCTION__, __LINE__);
  }

  if (file_alignment > section_alignment) {
    return error("{}:{}", __FUNCTION__, __LINE__);
  }

  if ((file_alignment & (0x200 - 1)) != 0 && file_alignment != section_alignment) {
    return error("{}:{}", __FUNCTION__, __LINE__);
  }

  uint64_t end_of_raw = e_lfanew + sizeof(details::pe_header) +
                        pe.header().sizeof_optional_header() +
                        pe.sections().size() * sizeof(details::pe_section);

  if (end_of_raw >= DEFAULT_PAGE_SIZE) {
    return error("{}:{}", __FUNCTION__, __LINE__);
  }

  for (const Section& sec : pe.sections()) {
    if (pe.type() == LIEF::PE::PE_TYPE::PE32_PLUS) {
      if (sec.pointerto_raw_data() != 0 && sec.sizeof_raw_data() != 0 &&
          sec.pointerto_raw_data() < pe.optional_header().sizeof_headers())
      {
        return error("{}:{}", __FUNCTION__, __LINE__);
      }
    }

    if (sec.pointerto_raw_data() != 0 && sec.pointerto_raw_data() < end_of_raw) {
      return error("{}:{}", __FUNCTION__, __LINE__);
    }

    if ((sec.pointerto_raw_data() + sec.sizeof_raw_data()) <
        sec.pointerto_raw_data())
    {
      return error("{}:{}", __FUNCTION__, __LINE__);
    }

    if ((sec.pointerto_raw_data() + sec.sizeof_raw_data()) > pe.original_size()) {
      return error("{}:{}", __FUNCTION__, __LINE__);
    }

    if ((sec.virtual_address() + sec.sizeof_raw_data() - 1) <
        sec.sizeof_raw_data())
    {
      return error("{}:{}", __FUNCTION__, __LINE__);
    }

    if (sec.sizeof_raw_data() != 0) {
      end_of_raw = std::max<uint64_t>(end_of_raw, sec.pointerto_raw_data() +
                                                      sec.sizeof_raw_data());
    }
  }
  return true;
}

bool LayoutChecker::check_load_config() {
  const LoadConfiguration* config = pe.load_configuration();
  if (config == nullptr) {
    return true;
  }

  // NOTE(romain): dynamic_value_reloctable_section starts by indexing from 1
  if (auto value = config->dynamic_value_reloctable_section();
      value.value_or(0) > 0)
  {
    if ((*value - 1) < 0 ||
        static_cast<size_t>((*value - 1)) >= pe.sections().size())
    {
      return false;
    }
  }

  if (auto value = config->guard_cf_function_table(); value.value_or(0) > 0) {
    if (*value < pe.optional_header().imagebase()) {
      return error("{}:{}", __FUNCTION__, __LINE__);
    }

    if (config->guard_cf_function_count().value_or(0) == 0) {
      return error("{}:{}", __FUNCTION__, __LINE__);
    }
  }

  if (!contains(config->security_cookie())) {
    return error("Security cookie out of range: {:#010x} ([{:#010x}, {:#010x}])",
                 config->security_cookie(), vaddr_start, vaddr_end);
  }

  if (!contains(config->lock_prefix_table())) {
    return error("Lock prefix table out of range: {:#010x} ([{:#010x}, {:#010x}])",
                 config->lock_prefix_table(), vaddr_start, vaddr_end);
  }

  if (!contains(config->se_handler_table())) {
    return error("SE handler table out of range: {:#010x} ([{:#010x}, {:#010x}])",
                 config->se_handler_table().value_or(0), vaddr_start, vaddr_end);
  }

  if (!contains(config->guard_cf_check_function_pointer())) {
    return error("Guard CF check function pointer out of range: {:#010x} "
                 "([{:#010x}, {:#010x}])",
                 config->guard_cf_check_function_pointer().value_or(0),
                 vaddr_start, vaddr_end);
  }

  if (!contains(config->guard_cf_dispatch_function_pointer())) {
    return error("Guard CF dispatch function pointer out of range: {:#010x} "
                 "([{:#010x}, {:#010x}])",
                 config->guard_cf_dispatch_function_pointer().value_or(0),
                 vaddr_start, vaddr_end);
  }

  if (!contains(config->guard_address_taken_iat_entry_table())) {
    return error("Guard address taken IAT entry table out of range: {:#010x} "
                 "([{:#010x}, {:#010x}])",
                 config->guard_address_taken_iat_entry_table().value_or(0),
                 vaddr_start, vaddr_end);
  }

  if (!contains(config->guard_long_jump_target_table())) {
    return error("Guard long jump target table out of range: {:#010x} ([{:#010x}, "
                 "{:#010x}])",
                 config->guard_long_jump_target_table().value_or(0), vaddr_start,
                 vaddr_end);
  }

  if (!contains(config->dynamic_value_reloc_table())) {
    return error(
        "Dynamic value reloc table out of range: {:#010x} ([{:#010x}, {:#010x}])",
        config->dynamic_value_reloc_table().value_or(0), vaddr_start, vaddr_end
    );
  }

  if (!contains(config->hybrid_metadata_pointer())) {
    return error(
        "Hybrid metadata pointer out of range: {:#010x} ([{:#010x}, {:#010x}])",
        config->hybrid_metadata_pointer().value_or(0), vaddr_start, vaddr_end
    );
  }

  if (!contains(config->guard_rf_failure_routine())) {
    return error(
        "Guard RF failure routine out of range: {:#010x} ([{:#010x}, {:#010x}])",
        config->guard_rf_failure_routine().value_or(0), vaddr_start, vaddr_end
    );
  }

  if (!contains(config->guard_rf_failure_routine_function_pointer())) {
    return error("Guard RF failure routine function pointer out of range: "
                 "{:#010x} ([{:#010x}, {:#010x}])",
                 config->guard_rf_failure_routine_function_pointer().value_or(0),
                 vaddr_start, vaddr_end);
  }

  if (!contains(config->guard_rf_verify_stackpointer_function_pointer())) {
    return error(
        "Guard RF verify stack pointer function pointer out of range: {:#010x} "
        "([{:#010x}, {:#010x}])",
        config->guard_rf_verify_stackpointer_function_pointer().value_or(0),
        vaddr_start, vaddr_end
    );
  }

  if (!contains(config->enclave_configuration_ptr())) {
    return error("Enclave configuration pointer out of range: {:#010x} "
                 "([{:#010x}, {:#010x}])",
                 config->enclave_configuration_ptr().value_or(0), vaddr_start,
                 vaddr_end);
  }

  if (!contains(config->volatile_metadata_pointer())) {
    return error(
        "Volatile metadata pointer out of range: {:#010x} ([{:#010x}, {:#010x}])",
        config->volatile_metadata_pointer().value_or(0), vaddr_start, vaddr_end
    );
  }

  if (!contains(config->guard_eh_continuation_table())) {
    return error("Guard EH continuation table out of range: {:#010x} ([{:#010x}, "
                 "{:#010x}])",
                 config->guard_eh_continuation_table().value_or(0), vaddr_start,
                 vaddr_end);
  }

  if (!contains(config->guard_xfg_check_function_pointer())) {
    return error("Guard XFG check function pointer out of range: {:#010x} "
                 "([{:#010x}, {:#010x}])",
                 config->guard_xfg_check_function_pointer().value_or(0),
                 vaddr_start, vaddr_end);
  }

  if (!contains(config->guard_xfg_dispatch_function_pointer())) {
    return error("Guard XFG dispatch function pointer out of range: {:#010x} "
                 "([{:#010x}, {:#010x}])",
                 config->guard_xfg_dispatch_function_pointer().value_or(0),
                 vaddr_start, vaddr_end);
  }

  if (!contains(config->guard_xfg_table_dispatch_function_pointer())) {
    return error("Guard XFG table dispatch function pointer out of range: "
                 "{:#010x} ([{:#010x}, {:#010x}])",
                 config->guard_xfg_table_dispatch_function_pointer().value_or(0),
                 vaddr_start, vaddr_end);
  }

  if (!contains(config->cast_guard_os_determined_failure_mode())) {
    return error("Cast guard OS determined failure mode out of range: {:#010x} "
                 "([{:#010x}, {:#010x}])",
                 config->cast_guard_os_determined_failure_mode().value_or(0),
                 vaddr_start, vaddr_end);
  }

  if (!contains(config->guard_memcpy_function_pointer())) {
    return error("Guard memcpy function pointer out of range: {:#010x} "
                 "([{:#010x}, {:#010x}])",
                 config->guard_memcpy_function_pointer().value_or(0), vaddr_start,
                 vaddr_end);
  }

  if (!contains(config->uma_function_pointers())) {
    return error(
        "UMA function pointers out of range: {:#010x} ([{:#010x}, {:#010x}])",
        config->uma_function_pointers().value_or(0), vaddr_start, vaddr_end
    );
  }

  return true;
}

bool LayoutChecker::check_imports() {
  auto imports = pe.imports();
  if (imports.empty()) {
    return true;
  }

  const DataDirectory* iat_dir = pe.iat_dir();
  if (iat_dir == nullptr) {
    return true;
  }

  for (const Import& imp : imports) {
    for (const ImportEntry& entry : imp.entries()) {
      const uint32_t iat_address = entry.iat_address(); // RVA
      std::string entry_name = entry.is_ordinal() ?
                                   fmt::format("#{:04d}", entry.ordinal()) :
                                   entry.name();
      // 2-byte alignment seems sufficient
      if (iat_address % sizeof(uint16_t) != 0) {
        return error("{}:{} IAT is wrongly aligned: {:#010x}", imp.name(),
                     entry_name, entry.iat_address());
      }

      if (iat_dir->RVA() == 0 || iat_dir->size() == 0) {
        return error("IAT directory is not set");
      }

      const uint32_t iat_dir_start = iat_dir->RVA();
      const uint32_t iat_dir_end = iat_dir_start + iat_dir->size();

      if (iat_dir_start <= iat_address && iat_address < iat_dir_end) {
        const Section* section = pe.section_from_rva(iat_address);
        if (section == nullptr) {
          return error("Can't find section associated with the IAT "
                       "address: {:#010x}",
                       iat_address);
        }
        /* Not a requirement (even though most of the PE have it)
         * if
         * (!section->has_characteristic(Section::CHARACTERISTICS::CNT_INITIALIZED_DATA))
         * { return error(R"err(
         *     {}:{} IAT: {:#010x} -- Missing CNT_INITIALIZED_DATA in the pointed
         * section ('{}') )err", imp.name(), entry_name, iat_address,
         * section->name());
         * }
         */

        if (!section->has_characteristic(Section::CHARACTERISTICS::MEM_READ)) {
          return error(R"err(
            {}:{} IAT: {:#010x} -- Missing MEM_READ in the pointed section ('{}')
          )err",
                       imp.name(), entry_name, iat_address, section->name());
        }
      } else {
        const Section* section = pe.section_from_rva(iat_address);
        if (section == nullptr) {
          return error("Can't find section associated with the IAT "
                       "address: {:#010x}",
                       iat_address);
        }
        /*
         * if
         * (!section->has_characteristic(Section::CHARACTERISTICS::CNT_INITIALIZED_DATA))
         * { return error(R"err(
         *     {}:{} IAT: {:#010x} -- Missing CNT_INITIALIZED_DATA in the pointed
         * section ('{}') )err", imp.name(), entry_name, iat_address,
         * section->name());
         * }
         */

        if (!section->has_characteristic(Section::CHARACTERISTICS::MEM_READ)) {
          return error(R"err(
            {}:{} IAT: {:#010x} -- Missing MEM_READ in the pointed section ('{}')
          )err",
                       imp.name(), entry_name, iat_address, section->name());
        }

        if (!section->has_characteristic(Section::CHARACTERISTICS::MEM_WRITE)) {
          return error(R"err(
            {}:{} IAT: {:#010x} -- Missing MEM_WRITE in the pointed section ('{}')
          )err",
                       imp.name(), entry_name, iat_address, section->name());
        }

        if (!section->has_characteristic(Section::CHARACTERISTICS::CNT_CODE)) {
          return error(R"err(
            {}:{} IAT: {:#010x} -- Missing CNT_CODE in the pointed section ('{}')
          )err",
                       imp.name(), entry_name, iat_address, section->name());
        }
      }
    }
  }
  return true;
}

bool LayoutChecker::check_tls() {
  const TLS* tls = pe.tls();
  const DataDirectory* tls_dir = pe.tls_dir();

  if (tls == nullptr || tls_dir == nullptr) {
    return true;
  }
  const size_t ptr_size = this->ptr_size();
  const uint64_t imagebase = pe.optional_header().imagebase();

  const Section* tls_sec = tls_dir->section();
  if (tls_sec == nullptr) {
    return error("Missing section associated with the TLS data directory");
  }

  if (!tls_sec->has_characteristic(Section::CHARACTERISTICS::MEM_READ)) {
    return error("Missing MEM_READ for TLS section: '{}'", tls_sec->name());
  }

  if (!tls_sec->has_characteristic(Section::CHARACTERISTICS::CNT_INITIALIZED_DATA))
  {
    return error("Missing CNT_INITIALIZED_DATA for TLS section: '{}'",
                 tls_sec->name());
  }

  if (tls->addressof_index() == 0) {
    return error("Missing address of index in the TLS structure");
  }

  for (uint64_t addr : tls->callbacks()) {
    if (addr <= imagebase) {
      return error("TLS callback: {:#018x} not in imagebase range", addr);
    }
    const uint64_t rva = addr - imagebase;
    const Section* sec = pe.section_from_rva(rva);
    if (sec == nullptr) {
      return error("Can't find section associated with TLS callback: {:#018x}",
                   addr);
    }

    if (!sec->has_characteristic(Section::CHARACTERISTICS::MEM_READ)) {
      return error("Missing MEM_READ for TLS callback: {:#018x} ('{}')", addr,
                   sec->name());
    }

    if (!sec->has_characteristic(Section::CHARACTERISTICS::MEM_EXECUTE)) {
      return error("Missing MEM_EXECUTE for TLS callback: {:#018x} ('{}')", addr,
                   sec->name());
    }
  }

  const uint64_t addr_cbk = tls->addressof_callbacks();
  int64_t addr_cbk_rva = addr_cbk - imagebase;

  if (addr_cbk > 0 && addr_cbk_rva < 0) {
    return error("TLS's address of callbacks should be a VA. Addr={:#08x}, "
                 "Imagebase={:#018x}",
                 addr_cbk, imagebase);
  }

  // If the binary is pie, make sure it has relocations for the TLS structures
  if (pe.is_pie()) {
    auto relocations = pe.relocations();
    if (relocations.empty()) {
      return error("Missing relocations for the TLS structures");
    }

    const size_t nb_cbk = tls->callbacks().size();

    uint32_t tls_hdr_start = tls_dir->RVA();
    uint32_t tls_hdr_end = tls_hdr_start + tls_dir->size();

    uint32_t tls_cbk_start = addr_cbk_rva;
    uint32_t tls_cbk_end = addr_cbk_rva + nb_cbk * ptr_size;

    size_t nb_hdr_reloc = 0;
    size_t nb_cbk_reloc = 0;

    for (const Relocation& R : relocations) {
      for (const RelocationEntry& E : R.entries()) {
        const uint64_t addr = E.address();
        if (tls_hdr_start <= addr && addr < tls_hdr_end) {
          ++nb_hdr_reloc;
        }

        if (tls_cbk_start <= addr && addr < tls_cbk_end) {
          ++nb_cbk_reloc;
        }
      }
    }

    if (nb_cbk_reloc < nb_cbk) {
      return error("Expecting #{} callback relocations. Found: #{}", nb_cbk,
                   nb_cbk_reloc);
    }

    size_t expected_hdr_reloc = 0;

    expected_hdr_reloc += tls->addressof_callbacks() > 0 ? 1 : 0;
    expected_hdr_reloc += tls->addressof_raw_data().first > 0 ? 2 : 0;
    expected_hdr_reloc += tls->addressof_index() > 0 ? 1 : 0;

    if (expected_hdr_reloc > nb_hdr_reloc) {
      return error("Expecting #{} TLS header relocations. Found: #{}",
                   expected_hdr_reloc, nb_hdr_reloc);
    }
  }
  return true;
}


bool LayoutChecker::check_relocations() {
  auto relocations = pe.relocations();
  if (relocations.empty()) {
    return true;
  }

  const DataDirectory* reloc_dir = pe.relocation_dir();
  if (reloc_dir == nullptr) {
    return false;
  }

  const uint64_t vsize = pe.virtual_size();

  const uint64_t size = reloc_dir->size();
  uint64_t computed_size = 0;

  for (const Relocation& R : relocations) {
    if (R.block_size() % 4) {
      return error("Relocation block {:#x} is not correctly aligned",
                   R.virtual_address());
    }

    if (R.virtual_address() > vsize) {
      return error("Relocation block {:#x} is beyond the binary virtual size",
                   R.virtual_address());
    }

    if (R.entries().size() !=
        (R.block_size() - sizeof(details::pe_base_relocation_block)) /
            sizeof(uint16_t))
    {
      return error("Relocation block {:#x} is corrupted", R.virtual_address());
    }

    computed_size += R.block_size();

    for (const RelocationEntry& E : R.entries()) {
      if (E.address() > vsize) {
        return error("Relocation {:#x} is beyond the binary virtual size",
                     E.address());
      }
    }
  }

  if (computed_size != size) {
    return error("Size mismatch. DataDirectory={:#06x}, Computed={:#06x}", size,
                 computed_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;
}
}