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

#include "logging.hpp"
#include "BinaryParser.tcc"

#include "LIEF/BinaryStream/VectorStream.hpp"

#include "LIEF/MachO/BinaryParser.hpp"
#include "LIEF/MachO/utils.hpp"
#include "LIEF/MachO/SegmentCommand.hpp"
#include "LIEF/MachO/Symbol.hpp"
#include "LIEF/MachO/ExportInfo.hpp"
#include "LIEF/MachO/DyldExportsTrie.hpp"

#include "internal_utils.hpp"


namespace LIEF::MachO {

BinaryParser::BinaryParser() = default;
BinaryParser::~BinaryParser() = default;


std::unique_ptr<Binary> BinaryParser::parse(const std::string& file) {
  return parse(file, ParserConfig::deep());
}

std::unique_ptr<Binary> BinaryParser::parse(const std::string& file,
                                            const ParserConfig& conf) {
  if (!is_macho(file)) {
    LIEF_DEBUG("{} is not a Mach-O file", file);
    return nullptr;
  }

  if (!is_fat(file)) {
    LIEF_ERR("{} is a Fat Mach-O file. Please use MachO::Parser::parse(...)",
             file);
    return nullptr;
  }

  auto stream = VectorStream::from_file(file);
  if (!stream) {
    LIEF_ERR("Failed to create binary stream");
    return nullptr;
  }

  BinaryParser parser;
  parser.config_ = conf;
  parser.stream_ = std::make_unique<VectorStream>(std::move(*stream));
  parser.binary_ = std::unique_ptr<Binary>(new Binary{});
  parser.binary_->fat_offset_ = 0;

  if (!parser.init_and_parse()) {
    LIEF_WARN("Parsing errors detected; binary may be inconsistent");
  }

  return std::move(parser.binary_);
}

std::unique_ptr<Binary> BinaryParser::parse(const std::vector<uint8_t>& data,
                                            const ParserConfig& conf) {
  return parse(data, 0, conf);
}

std::unique_ptr<Binary> BinaryParser::parse(const std::vector<uint8_t>& data,
                                            uint64_t fat_offset,
                                            const ParserConfig& conf) {
  if (!is_macho(data)) {
    return nullptr;
  }

  // TODO(romain): To implement
  // if (!is_fat(data)) {
  //   LIEF_ERR("{} is a Fat Mach-O file. Please use MachO::Parser::parse(...)");
  //   return nullptr;
  // }

  BinaryParser parser;
  parser.config_ = conf;
  parser.stream_ = std::make_unique<VectorStream>(data);
  parser.binary_ = std::unique_ptr<Binary>(new Binary{});
  parser.binary_->fat_offset_ = fat_offset;

  if (!parser.init_and_parse()) {
    LIEF_WARN("Parsing errors detected; binary may be inconsistent");
  }

  return std::move(parser.binary_);
}


std::unique_ptr<Binary> BinaryParser::parse(std::unique_ptr<BinaryStream> stream,
                                            uint64_t fat_offset,
                                            const ParserConfig& conf) {
  BinaryParser parser;
  parser.config_ = conf;
  parser.stream_ = std::move(stream);
  parser.binary_ = std::unique_ptr<Binary>(new Binary{});
  parser.binary_->fat_offset_ = fat_offset;

  if (!parser.init_and_parse()) {
    LIEF_WARN("Parsing errors detected; binary may be inconsistent");
  }

  return std::move(parser.binary_);
}


ok_error_t BinaryParser::init_and_parse() {
  LIEF_DEBUG("Parsing MachO");
  if (!stream_->can_read<uint32_t>()) {
    return make_error_code(lief_errors::read_error);
  }
  const auto type = static_cast<MACHO_TYPES>(*stream_->peek<uint32_t>());

  is64_ = type == MACHO_TYPES::MAGIC_64 || type == MACHO_TYPES::CIGAM_64 ||
          type == MACHO_TYPES::NEURAL_MODEL;

  binary_->is64_ = is64_;
  type_ = type;
  binary_->original_size_ = stream_->size();

  bool should_swap = type == MACHO_TYPES::CIGAM_64 || type == MACHO_TYPES::CIGAM;

  stream_->set_endian_swap(should_swap);

  return is64_ ? parse<details::MachO64>() : parse<details::MachO32>();
}


result<BinaryParser::exports_list_t>
    BinaryParser::parse_export_trie(BinaryStream& stream, uint64_t start,
                                    const std::string& prefix, uint32_t depth,
                                    bool* invalid_names) {
  static constexpr auto MAX_DEPTH = 60;
  exports_list_t exports;

  if (depth > MAX_DEPTH) {
    LIEF_WARN("Maximum recursion depth reached ({})", MAX_DEPTH);
    return make_error_code(lief_errors::parsing_error);
  }

  if (!stream) {
    return make_error_code(lief_errors::read_error);
  }

  // Pre-populate the symbol cache to avoid O(n) searches for each export
  if (memoized_symbols_.empty()) {
    for (const std::unique_ptr<LIEF::MachO::Symbol>& sym : binary_->symbols_) {
      if (const std::string& name = sym->name(); !name.empty()) {
        memoized_symbols_[name] = sym.get();
      }
    }
  }

  const auto terminal_size = stream.read<uint8_t>();
  if (!terminal_size) {
    LIEF_ERR("Failed to read terminal size");
    return make_error_code(lief_errors::read_error);
  }
  uint64_t children_offset = stream.pos() + *terminal_size;

  if (*terminal_size != 0) {
    uint64_t offset = stream.pos();

    auto res_flags = stream.read_uleb128();
    if (!res_flags) {
      return make_error_code(lief_errors::read_error);
    }
    uint64_t flags = *res_flags;
    // uint64_t address = stream_->read_uleb128();

    const std::string& symbol_name = prefix;
    auto export_info = std::make_unique<ExportInfo>(0, flags, offset);
    Symbol* symbol = nullptr;
    if (auto search = memoized_symbols_.find(symbol_name);
        search != memoized_symbols_.end())
    {
      symbol = search->second;
    } else {
      LIEF_DEBUG("Cache miss for symbol: {}", symbol_name);
      symbol = nullptr;
    }
    if (symbol != nullptr) {
      export_info->symbol_ = symbol;
      symbol->export_info_ = export_info.get();
    } else { // Register it into the symbol table
      auto symbol = std::make_unique<Symbol>();

      symbol->origin_ = Symbol::ORIGIN::DYLD_EXPORT;
      symbol->value_ = 0;
      symbol->type_ = 0;
      symbol->numberof_sections_ = 0;
      symbol->description_ = 0;
      symbol->name(symbol_name);

      // Weak bind of the pointer
      symbol->export_info_ = export_info.get();
      export_info->symbol_ = symbol.get();
      memoized_symbols_[symbol_name] = symbol.get();
      binary_->symbols_.push_back(std::move(symbol));
    }

    // REEXPORT
    // ========
    if (export_info->has(ExportInfo::FLAGS::REEXPORT)) {
      auto res_ordinal = stream.read_uleb128();
      if (!res_ordinal) {
        LIEF_ERR("Failed to read uleb128 ordinal value");
        return make_error_code(lief_errors::parsing_error);
      }
      const uint64_t ordinal = *res_ordinal;
      export_info->other_ = ordinal;

      auto res_imported_name = stream.peek_string();
      if (!res_imported_name) {
        LIEF_ERR("Failed to read imported_name");
        return make_error_code(lief_errors::parsing_error);
      }

      std::string imported_name = std::move(*res_imported_name);

      if (imported_name.empty() && export_info->has_symbol()) {
        imported_name = export_info->symbol()->name();
      }

      Symbol* symbol = nullptr;
      if (auto search = memoized_symbols_.find(imported_name);
          search != memoized_symbols_.end())
      {
        symbol = search->second;
      } else {
        LIEF_DEBUG("Cache miss for symbol: {}", imported_name);
        symbol = nullptr;
      }
      if (symbol != nullptr) {
        export_info->alias_ = symbol;
        symbol->export_info_ = export_info.get();
        symbol->value_ = export_info->address();
      } else {
        auto symbol = std::make_unique<Symbol>();
        symbol->origin_ = Symbol::ORIGIN::DYLD_EXPORT;
        symbol->value_ = export_info->address();
        symbol->type_ = 0;
        symbol->numberof_sections_ = 0;
        symbol->description_ = 0;
        symbol->name(symbol_name);

        // Weak bind of the pointer
        symbol->export_info_ = export_info.get();
        export_info->alias_ = symbol.get();
        memoized_symbols_[symbol_name] = symbol.get();
        binary_->symbols_.push_back(std::move(symbol));
      }


      if (ordinal < binary_->libraries().size()) {
        DylibCommand& lib = binary_->libraries()[ordinal];
        export_info->alias_location_ = &lib;
      } else {
        LIEF_WARN("Library ordinal out of range");
      }
    } else {
      auto address = stream.read_uleb128();
      if (!address) {
        LIEF_ERR("Failed to read export address");
        return make_error_code(lief_errors::parsing_error);
      }
      export_info->address(*address);
    }

    // STUB_AND_RESOLVER
    // =================
    if (export_info->has(ExportInfo::FLAGS::STUB_AND_RESOLVER)) {
      auto other = stream.read_uleb128();
      if (!other) {
        LIEF_ERR("Failed to read 'other' value for export info");
        return make_error_code(lief_errors::parsing_error);
      }
      export_info->other_ = *other;
    }

    exports.push_back(std::move(export_info));
  }
  stream.setpos(children_offset);
  const auto nb_children = stream.read<uint8_t>();
  if (!nb_children) {
    LIEF_ERR("Failed to read nb_children");
    return make_error_code(lief_errors::parsing_error);
  }

  for (size_t i = 0; i < *nb_children; ++i) {
    auto suffix = stream.read_string();
    if (!suffix) {
      LIEF_ERR("Failed to read suffix");
      break;
    }
    std::string name = prefix + std::move(*suffix);

    if (!is_printable(name)) {
      if (!*invalid_names) {
        LIEF_WARN("Export trie contains non-printable symbols");
        *invalid_names = true;
      }
    }

    auto res_child_node_offet = stream.read_uleb128();
    if (!res_child_node_offet) {
      LIEF_ERR("Failed to read child_node_offset");
      break;
    }
    auto child_node_offet = static_cast<uint32_t>(*res_child_node_offet);

    if (child_node_offet == 0) {
      break;
    }

    if (!visited_.insert(child_node_offet).second) {
      LIEF_DEBUG("Cycle detected in export trie at offset {:#x}",
                 child_node_offet);
      break;
    }

    {
      ScopedStream scoped(stream, child_node_offet);
      if (auto res =
              parse_export_trie(*scoped, start, name, depth + 1, invalid_names))
      {
        exports.insert(exports.end(), std::make_move_iterator(res->begin()),
                       std::make_move_iterator(res->end()));
      }
    }
  }

  return exports;
}

ok_error_t BinaryParser::parse_dyld_exports() {
  DyldExportsTrie* exports = binary_->dyld_exports_trie();
  if (exports == nullptr) {
    LIEF_ERR("Missing LC_DYLD_EXPORTS_TRIE in main binary");
    return make_error_code(lief_errors::not_found);
  }

  uint32_t offset = exports->data_offset();
  uint32_t size = exports->data_size();

  if (offset == 0 || size == 0) {
    return ok();
  }

  SegmentCommand* linkedit = binary_->segment_from_offset(offset);

  if (linkedit == nullptr) {
    linkedit = binary_->get_segment("__LINKEDIT");
  }

  if (linkedit == nullptr) {
    LIEF_WARN("Segment containing export trie not found");
    return make_error_code(lief_errors::not_found);
  }

  span<uint8_t> content = linkedit->content();
  const uint64_t rel_offset = offset - linkedit->file_offset();
  if (rel_offset > content.size() || (rel_offset + size) > content.size()) {
    LIEF_ERR("Export trie out of bounds for segment {}", linkedit->name());
    return make_error_code(lief_errors::read_out_of_bound);
  }

  exports->content_ = content.subspan(rel_offset, size);
  SpanStream trie_stream(exports->content_);

  bool invalid_names = false;
  if (auto res = parse_export_trie(trie_stream, offset, "",
                                   /*depth=*/0, &invalid_names))
  {
    exports->export_info_ = std::move(*res);
  }
  return ok();
}

ok_error_t BinaryParser::parse_dyldinfo_export() {
  LIEF_DEBUG("[+] LC_DYLD_INFO.exports");
  DyldInfo* dyldinfo = binary_->dyld_info();
  if (dyldinfo == nullptr) {
    LIEF_ERR("Missing DyldInfo in main binary");
    return make_error_code(lief_errors::not_found);
  }

  uint32_t offset = std::get<0>(dyldinfo->export_info());
  uint32_t size = std::get<1>(dyldinfo->export_info());

  if (offset == 0 || size == 0) {
    return ok();
  }

  SegmentCommand* linkedit = binary_->segment_from_offset(offset);

  if (linkedit == nullptr) {
    linkedit = binary_->get_segment("__LINKEDIT");
  }

  if (linkedit == nullptr) {
    LIEF_WARN("Segment containing export trie not found");
    return make_error_code(lief_errors::not_found);
  }

  span<uint8_t> content = linkedit->content();
  const uint64_t rel_offset = offset - linkedit->file_offset();
  if (rel_offset > content.size() || (rel_offset + size) > content.size()) {
    LIEF_ERR("Export trie out of bounds for segment {}", linkedit->name());
    return make_error_code(lief_errors::read_out_of_bound);
  }

  dyldinfo->export_trie_ = content.subspan(rel_offset, size);

  SpanStream trie_stream(dyldinfo->export_trie_);

  bool invalid_names = false;
  if (auto exports = parse_export_trie(trie_stream, offset, "",
                                       /*depth=*/0, &invalid_names))
  {
    dyldinfo->export_info_ = std::move(*exports);
  }
  return ok();
}


ok_error_t BinaryParser::parse_overlay() {
  const uint64_t last_offset = binary_->off_ranges().end;
  if (last_offset >= stream_->size()) {
    return ok();
  }

  const uint64_t overlay_size = stream_->size() - last_offset;
  LIEF_INFO("Overlay detected at {:#x} ({} bytes)", last_offset, overlay_size);
  if (!stream_->peek_data(binary_->overlay_, last_offset, overlay_size)) {
    LIEF_WARN("Failed to read overlay data");
    return make_error_code(lief_errors::read_error);
  }
  return ok();
}


ok_error_t BinaryParser::parse_indirect_symbols(DynamicSymbolCommand& cmd,
                                                std::vector<Symbol*>& symtab,
                                                BinaryStream& indirect_stream) {
  if (symtab.empty()) {
    LIEF_WARN("Empty symtab: cannot process indirect symbols");
    return make_error_code(lief_errors::corrupted);
  }

  for (size_t i = 0; i < cmd.nb_indirect_symbols(); ++i) {
    uint32_t index = 0;
    auto res = indirect_stream.read<uint32_t>();
    if (!res) {
      LIEF_ERR("Failed to read indirect symbol #{}", index);
      return make_error_code(lief_errors::read_error);
    }
    index = *res;

    if (index == details::INDIRECT_SYMBOL_ABS) {
      cmd.indirect_symbols_.push_back(
          const_cast<Symbol*>(&Symbol::indirect_abs())
      );
      continue;
    }

    if (index == details::INDIRECT_SYMBOL_LOCAL) {
      cmd.indirect_symbols_.push_back(
          const_cast<Symbol*>(&Symbol::indirect_local())
      );
      continue;
    }

    if (index == (details::INDIRECT_SYMBOL_LOCAL | details::INDIRECT_SYMBOL_ABS)) {
      cmd.indirect_symbols_.push_back(
          const_cast<Symbol*>(&Symbol::indirect_abs_local())
      );
      continue;
    }

    if (index >= symtab.size()) {
      LIEF_ERR("Indirect symbol index out of range ({}/{:#x}, max: {})", index,
               index, symtab.size());
      continue;
    }

    Symbol* indirect = symtab[index];
    LIEF_DEBUG("  indirectsyms[{}] = {}", index, indirect->name());
    cmd.indirect_symbols_.push_back(indirect);
  }
  LIEF_DEBUG("indirect_symbols_.size(): {} (nb_indirect_symbols: {})",
             cmd.indirect_symbols_.size(), cmd.nb_indirect_symbols());
  return ok();
}

} // namespace LIEF::MachO