@ 261107.7
current
/* Copyright 2021 - 2026 R. Thomas
 *
 * 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 <cstddef>
#include <spdlog/fmt/fmt.h>

#include "MachO/Structures.hpp"
#include "LIEF/range.hpp"

#include "LIEF/BinaryStream/SpanStream.hpp"

#include "LIEF/MachO/AtomInfo.hpp"
#include "LIEF/MachO/FatBinary.hpp"
#include "LIEF/MachO/Binary.hpp"
#include "LIEF/MachO/Routine.hpp"
#include "LIEF/MachO/CodeSignature.hpp"
#include "LIEF/MachO/CodeSignatureDir.hpp"
#include "LIEF/MachO/DataInCode.hpp"
#include "LIEF/MachO/DyldChainedFixups.hpp"
#include "LIEF/MachO/DyldExportsTrie.hpp"
#include "LIEF/MachO/DyldInfo.hpp"
#include "LIEF/MachO/DylibCommand.hpp"
#include "LIEF/MachO/DynamicSymbolCommand.hpp"
#include "LIEF/MachO/ExportInfo.hpp"
#include "LIEF/MachO/FunctionStarts.hpp"
#include "LIEF/MachO/FunctionVariants.hpp"
#include "LIEF/MachO/FunctionVariantFixups.hpp"
#include "LIEF/MachO/LazyLoadDylibInfo.hpp"
#include "LIEF/MachO/LinkerOptHint.hpp"
#include "LIEF/MachO/MainCommand.hpp"
#include "LIEF/MachO/RPathCommand.hpp"
#include "LIEF/MachO/Section.hpp"
#include "LIEF/MachO/SegmentCommand.hpp"
#include "LIEF/MachO/SegmentSplitInfo.hpp"
#include "LIEF/MachO/Symbol.hpp"
#include "LIEF/MachO/SymbolCommand.hpp"
#include "LIEF/MachO/ThreadCommand.hpp"
#include "LIEF/MachO/ThreadLocalVariables.hpp"
#include "LIEF/MachO/TwoLevelHints.hpp"
#include "LIEF/MachO/utils.hpp"
#include "LIEF/utils.hpp"

#include "MachO/ChainedFixup.hpp"

#include "logging.hpp"
#include "Object.tcc"

#include <algorithm>
#include <string>
#include <unordered_set>

namespace LIEF::MachO {
template<typename T, typename U>
inline bool greater_than_add_or_overflow(T LHS, T RHS, U B) {
  return (LHS > B) || (RHS > (B - LHS));
}

inline uint64_t rnd64(uint64_t v, uint64_t r) {
  r--;
  v += r;
  v &= ~static_cast<int64_t>(r);
  return v;
}

inline uint64_t rnd(uint64_t v, uint64_t r) {
  return rnd64(v, r);
}

class LayoutChecker {
  public:
  LayoutChecker() = delete;
  LayoutChecker(const Binary& macho) :
    binary(macho) {}


  bool check();

  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;
  }

  // Return true if segments overlap
  bool check_overlapping();

  // Mirror MachOAnalyzer::validLoadCommands
  bool check_load_commands();

  // Mirror of MachOAnalyzer::validMain
  bool check_main();

  // Mirror of MachOAnalyzer::validEmbeddedPaths
  bool check_valid_paths();

  // Mirror of Image::validStructureLinkedit (version: dyld-1235.2)
  bool check_linkedit();

  // Mirror of MachOFile::validSegments
  bool check_segments();

  // Mirror Image::validInitializers
  bool check_initializers();

  // Mirror ChainedFixups::valid
  bool check_chained_fixups();

  // Mirror of mach_o::ExportsTrie::valid (dyld's mach_o/ExportsTrie.cpp)
  // combined with the maxVmOffset computation from Image::validLinkedit.
  bool check_exports_trie();

  // Check from PR #1136
  // See: https://github.com/lief-project/LIEF/pull/1136/files#r1882625692
  bool check_section_contiguity();

  // From FunctionVariants::valid()
  bool check_function_variants();

  // Consistency of LC_FUNCTION_VARIANT_FIXUPS (cf. dyld's interpretation of
  // FunctionVariantFixups::InternalFixup)
  bool check_function_variant_fixups();

  // Mirror of mach_o::LazyLoadDylib::valid() for LC_LAZY_LOAD_DYLIB_INFO
  bool check_lazy_load_dylib_infos();

  bool check_linkedit_end();

  bool check_tls();

  bool check_export(uint64_t imagebase, uint64_t max_vm_offset,
                    const ExportInfo& info);

  size_t ptr_size() const {
    return binary.header().is_32bit() ? sizeof(uint32_t) : sizeof(uint64_t);
  }

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

  private:
  std::string error_msg;
  const Binary& binary;
};

bool LayoutChecker::check_initializers() {
  struct range_t {
    uint64_t va_start = 0;
    uint64_t va_end = 0;
    uint64_t filesz = 0;
  };

  class ranges_t : public std::vector<range_t> {
    public:
    using base_t = std::vector<range_t>;
    using base_t::base_t;

    bool contain(uint64_t addr) const {
      return std::find_if(begin(), end(), [addr](const range_t& R) {
               return R.va_start <= addr && addr < (R.va_start + R.va_end);
             }) != end();
    }
  };

  const size_t ptr_size = this->ptr_size();
  const uint64_t imagebase = binary.imagebase();

  ranges_t ranges;
  ranges.reserve(binary.segments().size());
  for (const SegmentCommand& segment : binary.segments()) {
    if (segment.is(SegmentCommand::VM_PROTECTIONS::EXECUTE)) {
      ranges.push_back({segment.virtual_address(),
                        segment.virtual_address() + segment.virtual_size(),
                        segment.file_size()});
    }
  }

  if (ranges.empty()) {
    return error("no executable segments");
  }

  for (const LoadCommand& cmd : binary.commands()) {
    const auto* routines = cmd.cast<Routine>();
    if (routines == nullptr) {
      continue;
    }

    uint64_t init_addr = routines->init_address();
    if (!ranges.contain(init_addr)) {
      return error("LC_ROUTINE/64 initializer {:#018x} is not an offset "
                   "to an executable segment",
                   init_addr);
    }
  }

  for (const Section& section : binary.sections()) {
    const bool contains_init_ptr =
        section.type() == Section::TYPE::MOD_INIT_FUNC_POINTERS ||
        section.type() == Section::TYPE::MOD_TERM_FUNC_POINTERS;

    if (!contains_init_ptr) {
      continue;
    }

    if (section.size() % ptr_size != 0) {
      return error(
          "Section '{}' size ({:#010x}) is not a multiple of pointer-size",
          section.name(), section.size()
      );
    }

    if (section.address() % ptr_size != 0) {
      return error("Section '{}' address ({:#010x}) is not pointer aligned",
                   section.name(), section.address());
    }

    SpanStream stream(section.content());
    while (stream) {
      uint64_t ptr = 0;
      if (ptr_size == sizeof(uint32_t)) {
        auto res = stream.read<uint32_t>();
        if (!res) {
          return error("Can't read pointer at {:#06x} in {}", stream.pos(),
                       section.name());
        }

        // As mentioned in the dyld documentation:
        //  > FIXME: as a quick hack, the low 32-bits with either rebase opcodes
        //  > or chained fixups is offset in image
        ptr = (uint32_t)*res;
      } else {
        auto res = stream.read<uint64_t>();
        if (!res) {
          return error("Can't read pointer at {:#06x} in {}", stream.pos(),
                       section.name());
        }
        // As mentioned in the dyld documentation:
        //  > FIXME: as a quick hack, the low 32-bits with either rebase opcodes
        //  > or chained fixups is offset in image
        ptr = *res & 0x03FFFFFF;
      }
      if (!ranges.contain(imagebase + ptr)) {
        return error(
            "initializer at {:#08x} in {} is not in an executable segment",
            stream.pos(), section.name()
        );
      }
    }
  }


  for (const Section& section : binary.sections()) {
    if (section.type() != Section::TYPE::INIT_FUNC_OFFSETS) {
      continue;
    }
    if (section.segment()->is(SegmentCommand::VM_PROTECTIONS::WRITE)) {
      return error(
          "initializer offsets section {}/{} must be in read-only segment",
          section.segment_name(), section.name()
      );
    }

    if (section.size() % 4 != 0) {
      return error("initializer offsets section {}/{} has bad size",
                   section.segment_name(), section.name());
    }

    if (section.address() % 4 != 0) {
      return error("initializer offsets section {}/{} is not 4-byte aligned",
                   section.segment_name(), section.name());
    }

    SpanStream stream(section.content());

    while (stream) {
      auto res = stream.read<uint32_t>();
      if (!res) {
        return error("Can't read pointer at {:#06x} in {}", stream.pos(),
                     section.name());
      }

      if (!ranges.contain(imagebase + *res)) {
        return error("initializer {:#010x} is not an offset to an executable "
                     "segment",
                     *res);
      }
    }
  }
  return true;
}

bool LayoutChecker::check_segments() {
  const size_t sizeof_segment_hdr = binary.header().is_32bit() ?
                                        sizeof(details::segment_command_32) :
                                        sizeof(details::segment_command_64);

  const size_t sizeof_section_hdr = binary.header().is_32bit() ?
                                        sizeof(details::section_32) :
                                        sizeof(details::section_64);

  for (const SegmentCommand& segment : binary.segments()) {
    int32_t section_space = segment.size() - sizeof_segment_hdr;
    if (section_space < 0) {
      return error("load command is too small for LC_SEGMENT/64: {}",
                   segment.name());
    }

    if ((section_space % sizeof_section_hdr) != 0) {
      return error("segment load command size {}: {:#06x} will not fit "
                   "whole number of sections",
                   segment.name(), segment.size());
    }

    if ((uint32_t)section_space !=
        segment.numberof_sections() * sizeof_section_hdr)
    {
      return error("segment {} does not match nsects", segment.name(),
                   segment.numberof_sections());
    }

    if ((segment.file_size() > segment.virtual_size()) &&
        (segment.virtual_size() != 0 ||
         (segment.flags() & (uint32_t)SegmentCommand::FLAGS::NORELOC) != 0))
    {
      return error("in segment {}, filesize exceeds vmsize", segment.name());
    }

    if ((segment.init_protection() & 0xFFFFFFF8) != 0) {
      return error("{} segment permissions has invalid bits set", segment.name());
    }
  }

  if (const SegmentCommand* text_seg = binary.get_segment("__TEXT")) {
    static constexpr auto R_X = (uint32_t)SegmentCommand::VM_PROTECTIONS::READ |
                                (uint32_t)SegmentCommand::VM_PROTECTIONS::EXECUTE;
    if (text_seg->init_protection() != R_X) {
      return error("__TEXT segment must be r-x");
    }
  }

  if (const SegmentCommand* lnk_seg = binary.get_segment("__LINKEDIT")) {
    static constexpr auto RO = (uint32_t)SegmentCommand::VM_PROTECTIONS::READ;
    if (lnk_seg->init_protection() != RO) {
      return error("__LINKEDIT segment must be read only");
    }
  }

  return true;
}

bool LayoutChecker::check_overlapping() {
  for (const SegmentCommand& lhs : binary.segments()) {
    const uint64_t lhs_vm_end = lhs.virtual_address() + lhs.virtual_size();
    const uint64_t lhs_file_end = lhs.file_offset() + lhs.file_size();
    for (const SegmentCommand& rhs : binary.segments()) {
      if (lhs.index() == rhs.index()) {
        continue;
      }
      const uint64_t rhs_vm_end = rhs.virtual_address() + rhs.virtual_size();
      const uint64_t rhs_file_end = rhs.file_offset() + rhs.file_size();

      const bool vm_overalp = (rhs.virtual_address() <= lhs.virtual_address() &&
                               rhs_vm_end > lhs.virtual_address() &&
                               lhs_vm_end > lhs.virtual_address()) ||
                              (rhs.virtual_address() >= lhs.virtual_address() &&
                               rhs.virtual_address() < lhs_vm_end &&
                               rhs_vm_end > rhs.virtual_address());
      if (vm_overalp) {
        error(R"delim(
              Segments '{}' and '{}' overlap (virtual addresses):
              [{:#010x}, {:#010x}] [{:#010x}, {:#010x}]
              )delim",
              lhs.name(), rhs.name(), lhs.virtual_address(), lhs_vm_end,
              rhs.virtual_address(), rhs_vm_end);
        return true;
      }
      const bool file_overlap =
          (rhs.file_offset() <= lhs.file_offset() &&
           rhs_file_end > lhs.file_offset() && lhs_file_end > lhs.file_offset()) ||
          (rhs.file_offset() >= lhs.file_offset() &&
           rhs.file_offset() < lhs_file_end && rhs_file_end > rhs.file_offset());
      if (file_overlap) {
        error(R"delim(
              Segments '{}' and '{}' overlap (file offsets):
              [{:#010x}, {:#010x}] [{:#010x}, {:#010x}]
              )delim",
              lhs.name(), rhs.name(), lhs.file_offset(), lhs_file_end,
              rhs.file_offset(), rhs_file_end);
        return true;
      }

      if (lhs.index() < rhs.index()) {

        const bool wrong_order =
            lhs.virtual_address() > rhs.virtual_address() ||
            (lhs.file_offset() > rhs.file_offset() && lhs.file_offset() != 0 &&
             rhs.file_offset() != 0);
        if (wrong_order) {
          error(R"delim(
                Segments '{}' and '{}' are wrongly ordered
                )delim",
                lhs.name(), rhs.name());
          return true;
        }
      }
    }
  }
  return false;
}


// Mirror MachOAnalyzer::validLoadCommands
bool LayoutChecker::check_load_commands() {
  const size_t sizeof_header = binary.header().is_32bit() ?
                                   sizeof(details::mach_header) :
                                   sizeof(details::mach_header_64);

  const size_t last_cmd_offset = sizeof_header + binary.header().sizeof_cmds();

  if (last_cmd_offset > binary.original_size()) {
    return error("Load commands exceed length of file");
  }

  const auto& commands = binary.commands();
  for (size_t i = 0; i < commands.size(); ++i) {
    const LoadCommand& cmd = commands[i];
    if (cmd.command_offset() > last_cmd_offset ||
        (cmd.command_offset() + cmd.size()) > last_cmd_offset)
    {
      return error("Command #{} ({}) pasts the end of the commands", i,
                   to_string(cmd.command()));
    }

    if (const auto* lib = cmd.cast<DylibCommand>()) {
      if (lib->name_offset() > lib->size()) {
        return error("{}: load command {} name offset ({}) outside its size ({})",
                     lib->name(), to_string(cmd.command()), lib->name_offset(),
                     lib->size());
      }
      span<const uint8_t> raw = cmd.data();
      const auto start = raw.begin() + lib->name_offset();
      auto it = std::find(start, raw.end(), '\0');
      if (it == raw.end()) {
        return error("{}: string extends beyond end of load command", lib->name());
      }
    }

    if (const auto* rpath = cmd.cast<RPathCommand>()) {
      if (rpath->path_offset() > rpath->size()) {
        return error("{}: load command {} name offset ({}) outside its size ({})",
                     rpath->path(), to_string(cmd.command()), rpath->path_offset(),
                     rpath->size());
      }

      span<const uint8_t> raw = cmd.data();
      const auto start = raw.begin() + rpath->path_offset();
      auto it = std::find(start, raw.end(), '\0');
      if (it == raw.end()) {
        return error("{}: string extends beyond end of load command",
                     rpath->path());
      }
    }
  }
  return true;
}

// Mirror of MachOAnalyzer::validMain
bool LayoutChecker::check_main() {
  if (binary.header().file_type() != Header::FILE_TYPE::EXECUTE) {
    return true;
  }

  bool has_main = false;
  bool has_thread = false;

  if (const MainCommand* cmd = binary.main_command()) {
    has_main = true;
    uint64_t start_address = binary.imagebase() + cmd->entrypoint();
    const SegmentCommand* segment =
        binary.segment_from_virtual_address(start_address);
    if (segment == nullptr) {
      return error("LC_MAIN entryoff is out of range");
    }

    if (!segment->is(SegmentCommand::VM_PROTECTIONS::EXECUTE)) {
      return error("LC_MAIN points to non-executable segment");
    }
  }


  if (const ThreadCommand* cmd = binary.thread_command()) {
    has_thread = true;
    uint64_t start_address = cmd->pc();
    if (start_address == 0) {
      return error("LC_UNIXTHREAD not valid for the current arch");
    }

    const SegmentCommand* segment =
        binary.segment_from_virtual_address(start_address);
    if (segment == nullptr) {
      return error("LC_MAIN entryoff is out of range");
    }

    if (!segment->is(SegmentCommand::VM_PROTECTIONS::EXECUTE)) {
      return error("LC_MAIN points to non-executable segment");
    }
  }

  if (has_main || has_thread) {
    return true;
  }

  if (!has_main && !has_thread) {
    return error("Missing LC_MAIN or LC_UNIXTHREAD command");
  }

  return error("only one LC_MAIN or LC_UNIXTHREAD is allowed");
}

// Mirror of MachOAnalyzer::validEmbeddedPaths
bool LayoutChecker::check_valid_paths() {
  bool has_install_name = false;
  int dependents_count = 0;
  for (const LoadCommand& cmd : binary.commands()) {
    switch (cmd.command()) {
      case LoadCommand::TYPE::ID_DYLIB:
      {
        has_install_name = true;
        [[fallthrough]];
      }
      case LoadCommand::TYPE::LOAD_DYLIB:
      case LoadCommand::TYPE::LOAD_WEAK_DYLIB:
      case LoadCommand::TYPE::REEXPORT_DYLIB:
      case LoadCommand::TYPE::LOAD_UPWARD_DYLIB:
      {
        if (!DylibCommand::classof(&cmd)) {
          LIEF_ERR("{} is not associated with a DylibCommand",
                   to_string(cmd.command()));
          break;
        }
        auto& dylib = *cmd.as<DylibCommand>();
        if (dylib.command() != LoadCommand::TYPE::ID_DYLIB) {
          ++dependents_count;
        }
        break;
      }
      default:
      {
      }
    }
  }

  const Header::FILE_TYPE ftype = binary.header().file_type();
  if (ftype == Header::FILE_TYPE::DYLIB) {
    if (!has_install_name) {
      return error("Missing a LC_ID_DYLIB command for a MH_DYLIB file");
    }
  } else {
    if (has_install_name) {
      return error("LC_ID_DYLIB command found in a non MH_DYLIB file");
    }
  }
  const bool is_dynamic_exe = ftype == Header::FILE_TYPE::EXECUTE &&
                              binary.has(LoadCommand::TYPE::LOAD_DYLINKER);
  if (dependents_count == 0 && is_dynamic_exe) {
    return error("Missing libraries. It must link with at least one library "
                 "(like libSystem.dylib)");
  }
  return true;
}

// Mirror of Image::validStructureLinkedit (version: dyld-1235.2)
bool LayoutChecker::check_linkedit() {
  struct chunk_t {
    enum class KIND {
      UNKNOWN = 0,
      SYMTAB,
      SYMTAB_STR,
      DYSYMTAB_LOC_REL,
      DYSYMTAB_EXT_REL,
      DYSYMTAB_INDIRECT_SYM,
      DYLD_INFO_REBASES,
      DYLD_INFO_BIND,
      DYLD_INFO_WEAK_BIND,
      DYLD_INFO_LAZY_BIND,
      DYLD_INFO_EXPORT,
      SEGMENT_SPLIT_INFO,
      ATOM_INFO,
      FUNCTION_STARTS,
      DATA_IN_CODE,
      CODE_SIGNATURE,
      DYLD_EXPORT_TRIE,
      DYLD_CHAINED_FIXUPS,
    };
    static const char* to_string(KIND kind) {
      switch (kind) {
        case KIND::UNKNOWN: return "UNKNOWN";
        case KIND::SYMTAB: return "SYMTAB";
        case KIND::SYMTAB_STR: return "SYMTAB_STR";
        case KIND::DYSYMTAB_LOC_REL: return "DYSYMTAB_LOC_REL";
        case KIND::DYSYMTAB_EXT_REL: return "DYSYMTAB_EXT_REL";
        case KIND::DYSYMTAB_INDIRECT_SYM: return "DYSYMTAB_INDIRECT_SYM";
        case KIND::DYLD_INFO_REBASES: return "DYLD_INFO_REBASES";
        case KIND::DYLD_INFO_BIND: return "DYLD_INFO_BIND";
        case KIND::DYLD_INFO_WEAK_BIND: return "DYLD_INFO_WEAK_BIND";
        case KIND::DYLD_INFO_LAZY_BIND: return "DYLD_INFO_LAZY_BIND";
        case KIND::DYLD_INFO_EXPORT: return "DYLD_INFO_EXPORT";
        case KIND::SEGMENT_SPLIT_INFO: return "SEGMENT_SPLIT_INFO";
        case KIND::ATOM_INFO: return "ATOM_INFO";
        case KIND::FUNCTION_STARTS: return "FUNCTION_STARTS";
        case KIND::DATA_IN_CODE: return "DATA_IN_CODE";
        case KIND::CODE_SIGNATURE: return "CODE_SIGNATURE";
        case KIND::DYLD_EXPORT_TRIE: return "DYLD_EXPORT_TRIE";
        case KIND::DYLD_CHAINED_FIXUPS: return "DYLD_CHAINED_FIXUPS";
      }
      return "UNKNOWN";
    }
    chunk_t() = delete;
    chunk_t(KIND kind, uint32_t align, uint32_t off, size_t size) :
      kind(kind),
      alignment(align),
      file_offset(off),
      size(size) {}
    chunk_t(const chunk_t&) = default;
    chunk_t& operator=(const chunk_t&) = default;

    chunk_t(chunk_t&&) = default;
    chunk_t& operator=(chunk_t&&) = default;

    ~chunk_t() = default;

    KIND kind = KIND::UNKNOWN;
    uint32_t alignment = 0;
    uint32_t file_offset = 0;
    size_t size = 0;
  };
  using chunks_t = std::vector<chunk_t>;

  chunks_t chunks;
  chunks.reserve(binary.commands().size());

  static const auto sort_chunks = [](chunks_t& chunks) {
    const size_t count = chunks.size();
    for (size_t i = 0; i < count; ++i) {
      bool done = true;
      for (size_t j = 0; j < count - i - 1; ++j) {
        if (chunks[j].file_offset > chunks[j + 1].file_offset) {
          std::swap(chunks[j], chunks[j + 1]);
          done = false;
        }
      }
      if (done) {
        break;
      }
    }
  };

  bool has_external_relocs = false;
  bool has_local_relocs = false;
  uint32_t sym_count = 0;
  uint32_t ind_sym_count = 0;
  bool has_ind_sym_tab = false;
  const size_t ptr_size = this->ptr_size();

  for (const LoadCommand& cmd : binary.commands()) {
    // LC_SYMTAB
    if (const auto* symtab = cmd.cast<SymbolCommand>()) {
      sym_count = symtab->numberof_symbols();
      if (sym_count > 0x10000000) {
        return error("LC_SYMTAB: symbol table too large ({})", sym_count);
      }
      chunks.emplace_back(chunk_t::KIND::SYMTAB, ptr_size, symtab->symbol_offset(),
                          symtab->symbol_table().size());

      if (symtab->strings_size() > 0) {
        chunks.emplace_back(chunk_t::KIND::SYMTAB_STR, 1, symtab->strings_offset(),
                            symtab->strings_size());
      }
    }

    // LC_DYSYMTAB
    if (const auto* dynsym = cmd.cast<DynamicSymbolCommand>()) {
      has_ind_sym_tab = true;
      has_external_relocs = dynsym->nb_external_relocations() != 0;
      has_local_relocs = dynsym->nb_local_relocations() != 0;
      if (size_t cnt = dynsym->nb_indirect_symbols(); cnt > 0x10000000) {
        return error("LC_DYSYMTAB: indirect symbol table too large ({})", cnt);
      }

      if (size_t idx = dynsym->idx_local_symbol(); idx != 0) {
        return error("LC_DYSYMTAB: indirect symbol table ilocalsym != 0");
      }

      if (dynsym->idx_external_define_symbol() != dynsym->nb_local_symbols()) {
        return error("LC_DYSYMTAB: indirect symbol table ilocalsym != 0");
      }

      if (dynsym->idx_external_define_symbol() != dynsym->nb_local_symbols()) {
        return error("LC_DYSYMTAB: indirect symbol table ilocalsym != 0");
      }

      if (dynsym->idx_undefined_symbol() != (dynsym->idx_external_define_symbol() +
                                             dynsym->nb_external_define_symbols()))
      {
        return error("LC_DYSYMTAB: indirect symbol table "
                     "iundefsym != iextdefsym+nextdefsym");
      }

      ind_sym_count =
          dynsym->idx_undefined_symbol() + dynsym->nb_undefined_symbols();

      if (dynsym->nb_local_relocations() != 0) {
        chunks.emplace_back(chunk_t::KIND::DYSYMTAB_LOC_REL, ptr_size,
                            dynsym->local_relocation_offset(),
                            dynsym->nb_local_relocations() *
                                sizeof(details::relocation_info));
      }
      if (dynsym->nb_external_relocations() != 0) {
        chunks.emplace_back(chunk_t::KIND::DYSYMTAB_EXT_REL, ptr_size,
                            dynsym->external_relocation_offset(),
                            dynsym->nb_external_relocations() *
                                sizeof(details::relocation_info));
      }
      if (dynsym->nb_indirect_symbols() != 0) {
        chunks.emplace_back(chunk_t::KIND::DYSYMTAB_INDIRECT_SYM, 4,
                            dynsym->indirect_symbol_offset(),
                            dynsym->nb_indirect_symbols() * 4);
      }
    }

    if (const auto* dyldinfo = cmd.cast<DyldInfo>()) {
      if (auto info = dyldinfo->rebase(); info.second != 0) {
        chunks.emplace_back(chunk_t::KIND::DYLD_INFO_REBASES, ptr_size, info.first,
                            info.second);
      }

      if (auto info = dyldinfo->bind(); info.second != 0) {
        chunks.emplace_back(chunk_t::KIND::DYLD_INFO_BIND, ptr_size, info.first,
                            info.second);
      }

      if (auto info = dyldinfo->weak_bind(); info.second != 0) {
        chunks.emplace_back(chunk_t::KIND::DYLD_INFO_WEAK_BIND, ptr_size,
                            info.first, info.second);
      }

      if (auto info = dyldinfo->lazy_bind(); info.second != 0) {
        chunks.emplace_back(chunk_t::KIND::DYLD_INFO_LAZY_BIND, ptr_size,
                            info.first, info.second);
      }

      if (auto info = dyldinfo->export_info(); info.second != 0) {
        chunks.emplace_back(chunk_t::KIND::DYLD_INFO_EXPORT, ptr_size, info.first,
                            info.second);
      }
    }

    if (const auto* split_info = cmd.cast<SegmentSplitInfo>()) {
      if (split_info->data_size() != 0) {
        chunks.emplace_back(chunk_t::KIND::SEGMENT_SPLIT_INFO, ptr_size,
                            split_info->data_offset(), split_info->data_size());
      }
    }

    if (const auto* info = cmd.cast<AtomInfo>()) {
      if (info->data_size() != 0) {
        chunks.emplace_back(chunk_t::KIND::ATOM_INFO, ptr_size,
                            info->data_offset(), info->data_size());
      }
    }

    if (const auto* lnk_cmd = cmd.cast<FunctionStarts>()) {
      if (lnk_cmd->data_size() != 0) {
        chunks.emplace_back(chunk_t::KIND::FUNCTION_STARTS, ptr_size,
                            lnk_cmd->data_offset(), lnk_cmd->data_size());
      }
    }

    if (const auto* lnk_cmd = cmd.cast<DataInCode>()) {
      if (lnk_cmd->data_size() != 0) {
        chunks.emplace_back(chunk_t::KIND::DATA_IN_CODE, ptr_size,
                            lnk_cmd->data_offset(), lnk_cmd->data_size());
      }
    }

    if (const auto* lnk_cmd = cmd.cast<CodeSignature>()) {
      if (lnk_cmd->data_size() != 0) {
        chunks.emplace_back(chunk_t::KIND::CODE_SIGNATURE, ptr_size,
                            lnk_cmd->data_offset(), lnk_cmd->data_size());
      }
    }

    if (const auto* lnk_cmd = cmd.cast<DyldExportsTrie>()) {
      if (lnk_cmd->data_size() != 0) {
        chunks.emplace_back(chunk_t::KIND::DYLD_EXPORT_TRIE, ptr_size,
                            lnk_cmd->data_offset(), lnk_cmd->data_size());
      }
    }

    if (const auto* lnk_cmd = cmd.cast<DyldChainedFixups>()) {
      if (lnk_cmd->data_size() != 0) {
        chunks.emplace_back(chunk_t::KIND::DYLD_CHAINED_FIXUPS, ptr_size,
                            lnk_cmd->data_offset(), lnk_cmd->data_size());
      }
    }
  }

  if (has_ind_sym_tab && sym_count != ind_sym_count) {
    return error(
        "symbol count from symbol table and dynamic symbol table differ."
    );
  }

  const bool has_dyld_info = binary.has(LoadCommand::TYPE::DYLD_INFO_ONLY);
  const bool has_dyld_chained_fixups =
      binary.has(LoadCommand::TYPE::DYLD_CHAINED_FIXUPS);

  if (has_dyld_info && has_dyld_chained_fixups) {
    return error("Contains LC_DYLD_INFO and LC_DYLD_CHAINED_FIXUPS.");
  }

  if (has_dyld_chained_fixups) {
    if (has_local_relocs) {
      return error("Contains LC_DYLD_CHAINED_FIXUPS and local relocations.");
    }

    if (has_external_relocs) {
      return error("Contains LC_DYLD_CHAINED_FIXUPS and external relocations.");
    }
  }

  uint64_t linkedit_file_offset_start = 0;
  uint64_t linkedit_file_offset_end = 0;

  if (binary.header().file_type() == Header::FILE_TYPE::OBJECT ||
      binary.header().file_type() == Header::FILE_TYPE::PRELOAD)
  {
    for (const Section& section : binary.sections()) {
      const bool is_zero_fill =
          section.type() == Section::TYPE::ZEROFILL ||
          section.type() == Section::TYPE::THREAD_LOCAL_ZEROFILL;
      if (is_zero_fill) {
        continue;
      }

      const uint64_t section_end = section.offset() + section.size();
      if (section_end > linkedit_file_offset_start) {
        linkedit_file_offset_start = section_end;
      }
      linkedit_file_offset_end = binary.original_size();
      if (linkedit_file_offset_start == 0) {
        if (const auto* cmd =
                binary.get(LoadCommand::TYPE::SYMTAB)->cast<SymbolCommand>())
        {
          linkedit_file_offset_start = cmd->symbol_offset();
        }
      }
    }
  } else {
    if (const SegmentCommand* segment = binary.get_segment("__LINKEDIT")) {
      linkedit_file_offset_start = segment->file_offset();
      linkedit_file_offset_end = segment->file_offset() + segment->file_size();
    }
    if (linkedit_file_offset_start == 0 || linkedit_file_offset_end == 0) {
      return error("bad or unknown fileoffset/size for LINKEDIT");
    }
  }

  if (chunks.empty()) {
    if (binary.header().file_type() == Header::FILE_TYPE::OBJECT) {
      return true;
    }
    return error("Missing __LINKEDIT data");
  }

  sort_chunks(chunks);

  uint64_t prev_end = linkedit_file_offset_start;
  const char* prev_name = "start of __LINKEDIT";
  for (const chunk_t& chunk : chunks) {
    if (chunk.file_offset < prev_end) {
      return error("LINKEDIT overlap of {} and {}", prev_name,
                   chunk_t::to_string(chunk.kind));
    }

    if (greater_than_add_or_overflow<uint64_t>(chunk.file_offset, chunk.size,
                                               linkedit_file_offset_end))
    {
      return error("LINKEDIT content '{}' extends beyond end of segment",
                   chunk_t::to_string(chunk.kind));
    }

    if (chunk.file_offset & (chunk.alignment - 1)) {
      // In the source code of dyld this is enforced only if the provided
      // policy asks for it, but we might want to enforce it by default
      return error("mis-aligned LINKEDIT content: {}",
                   chunk_t::to_string(chunk.kind));
    }

    prev_end = chunk.file_offset + chunk.size;
    prev_name = chunk_t::to_string(chunk.kind);
  }

  return true;
}


bool LayoutChecker::check_linkedit_end() {
  if (binary.header().file_type() == Header::FILE_TYPE::OBJECT) {
    return true;
  }

  const SegmentCommand* linkedit = binary.get_segment("__LINKEDIT");
  if (linkedit == nullptr) {
    return error("Missing __LINKEDIT segment");
  }

  if (linkedit->file_offset() + linkedit->file_size() != binary.original_size()) {
    return error("__LINKEDIT segment does not wrap the end of the binary");
  }
  return true;
}


bool LayoutChecker::check_section_contiguity() {
  if (binary.header().file_type() == Header::FILE_TYPE::OBJECT) {
    // Skip this check for object file
    return true;
  }

  // LIEF allocates space for new/extended sections between the last load command
  // and the first section in the first segment.
  // We do not want to change the distance between the end of the `__text`
  // section and start of `__DATA` segment, keeping `__text` section in a "fixed"
  // position. Due to the above, there might be a "reverse" alignment gap between
  // `__text` section and a section that was allocated in front of it.
  auto is_gap_reversed = [](const Section* LHS, const Section* RHS) {
    return align_down(RHS->offset() - LHS->size(), LHS->alignment());
  };

  for (const SegmentCommand& segment : binary.segments()) {
    std::vector<const Section*> sections_vec;
    auto sections = segment.sections();
    if (sections.empty()) {
      continue;
    }

    sections_vec.reserve(segment.sections().size());
    for (const Section& sec : sections) {
      const Section::TYPE ty = sec.type();
      if (ty == Section::TYPE::ZEROFILL ||
          ty == Section::TYPE::THREAD_LOCAL_ZEROFILL ||
          ty == Section::TYPE::GB_ZEROFILL)
      {
        continue;
      }
      sections_vec.push_back(&sec);
    }

    if (sections_vec.empty()) {
      return true;
    }

    std::sort(sections_vec.begin(), sections_vec.end(),
              [](const Section* LHS, const Section* RHS) {
                return LHS->offset() < RHS->offset();
              });

    uint64_t next_expected_offset = sections_vec[0]->offset();
    for (auto it = sections_vec.begin(); it != sections_vec.end(); ++it) {
      const Section* section = *it;
      const uint32_t alignment = 1 << section->alignment();
      if ((section->offset() % alignment) != 0) {
        return error("section '{}' offset ({:#06x}) is misaligned (align={:#06x})",
                     section->name(), section->virtual_address(), alignment);
      }

      next_expected_offset = align(next_expected_offset, alignment);

      if (section->offset() != next_expected_offset) {
        auto message =
            fmt::format("section '{}' is not at the expected offset: {:#06x} "
                        "(expected={:#06x}, align={:#06x})",
                        section->name(), section->offset(), next_expected_offset,
                        alignment);
        if (it != sections_vec.begin() && is_gap_reversed(*std::prev(it), *it) &&
            section->name() == "__text")
        {
          LIEF_WARN("Permitting section gap (possibly caused by "
                    "add_section/extend_section): {}",
                    message);
          next_expected_offset = section->offset();
        } else {
          return error(message);
        }
      }
      next_expected_offset += section->size();
    }
  }
  return true;
}

bool LayoutChecker::check() {
  if (binary.header().magic() == MACHO_TYPES::NEURAL_MODEL) {
    return true;
  }

  if (check_overlapping()) {
    return false;
  }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  // The following checks are only relevant for non-object files
  if (binary.header().file_type() == Header::FILE_TYPE::OBJECT) {
    return true;
  }

  const SegmentCommand* linkedit = binary.get_segment("__LINKEDIT");
  if (linkedit == nullptr) {
    return error("Missing __LINKEDIT segment");
  }

  const bool is64 = static_cast<const LIEF::Binary&>(binary).header().is_64();
  uint64_t offset = linkedit->file_offset();

  if (const DyldInfo* dyld_info = binary.dyld_info()) {
    if (dyld_info->rebase().first != 0) {
      if (dyld_info->rebase().first != offset) {
        return error(R"delim(
        __LINKEDIT does not start with LC_DYLD_INFO.rebase:
          Expecting offset: {:#x} while it is {:#x}
        )delim",
                     offset, dyld_info->rebase().first);
      }
    } else if (dyld_info->bind().first != 0) {
      if (dyld_info->bind().first != offset) {
        return error(R"delim(
        __LINKEDIT does not start with LC_DYLD_INFO.bind:
          Expecting offset: {:#x} while it is {:#x}
        )delim",
                     offset, dyld_info->bind().first);
      }
    } else if (dyld_info->export_info().first != 0) {
      if (dyld_info->export_info().first != offset &&
          dyld_info->weak_bind().first != 0 && dyld_info->lazy_bind().first != 0)
      {
        return error(R"delim(
                     LC_DYLD_INFO.exports out of place:
                     Expecting offset: {:#x} while it is {:#x}
                     )delim",
                     offset, dyld_info->export_info().first);
      }
    }

    // Update Offset to end of dyld_info->contents
    if (dyld_info->export_info().second != 0) {
      offset = dyld_info->export_info().first + dyld_info->export_info().second;
    } else if (dyld_info->lazy_bind().second != 0) {
      offset = dyld_info->lazy_bind().first + dyld_info->lazy_bind().second;
    } else if (dyld_info->weak_bind().second != 0) {
      offset = dyld_info->weak_bind().first + dyld_info->weak_bind().second;
    } else if (dyld_info->bind().second != 0) {
      offset = dyld_info->bind().first + dyld_info->bind().second;
    } else if (dyld_info->rebase().second != 0) {
      offset = dyld_info->rebase().first + dyld_info->rebase().second;
    }
  }


  if (const DyldChainedFixups* fixups = binary.dyld_chained_fixups()) {
    if (fixups->data_offset() != 0) {
      if (fixups->data_offset() != offset) {
        return error(R"delim(
        __LINKEDIT does not start with LC_DYLD_CHAINED_FIXUPS:
          Expecting offset: {:#x} while it is {:#x}
        )delim",
                     offset, fixups->data_offset());
      }
      offset += fixups->data_size();
    }
  }

  if (const DyldExportsTrie* exports = binary.dyld_exports_trie()) {
    if (exports->data_offset() != 0) {
      if (exports->data_offset() != offset) {
        return error(R"delim(
        LC_DYLD_EXPORTS_TRIE out of place in __LINKEDIT:
          Expecting offset: {:#x} while it is {:#x}
        )delim",
                     offset, exports->data_offset());
      }
    }
    offset += exports->data_size();
  }

  if (const FunctionVariants* variants = binary.function_variants()) {
    if (variants->data_offset() != 0) {
      if (variants->data_offset() != offset) {
        return error(R"delim(
        LC_FUNCTION_VARIANTS out of place in __LINKEDIT:
          Expecting offset: {:#x} while it is {:#x}
        )delim",
                     offset, variants->data_offset());
      }
    }
    offset += variants->data_size();
  }

  if (const FunctionVariantFixups* fixups = binary.function_variant_fixups()) {
    if (fixups->data_offset() != 0) {
      if (fixups->data_offset() != offset) {
        return error(R"delim(
        LC_FUNCTION_VARIANT_FIXUPS out of place in __LINKEDIT:
          Expecting offset: {:#x} while it is {:#x}
        )delim",
                     offset, fixups->data_offset());
      }
    }
    offset += fixups->data_size();
  }

  const DynamicSymbolCommand* dyst = binary.dynamic_symbol_command();
  if (dyst == nullptr) {
    return error("LC_DYSYMTAB not found");
  }

  if (dyst->nb_local_relocations() != 0) {
    if (dyst->local_relocation_offset() != offset) {
      return error(R"delim(
      LC_DYSYMTAB local relocations out of place:
        Expecting offset: {:#x} while it is {:#x}
      )delim",
                   offset, dyst->local_relocation_offset());
    }
    offset += dyst->nb_local_relocations() * sizeof(details::relocation_info);
  }

  // Check consistency of Segment Split Info command
  if (const SegmentSplitInfo* spi = binary.segment_split_info()) {
    if (spi->data_offset() != 0 && spi->data_offset() != offset) {
      return error(R"delim(
      LC_SEGMENT_SPLIT_INFO out of place:
        Expecting offset: {:#x} while it is {:#x}
      )delim",
                   offset, spi->data_offset());
    }
    offset += spi->data_size();
  }

  for (const LoadCommand& cmd : binary.commands()) {
    const auto* lazy = cmd.cast<LazyLoadDylibInfo>();
    if (lazy == nullptr) {
      continue;
    }
    if (lazy->data_offset() != 0) {
      if (lazy->data_offset() != offset) {
        return error(R"delim(
        LC_LAZY_LOAD_DYLIB_INFO out of place in __LINKEDIT:
          Expecting offset: {:#x} while it is {:#x}
        )delim",
                     offset, lazy->data_offset());
      }
    }
    offset += lazy->data_size();
  }

  // Check consistency of Function starts
  if (const FunctionStarts* fs = binary.function_starts()) {
    if (fs->data_offset() != 0 && fs->data_offset() != offset) {
      return error(R"delim(
      LC_FUNCTION_STARTS out of place:
        Expecting offset: {:#x} while it is {:#x}
      )delim",
                   offset, fs->data_offset());
    }
    offset += fs->data_size();
  }

  // Check consistency of Data in Code
  if (const DataInCode* dic = binary.data_in_code()) {
    if (dic->data_offset() != 0 && dic->data_offset() != offset) {
      return error(R"delim(
      LC_DATA_IN_CODE out of place:
        Expecting offset: {:#x} while it is {:#x}
      )delim",
                   offset, dic->data_offset());
    }
    offset += dic->data_size();
  }

  if (const AtomInfo* info = binary.atom_info()) {
    if (info->data_offset() != 0 && info->data_offset() != offset) {
      return error(R"delim(
      LC_ATOM_INFO out of place:
        Expecting offset: {:#x} while it is {:#x}
      )delim",
                   offset, info->data_offset());
    }
    offset += info->data_size();
  }

  if (const CodeSignatureDir* cs = binary.code_signature_dir()) {
    if (cs->data_offset() != 0 && cs->data_offset() != offset) {
      return error(R"delim(
      LC_DYLIB_CODE_SIGN_DRS out of place:
        Expecting offset: {:#x} while it is {:#x}
      )delim",
                   offset, cs->data_offset());
    }
    offset += cs->data_size();
  }

  if (const LinkerOptHint* opt = binary.linker_opt_hint()) {
    if (opt->data_offset() != 0 && opt->data_offset() != offset) {
      return error(R"delim(
      LC_LINKER_OPTIMIZATION_HINT out of place:
        Expecting offset: {:#x} while it is {:#x}
      )delim",
                   offset, opt->data_offset());
    }
    offset += opt->data_size();
  }

  const SymbolCommand* st = binary.symbol_command();
  if (st == nullptr) {
    return error("LC_SYMTAB not found!");
  }

  if (st->numberof_symbols() != 0) {
    // Check offset
    if (st->symbol_offset() != offset) {
      return error(R"delim(
      LC_SYMTAB.nlist out of place:
        Expecting offset: {:#x} while it is {:#x}
      )delim",
                   offset, st->symbol_offset());
    }
    offset += st->numberof_symbols() *
              (is64 ? sizeof(details::nlist_64) : sizeof(details::nlist_32));
  }

  size_t isym = 0;

  if (dyst->nb_local_symbols() != 0) {
    if (isym != dyst->idx_local_symbol()) {
      return error(R"delim(
      LC_DYSYMTAB.nlocalsym out of place:
        Expecting index: {} while it is {}
      )delim",
                   isym, dyst->idx_local_symbol());
    }
    isym += dyst->nb_local_symbols();
  }


  if (dyst->nb_external_define_symbols() != 0) {
    if (isym != dyst->idx_external_define_symbol()) {
      return error(R"delim(
      LC_DYSYMTAB.iextdefsym out of place:
        Expecting index: {} while it is {}
      )delim",
                   isym, dyst->idx_external_define_symbol());
    }
    isym += dyst->nb_external_define_symbols();
  }

  if (dyst->nb_undefined_symbols() != 0) {
    if (isym != dyst->idx_undefined_symbol()) {
      return error(R"delim(
      LC_DYSYMTAB.nundefsym out of place:
        Expecting index: {} while it is {}
      )delim",
                   isym, dyst->idx_undefined_symbol());
    }
    isym += dyst->nb_undefined_symbols();
  }


  if (const TwoLevelHints* two = binary.two_level_hints()) {
    if (two->offset() != 0 && two->offset() != offset) {
      return error(R"delim(
      LC_TWOLEVEL_HINTS out of place:
        Expecting offset: {:#x} while it is {:#x}
      )delim",
                   offset, two->offset());
    }
    offset += two->hints().size() * sizeof(details::twolevel_hint);
  }


  if (dyst->nb_external_relocations() != 0) {
    if (dyst->external_relocation_offset() != offset) {
      return error(R"delim(
      LC_DYSYMTAB.extrel out of place:
        Expecting offset: {:#x} while it is {:#x}
      )delim",
                   offset, dyst->external_relocation_offset());
    }

    offset += dyst->nb_external_relocations() * sizeof(details::relocation_info);
  }


  if (dyst->nb_indirect_symbols() != 0) {
    if (dyst->indirect_symbol_offset() != offset) {
      return error(R"delim(
      LC_DYSYMTAB.nindirect out of place:
        Expecting offset: {:#x} while it is {:#x}
      )delim",
                   offset, dyst->indirect_symbol_offset());
    }

    offset += dyst->nb_indirect_symbols() * sizeof(uint32_t);
  }

  uint64_t rounded_offset = offset;
  uint64_t input_indirectsym_pad = 0;
  if (is64 && (dyst->nb_indirect_symbols() % 2) != 0) {
    rounded_offset = rnd(offset, 8);
  }

  if (dyst->toc_offset() != 0) {
    if (dyst->toc_offset() != offset && dyst->toc_offset() != rounded_offset) {
      return error(R"delim(
      LC_DYSYMTAB.toc out of place:
        Expecting offsets: {:#x} or {:#x} while it is {:#x}
      )delim",
                   offset, rounded_offset, dyst->toc_offset());
    }
    if (dyst->toc_offset() == offset) {
      offset += dyst->nb_toc() * sizeof(details::dylib_table_of_contents);
      rounded_offset = offset;
    } else if (dyst->toc_offset() == rounded_offset) {
      input_indirectsym_pad = rounded_offset - offset;

      rounded_offset += dyst->nb_toc() * sizeof(details::dylib_table_of_contents);
      offset = rounded_offset;
    }
  }


  if (dyst->nb_module_table() != 0) {
    if (dyst->module_table_offset() != offset &&
        dyst->module_table_offset() != rounded_offset)
    {
      return error(R"delim(
      LC_DYSYMTAB.modtab out of place:
        Expecting offsets: {:#x} or {:#x} while it is {:#x}
      )delim",
                   offset, rounded_offset, dyst->module_table_offset());
    }

    if (is64) {
      if (dyst->module_table_offset() == offset) {
        offset += dyst->nb_module_table() * sizeof(details::dylib_module_64);
        rounded_offset = offset;
      } else if (dyst->module_table_offset() == rounded_offset) {
        input_indirectsym_pad = rounded_offset - offset;
        rounded_offset +=
            dyst->nb_module_table() * sizeof(details::dylib_module_64);
        offset = rounded_offset;
      }
    } else {
      offset += dyst->nb_module_table() * sizeof(details::dylib_module_32);
      rounded_offset = offset;
    }
  }


  if (dyst->nb_external_reference_symbols() != 0) {
    if (dyst->external_reference_symbol_offset() != offset &&
        dyst->external_reference_symbol_offset() != rounded_offset)
    {
      return error(R"delim(
      LC_DYSYMTAB.extrefsym out of place:
        Expecting offsets: {:#x} or {:#x} while it is {:#x}
      )delim",
                   offset, rounded_offset,
                   dyst->external_reference_symbol_offset());
    }

    if (dyst->external_reference_symbol_offset() == offset) {
      offset +=
          dyst->nb_external_reference_symbols() * sizeof(details::dylib_reference);
      rounded_offset = offset;
    } else if (dyst->external_reference_symbol_offset() == rounded_offset) {
      input_indirectsym_pad = rounded_offset - offset;
      rounded_offset +=
          dyst->nb_external_reference_symbols() * sizeof(details::dylib_reference);
      offset = rounded_offset;
    }
  }


  if (st->strings_size() != 0) {
    if (st->strings_offset() != offset && st->strings_offset() != rounded_offset) {
      return error(R"delim(
      LC_SYMTAB.strings out of place:
        Expecting offsets: {:#x} or {:#x} while it is {:#x}
      )delim",
                   offset, rounded_offset, st->strings_offset());
    }


    if (st->strings_offset() == offset) {
      offset += st->strings_size();
      rounded_offset = offset;
    } else if (st->strings_offset() == rounded_offset) {
      input_indirectsym_pad = rounded_offset - offset;
      rounded_offset += st->strings_size();
      offset = rounded_offset;
    }
  }

  if (const CodeSignature* cs = binary.code_signature()) {
    rounded_offset = align(rounded_offset, 16);
    if (cs->data_offset() != rounded_offset) {
      return error(R"delim(
      LC_CODE_SIGNATURE out of place:
        Expecting offsets: {:#x} while it is {:#x}
      )delim",
                   offset, cs->data_offset());
    }
    rounded_offset += cs->data_size();
    offset = rounded_offset;
  }

  LIEF_DEBUG("input_indirectsym_pad: {:x}", input_indirectsym_pad);
  const uint64_t object_size = linkedit->file_offset() + linkedit->file_size();
  if (offset != object_size && rounded_offset != object_size) {
    return error(R"delim(
    __LINKEDIT.end ({:#x}) does not match {:#x} nor {:#x}
    )delim",
                 object_size, offset, rounded_offset);
  }
  return true;
}

bool LayoutChecker::check_chained_fixups() {
  static constexpr auto DYLD_CHAINED_PTR_START_NONE = 0xFFFF;
  static constexpr auto DYLD_CHAINED_PTR_START_MULTI = 0x8000;
  const DyldChainedFixups* fixups = binary.dyld_chained_fixups();
  if (fixups == nullptr) {
    return true;
  }

  if (fixups->fixups_version() != 0) {
    return error("chained fixups, unknown header version ({})",
                 fixups->fixups_version());
  }

  if (fixups->starts_offset() >= fixups->data_size()) {
    return error(
        "chained fixups, starts_offset exceeds LC_DYLD_CHAINED_FIXUPS size"
    );
  }

  if (fixups->imports_offset() >= fixups->data_size()) {
    return error(
        "chained fixups, starts_offset exceeds LC_DYLD_CHAINED_FIXUPS size"
    );
  }

  uint32_t fmt_entry_size = 0;
  switch (fixups->imports_format()) {
    case DYLD_CHAINED_FORMAT::IMPORT:
      fmt_entry_size = sizeof(details::dyld_chained_import);
      break;

    case DYLD_CHAINED_FORMAT::IMPORT_ADDEND:
      fmt_entry_size = sizeof(details::dyld_chained_import_addend);
      break;

    case DYLD_CHAINED_FORMAT::IMPORT_ADDEND64:
      fmt_entry_size = sizeof(details::dyld_chained_import_addend64);
      break;
    default:
      return error("chained fixups, unknown imports_format: {}",
                   (int)fixups->imports_format());
  }

  if (greater_than_add_or_overflow<uint32_t>(fixups->imports_offset(),
                                             fmt_entry_size *
                                                 fixups->imports_count(),
                                             fixups->symbols_offset()))
  {
    return error("chained fixups, imports array overlaps symbols");
  }

  if (fixups->symbols_format() != 0) {
    return error("chained fixups, symbols_format unknown ({})",
                 fixups->symbols_format());
  }

  const auto starts_info = fixups->chained_starts_in_segments();
  const size_t nb_segments = binary.segments().size();
  const SegmentCommand& last_seg = binary.segments()[nb_segments - 1];

  if (starts_info.size() != nb_segments) {
    if (starts_info.size() > binary.segments().size()) {
      return error("chained fixups, seg_count exceeds number of segments");
    }
    if (last_seg.name() != "__CTF") {
      return error("chained fixups, seg_count does not match number of segments");
    }
  }
  const uint64_t end_of_start = fixups->imports_offset();
  uint32_t max_valid_pointer_seen = 0;
  size_t idx = 0;
  result<DYLD_CHAINED_PTR_FORMAT> ptr_format_for_all =
      make_error_code(lief_errors::not_found);

  for (const DyldChainedFixups::chained_starts_in_segment& seg_start : starts_info)
  {
    if (seg_start.offset == 0) {
      continue;
    }

    if (seg_start.size > end_of_start - seg_start.offset) {
      return error("chained fixups, dyld_chained_starts_in_segment for segment "
                   "#{} overruns imports table",
                   idx);
    }

    if (seg_start.page_size != 0x1000 && seg_start.page_size != 0x4000) {
      return error("chained fixups, page_size not 4KB or 16KB in segment "
                   "#{} (page_size: {:#06x})",
                   idx, seg_start.page_size);
    }

    if ((int)seg_start.pointer_format >
        (int)DYLD_CHAINED_PTR_FORMAT::PTR_ARM64E_SHARED_CACHE)
    {
      return error("chained fixups, unknown pointer_format in segment #{}", idx);
    }

    if (!ptr_format_for_all) {
      ptr_format_for_all = seg_start.pointer_format;
    }

    if (seg_start.pointer_format != ptr_format_for_all) {
      return error(
          "chained fixups, pointer_format not same for all segments {} and {}",
          to_string(seg_start.pointer_format), to_string(*ptr_format_for_all)
      );
    }

    if (seg_start.max_valid_pointer != 0) {
      if (max_valid_pointer_seen == 0) {
        max_valid_pointer_seen = seg_start.max_valid_pointer;
      } else if (max_valid_pointer_seen != seg_start.max_valid_pointer) {
        return error("chained fixups, different max_valid_pointer values seen in "
                     "different segments");
      }
    }
    if (seg_start.page_start.size() * sizeof(uint16_t) > seg_start.size) {
      return error("chained fixups, page_start array overflows size");
    }

    [[maybe_unused]] uint32_t max_overflow_idx =
        (seg_start.size - sizeof(details::dyld_chained_starts_in_segment)) /
        sizeof(uint16_t);

    for (size_t page_idx = 0; page_idx < seg_start.page_count(); ++page_idx) {
      uint16_t offset_in_page = seg_start.page_start[page_idx];
      if (offset_in_page == DYLD_CHAINED_PTR_START_NONE) {
        continue;
      }

      if ((offset_in_page & DYLD_CHAINED_PTR_START_MULTI) == 0) {
        if (offset_in_page > seg_start.page_size) {
          return error("chained fixups, in segment #{} page_start[{}]={:#06x} "
                       "exceeds page size",
                       idx, page_idx, offset_in_page);
        }
      } else {
        // TODO(romain): to implement
      }
    }
  }

  return true;
}

bool LayoutChecker::check_export(uint64_t imagebase, uint64_t max_vm_offset,
                                 const ExportInfo& info) {
  // skip other exports than regular and thread-local
  if (info.has(ExportInfo::FLAGS::REEXPORT) ||
      info.kind() == ExportInfo::KIND::ABSOLUTE_KIND)
  {
    return true;
  }

  uint64_t vm_offset = info.address();
  if ((int64_t)vm_offset < 0) {
    // Segments with an address lower than __TEXT
    vm_offset = imagebase - vm_offset;
  }

  if (vm_offset > max_vm_offset) {
    return error("vmOffset too large for {}",
                 info.has_symbol() ? info.symbol()->name() : "<empty>");
  }
  return true;
}


bool LayoutChecker::check_exports_trie() {
  const DyldInfo* dyld_info = binary.dyld_info();
  const DyldExportsTrie* exports_trie = binary.dyld_exports_trie();

  if (dyld_info == nullptr && exports_trie == nullptr) {
    return true;
  }

  // maxVmOffset is computed in Image::validLinkedit as the highest
  // `runtimeOffset + runtimeSize` over all the segments except __LINKEDIT, with a
  // lowest value of 0x4000. __PAGEZERO is *not* excluded.
  uint64_t max_vm_offset = 0x4000;
  uint64_t text_vmaddr = 0;
  for (const SegmentCommand& seg : binary.segments()) {
    if (seg.name() == "__TEXT") {
      text_vmaddr = seg.virtual_address();
    }
    if (seg.name() == "__LINKEDIT") {
      continue;
    }
    const uint64_t runtime_offset = seg.virtual_address() - text_vmaddr;
    max_vm_offset =
        std::max<uint64_t>(max_vm_offset, runtime_offset + seg.virtual_size());
  }

  const uint64_t imagebase = binary.imagebase();

  if (exports_trie != nullptr) {
    for (const ExportInfo& info : exports_trie->exports()) {
      if (!check_export(imagebase, max_vm_offset, info)) {
        return false;
      }
    }
  }

  if (dyld_info != nullptr) {
    for (const ExportInfo& info : dyld_info->exports()) {
      if (!check_export(imagebase, max_vm_offset, info)) {
        return false;
      }
    }
  }

  return true;
}


bool LayoutChecker::check_function_variants() {
  using KIND = FunctionVariants::RuntimeTable::KIND;
  using RuntimeTableEntry = FunctionVariants::RuntimeTableEntry;
  const FunctionVariants* variants = binary.function_variants();
  if (variants == nullptr) {
    return true;
  }

  const FunctionStarts* func_starts = binary.function_starts();
  const uint64_t imagebase = binary.imagebase();

  // Check that the impl for RuntimeTableEntry target a function that is
  // listed in LC_FUNCTION_STARTS. This is pretty conservative and dyld could relax
  // this condition but this can be used to identify malformed modifications.
  std::unordered_set<uint64_t> starts;
  if (func_starts != nullptr) {
    const std::vector<uint64_t>& fn = func_starts->functions();
    starts.insert(fn.begin(), fn.end());
  }

  for (const FunctionVariants::RuntimeTable& entry : variants->runtime_table()) {
    switch (entry.kind()) {
      case KIND::ARM64:
      case KIND::PER_PROCESS:
      case KIND::SYSTEM_WIDE:
      case KIND::X86_64: break;
      default:
        return error("unknown FunctionVariants::RuntimeTable::KIND ({})",
                     (uint32_t)entry.kind());
    }

    // Check that the last entry is "default"
    auto entries = entry.entries();
    if (entries.empty()) {
      return error(
          "Missing entries in FunctionVariants::RuntimeTable (offset={:#08x})",
          entry.offset()
      );
    }

    const RuntimeTableEntry& last = entries[entries.size() - 1];
    if (last.flag_bit_nums()[0] != 0) {
      return error(
          "last entry in FunctionVariants::RuntimeTable entries is not 'default'"
      );
    }

    if (func_starts == nullptr) {
      continue;
    }

    for (const RuntimeTableEntry& impl_entry : entries) {
      if (impl_entry.another_table()) {
        continue;
      }

      const uint64_t va = imagebase + impl_entry.impl();
      if (binary.segment_from_virtual_address(va) == nullptr) {
        // tolerated
        continue;
      }

      if (starts.find(impl_entry.impl()) == starts.end()) {
        return error(
            "FunctionVariants impl offset {:#x} (va: {:#x}) does not match any "
            "LC_FUNCTION_STARTS entry",
            impl_entry.impl(), va
        );
      }
    }
  }
  return true;
}

bool LayoutChecker::check_function_variant_fixups() {
  const FunctionVariantFixups* fixups = binary.function_variant_fixups();
  if (fixups == nullptr) {
    return true;
  }

  // The payload is a bare array of fixed-size entries: a trailing partial
  // entry would be silently dropped by dyld (it does size / sizeof(entry)).
  if (fixups->data_size() % sizeof(details::function_variant_fixup_t) != 0) {
    return error(
        "LC_FUNCTION_VARIANT_FIXUPS payload size ({:#x}) is not a multiple of "
        "the fixup entry size ({})",
        fixups->data_size(), sizeof(details::function_variant_fixup_t)
    );
  }

  auto segments = binary.segments();
  const size_t nb_segments = segments.size();

  // variant_index selects a FunctionVariants runtime table. dyld resolves a
  // fixup as `image.segment(segIndex) + segOffset` pointing at table
  // `#variantIndex`, so both indices must be in range.
  const FunctionVariants* variants = binary.function_variants();

  const size_t nb_tables =
      variants == nullptr ? 0 : variants->runtime_table().size();

  size_t idx = 0;
  for (const FunctionVariantFixups::Fixup& fixup : fixups->fixups()) {
    if (fixup.seg_index() >= nb_segments) {
      return error(
          "LC_FUNCTION_VARIANT_FIXUPS fixup #{} references segment #{} but the "
          "binary only has {} segment(s)",
          idx, fixup.seg_index(), nb_segments
      );
    }

    const SegmentCommand& segment = segments[fixup.seg_index()];
    if (fixup.seg_offset() + ptr_size() > segment.virtual_size()) {
      return error(
          "LC_FUNCTION_VARIANT_FIXUPS fixup #{} points outside of segment '{}' "
          "(seg_offset={:#x}, vmsize={:#x})",
          idx, segment.name(), fixup.seg_offset(), segment.virtual_size()
      );
    }

    if (fixup.variant_index() >= nb_tables) {
      return error(
          "LC_FUNCTION_VARIANT_FIXUPS fixup #{} references function-variant "
          "table #{} but the binary defines {} table(s)",
          idx, fixup.variant_index(), nb_tables
      );
    }
    ++idx;
  }

  return true;
}


bool LayoutChecker::check_lazy_load_dylib_infos() {
  // clang-format off
  //
  // NOTE(romain): this is a 1x1 copy of the definition in dyld (mach_o/LazyLoadDylib.h)
  struct LazyLoadDylibLinkEdit
  {
      uint32_t    loadPathOffset;          // path of dylib to load
      uint32_t    flagImageOffset;         // image offset to flags global
      uint16_t    flags;                   // weak linked or not
      uint16_t    pointerFormat;           // e.g. DYLD_CHAINED_PTR_ARM64E_USERLAND
      uint32_t    chainStartImageOffset;   // image offset to fixup chain start
      uint32_t    symbolsCount;            // how many symbol names to bind
      uint32_t    symbolStringArrayOffset; // offset of each symbol name within blob
      // room for future fields here
      // path string
      // symbols strings
  };
  static constexpr size_t HEADER_SIZE = sizeof(LazyLoadDylibLinkEdit);
  static_assert(HEADER_SIZE == 24);
  // clang-format on

  // 0x4000 according to Image::validStructureLinkedit
  uint64_t max_vm_offset = 0x4000;
  const uint64_t imagebase = binary.imagebase();
  for (const SegmentCommand& seg : binary.segments()) {
    if (seg.name() == "__LINKEDIT" || seg.name() == "__PAGEZERO") {
      continue;
    }
    const uint64_t runtime_off =
        seg.virtual_address() >= imagebase ? seg.virtual_address() - imagebase : 0;
    max_vm_offset =
        std::max<uint64_t>(max_vm_offset, runtime_off + seg.virtual_size());
  }

  for (const LoadCommand& cmd : binary.commands()) {
    const auto* lazy = cmd.cast<LazyLoadDylibInfo>();
    if (lazy == nullptr) {
      continue;
    }

    span<const uint8_t> content = lazy->content();
    if (content.size() < HEADER_SIZE) {
      return error("LC_LAZY_LOAD_DYLIB_INFO payload size ({}) is too small "
                   "(minimum is {})",
                   content.size(), HEADER_SIZE);
    }

    SpanStream stream(content);
    auto header = stream.peek<LazyLoadDylibLinkEdit>();
    if (!header) {
      return error("LC_LAZY_LOAD_DYLIB_INFO failed to read the header");
    }

    // sanity check loadPathOffset and its string terminator
    if (header->loadPathOffset > content.size()) {
      return error("LC_LAZY_LOAD_DYLIB_INFO loadPathOffset ({}) out of range "
                   "(max {})",
                   header->loadPathOffset, content.size());
    }

    const auto path_start = content.begin() + header->loadPathOffset;
    if (std::find(path_start, content.end(), '\0') == content.end()) {
      return error("LC_LAZY_LOAD_DYLIB_INFO load path is not null terminated");
    }

    // Ensure that `flagImageOffset` targets a valid segment.
    // NOTE(romain): should we check for RW- permission?
    if (header->flagImageOffset > max_vm_offset) {
      return error("LC_LAZY_LOAD_DYLIB_INFO flagImageOffset ({:#x}) beyond max "
                   "vm offset ({:#x})",
                   header->flagImageOffset, max_vm_offset);
    }

    if (header->chainStartImageOffset > max_vm_offset) {
      return error("LC_LAZY_LOAD_DYLIB_INFO chainStartImageOffset ({:#x}) beyond "
                   "max vm offset ({:#x})",
                   header->chainStartImageOffset, max_vm_offset);
    }

    // sanity check the symbol string-offset array bounds
    const bool overflow = greater_than_add_or_overflow<uint64_t>(
        header->symbolStringArrayOffset,
        (uint64_t)header->symbolsCount * sizeof(uint32_t), content.size()
    );
    if (overflow) {
      return error("LC_LAZY_LOAD_DYLIB_INFO symbolsCount ({}) too large to fit "
                   "in payload (size {})",
                   header->symbolsCount, content.size());
    }

    // sanity check each symbol string offset
    for (uint32_t i = 0; i < header->symbolsCount; ++i) {
      const uint64_t pos =
          header->symbolStringArrayOffset + (uint64_t)i * sizeof(uint32_t);
      auto sym_off = stream.peek<uint32_t>(pos);
      if (!sym_off || *sym_off > content.size()) {
        return error("LC_LAZY_LOAD_DYLIB_INFO symbolStringOffsets[{}] ({}) out "
                     "of range (max {})",
                     i, sym_off ? *sym_off : 0, content.size());
      }
    }
  }

  return true;
}


bool LayoutChecker::check_tls() {
  const bool is32 = ptr_size() == sizeof(uint32_t);
  const bool is64 = ptr_size() == sizeof(uint64_t);
  assert(is32 || is64);
  const ThreadLocalVariables* sec_vars = nullptr;
  const Binary::range_t initial_content = binary.tlv_initial_content_range();
  for (const Section& S : binary.sections()) {
    if (const auto* sec = S.cast<ThreadLocalVariables>()) {
      sec_vars = sec;
    }

    // See ThreadLocalVariables.cpp
    const size_t sizeof_ = 3 * ptr_size();

    if (sec_vars != nullptr) {
      const size_t size = sec_vars->content().size();
      if (size % sizeof_ != 0) {
        return error(
            "size ({}) of thread-locals section {} is not a multiple of {}", size,
            sec_vars->name(), sizeof_
        );
      }
    }
  }

  if (is64 && initial_content.size() > 4_GB) {
    return error("unsupported thread-local, larger than 4GB");
  }

  if (is32 && initial_content.size() > 4_GB) {
    return error("unsupported thread-local, larger than 4GB");
  }

  if (sec_vars != nullptr) {
    for (const ThreadLocalVariables::Thunk& thunk : sec_vars->thunks()) {
      if (thunk.offset > initial_content.size()) {
        return error("malformed thread-local, offset={:#08x} is larger than total "
                     "size={:#08x}",
                     thunk.offset, initial_content.size());
      }
      if (is64 && thunk.key > std::numeric_limits<uint32_t>::max()) {
        return error("thread_key_t {}, larger than uint32_t", thunk.key);
      }

      if (is32 && thunk.key > std::numeric_limits<uint16_t>::max()) {
        return error("thread_key_t {}, larger than uint16_t", thunk.key);
      }
    }
  }

  return true;
}

bool check_layout(const FatBinary& fat, std::string* error) {
  bool is_ok = true;
  for (Binary& bin : fat) {
    LayoutChecker checker(bin);
    if (!checker.check()) {
      is_ok = false;
      if (error) {
        *error += checker.get_error() + '\n';
      }
    }
  }
  return is_ok;
}


bool check_layout(const Binary& binary, std::string* error) {
  LayoutChecker checker(binary);
  if (!checker.check()) {
    if (error) {
      *error += checker.get_error() + '\n';
    }
    return false;
  }
  return true;
}


}