1 /* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*-
3 * Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
5 * @APPLE_LICENSE_HEADER_START@
7 * This file contains Original Code and/or Modifications of Original Code
8 * as defined in and that are subject to the Apple Public Source License
9 * Version 2.0 (the 'License'). You may not use this file except in
10 * compliance with the License. Please obtain a copy of the License at
11 * http://www.opensource.apple.com/apsl/ and read it before using this
14 * The Original Code and all software distributed under the License are
15 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
19 * Please see the License for the specific language governing rights and
20 * limitations under the License.
22 * @APPLE_LICENSE_HEADER_END@
25 #include <sys/types.h>
32 #include <mach-o/loader.h>
33 #include <mach-o/fat.h>
34 #include <mach-o/stab.h>
38 #include "MachOFileAbstraction.hpp"
39 #include "Architectures.hpp"
42 __attribute__((noreturn
))
43 void throwf(const char* format
, ...)
47 va_start(list
, format
);
48 vasprintf(&p
, format
, list
);
60 static bool validFile(const uint8_t* fileContent
);
61 static MachOChecker
<A
>* make(const uint8_t* fileContent
, uint32_t fileLength
, const char* path
)
62 { return new MachOChecker
<A
>(fileContent
, fileLength
, path
); }
63 virtual ~MachOChecker() {}
67 typedef typename
A::P P
;
68 typedef typename
A::P::E E
;
69 typedef typename
A::P::uint_t pint_t
;
71 MachOChecker(const uint8_t* fileContent
, uint32_t fileLength
, const char* path
);
72 void checkMachHeader();
73 void checkLoadCommands();
74 void checkSection(const macho_segment_command
<P
>* segCmd
, const macho_section
<P
>* sect
);
75 uint8_t loadCommandSizeMask();
76 void checkIndirectSymbolTable();
79 const macho_header
<P
>* fHeader
;
82 const char* fStringsEnd
;
83 const macho_nlist
<P
>* fSymbols
;
84 uint32_t fSymbolCount
;
85 const uint32_t* fIndirectTable
;
86 uint32_t fIndirectTableCount
;
93 bool MachOChecker
<ppc
>::validFile(const uint8_t* fileContent
)
95 const macho_header
<P
>* header
= (const macho_header
<P
>*)fileContent
;
96 if ( header
->magic() != MH_MAGIC
)
98 if ( header
->cputype() != CPU_TYPE_POWERPC
)
100 switch (header
->filetype()) {
111 bool MachOChecker
<ppc64
>::validFile(const uint8_t* fileContent
)
113 const macho_header
<P
>* header
= (const macho_header
<P
>*)fileContent
;
114 if ( header
->magic() != MH_MAGIC_64
)
116 if ( header
->cputype() != CPU_TYPE_POWERPC64
)
118 switch (header
->filetype()) {
129 bool MachOChecker
<x86
>::validFile(const uint8_t* fileContent
)
131 const macho_header
<P
>* header
= (const macho_header
<P
>*)fileContent
;
132 if ( header
->magic() != MH_MAGIC
)
134 if ( header
->cputype() != CPU_TYPE_I386
)
136 switch (header
->filetype()) {
148 template <> uint8_t MachOChecker
<ppc
>::loadCommandSizeMask() { return 0x03; }
149 template <> uint8_t MachOChecker
<ppc64
>::loadCommandSizeMask() { return 0x07; }
150 template <> uint8_t MachOChecker
<x86
>::loadCommandSizeMask() { return 0x03; }
153 template <typename A
>
154 MachOChecker
<A
>::MachOChecker(const uint8_t* fileContent
, uint32_t fileLength
, const char* path
)
155 : fHeader(NULL
), fLength(fileLength
), fStrings(NULL
), fSymbols(NULL
), fSymbolCount(0), fIndirectTableCount(0)
158 if ( ! validFile(fileContent
) )
159 throw "not a mach-o file that can be checked";
161 fPath
= strdup(path
);
162 fHeader
= (const macho_header
<P
>*)fileContent
;
164 // sanity check header
167 // check load commands
170 checkIndirectSymbolTable();
175 template <typename A
>
176 void MachOChecker
<A
>::checkMachHeader()
178 if ( (fHeader
->sizeofcmds() + sizeof(macho_header
<P
>)) > fLength
)
179 throw "sizeofcmds in mach_header is larger than file";
181 uint32_t flags
= fHeader
->flags();
182 uint32_t invalidBits
= MH_INCRLINK
| MH_LAZY_INIT
| 0xFFFC0000;
183 if ( flags
& invalidBits
)
184 throw "invalid bits in mach_header flags";
188 template <typename A
>
189 void MachOChecker
<A
>::checkLoadCommands()
191 // check that all load commands fit within the load command space file
192 const uint8_t* const endOfFile
= (uint8_t*)fHeader
+ fLength
;
193 const uint8_t* const endOfLoadCommands
= (uint8_t*)fHeader
+ sizeof(macho_header
<P
>) + fHeader
->sizeofcmds();
194 const uint32_t cmd_count
= fHeader
->ncmds();
195 const macho_load_command
<P
>* const cmds
= (macho_load_command
<P
>*)((uint8_t*)fHeader
+ sizeof(macho_header
<P
>));
196 const macho_load_command
<P
>* cmd
= cmds
;
197 for (uint32_t i
= 0; i
< cmd_count
; ++i
) {
198 uint32_t size
= cmd
->cmdsize();
199 if ( (size
& this->loadCommandSizeMask()) != 0 )
200 throwf("load command #%d has a unaligned size", i
);
201 const uint8_t* endOfCmd
= ((uint8_t*)cmd
)+cmd
->cmdsize();
202 if ( endOfCmd
> endOfLoadCommands
)
203 throwf("load command #%d extends beyond the end of the load commands", i
);
204 if ( endOfCmd
> endOfFile
)
205 throwf("load command #%d extends beyond the end of the file", i
);
206 switch ( cmd
->cmd() ) {
207 case macho_segment_command
<P
>::CMD
:
213 case LC_LOAD_DYLINKER
:
215 case macho_routines_command
<P
>::CMD
:
216 case LC_SUB_FRAMEWORK
:
217 case LC_SUB_UMBRELLA
:
219 case LC_TWOLEVEL_HINTS
:
220 case LC_PREBIND_CKSUM
:
221 case LC_LOAD_WEAK_DYLIB
:
225 throwf("load command #%d is an unknown kind 0x%X", i
, cmd
->cmd());
227 cmd
= (const macho_load_command
<P
>*)endOfCmd
;
232 std::vector
<std::pair
<pint_t
, pint_t
> > segmentAddressRanges
;
233 std::vector
<std::pair
<pint_t
, pint_t
> > segmentFileOffsetRanges
;
234 const macho_segment_command
<P
>* linkEditSegment
= NULL
;
235 for (uint32_t i
= 0; i
< cmd_count
; ++i
) {
236 if ( cmd
->cmd() == macho_segment_command
<P
>::CMD
) {
237 const macho_segment_command
<P
>* segCmd
= (const macho_segment_command
<P
>*)cmd
;
238 if ( segCmd
->cmdsize() != (sizeof(macho_segment_command
<P
>) + segCmd
->nsects() * sizeof(macho_section_content
<P
>)) )
239 throw "invalid segment load command size";
241 // see if this overlaps another segment address range
242 uint64_t startAddr
= segCmd
->vmaddr();
243 uint64_t endAddr
= startAddr
+ segCmd
->vmsize();
244 for (typename
std::vector
<std::pair
<pint_t
, pint_t
> >::iterator it
= segmentAddressRanges
.begin(); it
!= segmentAddressRanges
.end(); ++it
) {
245 if ( it
->first
< startAddr
) {
246 if ( it
->second
> startAddr
)
247 throw "overlapping segment vm addresses";
249 else if ( it
->first
> startAddr
) {
250 if ( it
->first
< endAddr
)
251 throw "overlapping segment vm addresses";
254 throw "overlapping segment vm addresses";
256 segmentAddressRanges
.push_back(std::make_pair
<pint_t
, pint_t
>(startAddr
, endAddr
));
258 // see if this overlaps another segment file offset range
259 uint64_t startOffset
= segCmd
->fileoff();
260 uint64_t endOffset
= startOffset
+ segCmd
->filesize();
261 for (typename
std::vector
<std::pair
<pint_t
, pint_t
> >::iterator it
= segmentFileOffsetRanges
.begin(); it
!= segmentFileOffsetRanges
.end(); ++it
) {
262 if ( it
->first
< startOffset
) {
263 if ( it
->second
> startOffset
)
264 throw "overlapping segment file data";
266 else if ( it
->first
> startOffset
) {
267 if ( it
->first
< endOffset
)
268 throw "overlapping segment file data";
271 throw "overlapping segment file data";
273 segmentFileOffsetRanges
.push_back(std::make_pair
<pint_t
, pint_t
>(startOffset
, endOffset
));
274 // check is within file bounds
275 if ( (startOffset
> fLength
) || (endOffset
> fLength
) )
276 throw "segment file data is past end of file";
278 // verify it fits in file
279 if ( startOffset
> fLength
)
280 throw "segment fileoff does not fit in file";
281 if ( endOffset
> fLength
)
282 throw "segment fileoff+filesize does not fit in file";
284 // keep LINKEDIT segment
285 if ( strcmp(segCmd
->segname(), "__LINKEDIT") == 0 )
286 linkEditSegment
= segCmd
;
288 // check section ranges
289 const macho_section
<P
>* const sectionsStart
= (macho_section
<P
>*)((char*)segCmd
+ sizeof(macho_segment_command
<P
>));
290 const macho_section
<P
>* const sectionsEnd
= §ionsStart
[segCmd
->nsects()];
291 for(const macho_section
<P
>* sect
= sectionsStart
; sect
< sectionsEnd
; ++sect
) {
292 // check all sections are within segment
293 if ( sect
->addr() < startAddr
)
294 throwf("section %s vm address not within segment", sect
->sectname());
295 if ( (sect
->addr()+sect
->size()) > endAddr
)
296 throwf("section %s vm address not within segment", sect
->sectname());
297 if ( (sect
->flags() &SECTION_TYPE
) != S_ZEROFILL
) {
298 if ( sect
->offset() < startOffset
)
299 throwf("section %s file offset not within segment", sect
->sectname());
300 if ( (sect
->offset()+sect
->size()) > endOffset
)
301 throwf("section %s file offset not within segment", sect
->sectname());
303 checkSection(segCmd
, sect
);
306 cmd
= (const macho_load_command
<P
>*)(((uint8_t*)cmd
)+cmd
->cmdsize());
309 // verify there was a LINKEDIT segment
310 if ( linkEditSegment
== NULL
)
311 throw "no __LINKEDIT segment";
313 // checks for executables
314 bool isStaticExecutable
= false;
315 if ( fHeader
->filetype() == MH_EXECUTE
) {
316 isStaticExecutable
= true;
318 for (uint32_t i
= 0; i
< cmd_count
; ++i
) {
319 switch ( cmd
->cmd() ) {
320 case LC_LOAD_DYLINKER
:
321 // the existence of a dyld load command makes a executable dynamic
322 isStaticExecutable
= false;
325 cmd
= (const macho_load_command
<P
>*)(((uint8_t*)cmd
)+cmd
->cmdsize());
327 if ( isStaticExecutable
) {
328 if ( fHeader
->flags() != MH_NOUNDEFS
)
329 throw "invalid bits in mach_header flags for static executable";
333 // check LC_SYMTAB and LC_DYSYMTAB
335 bool foundDynamicSymTab
= false;
336 for (uint32_t i
= 0; i
< cmd_count
; ++i
) {
337 switch ( cmd
->cmd() ) {
340 const macho_symtab_command
<P
>* symtab
= (macho_symtab_command
<P
>*)cmd
;
341 fSymbolCount
= symtab
->nsyms();
342 fSymbols
= (const macho_nlist
<P
>*)((char*)fHeader
+ symtab
->symoff());
343 if ( symtab
->symoff() < linkEditSegment
->fileoff() )
344 throw "symbol table not in __LINKEDIT";
345 if ( (symtab
->symoff() + fSymbolCount
*sizeof(macho_nlist
<P
>*)) > (linkEditSegment
->fileoff()+linkEditSegment
->filesize()) )
346 throw "symbol table end not in __LINKEDIT";
347 fStrings
= (char*)fHeader
+ symtab
->stroff();
348 fStringsEnd
= fStrings
+ symtab
->strsize();
349 if ( symtab
->stroff() < linkEditSegment
->fileoff() )
350 throw "string pool not in __LINKEDIT";
351 if ( (symtab
->stroff()+symtab
->strsize()) > (linkEditSegment
->fileoff()+linkEditSegment
->filesize()) )
352 throw "string pool extends beyond __LINKEDIT";
357 if ( isStaticExecutable
)
358 throw "LC_DYSYMTAB should not be used in static executable";
359 foundDynamicSymTab
= true;
360 const macho_dysymtab_command
<P
>* dsymtab
= (struct macho_dysymtab_command
<P
>*)cmd
;
361 fIndirectTable
= (uint32_t*)((char*)fHeader
+ dsymtab
->indirectsymoff());
362 fIndirectTableCount
= dsymtab
->nindirectsyms();
363 if ( dsymtab
->indirectsymoff() < linkEditSegment
->fileoff() )
364 throw "indirect symbol table not in __LINKEDIT";
365 if ( (dsymtab
->indirectsymoff()+fIndirectTableCount
*8) > (linkEditSegment
->fileoff()+linkEditSegment
->filesize()) )
366 throw "indirect symbol table not in __LINKEDIT";
370 cmd
= (const macho_load_command
<P
>*)(((uint8_t*)cmd
)+cmd
->cmdsize());
372 if ( !isStaticExecutable
&& !foundDynamicSymTab
)
373 throw "missing dynamic symbol table";
374 if ( fStrings
== NULL
)
375 throw "missing symbol table";
381 template <typename A
>
382 void MachOChecker
<A
>::checkSection(const macho_segment_command
<P
>* segCmd
, const macho_section
<P
>* sect
)
384 uint8_t sectionType
= (sect
->flags() & SECTION_TYPE
);
385 if ( sectionType
== S_ZEROFILL
) {
386 if ( sect
->offset() != 0 )
387 throwf("section offset should be zero for zero-fill section %s", sect
->sectname());
390 // more section tests here
393 template <typename A
>
394 void MachOChecker
<A
>::checkIndirectSymbolTable()
396 const macho_load_command
<P
>* const cmds
= (macho_load_command
<P
>*)((uint8_t*)fHeader
+ sizeof(macho_header
<P
>));
397 const uint32_t cmd_count
= fHeader
->ncmds();
398 const macho_load_command
<P
>* cmd
= cmds
;
399 for (uint32_t i
= 0; i
< cmd_count
; ++i
) {
400 if ( cmd
->cmd() == macho_segment_command
<P
>::CMD
) {
401 const macho_segment_command
<P
>* segCmd
= (const macho_segment_command
<P
>*)cmd
;
402 const macho_section
<P
>* const sectionsStart
= (macho_section
<P
>*)((char*)segCmd
+ sizeof(macho_segment_command
<P
>));
403 const macho_section
<P
>* const sectionsEnd
= §ionsStart
[segCmd
->nsects()];
404 for(const macho_section
<P
>* sect
= sectionsStart
; sect
< sectionsEnd
; ++sect
) {
405 // make sure all magic sections that use indirect symbol table fit within it
407 uint32_t elementSize
= 0;
408 switch ( sect
->flags() & SECTION_TYPE
) {
410 elementSize
= sect
->reserved2();
411 start
= sect
->reserved1();
413 case S_LAZY_SYMBOL_POINTERS
:
414 case S_NON_LAZY_SYMBOL_POINTERS
:
415 elementSize
= sizeof(pint_t
);
416 start
= sect
->reserved1();
419 if ( elementSize
!= 0 ) {
420 uint32_t count
= sect
->size() / elementSize
;
421 if ( (count
*elementSize
) != sect
->size() )
422 throwf("%s section size is not an even multiple of element size", sect
->sectname());
423 if ( (start
+count
) > fIndirectTableCount
)
424 throwf("%s section references beyond end of indirect symbol table (%d > %d)", sect
->sectname(), start
+count
, fIndirectTableCount
);
428 cmd
= (const macho_load_command
<P
>*)(((uint8_t*)cmd
)+cmd
->cmdsize());
433 static void check(const char* path
)
435 struct stat stat_buf
;
438 int fd
= ::open(path
, O_RDONLY
, 0);
440 throw "cannot open file";
441 ::fstat(fd
, &stat_buf
);
442 uint32_t length
= stat_buf
.st_size
;
443 uint8_t* p
= (uint8_t*)::mmap(NULL
, stat_buf
.st_size
, PROT_READ
, MAP_FILE
, fd
, 0);
445 const mach_header
* mh
= (mach_header
*)p
;
446 if ( mh
->magic
== OSSwapBigToHostInt32(FAT_MAGIC
) ) {
447 const struct fat_header
* fh
= (struct fat_header
*)p
;
448 const struct fat_arch
* archs
= (struct fat_arch
*)(p
+ sizeof(struct fat_header
));
449 for (unsigned long i
=0; i
< fh
->nfat_arch
; ++i
) {
450 if ( archs
[i
].cputype
== CPU_TYPE_POWERPC
) {
451 if ( MachOChecker
<ppc
>::validFile(p
+ archs
[i
].offset
) )
452 MachOChecker
<ppc
>::make(p
+ archs
[i
].offset
, archs
[i
].size
, path
);
454 throw "in universal file, ppc slice does not contain ppc mach-o";
456 else if ( archs
[i
].cputype
== CPU_TYPE_I386
) {
457 if ( MachOChecker
<x86
>::validFile(p
+ archs
[i
].offset
) )
458 MachOChecker
<x86
>::make(p
+ archs
[i
].offset
, archs
[i
].size
, path
);
460 throw "in universal file, i386 slice does not contain i386 mach-o";
462 else if ( archs
[i
].cputype
== CPU_TYPE_POWERPC64
) {
463 if ( MachOChecker
<ppc64
>::validFile(p
+ archs
[i
].offset
) )
464 MachOChecker
<ppc64
>::make(p
+ archs
[i
].offset
, archs
[i
].size
, path
);
466 throw "in universal file, ppc64 slice does not contain ppc64 mach-o";
469 throw "in universal file, unknown architecture slice";
473 else if ( MachOChecker
<x86
>::validFile(p
) ) {
474 MachOChecker
<x86
>::make(p
, length
, path
);
476 else if ( MachOChecker
<ppc
>::validFile(p
) ) {
477 MachOChecker
<ppc
>::make(p
, length
, path
);
479 else if ( MachOChecker
<ppc64
>::validFile(p
) ) {
480 MachOChecker
<ppc64
>::make(p
, length
, path
);
483 throw "not a known file type";
486 catch (const char* msg
) {
487 throwf("%s in %s", msg
, path
);
492 int main(int argc
, const char* argv
[])
495 for(int i
=1; i
< argc
; ++i
) {
496 const char* arg
= argv
[i
];
497 if ( arg
[0] == '-' ) {
498 if ( strcmp(arg
, "-no_content") == 0 ) {
502 throwf("unknown option: %s\n", arg
);
510 catch (const char* msg
) {
511 fprintf(stderr
, "machocheck failed: %s\n", msg
);