]> git.saurik.com Git - apple/security.git/blame - OSX/libsecurity_utilities/lib/macho++.cpp
Security-59306.140.5.tar.gz
[apple/security.git] / OSX / libsecurity_utilities / lib / macho++.cpp
CommitLineData
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
36namespace Security {
37
80e23899
A
38/* Maximum number of archs a fat binary can have */
39static const int MAX_ARCH_COUNT = 100;
40/* Maximum power of 2 that a mach-o can be aligned by */
41static const int MAX_ALIGN = 30;
b1ab9ed8
A
42
43//
44// Architecture values
45//
46Architecture::Architecture(const fat_arch &arch)
47 : pair<cpu_type_t, cpu_subtype_t>(arch.cputype, arch.cpusubtype)
48{
49}
50
51Architecture::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//
70Architecture Architecture::local()
71{
72 return MainMachOImage().architecture();
73}
74
75
76//
77// Translate between names and numbers
78//
79const char *Architecture::name() const
80{
81 if (const NXArchInfo *info = NXGetArchInfoFromCpuType(cpuType(), cpuSubtype()))
82 return info->name;
83 else
84 return NULL;
85}
86
87std::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//
101bool 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//
118MachOBase::~MachOBase()
119{ /* virtual */ }
120
121// provide the Mach-O file header, somehow
122void 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
149void 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
158size_t MachOBase::headerSize() const
159{
160 return m64 ? sizeof(mach_header_64) : sizeof(mach_header);
161}
162
163size_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//
175MachO::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
196void 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
241MachO::~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//
252MachOImage::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//
262MainMachOImage::MainMachOImage()
263 : MachOImage(mainImageAddress())
264{
265}
266
267const void *MainMachOImage::mainImageAddress()
268{
269 return _dyld_get_image_header(0);
270}
271
272
273//
274// Return various header fields
275//
276Architecture MachOBase::architecture() const
277{
278 return Architecture(flip(mHeader->cputype), flip(mHeader->cpusubtype));
279}
280
281uint32_t MachOBase::type() const
282{
283 return flip(mHeader->filetype);
284}
285
286uint32_t MachOBase::flags() const
287{
288 return flip(mHeader->flags);
289}
290
291
292//
293// Iterate through load commands
294//
295const 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//
315const 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//
327const 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
349const 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//
383const 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//
401const 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
412size_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
420size_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
428const 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
439const 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
455const 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
469bool 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//
515size_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//
527void MachO::seek(size_t offset)
528{
529 FileDesc::seek(mOffset + offset);
530}
531
532CFDataRef 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 544Universal::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
677Universal::~Universal()
678{
679 ::free(mArchList);
680}
681
6b200bc3 682size_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//
694MachO *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
702size_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//
715MachO *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
725size_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
735size_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 749MachO *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//
764const 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
785MachO *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
791MachO* 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//
810Architecture 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 826void 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//
842uint32_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//
876bool 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