chromium/chrome/common/safe_browsing/mach_o_image_reader_mac.cc

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/common/safe_browsing/mach_o_image_reader_mac.h"

#include <libkern/OSByteOrder.h>
#include <mach-o/fat.h>
#include <mach-o/loader.h>

#include <memory>

#include "base/check.h"
#include "base/memory/raw_ptr.h"
#include "base/numerics/safe_math.h"

namespace safe_browsing {

// ByteSlice is a bounds-checking view of an arbitrary byte array.
class ByteSlice {
 public:
  // Creates an invalid byte slice.
  ByteSlice() : ByteSlice(nullptr, 0) {}

  // Creates a slice for a given data array.
  explicit ByteSlice(const uint8_t* data, size_t size)
      : data_(data), size_(size) {}
  ~ByteSlice() {}

  bool IsValid() {
    return data_ != nullptr;
  }

  // Creates a sub-slice from the current slice.
  ByteSlice Slice(size_t at, size_t size) {
    if (!RangeCheck(at, size))
      return ByteSlice();
    return ByteSlice(data_ + at, size);
  }

  // Casts an offset to a specific type.
  template <typename T>
  const T* GetPointerAt(size_t at) {
    if (!RangeCheck(at, sizeof(T)))
      return nullptr;
    return reinterpret_cast<const T*>((data_ + at).get());
  }

  // Copies data from an offset to a buffer.
  bool CopyDataAt(size_t at, size_t size, uint8_t* out_data) {
    if (!RangeCheck(at, size))
      return false;
    memcpy(out_data, data_ + at, size);
    return true;
  }

  bool RangeCheck(size_t offset, size_t size) {
    if (offset >= size_)
      return false;
    base::CheckedNumeric<size_t> range(offset);
    range += size;
    if (!range.IsValid())
      return false;
    return range.ValueOrDie() <= size_;
  }

  const uint8_t* data() const { return data_; }
  size_t size() const { return size_; }

 private:
  raw_ptr<const uint8_t, AllowPtrArithmetic> data_;
  size_t size_;

