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 "logging.hpp"
#include "LIEF/errors.hpp"

#include "LIEF/MachO/AtomInfo.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/FunctionStarts.hpp"
#include "LIEF/MachO/FunctionVariants.hpp"
#include "LIEF/MachO/FunctionVariantFixups.hpp"
#include "LIEF/MachO/LazyLoadDylibInfo.hpp"
#include "LIEF/MachO/LinkEdit.hpp"
#include "LIEF/MachO/LinkerOptHint.hpp"
#include "LIEF/MachO/SegmentSplitInfo.hpp"
#include "LIEF/MachO/SymbolCommand.hpp"
#include "LIEF/MachO/TwoLevelHints.hpp"
#include "LIEF/MachO/SegmentCommand.hpp"


namespace LIEF::MachO {

/* The DyldInfo object has span fields (rebase_opcodes_, ...) that point to segment
 * data. When resizing the ``SegmentCommand.data_`` we can break this span as the
 * internal buffer of ``data_`` might be relocated.
 *
 * The following helpers keep an internally consistent state of the data
 */

inline ok_error_t update_span(span<uint8_t>& sp, uintptr_t original_data_addr,
                              uintptr_t original_data_end,
                              std::vector<uint8_t>& new_data) {
  auto span_data_addr = reinterpret_cast<uintptr_t>(sp.data());
  const bool is_encompassed =
      original_data_addr <= span_data_addr && span_data_addr < original_data_end;
  if (!is_encompassed) {
    return ok();
  }

  const uintptr_t original_size = original_data_end - original_data_addr;
  /*
   * Resize of the container without relocating
   */
  if (new_data.data() == sp.data() && new_data.size() >= original_size) {
    return ok();
  }

  const uintptr_t delta = span_data_addr - original_data_addr;
  const bool fit_in_data =
      delta < new_data.size() && (delta + original_size) <= new_data.size();
  if (!fit_in_data) {
    sp = {new_data.data(), static_cast<size_t>(0)};
    return make_error_code(lief_errors::corrupted);
  }

  sp = {new_data.data() + delta, sp.size()};
  return ok();
}

/// @param[in] offset    Offset where the insertion took place
/// @param[in] size      Size of the inserted data
inline ok_error_t update_span(span<uint8_t>& sp, uintptr_t original_data_addr,
                              uintptr_t original_data_end, size_t offset,
                              size_t size, std::vector<uint8_t>& new_data) {
  auto span_data_addr = reinterpret_cast<uintptr_t>(sp.data());
  const bool is_encompassed =
      original_data_addr <= span_data_addr && span_data_addr < original_data_end;
  if (!is_encompassed) {
    // No need to re-span
    return ok();
  }
  // Original relative offset of the span
  const uintptr_t original_rel_offset = span_data_addr - original_data_addr;
  uintptr_t delta_offset = 0;

  // If the insertion took place BEFORE our span,
  // we need to append the insertion size in the new span
  if (offset <= original_rel_offset) {
    delta_offset = size;
  }

  const bool fit_in_data =
      (original_rel_offset + delta_offset) < new_data.size() &&
      (original_rel_offset + delta_offset + sp.size()) < new_data.size();
  if (!fit_in_data) {
    sp = {new_data.data(), static_cast<size_t>(0)};
    return make_error_code(lief_errors::corrupted);
  }

  sp = {new_data.data() + original_rel_offset + delta_offset, sp.size()};
  return ok();
}

LinkEdit& LinkEdit::operator=(LinkEdit other) {
  swap(other);
  return *this;
}

void LinkEdit::swap(LinkEdit& other) noexcept {
  SegmentCommand::swap(other);
  std::swap(dyld_, other.dyld_);
  std::swap(chained_fixups_, other.chained_fixups_);
}

void LinkEdit::update_data(const update_fnc_t& f) {
  const auto original_data_addr = reinterpret_cast<uintptr_t>(data_.data());
  const auto original_data_size = static_cast<size_t>(data_.size());
  const uintptr_t original_data_end = original_data_addr + original_data_size;
  f(data_);
  if (dyld_ != nullptr) {
    if (!update_span(dyld_->rebase_opcodes_, original_data_addr, original_data_end,
                     data_))
    {
      LIEF_WARN("Failed to re-span rebase opcodes in segment {}", name_);
    }
    if (!update_span(dyld_->bind_opcodes_, original_data_addr, original_data_end,
                     data_))
    {
      LIEF_WARN("Failed to re-span bind opcodes in segment {}", name_);
    }
    if (!update_span(dyld_->weak_bind_opcodes_, original_data_addr,
                     original_data_end, data_))
    {
      LIEF_WARN("Failed to re-span weak bind opcodes in segment {}", name_);
    }
    if (!update_span(dyld_->lazy_bind_opcodes_, original_data_addr,
                     original_data_end, data_))
    {
      LIEF_WARN("Failed to re-span lazy bind opcodes in segment {}", name_);
    }
    if (!update_span(dyld_->export_trie_, original_data_addr, original_data_end,
                     data_))
    {
      LIEF_WARN("Failed to re-span the export trie in segment {}", name_);
    }
  }

  if (chained_fixups_ != nullptr) {
    if (!update_span(chained_fixups_->content_, original_data_addr,
                     original_data_end, data_))
    {
      LIEF_WARN("Failed to re-span the Dyld Chained fixups in segment {}", name_);
    }
  }

  if (exports_trie_ != nullptr) {
    if (!update_span(exports_trie_->content_, original_data_addr,
                     original_data_end, data_))
    {
      LIEF_WARN("Failed to re-span the Dyld Exports Trie in segment {}", name_);
    }
  }

  if (symtab_ != nullptr) {
    if (!update_span(symtab_->symbol_table_, original_data_addr, original_data_end,
                     data_))
    {
      LIEF_WARN("Failed to re-span the LC_SYMTAB.n_list in segment {}", name_);
    }
    if (!update_span(symtab_->string_table_, original_data_addr, original_data_end,
                     data_))
    {
      LIEF_WARN("Failed to re-span the LC_SYMTAB.string_table in segment {}",
                name_);
    }
  }

  if (fstarts_ != nullptr) {
    if (!update_span(fstarts_->content_, original_data_addr, original_data_end,
                     data_))
    {
      LIEF_WARN("Failed to re-span the LC_FUNCTION_STARTS in segment {}", name_);
    }
  }

  if (data_code_ != nullptr) {
    if (!update_span(data_code_->content_, original_data_addr, original_data_end,
                     data_))
    {
      LIEF_WARN("Failed to re-span the LC_DATA_IN_CODE in segment {}", name_);
    }
  }

  if (seg_split_ != nullptr) {
    if (!update_span(seg_split_->content_, original_data_addr, original_data_end,
                     data_))
    {
      LIEF_WARN("Failed to re-span the LC_SEGMENT_SPLIT_INFO in segment {}",
                name_);
    }
  }

  if (two_lvl_hint_ != nullptr) {
    if (!update_span(two_lvl_hint_->content_, original_data_addr,
                     original_data_end, data_))
    {
      LIEF_WARN("Failed to re-span the LC_TWOLEVEL_HINTS in segment {}", name_);
    }
  }

  if (linker_opt_ != nullptr) {
    if (!update_span(linker_opt_->content_, original_data_addr, original_data_end,
                     data_))
    {
      LIEF_WARN("Failed to re-span the LC_LINKER_OPTIMIZATION_HINT in segment {}",
                name_);
    }
  }

  if (code_sig_ != nullptr) {
    if (!update_span(code_sig_->content_, original_data_addr, original_data_end,
                     data_))
    {
      LIEF_WARN("Failed to re-span the LC_CODE_SIGNATURE in segment {}", name_);
    }
  }

  if (code_sig_dir_ != nullptr) {
    if (!update_span(code_sig_dir_->content_, original_data_addr,
                     original_data_end, data_))
    {
      LIEF_WARN("Failed to re-span the LC_DYLIB_CODE_SIGN_DRS in segment {}",
                name_);
    }
  }

  if (atom_info_ != nullptr) {
    if (!update_span(atom_info_->content_, original_data_addr, original_data_end,
                     data_))
    {
      LIEF_WARN("Failed to re-span the LC_ATOM_INFO in segment {}", name_);
    }
  }

  if (func_variants_ != nullptr) {
    if (!update_span(func_variants_->content_, original_data_addr,
                     original_data_end, data_))
    {
      LIEF_WARN("Failed to re-span the LC_FUNCTION_VARIANTS in segment {}", name_);
    }
  }

  if (func_variant_fixups_ != nullptr) {
    if (!update_span(func_variant_fixups_->content_, original_data_addr,
                     original_data_end, data_))
    {
      LIEF_WARN("Failed to re-span the LC_FUNCTION_VARIANT_FIXUPS in segment {}",
                name_);
    }
  }

  for (LazyLoadDylibInfo* lazy : lazy_load_dylibs_) {
    if (!update_span(lazy->content_, original_data_addr, original_data_end, data_))
    {
      LIEF_WARN("Failed to re-span the LC_LAZY_LOAD_DYLIB_INFO in segment {}",
                name_);
    }
  }
}

void LinkEdit::update_data(const update_fnc_ws_t& f, size_t where, size_t size) {
  const auto original_data_addr = reinterpret_cast<uintptr_t>(data_.data());
  const auto original_data_size = static_cast<size_t>(data_.size());
  const uintptr_t original_data_end = original_data_addr + original_data_size;
  f(data_, where, size);
  if (dyld_ != nullptr) {
    if (!update_span(dyld_->rebase_opcodes_, original_data_addr, original_data_end,
                     where, size, data_))
    {
      LIEF_WARN("Failed to re-span rebase opcodes in segment {}", name_);
    }
    if (!update_span(dyld_->bind_opcodes_, original_data_addr, original_data_end,
                     where, size, data_))
    {
      LIEF_WARN("Failed to re-span bind opcodes in segment {}", name_);
    }
    if (!update_span(dyld_->weak_bind_opcodes_, original_data_addr,
                     original_data_end, where, size, data_))
    {
      LIEF_WARN("Failed to re-span weak bind opcodes in segment {}", name_);
    }
    if (!update_span(dyld_->lazy_bind_opcodes_, original_data_addr,
                     original_data_end, where, size, data_))
    {
      LIEF_WARN("Failed to re-span lazy bind opcodes in segment {}", name_);
    }
    if (!update_span(dyld_->export_trie_, original_data_addr, original_data_end,
                     where, size, data_))
    {
      LIEF_WARN("Failed to re-span the export trie in segment {}", name_);
    }
  }

  if (chained_fixups_ != nullptr) {
    if (!update_span(chained_fixups_->content_, original_data_addr,
                     original_data_end, where, size, data_))
    {
      LIEF_WARN("Failed to re-span the Dyld Chained fixups in segment {}", name_);
    }
  }

  if (exports_trie_ != nullptr) {
    if (!update_span(exports_trie_->content_, original_data_addr,
                     original_data_end, where, size, data_))
    {
      LIEF_WARN("Failed to re-span the Dyld Exports Trie in segment {}", name_);
    }
  }

  if (symtab_ != nullptr) {
    if (!update_span(symtab_->symbol_table_, original_data_addr, original_data_end,
                     where, size, data_))
    {
      LIEF_WARN("Failed to re-span the LC_SYMTAB.n_list in segment {}", name_);
    }
    if (!update_span(symtab_->string_table_, original_data_addr, original_data_end,
                     where, size, data_))
    {
      LIEF_WARN("Failed to re-span the LC_SYMTAB.string_table in segment {}",
                name_);
    }
  }

  if (fstarts_ != nullptr) {
    if (!update_span(fstarts_->content_, original_data_addr, original_data_end,
                     where, size, data_))
    {
      LIEF_WARN("Failed to re-span the LC_FUNCTION_STARTS in segment {}", name_);
    }
  }

  if (data_code_ != nullptr) {
    if (!update_span(data_code_->content_, original_data_addr, original_data_end,
                     where, size, data_))
    {
      LIEF_WARN("Failed to re-span the LC_DATA_IN_CODE in segment {}", name_);
    }
  }

  if (seg_split_ != nullptr) {
    if (!update_span(seg_split_->content_, original_data_addr, original_data_end,
                     where, size, data_))
    {
      LIEF_WARN("Failed to re-span the LC_SEGMENT_SPLIT_INFO in segment {}",
                name_);
    }
  }

  if (two_lvl_hint_ != nullptr) {
    if (!update_span(two_lvl_hint_->content_, original_data_addr,
                     original_data_end, where, size, data_))
    {
      LIEF_WARN("Failed to re-span the LC_TWOLEVEL_HINTS in segment {}", name_);
    }
  }

  if (linker_opt_ != nullptr) {
    if (!update_span(linker_opt_->content_, original_data_addr, original_data_end,
                     where, size, data_))
    {
      LIEF_WARN("Failed to re-span the LC_LINKER_OPTIMIZATION_HINT in segment {}",
                name_);
    }
  }

  if (code_sig_ != nullptr) {
    if (!update_span(code_sig_->content_, original_data_addr, original_data_end,
                     where, size, data_))
    {
      LIEF_WARN("Failed to re-span the LC_CODE_SIGNATURE in segment {}", name_);
    }
  }

  if (code_sig_dir_ != nullptr) {
    if (!update_span(code_sig_dir_->content_, original_data_addr,
                     original_data_end, data_))
    {
      LIEF_WARN("Failed to re-span the LC_DYLIB_CODE_SIGN_DRS in segment {}",
                name_);
    }
  }

  if (atom_info_ != nullptr) {
    if (!update_span(atom_info_->content_, original_data_addr, original_data_end,
                     data_))
    {
      LIEF_WARN("Failed to re-span the LC_ATOM_INFO in segment {}", name_);
    }
  }

  if (func_variants_ != nullptr) {
    if (!update_span(func_variants_->content_, original_data_addr,
                     original_data_end, data_))
    {
      LIEF_WARN("Failed to re-span the LC_FUNCTION_VARIANTS in segment {}", name_);
    }
  }

  if (func_variant_fixups_ != nullptr) {
    if (!update_span(func_variant_fixups_->content_, original_data_addr,
                     original_data_end, data_))
    {
      LIEF_WARN("Failed to re-span the LC_FUNCTION_VARIANT_FIXUPS in segment {}",
                name_);
    }
  }

  for (LazyLoadDylibInfo* lazy : lazy_load_dylibs_) {
    if (!update_span(lazy->content_, original_data_addr, original_data_end, data_))
    {
      LIEF_WARN("Failed to re-span the LC_LAZY_LOAD_DYLIB_INFO in segment {}",
                name_);
    }
  }
}

}