1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/common/safe_browsing/mach_o_image_reader_mac.h"
6 
7 #include <libkern/OSByteOrder.h>
8 #include <mach-o/fat.h>
9 #include <mach-o/loader.h>
10 
11 #include "base/check.h"
12 #include "base/numerics/safe_math.h"
13 
14 namespace safe_browsing {
15 
16 // ByteSlice is a bounds-checking view of an arbitrary byte array.
17 class ByteSlice {
18  public:
19   // Creates an invalid byte slice.
ByteSlice()20   ByteSlice() : ByteSlice(nullptr, 0) {}
21 
22   // Creates a slice for a given data array.
ByteSlice(const uint8_t * data,size_t size)23   explicit ByteSlice(const uint8_t* data, size_t size)
24       : data_(data), size_(size) {}
~ByteSlice()25   ~ByteSlice() {}
26 
IsValid()27   bool IsValid() {
28     return data_ != nullptr;
29   }
30 
31   // Creates a sub-slice from the current slice.
Slice(size_t at,size_t size)32   ByteSlice Slice(size_t at, size_t size) {
33     if (!RangeCheck(at, size))
34       return ByteSlice();
35     return ByteSlice(data_ + at, size);
36   }
37 
38   // Casts an offset to a specific type.
39   template <typename T>
GetPointerAt(size_t at)40   const T* GetPointerAt(size_t at) {
41     if (!RangeCheck(at, sizeof(T)))
42       return nullptr;
43     return reinterpret_cast<const T*>(data_ + at);
44   }
45 
46   // Copies data from an offset to a buffer.
CopyDataAt(size_t at,size_t size,uint8_t * out_data)47   bool CopyDataAt(size_t at, size_t size, uint8_t* out_data) {
48     if (!RangeCheck(at, size))
49       return false;
50     memcpy(out_data, data_ + at, size);
51     return true;
52   }
53 
RangeCheck(size_t offset,size_t size)54   bool RangeCheck(size_t offset, size_t size) {
55     if (offset >= size_)
56       return false;
57     base::CheckedNumeric<size_t> range(offset);
58     range += size;
59     if (!range.IsValid())
60       return false;
61     return range.ValueOrDie() <= size_;
62   }
63 
data() const64   const uint8_t* data() const { return data_; }
size() const65   size_t size() const { return size_; }
66 
67  private:
68   const uint8_t* data_;
69   size_t size_;
70 
71   // Copy and assign allowed.
72 };
73 
LoadCommand()74 MachOImageReader::LoadCommand::LoadCommand() {}
75 
76 MachOImageReader::LoadCommand::LoadCommand(const LoadCommand& other) = default;
77 
~LoadCommand()78 MachOImageReader::LoadCommand::~LoadCommand() {}
79 
80 // static
IsMachOMagicValue(uint32_t magic)81 bool MachOImageReader::IsMachOMagicValue(uint32_t magic) {
82   return magic == FAT_MAGIC   || magic == FAT_CIGAM    ||
83          magic == MH_MAGIC    || magic == MH_CIGAM     ||
84          magic == MH_MAGIC_64 || magic == MH_CIGAM_64;
85 }
86 
MachOImageReader()87 MachOImageReader::MachOImageReader()
88     : data_(),
89       is_fat_(false),
90       is_64_bit_(false),
91       commands_() {
92 }
93 
~MachOImageReader()94 MachOImageReader::~MachOImageReader() {}
95 
Initialize(const uint8_t * image,size_t image_size)96 bool MachOImageReader::Initialize(const uint8_t* image, size_t image_size) {
97   if (!image)
98     return false;
99 
100   data_.reset(new ByteSlice(image, image_size));
101 
102   const uint32_t* magic = data_->GetPointerAt<uint32_t>(0);
103   if (!magic)
104     return false;
105 
106   // Check if this is a fat file. Note that the fat_header and fat_arch
107   // structs are always in big endian.
108   is_fat_ = *magic == FAT_MAGIC || *magic == FAT_CIGAM;
109   if (is_fat_) {
110     const fat_header* header = data_->GetPointerAt<fat_header>(0);
111     if (!header)
112       return false;
113 
114     bool do_swap = header->magic == FAT_CIGAM;
115     uint32_t nfat_arch = do_swap ? OSSwapInt32(header->nfat_arch)
116                                  : header->nfat_arch;
117 
118     size_t offset = sizeof(*header);
119     for (uint32_t i = 0; i < nfat_arch; ++i) {
120       const fat_arch* arch = data_->GetPointerAt<fat_arch>(offset);
121       if (!arch)
122         return false;
123 
124       uint32_t arch_offset = do_swap ? OSSwapInt32(arch->offset) : arch->offset;
125       uint32_t arch_size = do_swap ? OSSwapInt32(arch->size) : arch->size;
126 
127       // Cannot refer back to headers of previous arches to cause
128       // recursive processing.
129       if (arch_offset < offset)
130         return false;
131 
132       ByteSlice slice = data_->Slice(arch_offset, arch_size);
133       if (!slice.IsValid())
134         return false;
135 
136       fat_images_.push_back(std::make_unique<MachOImageReader>());
137       if (!fat_images_.back()->Initialize(slice.data(), slice.size()))
138         return false;
139 
140       offset += sizeof(*arch);
141     }
142 
143     return true;
144   }
145 
146   bool do_swap = *magic == MH_CIGAM || *magic == MH_CIGAM_64;
147 
148   // Make sure this is a Mach-O file.
149   is_64_bit_ = *magic == MH_MAGIC_64 || *magic == MH_CIGAM_64;
150   if (!(is_64_bit_ || *magic == MH_MAGIC || do_swap))
151     return false;
152 
153   // Read the full Mach-O image header.
154   if (is_64_bit_) {
155     if (!GetMachHeader64())
156       return false;
157   } else {
158     if (!GetMachHeader())
159       return false;
160   }
161 
162   // Collect all the load commands for the binary.
163   const size_t load_command_size = sizeof(load_command);
164   size_t offset = is_64_bit_ ? sizeof(mach_header_64) : sizeof(mach_header);
165   const uint32_t num_commands = do_swap ? OSSwapInt32(GetMachHeader()->ncmds)
166                                         : GetMachHeader()->ncmds;
167   commands_.resize(num_commands);
168   for (uint32_t i = 0; i < num_commands; ++i) {
169     LoadCommand* command = &commands_[i];
170 
171     command->data.resize(load_command_size);
172     if (!data_->CopyDataAt(offset, load_command_size, &command->data[0])) {
173       return false;
174     }
175 
176     uint32_t cmdsize = do_swap ? OSSwapInt32(command->cmdsize())
177                                : command->cmdsize();
178     // If the load_command's reported size is smaller than the size of the base
179     // struct, do not try to copy additional data (or resize to be smaller
180     // than the base struct). This may not be valid Mach-O.
181     if (cmdsize < load_command_size) {
182       offset += load_command_size;
183       continue;
184     }
185 
186     command->data.resize(cmdsize);
187     if (!data_->CopyDataAt(offset, cmdsize, &command->data[0])) {
188       return false;
189     }
190 
191     offset += cmdsize;
192   }
193 
194   return true;
195 }
196 
IsFat()197 bool MachOImageReader::IsFat() {
198   return is_fat_;
199 }
200 
GetFatImages()201 std::vector<MachOImageReader*> MachOImageReader::GetFatImages() {
202   DCHECK(is_fat_);
203   std::vector<MachOImageReader*> images;
204   for (const auto& image : fat_images_)
205     images.push_back(image.get());
206   return images;
207 }
208 
Is64Bit()209 bool MachOImageReader::Is64Bit() {
210   DCHECK(!is_fat_);
211   return is_64_bit_;
212 }
213 
GetMachHeader()214 const mach_header* MachOImageReader::GetMachHeader() {
215   DCHECK(!is_fat_);
216   return data_->GetPointerAt<mach_header>(0);
217 }
218 
GetMachHeader64()219 const mach_header_64* MachOImageReader::GetMachHeader64() {
220   DCHECK(is_64_bit_);
221   DCHECK(!is_fat_);
222   return data_->GetPointerAt<mach_header_64>(0);
223 }
224 
GetFileType()225 uint32_t MachOImageReader::GetFileType() {
226   DCHECK(!is_fat_);
227   return GetMachHeader()->filetype;
228 }
229 
230 const std::vector<MachOImageReader::LoadCommand>&
GetLoadCommands()231 MachOImageReader::GetLoadCommands() {
232   DCHECK(!is_fat_);
233   return commands_;
234 }
235 
GetCodeSignatureInfo(std::vector<uint8_t> * info)236 bool MachOImageReader::GetCodeSignatureInfo(std::vector<uint8_t>* info) {
237   DCHECK(!is_fat_);
238   DCHECK(info->empty());
239 
240   // Find the LC_CODE_SIGNATURE command and cast it to its linkedit format.
241   const linkedit_data_command* lc_code_signature = nullptr;
242   for (const auto& command : commands_) {
243     if (command.cmd() == LC_CODE_SIGNATURE) {
244       lc_code_signature = command.as_command<linkedit_data_command>();
245       break;
246     }
247   }
248   if (lc_code_signature == nullptr)
249     return false;
250 
251   info->resize(lc_code_signature->datasize);
252   return data_->CopyDataAt(lc_code_signature->dataoff,
253                            lc_code_signature->datasize,
254                            &(*info)[0]);
255 }
256 
257 }  // namespace safe_browsing
258