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