]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 | 1 | /* |
d8f41ccd | 2 | * Copyright (c) 2006-2014 Apple Inc. All Rights Reserved. |
b1ab9ed8 A |
3 | * |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | ||
24 | // | |
25 | // macho++ - Mach-O object file helpers | |
26 | // | |
27 | #include "macho++.h" | |
80e23899 | 28 | #include <security_utilities/alloc.h> |
b1ab9ed8 A |
29 | #include <security_utilities/memutils.h> |
30 | #include <security_utilities/endian.h> | |
31 | #include <mach-o/dyld.h> | |
80e23899 A |
32 | #include <list> |
33 | #include <algorithm> | |
34 | #include <iterator> | |
b1ab9ed8 A |
35 | |
36 | namespace Security { | |
37 | ||
80e23899 A |
38 | /* Maximum number of archs a fat binary can have */ |
39 | static const int MAX_ARCH_COUNT = 100; | |
40 | /* Maximum power of 2 that a mach-o can be aligned by */ | |
41 | static const int MAX_ALIGN = 30; | |
b1ab9ed8 A |
42 | |
43 | // | |
44 | // Architecture values | |
45 | // | |
46 | Architecture::Architecture(const fat_arch &arch) | |
47 | : pair<cpu_type_t, cpu_subtype_t>(arch.cputype, arch.cpusubtype) | |
48 | { | |
49 | } | |
50 | ||
51 | Architecture::Architecture(const char *name) | |
52 | { | |
53 | if (const NXArchInfo *nxa = NXGetArchInfoFromName(name)) { | |
54 | this->first = nxa->cputype; | |
55 | this->second = nxa->cpusubtype; | |
56 | } else { | |
57 | this->first = this->second = none; | |
58 | } | |
59 | } | |
60 | ||
61 | ||
62 | // | |
63 | // The local architecture. | |
64 | // | |
65 | // We take this from ourselves - the architecture of our main program Mach-O binary. | |
66 | // There's the NXGetLocalArchInfo API, but it insists on saying "i386" on modern | |
67 | // x86_64-centric systems, and lies to ppc (Rosetta) programs claiming they're native ppc. | |
68 | // So let's not use that. | |
69 | // | |
70 | Architecture Architecture::local() | |
71 | { | |
72 | return MainMachOImage().architecture(); | |
73 | } | |
74 | ||
75 | ||
76 | // | |
77 | // Translate between names and numbers | |
78 | // | |
79 | const char *Architecture::name() const | |
80 | { | |
81 | if (const NXArchInfo *info = NXGetArchInfoFromCpuType(cpuType(), cpuSubtype())) | |
82 | return info->name; | |
83 | else | |
84 | return NULL; | |
85 | } | |
86 | ||
87 | std::string Architecture::displayName() const | |
88 | { | |
89 | if (const char *s = this->name()) | |
90 | return s; | |
91 | char buf[20]; | |
92 | snprintf(buf, sizeof(buf), "(%d:%d)", cpuType(), cpuSubtype()); | |
93 | return buf; | |
94 | } | |
95 | ||
96 | ||
97 | // | |
98 | // Compare architectures. | |
99 | // This is asymmetrical; the second argument provides for some templating. | |
100 | // | |
101 | bool Architecture::matches(const Architecture &templ) const | |
102 | { | |
103 | if (first != templ.first) | |
104 | return false; // main architecture mismatch | |
105 | if (templ.second == CPU_SUBTYPE_MULTIPLE) | |
106 | return true; // subtype wildcard | |
107 | // match subtypes, ignoring feature bits | |
108 | return ((second ^ templ.second) & ~CPU_SUBTYPE_MASK) == 0; | |
109 | } | |
110 | ||
111 | ||
112 | // | |
113 | // MachOBase contains knowledge of the Mach-O object file format, | |
114 | // but abstracts from any particular sourcing. It must be subclassed, | |
115 | // and the subclass must provide the file header and commands area | |
116 | // during its construction. Memory is owned by the subclass. | |
117 | // | |
118 | MachOBase::~MachOBase() | |
119 | { /* virtual */ } | |
120 | ||
121 | // provide the Mach-O file header, somehow | |
122 | void MachOBase::initHeader(const mach_header *header) | |
123 | { | |
124 | mHeader = header; | |
125 | switch (mHeader->magic) { | |
126 | case MH_MAGIC: | |
127 | mFlip = false; | |
128 | m64 = false; | |
129 | break; | |
130 | case MH_CIGAM: | |
131 | mFlip = true; | |
132 | m64 = false; | |
133 | break; | |
134 | case MH_MAGIC_64: | |
135 | mFlip = false; | |
136 | m64 = true; | |
137 | break; | |
138 | case MH_CIGAM_64: | |
139 | mFlip = true; | |
140 | m64 = true; | |
141 | break; | |
142 | default: | |
fa7225c8 | 143 | secinfo("macho", "%p: unrecognized header magic (%x)", this, mHeader->magic); |
b1ab9ed8 A |
144 | UnixError::throwMe(ENOEXEC); |
145 | } | |
146 | } | |
147 | ||
148 | // provide the Mach-O commands section, somehow | |
149 | void MachOBase::initCommands(const load_command *commands) | |
150 | { | |
151 | mCommands = commands; | |
152 | mEndCommands = LowLevelMemoryUtilities::increment<load_command>(commands, flip(mHeader->sizeofcmds)); | |
427c49bc A |
153 | if (mCommands + 1 > mEndCommands) // ensure initial load command core available |
154 | UnixError::throwMe(ENOEXEC); | |
b1ab9ed8 A |
155 | } |
156 | ||
157 | ||
158 | size_t MachOBase::headerSize() const | |
159 | { | |
160 | return m64 ? sizeof(mach_header_64) : sizeof(mach_header); | |
161 | } | |
162 | ||
163 | size_t MachOBase::commandSize() const | |
164 | { | |
165 | return flip(mHeader->sizeofcmds); | |
166 | } | |
167 | ||
168 | ||
169 | // | |
170 | // Create a MachO object from an open file and a starting offset. | |
171 | // We load (only) the header and load commands into memory at that time. | |
172 | // Note that the offset must be relative to the start of the containing file | |
173 | // (not relative to some intermediate container). | |
174 | // | |
175 | MachO::MachO(FileDesc fd, size_t offset, size_t length) | |
80e23899 | 176 | : FileDesc(fd), mOffset(offset), mLength(length), mSuspicious(false) |
b1ab9ed8 | 177 | { |
80e23899 A |
178 | if (mOffset == 0) |
179 | mLength = fd.fileSize(); | |
b1ab9ed8 A |
180 | size_t size = fd.read(&mHeaderBuffer, sizeof(mHeaderBuffer), mOffset); |
181 | if (size != sizeof(mHeaderBuffer)) | |
182 | UnixError::throwMe(ENOEXEC); | |
183 | this->initHeader(&mHeaderBuffer); | |
184 | size_t cmdSize = this->commandSize(); | |
185 | mCommandBuffer = (load_command *)malloc(cmdSize); | |
186 | if (!mCommandBuffer) | |
187 | UnixError::throwMe(); | |
188 | if (fd.read(mCommandBuffer, cmdSize, this->headerSize() + mOffset) != cmdSize) | |
189 | UnixError::throwMe(ENOEXEC); | |
190 | this->initCommands(mCommandBuffer); | |
80e23899 A |
191 | /* If we do not know the length, we cannot do a verification of the mach-o structure */ |
192 | if (mLength != 0) | |
193 | this->validateStructure(); | |
194 | } | |
195 | ||
196 | void MachO::validateStructure() | |
197 | { | |
198 | bool isValid = false; | |
199 | ||
200 | /* There should be either an LC_SEGMENT, an LC_SEGMENT_64, or an LC_SYMTAB | |
201 | load_command and that + size must be equal to the end of the arch */ | |
202 | for (const struct load_command *cmd = loadCommands(); cmd != NULL; cmd = nextCommand(cmd)) { | |
203 | uint32_t cmd_type = flip(cmd->cmd); | |
204 | struct segment_command *seg = NULL; | |
205 | struct segment_command_64 *seg64 = NULL; | |
206 | struct symtab_command *symtab = NULL; | |
207 | ||
208 | if (cmd_type == LC_SEGMENT) { | |
6b200bc3 A |
209 | if(flip(cmd->cmdsize) < sizeof(struct segment_command)) { |
210 | UnixError::throwMe(ENOEXEC); | |
211 | } | |
80e23899 | 212 | seg = (struct segment_command *)cmd; |
6b200bc3 | 213 | if (strncmp(seg->segname, SEG_LINKEDIT, sizeof(seg->segname)) == 0) { |
80e23899 A |
214 | isValid = flip(seg->fileoff) + flip(seg->filesize) == this->length(); |
215 | break; | |
216 | } | |
217 | } else if (cmd_type == LC_SEGMENT_64) { | |
6b200bc3 A |
218 | if(flip(cmd->cmdsize) < sizeof(struct segment_command_64)) { |
219 | UnixError::throwMe(ENOEXEC); | |
220 | } | |
80e23899 | 221 | seg64 = (struct segment_command_64 *)cmd; |
6b200bc3 | 222 | if (strncmp(seg64->segname, SEG_LINKEDIT, sizeof(seg64->segname)) == 0) { |
80e23899 A |
223 | isValid = flip(seg64->fileoff) + flip(seg64->filesize) == this->length(); |
224 | break; | |
225 | } | |
226 | /* PPC binaries have a SYMTAB section */ | |
227 | } else if (cmd_type == LC_SYMTAB) { | |
6b200bc3 A |
228 | if(flip(cmd->cmdsize) < sizeof(struct symtab_command)) { |
229 | UnixError::throwMe(ENOEXEC); | |
230 | } | |
80e23899 A |
231 | symtab = (struct symtab_command *)cmd; |
232 | isValid = flip(symtab->stroff) + flip(symtab->strsize) == this->length(); | |
233 | break; | |
234 | } | |
235 | } | |
236 | ||
237 | if (!isValid) | |
238 | mSuspicious = true; | |
b1ab9ed8 A |
239 | } |
240 | ||
241 | MachO::~MachO() | |
242 | { | |
243 | ::free(mCommandBuffer); | |
244 | } | |
245 | ||
246 | ||
247 | // | |
248 | // Create a MachO object that is (entirely) mapped into memory. | |
249 | // The caller must ensire that the underlying mapping persists | |
250 | // at least as long as our object. | |
251 | // | |
252 | MachOImage::MachOImage(const void *address) | |
253 | { | |
254 | this->initHeader((const mach_header *)address); | |
255 | this->initCommands(LowLevelMemoryUtilities::increment<const load_command>(address, this->headerSize())); | |
256 | } | |
257 | ||
258 | ||
259 | // | |
260 | // Locate the Mach-O image of the main program | |
261 | // | |
262 | MainMachOImage::MainMachOImage() | |
263 | : MachOImage(mainImageAddress()) | |
264 | { | |
265 | } | |
266 | ||
267 | const void *MainMachOImage::mainImageAddress() | |
268 | { | |
269 | return _dyld_get_image_header(0); | |
270 | } | |
271 | ||
272 | ||
273 | // | |
274 | // Return various header fields | |
275 | // | |
276 | Architecture MachOBase::architecture() const | |
277 | { | |
278 | return Architecture(flip(mHeader->cputype), flip(mHeader->cpusubtype)); | |
279 | } | |
280 | ||
281 | uint32_t MachOBase::type() const | |
282 | { | |
283 | return flip(mHeader->filetype); | |
284 | } | |
285 | ||
286 | uint32_t MachOBase::flags() const | |
287 | { | |
288 | return flip(mHeader->flags); | |
289 | } | |
290 | ||
291 | ||
292 | // | |
293 | // Iterate through load commands | |
294 | // | |
295 | const load_command *MachOBase::nextCommand(const load_command *command) const | |
296 | { | |
297 | using LowLevelMemoryUtilities::increment; | |
d87e1158 A |
298 | /* Do not try and increment by 0, or it will loop forever */ |
299 | if (flip(command->cmdsize) == 0) | |
300 | UnixError::throwMe(ENOEXEC); | |
b1ab9ed8 | 301 | command = increment<const load_command>(command, flip(command->cmdsize)); |
427c49bc A |
302 | if (command >= mEndCommands) // end of load commands |
303 | return NULL; | |
304 | if (increment(command, sizeof(load_command)) > mEndCommands | |
305 | || increment(command, flip(command->cmdsize)) > mEndCommands) | |
306 | UnixError::throwMe(ENOEXEC); | |
307 | return command; | |
b1ab9ed8 A |
308 | } |
309 | ||
310 | ||
311 | // | |
312 | // Find a specific load command, by command number. | |
313 | // If there are multiples, returns the first one found. | |
314 | // | |
315 | const load_command *MachOBase::findCommand(uint32_t cmd) const | |
316 | { | |
317 | for (const load_command *command = loadCommands(); command; command = nextCommand(command)) | |
318 | if (flip(command->cmd) == cmd) | |
319 | return command; | |
320 | return NULL; | |
321 | } | |
322 | ||
323 | ||
324 | // | |
325 | // Locate a segment command, by name | |
326 | // | |
327 | const segment_command *MachOBase::findSegment(const char *segname) const | |
328 | { | |
329 | for (const load_command *command = loadCommands(); command; command = nextCommand(command)) { | |
330 | switch (flip(command->cmd)) { | |
331 | case LC_SEGMENT: | |
332 | case LC_SEGMENT_64: | |
333 | { | |
6b200bc3 A |
334 | if(flip(command->cmdsize) < sizeof(struct segment_command)) { |
335 | UnixError::throwMe(ENOEXEC); | |
336 | } | |
b1ab9ed8 | 337 | const segment_command *seg = reinterpret_cast<const segment_command *>(command); |
6b200bc3 | 338 | if (!strncmp(seg->segname, segname, sizeof(seg->segname))) |
b1ab9ed8 A |
339 | return seg; |
340 | break; | |
341 | } | |
342 | default: | |
343 | break; | |
344 | } | |
345 | } | |
346 | return NULL; | |
347 | } | |
348 | ||
349 | const section *MachOBase::findSection(const char *segname, const char *sectname) const | |
350 | { | |
351 | using LowLevelMemoryUtilities::increment; | |
352 | if (const segment_command *seg = findSegment(segname)) { | |
353 | if (is64()) { | |
6b200bc3 A |
354 | if(flip(seg->cmdsize) < sizeof(segment_command_64)) { |
355 | UnixError::throwMe(ENOEXEC); | |
356 | } | |
b1ab9ed8 | 357 | const segment_command_64 *seg64 = reinterpret_cast<const segment_command_64 *>(seg); |
6b200bc3 | 358 | if (sizeof(*seg64) + (seg64->nsects * sizeof(section_64)) > flip(seg64->cmdsize)) // too many segments; doesn't fit (malformed Mach-O) |
d8f41ccd | 359 | return NULL; |
b1ab9ed8 A |
360 | const section_64 *sect = increment<const section_64>(seg64 + 1, 0); |
361 | for (unsigned n = flip(seg64->nsects); n > 0; n--, sect++) { | |
6b200bc3 | 362 | if (!strncmp(sect->sectname, sectname, sizeof(sect->sectname))) |
b1ab9ed8 A |
363 | return reinterpret_cast<const section *>(sect); |
364 | } | |
365 | } else { | |
6b200bc3 A |
366 | if (sizeof(*seg) + (seg->nsects * sizeof(section)) > flip(seg->cmdsize)) // too many segments; doesn't fit (malformed Mach-O) |
367 | return NULL; | |
b1ab9ed8 A |
368 | const section *sect = increment<const section>(seg + 1, 0); |
369 | for (unsigned n = flip(seg->nsects); n > 0; n--, sect++) { | |
6b200bc3 | 370 | if (!strncmp(sect->sectname, sectname, sizeof(sect->sectname))) |
b1ab9ed8 A |
371 | return sect; |
372 | } | |
373 | } | |
374 | } | |
375 | return NULL; | |
376 | } | |
377 | ||
378 | ||
379 | // | |
380 | // Translate a union lc_str into the string it denotes. | |
381 | // Returns NULL (no exceptions) if the entry is corrupt. | |
382 | // | |
383 | const char *MachOBase::string(const load_command *cmd, const lc_str &str) const | |
384 | { | |
385 | size_t offset = flip(str.offset); | |
386 | const char *sp = LowLevelMemoryUtilities::increment<const char>(cmd, offset); | |
387 | if (offset + strlen(sp) + 1 > flip(cmd->cmdsize)) // corrupt string reference | |
388 | return NULL; | |
389 | return sp; | |
390 | } | |
391 | ||
392 | ||
393 | // | |
394 | // Figure out where the Code Signing information starts in the Mach-O binary image. | |
395 | // The code signature is at the end of the file, and identified | |
396 | // by a specially-named section. So its starting offset is also the end | |
397 | // of the signable part. | |
398 | // Note that the offset returned is relative to the start of the Mach-O image. | |
399 | // Returns zero if not found (usually indicating that the binary was not signed). | |
400 | // | |
401 | const linkedit_data_command *MachOBase::findCodeSignature() const | |
402 | { | |
6b200bc3 A |
403 | if (const load_command *cmd = findCommand(LC_CODE_SIGNATURE)) { |
404 | if(flip(cmd->cmdsize) < sizeof(linkedit_data_command)) { | |
405 | UnixError::throwMe(ENOEXEC); | |
406 | } | |
b1ab9ed8 | 407 | return reinterpret_cast<const linkedit_data_command *>(cmd); |
6b200bc3 | 408 | } |
b1ab9ed8 A |
409 | return NULL; // not found |
410 | } | |
411 | ||
412 | size_t MachOBase::signingOffset() const | |
413 | { | |
414 | if (const linkedit_data_command *lec = findCodeSignature()) | |
415 | return flip(lec->dataoff); | |
416 | else | |
417 | return 0; | |
418 | } | |
419 | ||
420 | size_t MachOBase::signingLength() const | |
421 | { | |
422 | if (const linkedit_data_command *lec = findCodeSignature()) | |
423 | return flip(lec->datasize); | |
424 | else | |
425 | return 0; | |
426 | } | |
427 | ||
428 | const linkedit_data_command *MachOBase::findLibraryDependencies() const | |
429 | { | |
6b200bc3 A |
430 | if (const load_command *cmd = findCommand(LC_DYLIB_CODE_SIGN_DRS)) { |
431 | if(flip(cmd->cmdsize) < sizeof(linkedit_data_command)) { | |
432 | UnixError::throwMe(ENOEXEC); | |
433 | } | |
b1ab9ed8 | 434 | return reinterpret_cast<const linkedit_data_command *>(cmd); |
6b200bc3 | 435 | } |
b1ab9ed8 A |
436 | return NULL; // not found |
437 | } | |
e3d460c9 A |
438 | |
439 | const version_min_command *MachOBase::findMinVersion() const | |
440 | { | |
441 | for (const load_command *command = loadCommands(); command; command = nextCommand(command)) | |
442 | switch (flip(command->cmd)) { | |
443 | case LC_VERSION_MIN_MACOSX: | |
444 | case LC_VERSION_MIN_IPHONEOS: | |
445 | case LC_VERSION_MIN_WATCHOS: | |
446 | case LC_VERSION_MIN_TVOS: | |
6b200bc3 A |
447 | if(flip(command->cmdsize) < sizeof(version_min_command)) { |
448 | UnixError::throwMe(ENOEXEC); | |
449 | } | |
e3d460c9 A |
450 | return reinterpret_cast<const version_min_command *>(command); |
451 | } | |
452 | return NULL; | |
453 | } | |
b1ab9ed8 | 454 | |
90dc47c2 A |
455 | const build_version_command *MachOBase::findBuildVersion() const |
456 | { | |
457 | for (const load_command *command = loadCommands(); command; command = nextCommand(command)) { | |
458 | if (flip(command->cmd) == LC_BUILD_VERSION) { | |
459 | if(flip(command->cmdsize) < sizeof(build_version_command)) { | |
460 | UnixError::throwMe(ENOEXEC); | |
461 | } | |
462 | ||
463 | return reinterpret_cast<const build_version_command *>(command); | |
464 | } | |
465 | } | |
466 | return NULL; | |
467 | } | |
468 | ||
469 | bool MachOBase::version(uint32_t *platform, uint32_t *minVersion, uint32_t *sdkVersion) const | |
470 | { | |
471 | const build_version_command *bc = findBuildVersion(); | |
472 | ||
473 | if (bc != NULL) { | |
474 | if (platform != NULL) { *platform = flip(bc->platform); } | |
475 | if (minVersion != NULL) { *minVersion = flip(bc->minos); } | |
476 | if (sdkVersion != NULL) { *sdkVersion = flip(bc->sdk); } | |
477 | return true; | |
478 | } | |
479 | ||
480 | const version_min_command *vc = findMinVersion(); | |
481 | ||
482 | if (vc != NULL) { | |
483 | uint32_t pf; | |
484 | switch (flip(vc->cmd)) { | |
485 | case LC_VERSION_MIN_MACOSX: | |
486 | pf = PLATFORM_MACOS; | |
487 | break; | |
488 | case LC_VERSION_MIN_IPHONEOS: | |
489 | pf = PLATFORM_IOS; | |
490 | break; | |
491 | case LC_VERSION_MIN_WATCHOS: | |
492 | pf = PLATFORM_WATCHOS; | |
493 | break; | |
494 | case LC_VERSION_MIN_TVOS: | |
495 | pf = PLATFORM_TVOS; | |
496 | break; | |
497 | default: | |
498 | // Old style load command, but we don't know what platform to map to. | |
499 | pf = 0; | |
500 | } | |
501 | ||
502 | if (platform != NULL) { *platform = pf; } | |
503 | if (minVersion != NULL) { *minVersion = flip(vc->version); } | |
504 | if (sdkVersion != NULL) { *sdkVersion = flip(vc->sdk); } | |
505 | return true; | |
506 | } | |
507 | ||
508 | return false; | |
509 | } | |
b1ab9ed8 A |
510 | |
511 | // | |
512 | // Return the signing-limit length for this Mach-O binary image. | |
513 | // This is the signingOffset if present, or the full length if not. | |
514 | // | |
515 | size_t MachO::signingExtent() const | |
516 | { | |
517 | if (size_t offset = signingOffset()) | |
518 | return offset; | |
519 | else | |
520 | return length(); | |
521 | } | |
522 | ||
523 | ||
524 | // | |
525 | // I/O operations | |
526 | // | |
527 | void MachO::seek(size_t offset) | |
528 | { | |
529 | FileDesc::seek(mOffset + offset); | |
530 | } | |
531 | ||
532 | CFDataRef MachO::dataAt(size_t offset, size_t size) | |
533 | { | |
534 | CFMallocData buffer(size); | |
535 | if (this->read(buffer, size, mOffset + offset) != size) | |
536 | UnixError::throwMe(); | |
537 | return buffer; | |
538 | } | |
539 | ||
b1ab9ed8 A |
540 | // |
541 | // Fat (aka universal) file wrappers. | |
542 | // The offset is relative to the start of the containing file. | |
543 | // | |
80e23899 | 544 | Universal::Universal(FileDesc fd, size_t offset /* = 0 */, size_t length /* = 0 */) |
5c19dc3a | 545 | : FileDesc(fd), mBase(offset), mLength(length), mMachType(0), mSuspicious(false) |
b1ab9ed8 A |
546 | { |
547 | union { | |
548 | fat_header header; // if this is a fat file | |
549 | mach_header mheader; // if this is a thin file | |
6b200bc3 A |
550 | } unionHeader; |
551 | ||
552 | if (fd.read(&unionHeader, sizeof(unionHeader), offset) != sizeof(unionHeader)) | |
b1ab9ed8 | 553 | UnixError::throwMe(ENOEXEC); |
6b200bc3 | 554 | switch (unionHeader.header.magic) { |
b1ab9ed8 A |
555 | case FAT_MAGIC: |
556 | case FAT_CIGAM: | |
557 | { | |
4d3cab3d A |
558 | // |
559 | // Hack alert. | |
560 | // Under certain circumstances (15001604), mArchCount under-counts the architectures | |
561 | // by one, and special testing is required to validate the extra-curricular entry. | |
562 | // We always read an extra entry; in the situations where this might hit end-of-file, | |
563 | // we are content to fail. | |
564 | // | |
6b200bc3 | 565 | mArchCount = ntohl(unionHeader.header.nfat_arch); |
d8f41ccd A |
566 | |
567 | if (mArchCount > MAX_ARCH_COUNT) | |
568 | UnixError::throwMe(ENOEXEC); | |
569 | ||
4d3cab3d | 570 | size_t archSize = sizeof(fat_arch) * (mArchCount + 1); |
b1ab9ed8 A |
571 | mArchList = (fat_arch *)malloc(archSize); |
572 | if (!mArchList) | |
573 | UnixError::throwMe(); | |
6b200bc3 | 574 | if (fd.read(mArchList, archSize, mBase + sizeof(unionHeader.header)) != archSize) { |
b1ab9ed8 A |
575 | ::free(mArchList); |
576 | UnixError::throwMe(ENOEXEC); | |
577 | } | |
4d3cab3d | 578 | for (fat_arch *arch = mArchList; arch <= mArchList + mArchCount; arch++) { |
b1ab9ed8 A |
579 | n2hi(arch->cputype); |
580 | n2hi(arch->cpusubtype); | |
581 | n2hi(arch->offset); | |
582 | n2hi(arch->size); | |
583 | n2hi(arch->align); | |
584 | } | |
4d3cab3d A |
585 | const fat_arch *last_arch = mArchList + mArchCount; |
586 | if (last_arch->cputype == (CPU_ARCH_ABI64 | CPU_TYPE_ARM)) { | |
587 | mArchCount++; | |
588 | } | |
fa7225c8 | 589 | secinfo("macho", "%p is a fat file with %d architectures", |
b1ab9ed8 | 590 | this, mArchCount); |
80e23899 A |
591 | |
592 | /* A Mach-O universal file has padding of no more than "page size" | |
593 | * between the header and slices. This padding must be zeroed out or the file | |
594 | is not valid */ | |
595 | std::list<struct fat_arch *> sortedList; | |
596 | for (unsigned i = 0; i < mArchCount; i++) | |
597 | sortedList.push_back(mArchList + i); | |
598 | ||
599 | sortedList.sort(^ bool (const struct fat_arch *arch1, const struct fat_arch *arch2) { return arch1->offset < arch2->offset; }); | |
600 | ||
6b200bc3 | 601 | const size_t universalHeaderEnd = mBase + sizeof(unionHeader.header) + (sizeof(fat_arch) * mArchCount); |
80e23899 A |
602 | size_t prevHeaderEnd = universalHeaderEnd; |
603 | size_t prevArchSize = 0, prevArchStart = 0; | |
604 | ||
605 | for (auto iterator = sortedList.begin(); iterator != sortedList.end(); ++iterator) { | |
606 | auto ret = mSizes.insert(std::pair<size_t, size_t>((*iterator)->offset, (*iterator)->size)); | |
607 | if (ret.second == false) { | |
608 | ::free(mArchList); | |
609 | MacOSError::throwMe(errSecInternalError); // Something is wrong if the same size was encountered twice | |
610 | } | |
611 | ||
612 | size_t gapSize = (*iterator)->offset - prevHeaderEnd; | |
613 | ||
614 | /* The size of the padding after the universal cannot be calculated to a fixed size */ | |
615 | if (prevHeaderEnd != universalHeaderEnd) { | |
616 | if (((*iterator)->align > MAX_ALIGN) || gapSize >= (1 << (*iterator)->align)) { | |
617 | mSuspicious = true; | |
618 | break; | |
619 | } | |
620 | } | |
621 | ||
622 | // validate gap bytes in tasty page-sized chunks | |
623 | CssmAutoPtr<uint8_t> gapBytes(Allocator::standard().malloc<uint8_t>(PAGE_SIZE)); | |
624 | size_t off = 0; | |
625 | while (off < gapSize) { | |
626 | size_t want = min(gapSize - off, (size_t)PAGE_SIZE); | |
627 | size_t got = fd.read(gapBytes, want, prevHeaderEnd + off); | |
822b670c A |
628 | if (got == 0) { |
629 | mSuspicious = true; | |
630 | break; | |
631 | } | |
80e23899 A |
632 | off += got; |
633 | for (size_t x = 0; x < got; x++) { | |
634 | if (gapBytes[x] != 0) { | |
635 | mSuspicious = true; | |
636 | break; | |
637 | } | |
638 | } | |
639 | if (mSuspicious) | |
640 | break; | |
641 | } | |
642 | if (off != gapSize) | |
643 | mSuspicious = true; | |
644 | if (mSuspicious) | |
645 | break; | |
646 | ||
647 | prevHeaderEnd = (*iterator)->offset + (*iterator)->size; | |
648 | prevArchSize = (*iterator)->size; | |
649 | prevArchStart = (*iterator)->offset; | |
650 | } | |
651 | ||
652 | /* If there is anything extra at the end of the file, reject this */ | |
653 | if (!mSuspicious && (prevArchStart + prevArchSize != fd.fileSize())) | |
654 | mSuspicious = true; | |
655 | ||
b1ab9ed8 A |
656 | break; |
657 | } | |
658 | case MH_MAGIC: | |
659 | case MH_MAGIC_64: | |
660 | mArchList = NULL; | |
661 | mArchCount = 0; | |
6b200bc3 | 662 | mThinArch = Architecture(unionHeader.mheader.cputype, unionHeader.mheader.cpusubtype); |
fa7225c8 | 663 | secinfo("macho", "%p is a thin file (%s)", this, mThinArch.name()); |
b1ab9ed8 A |
664 | break; |
665 | case MH_CIGAM: | |
666 | case MH_CIGAM_64: | |
667 | mArchList = NULL; | |
668 | mArchCount = 0; | |
6b200bc3 | 669 | mThinArch = Architecture(flip(unionHeader.mheader.cputype), flip(unionHeader.mheader.cpusubtype)); |
fa7225c8 | 670 | secinfo("macho", "%p is a thin file (%s)", this, mThinArch.name()); |
b1ab9ed8 A |
671 | break; |
672 | default: | |
673 | UnixError::throwMe(ENOEXEC); | |
674 | } | |
675 | } | |
676 | ||
677 | Universal::~Universal() | |
678 | { | |
679 | ::free(mArchList); | |
680 | } | |
681 | ||
6b200bc3 | 682 | size_t Universal::lengthOfSlice(size_t offset) const |
80e23899 A |
683 | { |
684 | auto ret = mSizes.find(offset); | |
685 | if (ret == mSizes.end()) | |
686 | MacOSError::throwMe(errSecInternalError); | |
687 | return ret->second; | |
688 | } | |
b1ab9ed8 A |
689 | |
690 | // | |
691 | // Get the "local" architecture from the fat file | |
692 | // Throws ENOEXEC if not found. | |
693 | // | |
694 | MachO *Universal::architecture() const | |
695 | { | |
696 | if (isUniversal()) | |
697 | return findImage(bestNativeArch()); | |
698 | else | |
80e23899 | 699 | return new MachO(*this, mBase, mLength); |
b1ab9ed8 A |
700 | } |
701 | ||
702 | size_t Universal::archOffset() const | |
703 | { | |
704 | if (isUniversal()) | |
705 | return mBase + findArch(bestNativeArch())->offset; | |
706 | else | |
707 | return mBase; | |
708 | } | |
709 | ||
710 | ||
711 | // | |
712 | // Get the specified architecture from the fat file | |
713 | // Throws ENOEXEC if not found. | |
714 | // | |
715 | MachO *Universal::architecture(const Architecture &arch) const | |
716 | { | |
717 | if (isUniversal()) | |
718 | return findImage(arch); | |
719 | else if (mThinArch.matches(arch)) | |
720 | return new MachO(*this, mBase); | |
721 | else | |
722 | UnixError::throwMe(ENOEXEC); | |
723 | } | |
724 | ||
725 | size_t Universal::archOffset(const Architecture &arch) const | |
726 | { | |
727 | if (isUniversal()) | |
728 | return mBase + findArch(arch)->offset; | |
729 | else if (mThinArch.matches(arch)) | |
730 | return 0; | |
731 | else | |
732 | UnixError::throwMe(ENOEXEC); | |
733 | } | |
734 | ||
80e23899 A |
735 | size_t Universal::archLength(const Architecture &arch) const |
736 | { | |
737 | if (isUniversal()) | |
738 | return mBase + findArch(arch)->size; | |
739 | else if (mThinArch.matches(arch)) | |
740 | return this->fileSize(); | |
741 | else | |
742 | UnixError::throwMe(ENOEXEC); | |
743 | } | |
b1ab9ed8 A |
744 | |
745 | // | |
746 | // Get the architecture at a specified offset from the fat file. | |
747 | // Throws an exception of the offset does not point at a Mach-O image. | |
748 | // | |
427c49bc | 749 | MachO *Universal::architecture(size_t offset) const |
b1ab9ed8 A |
750 | { |
751 | if (isUniversal()) | |
5c19dc3a | 752 | return make(new MachO(*this, offset)); |
b1ab9ed8 A |
753 | else if (offset == mBase) |
754 | return new MachO(*this); | |
755 | else | |
756 | UnixError::throwMe(ENOEXEC); | |
757 | } | |
758 | ||
759 | ||
760 | // | |
761 | // Locate an architecture from the fat file's list. | |
762 | // Throws ENOEXEC if not found. | |
763 | // | |
764 | const fat_arch *Universal::findArch(const Architecture &target) const | |
765 | { | |
766 | assert(isUniversal()); | |
767 | const fat_arch *end = mArchList + mArchCount; | |
768 | // exact match | |
769 | for (const fat_arch *arch = mArchList; arch < end; ++arch) | |
770 | if (arch->cputype == target.cpuType() | |
771 | && arch->cpusubtype == target.cpuSubtype()) | |
772 | return arch; | |
773 | // match for generic model of main architecture | |
774 | for (const fat_arch *arch = mArchList; arch < end; ++arch) | |
775 | if (arch->cputype == target.cpuType() && arch->cpusubtype == 0) | |
776 | return arch; | |
777 | // match for any subarchitecture of the main architecture (questionable) | |
778 | for (const fat_arch *arch = mArchList; arch < end; ++arch) | |
779 | if (arch->cputype == target.cpuType()) | |
780 | return arch; | |
781 | // no match | |
782 | UnixError::throwMe(ENOEXEC); // not found | |
783 | } | |
784 | ||
785 | MachO *Universal::findImage(const Architecture &target) const | |
786 | { | |
787 | const fat_arch *arch = findArch(target); | |
5c19dc3a A |
788 | return make(new MachO(*this, mBase + arch->offset, arch->size)); |
789 | } | |
790 | ||
791 | MachO* Universal::make(MachO* macho) const | |
792 | { | |
793 | auto_ptr<MachO> mo(macho); // safe resource | |
794 | uint32_t type = mo->type(); | |
795 | if (type == 0) // not a recognized Mach-O type | |
796 | UnixError::throwMe(ENOEXEC); | |
797 | if (mMachType && mMachType != type) // inconsistent members | |
798 | UnixError::throwMe(ENOEXEC); | |
799 | mMachType = type; // record | |
800 | return mo.release(); | |
b1ab9ed8 A |
801 | } |
802 | ||
803 | ||
804 | // | |
805 | // Find the best-matching architecture for this fat file. | |
806 | // We pick the native architecture if it's available. | |
807 | // If it contains exactly one architecture, we take that. | |
808 | // Otherwise, we throw. | |
809 | // | |
810 | Architecture Universal::bestNativeArch() const | |
811 | { | |
812 | if (isUniversal()) { | |
813 | // ask the NXArch API for our native architecture | |
814 | const Architecture native = Architecture::local(); | |
815 | if (fat_arch *match = NXFindBestFatArch(native.cpuType(), native.cpuSubtype(), mArchList, mArchCount)) | |
816 | return *match; | |
817 | // if the system can't figure it out, pick (arbitrarily) the first one | |
818 | return mArchList[0]; | |
819 | } else | |
820 | return mThinArch; | |
821 | } | |
822 | ||
b1ab9ed8 A |
823 | // |
824 | // List all architectures from the fat file's list. | |
825 | // | |
80e23899 | 826 | void Universal::architectures(Architectures &archs) const |
b1ab9ed8 A |
827 | { |
828 | if (isUniversal()) { | |
829 | for (unsigned n = 0; n < mArchCount; n++) | |
830 | archs.insert(mArchList[n]); | |
831 | } else { | |
832 | auto_ptr<MachO> macho(architecture()); | |
833 | archs.insert(macho->architecture()); | |
834 | } | |
835 | } | |
836 | ||
b1ab9ed8 A |
837 | // |
838 | // Quickly guess the Mach-O type of a file. | |
839 | // Returns type zero if the file isn't Mach-O or Universal. | |
840 | // Always looks at the start of the file, and does not change the file pointer. | |
841 | // | |
842 | uint32_t Universal::typeOf(FileDesc fd) | |
843 | { | |
844 | mach_header header; | |
845 | int max_tries = 3; | |
846 | if (fd.read(&header, sizeof(header), 0) != sizeof(header)) | |
847 | return 0; | |
848 | while (max_tries > 0) { | |
849 | switch (header.magic) { | |
850 | case MH_MAGIC: | |
851 | case MH_MAGIC_64: | |
852 | return header.filetype; | |
b1ab9ed8 A |
853 | case MH_CIGAM: |
854 | case MH_CIGAM_64: | |
855 | return flip(header.filetype); | |
b1ab9ed8 A |
856 | case FAT_MAGIC: |
857 | case FAT_CIGAM: | |
858 | { | |
859 | const fat_arch *arch1 = | |
860 | LowLevelMemoryUtilities::increment<fat_arch>(&header, sizeof(fat_header)); | |
861 | if (fd.read(&header, sizeof(header), ntohl(arch1->offset)) != sizeof(header)) | |
862 | return 0; | |
863 | max_tries--; | |
864 | continue; | |
865 | } | |
866 | default: | |
867 | return 0; | |
868 | } | |
869 | } | |
427c49bc | 870 | return 0; |
b1ab9ed8 A |
871 | } |
872 | ||
80e23899 A |
873 | // |
874 | // Strict validation | |
875 | // | |
876 | bool Universal::isSuspicious() const | |
877 | { | |
878 | if (mSuspicious) | |
879 | return true; | |
880 | Universal::Architectures archList; | |
881 | architectures(archList); | |
882 | for (Universal::Architectures::const_iterator it = archList.begin(); it != archList.end(); ++it) { | |
883 | auto_ptr<MachO> macho(architecture(*it)); | |
884 | if (macho->isSuspicious()) | |
885 | return true; | |
886 | } | |
887 | return false; | |
888 | } | |
889 | ||
b1ab9ed8 A |
890 | |
891 | } // Security |