  // Copy and assign allowed.
};

MachOImageReader::LoadCommand::LoadCommand() {}

MachOImageReader::LoadCommand::LoadCommand(const LoadCommand& other) = default;

MachOImageReader::LoadCommand::~LoadCommand() {}

// static
bool MachOImageReader::IsMachOMagicValue(uint32_t magic) {
  return magic == FAT_MAGIC   || magic == FAT_CIGAM    ||
         magic == MH_MAGIC    || magic == MH_CIGAM     ||
         magic == MH_MAGIC_64 || magic == MH_CIGAM_64;
}

MachOImageReader::MachOImageReader()
    : data_(),
      is_fat_(false),
      is_64_bit_(false),
      commands_() {
}

MachOImageReader::~MachOImageReader() {}

bool MachOImageReader::Initialize(const uint8_t* image, size_t image_size) {
  if (!image)
    return false;

  data_ = std::make_unique<ByteSlice>(image, image_size);

  const uint32_t* magic = data_->GetPointerAt<uint32_t>(0);
  if (!magic)
    return false;

  // Check if this is a fat file. Note that the fat_header and fat_arch
  // structs are always in big endian.
  is_fat_ = *magic == FAT_MAGIC || *magic == FAT_CIGAM;
  if (is_fat_) {
    const fat_header* header = data_->GetPointerAt<fat_header>(0);
    if (!header)
      return false;

    bool do_swap = header->magic == FAT_CIGAM;
    uint32_t nfat_arch = do_swap ? OSSwapInt32(header->nfat_arch)
                                 : header->nfat_arch;

    size_t offset = sizeof(*header);
    for (uint32_t i = 0; i < nfat_arch; ++i) {
      const fat_arch* arch = data_->GetPointerAt<fat_arch>(offset);
      if (!arch)
        return false;

      uint32_t arch_offset = do_swap ? OSSwapInt32(arch->offset) : arch->offset;
      uint32_t arch_size = do_swap ? OSSwapInt32(arch->size) : arch->size;

      // Cannot refer back to headers of previous arches to cause
      // recursive processing.
      if (arch_offset < offset)
        return false;

      ByteSlice slice = data_->Slice(arch_offset, arch_size);
      if (!slice.IsValid())
        return false;

      fat_images_.push_back(std::make_unique<MachOImageReader>());
      if (!fat_images_.back()->Initialize(slice.data(), slice.size()))
        return false;

      offset += sizeof(*arch);
    }

    return true;
  }

  bool do_swap = *magic == MH_CIGAM || *magic == MH_CIGAM_64;

  // Make sure this is a Mach-O file.
  is_64_bit_ = *magic == MH_MAGIC_64 || *magic == MH_CIGAM_64;
  if (!(is_64_bit_ || *magic == MH_MAGIC || do_swap))
    return false;

  // Read the full Mach-O image header.
  if (is_64_bit_) {
    if (!GetMachHeader64())
      return false;
  } else {
    if (!GetMachHeader())
      return false;
  }

  // Collect all the load commands for the binary.
  const size_t load_command_size = sizeof(load_command);
  size_t offset = is_64_bit_ ? sizeof(mach_header_64) : sizeof(mach_header);
  const uint32_t num_commands = do_swap ? OSSwapInt32(GetMachHeader()->ncmds)
                                        : GetMachHeader()->ncmds;
  commands_.resize(num_commands);
  for (uint32_t i = 0; i < num_commands; ++i) {
    LoadCommand* command = &commands_[i];

    command->data.resize(load_command_size);
    if (!data_->CopyDataAt(offset, load_command_size, &command->data[0])) {
      return false;
    }

    uint32_t cmdsize = do_swap ? OSSwapInt32(command->cmdsize())
                               : command->cmdsize();
    // If the load_command's reported size is smaller than the size of the base
    // struct, do not try to copy additional data (or resize to be smaller
    // than the base struct). This may not be valid Mach-O.
    if (cmdsize < load_command_size) {
      offset += load_command_size;
      continue;
    }

    command->data.resize(cmdsize);
    if (!data_->CopyDataAt(offset, cmdsize, &command->data[0])) {
      return false;
    }

    offset += cmdsize;
  }

  return true;
}

bool MachOImageReader::IsFat() {
  return is_fat_;
}

std::vector<MachOImageReader*> MachOImageReader::GetFatImages() {
  DCHECK(is_fat_);
  std::vector<MachOImageReader*> images;
  for (const auto& image : fat_images_)
    images.push_back(image.get());
  return images;
}

bool MachOImageReader::Is64Bit() {
  DCHECK(!is_fat_);
  return is_64_bit_;
}

const mach_header* MachOImageReader::GetMachHeader() {
  DCHECK(!is_fat_);
  return data_->GetPointerAt<mach_header>(0);
}

const mach_header_64* MachOImageReader::GetMachHeader64() {
  DCHECK(is_64_bit_);
  DCHECK(!is_fat_);
  return data_->GetPointerAt<mach_header_64>(0);
}

uint32_t MachOImageReader::GetFileType() {
  DCHECK(!is_fat_);
  return GetMachHeader()->filetype;
}

const std::vector<MachOImageReader::LoadCommand>&
MachOImageReader::GetLoadCommands() {
  DCHECK(!is_fat_);
  return commands_;
}

bool MachOImageReader::GetCodeSignatureInfo(std::vector<uint8_t>* info) {
  DCHECK(!is_fat_);
  DCHECK(info->empty());

  // Find the LC_CODE_SIGNATURE command and cast it to its linkedit format.
  const linkedit_data_command* lc_code_signature = nullptr;
  for (const auto& command : commands_) {
    if (command.cmd() == LC_CODE_SIGNATURE) {
      lc_code_signature = command.as_command<linkedit_data_command>();
      break;
    }
  }
  if (lc_code_signature == nullptr)
    return false;

  info->resize(lc_code_signature->datasize);
  return data_->CopyDataAt(lc_code_signature->dataoff,
                           lc_code_signature->datasize,
                           &(*info)[0]);
}

}  // namespace safe_browsing