1/*
2 * Copyright (c) 2006-2014 Apple Inc. All Rights Reserved.
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"
28#include <security_utilities/alloc.h>
29#include <security_utilities/memutils.h>
30#include <security_utilities/endian.h>
31#include <mach-o/dyld.h>
32#include <list>
33#include <algorithm>
34#include <iterator>
35
36namespace Security {
37
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;
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:
143		secdebug("macho", "%p: unrecognized header magic (%x)", this, mHeader->magic);
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));
153	if (mCommands + 1 > mEndCommands)	// ensure initial load command core available
154		UnixError::throwMe(ENOEXEC);
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)
176	: FileDesc(fd), mOffset(offset), mLength(length), mSuspicious(false)
177{
178	if (mOffset == 0)
179		mLength = fd.fileSize();
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);
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) {
209			seg = (struct segment_command *)cmd;
210			if (strcmp(seg->segname, SEG_LINKEDIT) == 0) {
211				isValid = flip(seg->fileoff) + flip(seg->filesize) == this->length();
212				break;
213			}
214		} else if (cmd_type == LC_SEGMENT_64) {
215			seg64 = (struct segment_command_64 *)cmd;
216			if (strcmp(seg64->segname, SEG_LINKEDIT) == 0) {
217				isValid = flip(seg64->fileoff) + flip(seg64->filesize) == this->length();
218				break;
219			}
220		/* PPC binaries have a SYMTAB section */
221		} else if (cmd_type == LC_SYMTAB) {
222			symtab = (struct symtab_command *)cmd;
223			isValid = flip(symtab->stroff) + flip(symtab->strsize) == this->length();
224			break;
225		}
226	}
227
228	if (!isValid)
229		mSuspicious = true;
230}
231
232MachO::~MachO()
233{
234	::free(mCommandBuffer);
235}
236
237
238//
239// Create a MachO object that is (entirely) mapped into memory.
240// The caller must ensire that the underlying mapping persists
241// at least as long as our object.
242//
243MachOImage::MachOImage(const void *address)
244{
245	this->initHeader((const mach_header *)address);
246	this->initCommands(LowLevelMemoryUtilities::increment<const load_command>(address, this->headerSize()));
247}
248
249
250//
251// Locate the Mach-O image of the main program
252//
253MainMachOImage::MainMachOImage()
254	: MachOImage(mainImageAddress())
255{
256}
257
258const void *MainMachOImage::mainImageAddress()
259{
260	return _dyld_get_image_header(0);
261}
262
263
264//
265// Return various header fields
266//
267Architecture MachOBase::architecture() const
268{
269	return Architecture(flip(mHeader->cputype), flip(mHeader->cpusubtype));
270}
271
272uint32_t MachOBase::type() const
273{
274	return flip(mHeader->filetype);
275}
276
277uint32_t MachOBase::flags() const
278{
279	return flip(mHeader->flags);
280}
281
282
283//
284// Iterate through load commands
285//
286const load_command *MachOBase::nextCommand(const load_command *command) const
287{
288	using LowLevelMemoryUtilities::increment;
289	command = increment<const load_command>(command, flip(command->cmdsize));
290	if (command >= mEndCommands)	// end of load commands
291		return NULL;
292	if (increment(command, sizeof(load_command)) > mEndCommands
293		|| increment(command, flip(command->cmdsize)) > mEndCommands)
294		UnixError::throwMe(ENOEXEC);
295	return command;
296}
297
298
299//
300// Find a specific load command, by command number.
301// If there are multiples, returns the first one found.
302//
303const load_command *MachOBase::findCommand(uint32_t cmd) const
304{
305	for (const load_command *command = loadCommands(); command; command = nextCommand(command))
306		if (flip(command->cmd) == cmd)
307			return command;
308	return NULL;
309}
310
311
312//
313// Locate a segment command, by name
314//
315const segment_command *MachOBase::findSegment(const char *segname) const
316{
317	for (const load_command *command = loadCommands(); command; command = nextCommand(command)) {
318		switch (flip(command->cmd)) {
319		case LC_SEGMENT:
320		case LC_SEGMENT_64:
321			{
322				const segment_command *seg = reinterpret_cast<const segment_command *>(command);
323				if (!strcmp(seg->segname, segname))
324					return seg;
325				break;
326			}
327		default:
328			break;
329		}
330	}
331	return NULL;
332}
333
334const section *MachOBase::findSection(const char *segname, const char *sectname) const
335{
336	using LowLevelMemoryUtilities::increment;
337	if (const segment_command *seg = findSegment(segname)) {
338		if (is64()) {
339			const segment_command_64 *seg64 = reinterpret_cast<const segment_command_64 *>(seg);
340			// As a sanity check, if the reported number of sections is not consistent
341			// with the reported size, return NULL
342			if (sizeof(*seg64) + (seg64->nsects * sizeof(section_64)) > seg64->cmdsize)
343				return NULL;
344			const section_64 *sect = increment<const section_64>(seg64 + 1, 0);
345			for (unsigned n = flip(seg64->nsects); n > 0; n--, sect++) {
346				if (!strcmp(sect->sectname, sectname))
347					return reinterpret_cast<const section *>(sect);
348			}
349		} else {
350			const section *sect = increment<const section>(seg + 1, 0);
351			for (unsigned n = flip(seg->nsects); n > 0; n--, sect++) {
352				if (!strcmp(sect->sectname, sectname))
353					return sect;
354			}
355		}
356	}
357	return NULL;
358}
359
360
361//
362// Translate a union lc_str into the string it denotes.
363// Returns NULL (no exceptions) if the entry is corrupt.
364//
365const char *MachOBase::string(const load_command *cmd, const lc_str &str) const
366{
367	size_t offset = flip(str.offset);
368	const char *sp = LowLevelMemoryUtilities::increment<const char>(cmd, offset);
369	if (offset + strlen(sp) + 1 > flip(cmd->cmdsize))	// corrupt string reference
370		return NULL;
371	return sp;
372}
373
374
375//
376// Figure out where the Code Signing information starts in the Mach-O binary image.
377// The code signature is at the end of the file, and identified
378// by a specially-named section. So its starting offset is also the end
379// of the signable part.
380// Note that the offset returned is relative to the start of the Mach-O image.
381// Returns zero if not found (usually indicating that the binary was not signed).
382//
383const linkedit_data_command *MachOBase::findCodeSignature() const
384{
385	if (const load_command *cmd = findCommand(LC_CODE_SIGNATURE))
386		return reinterpret_cast<const linkedit_data_command *>(cmd);
387	return NULL;		// not found
388}
389
390size_t MachOBase::signingOffset() const
391{
392	if (const linkedit_data_command *lec = findCodeSignature())
393		return flip(lec->dataoff);
394	else
395		return 0;
396}
397
398size_t MachOBase::signingLength() const
399{
400	if (const linkedit_data_command *lec = findCodeSignature())
401		return flip(lec->datasize);
402	else
403		return 0;
404}
405
406const linkedit_data_command *MachOBase::findLibraryDependencies() const
407{
408	if (const load_command *cmd = findCommand(LC_DYLIB_CODE_SIGN_DRS))
409		return reinterpret_cast<const linkedit_data_command *>(cmd);
410	return NULL;		// not found
411}
412
413
414//
415// Return the signing-limit length for this Mach-O binary image.
416// This is the signingOffset if present, or the full length if not.
417//
418size_t MachO::signingExtent() const
419{
420	if (size_t offset = signingOffset())
421		return offset;
422	else
423		return length();
424}
425
426
427//
428// I/O operations
429//
430void MachO::seek(size_t offset)
431{
432	FileDesc::seek(mOffset + offset);
433}
434
435CFDataRef MachO::dataAt(size_t offset, size_t size)
436{
437	CFMallocData buffer(size);
438	if (this->read(buffer, size, mOffset + offset) != size)
439		UnixError::throwMe();
440	return buffer;
441}
442
443//
444// Fat (aka universal) file wrappers.
445// The offset is relative to the start of the containing file.
446//
447Universal::Universal(FileDesc fd, size_t offset /* = 0 */, size_t length /* = 0 */)
448	: FileDesc(fd), mBase(offset), mLength(length), mSuspicious(false)
449{
450	union {
451		fat_header header;		// if this is a fat file
452		mach_header mheader;	// if this is a thin file
453	};
454	const size_t size = max(sizeof(header), sizeof(mheader));
455	if (fd.read(&header, size, offset) != size)
456		UnixError::throwMe(ENOEXEC);
457	switch (header.magic) {
458	case FAT_MAGIC:
459	case FAT_CIGAM:
460		{
461			//
462			// Hack alert.
463			// Under certain circumstances (15001604), mArchCount under-counts the architectures
464			// by one, and special testing is required to validate the extra-curricular entry.
465			// We always read an extra entry; in the situations where this might hit end-of-file,
466			// we are content to fail.
467			//
468			mArchCount = ntohl(header.nfat_arch);
469
470			if (mArchCount > MAX_ARCH_COUNT)
471				UnixError::throwMe(ENOEXEC);
472
473			size_t archSize = sizeof(fat_arch) * (mArchCount + 1);
474			mArchList = (fat_arch *)malloc(archSize);
475			if (!mArchList)
476				UnixError::throwMe();
477			if (fd.read(mArchList, archSize, mBase + sizeof(header)) != archSize) {
478				::free(mArchList);
479				UnixError::throwMe(ENOEXEC);
480			}
481			for (fat_arch *arch = mArchList; arch <= mArchList + mArchCount; arch++) {
482				n2hi(arch->cputype);
483				n2hi(arch->cpusubtype);
484				n2hi(arch->offset);
485				n2hi(arch->size);
486				n2hi(arch->align);
487			}
488			const fat_arch *last_arch = mArchList + mArchCount;
489			if (last_arch->cputype == (CPU_ARCH_ABI64 | CPU_TYPE_ARM)) {
490				mArchCount++;
491			}
492			secdebug("macho", "%p is a fat file with %d architectures",
493				this, mArchCount);
494
495			/* A Mach-O universal file has padding of no more than "page size"
496			 * between the header and slices. This padding must be zeroed out or the file
497			   is not valid */
498			std::list<struct fat_arch *> sortedList;
499			for (unsigned i = 0; i < mArchCount; i++)
500				sortedList.push_back(mArchList + i);
501
502			sortedList.sort(^ bool (const struct fat_arch *arch1, const struct fat_arch *arch2) { return arch1->offset < arch2->offset; });
503
504			const size_t universalHeaderEnd = mBase + sizeof(header) + (sizeof(fat_arch) * mArchCount);
505			size_t prevHeaderEnd = universalHeaderEnd;
506			size_t prevArchSize = 0, prevArchStart = 0;
507
508			for (auto iterator = sortedList.begin(); iterator != sortedList.end(); ++iterator) {
509				auto ret = mSizes.insert(std::pair<size_t, size_t>((*iterator)->offset, (*iterator)->size));
510				if (ret.second == false) {
511					::free(mArchList);
512					MacOSError::throwMe(errSecInternalError); // Something is wrong if the same size was encountered twice
513				}
514
515				size_t gapSize = (*iterator)->offset - prevHeaderEnd;
516
517				/* The size of the padding after the universal cannot be calculated to a fixed size */
518				if (prevHeaderEnd != universalHeaderEnd) {
519					if (((*iterator)->align > MAX_ALIGN) || gapSize >= (1 << (*iterator)->align)) {
520						mSuspicious = true;
521						break;
522					}
523				}
524
525				// validate gap bytes in tasty page-sized chunks
526				CssmAutoPtr<uint8_t> gapBytes(Allocator::standard().malloc<uint8_t>(PAGE_SIZE));
527				size_t off = 0;
528				while (off < gapSize) {
529					size_t want = min(gapSize - off, (size_t)PAGE_SIZE);
530					size_t got = fd.read(gapBytes, want, prevHeaderEnd + off);
531					off += got;
532					for (size_t x = 0; x < got; x++) {
533						if (gapBytes[x] != 0) {
534							mSuspicious = true;
535							break;
536						}
537					}
538					if (mSuspicious)
539						break;
540				}
541				if (off != gapSize)
542					mSuspicious = true;
543				if (mSuspicious)
544					break;
545
546				prevHeaderEnd = (*iterator)->offset + (*iterator)->size;
547				prevArchSize = (*iterator)->size;
548				prevArchStart = (*iterator)->offset;
549			}
550
551			/* If there is anything extra at the end of the file, reject this */
552			if (!mSuspicious && (prevArchStart + prevArchSize != fd.fileSize()))
553				mSuspicious = true;
554
555			break;
556		}
557	case MH_MAGIC:
558	case MH_MAGIC_64:
559		mArchList = NULL;
560		mArchCount = 0;
561		mThinArch = Architecture(mheader.cputype, mheader.cpusubtype);
562		secdebug("macho", "%p is a thin file (%s)", this, mThinArch.name());
563		break;
564	case MH_CIGAM:
565	case MH_CIGAM_64:
566		mArchList = NULL;
567		mArchCount = 0;
568		mThinArch = Architecture(flip(mheader.cputype), flip(mheader.cpusubtype));
569		secdebug("macho", "%p is a thin file (%s)", this, mThinArch.name());
570		break;
571	default:
572		UnixError::throwMe(ENOEXEC);
573	}
574}
575
576Universal::~Universal()
577{
578	::free(mArchList);
579}
580
581const size_t Universal::lengthOfSlice(size_t offset) const
582{
583	auto ret = mSizes.find(offset);
584	if (ret == mSizes.end())
585		MacOSError::throwMe(errSecInternalError);
586	return ret->second;
587}
588
589//
590// Get the "local" architecture from the fat file
591// Throws ENOEXEC if not found.
592//
593MachO *Universal::architecture() const
594{
595	if (isUniversal())
596		return findImage(bestNativeArch());
597	else
598		return new MachO(*this, mBase, mLength);
599}
600
601size_t Universal::archOffset() const
602{
603	if (isUniversal())
604		return mBase + findArch(bestNativeArch())->offset;
605	else
606		return mBase;
607}
608
609
610//
611// Get the specified architecture from the fat file
612// Throws ENOEXEC if not found.
613//
614MachO *Universal::architecture(const Architecture &arch) const
615{
616	if (isUniversal())
617		return findImage(arch);
618	else if (mThinArch.matches(arch))
619		return new MachO(*this, mBase);
620	else
621		UnixError::throwMe(ENOEXEC);
622}
623
624size_t Universal::archOffset(const Architecture &arch) const
625{
626	if (isUniversal())
627		return mBase + findArch(arch)->offset;
628	else if (mThinArch.matches(arch))
629		return 0;
630	else
631		UnixError::throwMe(ENOEXEC);
632}
633
634size_t Universal::archLength(const Architecture &arch) const
635{
636	if (isUniversal())
637		return mBase + findArch(arch)->size;
638	else if (mThinArch.matches(arch))
639		return this->fileSize();
640	else
641		UnixError::throwMe(ENOEXEC);
642}
643
644//
645// Get the architecture at a specified offset from the fat file.
646// Throws an exception of the offset does not point at a Mach-O image.
647//
648MachO *Universal::architecture(size_t offset) const
649{
650	if (isUniversal())
651		return new MachO(*this, offset);
652	else if (offset == mBase)
653		return new MachO(*this);
654	else
655		UnixError::throwMe(ENOEXEC);
656}
657
658
659//
660// Locate an architecture from the fat file's list.
661// Throws ENOEXEC if not found.
662//
663const fat_arch *Universal::findArch(const Architecture &target) const
664{
665	assert(isUniversal());
666	const fat_arch *end = mArchList + mArchCount;
667	// exact match
668	for (const fat_arch *arch = mArchList; arch < end; ++arch)
669		if (arch->cputype == target.cpuType()
670			&& arch->cpusubtype == target.cpuSubtype())
671			return arch;
672	// match for generic model of main architecture
673	for (const fat_arch *arch = mArchList; arch < end; ++arch)
674		if (arch->cputype == target.cpuType() && arch->cpusubtype == 0)
675			return arch;
676	// match for any subarchitecture of the main architecture (questionable)
677	for (const fat_arch *arch = mArchList; arch < end; ++arch)
678		if (arch->cputype == target.cpuType())
679			return arch;
680	// no match
681	UnixError::throwMe(ENOEXEC);	// not found
682}
683
684MachO *Universal::findImage(const Architecture &target) const
685{
686	const fat_arch *arch = findArch(target);
687	return new MachO(*this, mBase + arch->offset, arch->size);
688}
689
690
691//
692// Find the best-matching architecture for this fat file.
693// We pick the native architecture if it's available.
694// If it contains exactly one architecture, we take that.
695// Otherwise, we throw.
696//
697Architecture Universal::bestNativeArch() const
698{
699	if (isUniversal()) {
700		// ask the NXArch API for our native architecture
701		const Architecture native = Architecture::local();
702		if (fat_arch *match = NXFindBestFatArch(native.cpuType(), native.cpuSubtype(), mArchList, mArchCount))
703			return *match;
704		// if the system can't figure it out, pick (arbitrarily) the first one
705		return mArchList[0];
706	} else
707		return mThinArch;
708}
709
710//
711// List all architectures from the fat file's list.
712//
713void Universal::architectures(Architectures &archs) const
714{
715	if (isUniversal()) {
716		for (unsigned n = 0; n < mArchCount; n++)
717			archs.insert(mArchList[n]);
718	} else {
719		auto_ptr<MachO> macho(architecture());
720		archs.insert(macho->architecture());
721	}
722}
723
724//
725// Quickly guess the Mach-O type of a file.
726// Returns type zero if the file isn't Mach-O or Universal.
727// Always looks at the start of the file, and does not change the file pointer.
728//
729uint32_t Universal::typeOf(FileDesc fd)
730{
731	mach_header header;
732	int max_tries = 3;
733	if (fd.read(&header, sizeof(header), 0) != sizeof(header))
734		return 0;
735	while (max_tries > 0) {
736		switch (header.magic) {
737		case MH_MAGIC:
738		case MH_MAGIC_64:
739			return header.filetype;
740			break;
741		case MH_CIGAM:
742		case MH_CIGAM_64:
743			return flip(header.filetype);
744			break;
745		case FAT_MAGIC:
746		case FAT_CIGAM:
747			{
748				const fat_arch *arch1 =
749					LowLevelMemoryUtilities::increment<fat_arch>(&header, sizeof(fat_header));
750				if (fd.read(&header, sizeof(header), ntohl(arch1->offset)) != sizeof(header))
751					return 0;
752				max_tries--;
753				continue;
754			}
755		default:
756			return 0;
757		}
758	}
759    return 0;
760}
761
762//
763// Strict validation
764//
765bool Universal::isSuspicious() const
766{
767	if (mSuspicious)
768		return true;
769	Universal::Architectures archList;
770	architectures(archList);
771	for (Universal::Architectures::const_iterator it = archList.begin(); it != archList.end(); ++it) {
772		auto_ptr<MachO> macho(architecture(*it));
773		if (macho->isSuspicious())
774			return true;
775	}
776	return false;
777}
778
779
780} // Security
781