]> git.saurik.com Git - apple/ld64.git/blame_incremental - src/machochecker.cpp
ld64-84.1.2.tar.gz
[apple/ld64.git] / src / machochecker.cpp
... / ...
CommitLineData
1/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*-
2 *
3 * Copyright (c) 2006-2007 Apple Inc. All rights reserved.
4 *
5 * @APPLE_LICENSE_HEADER_START@
6 *
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
12 * file.
13 *
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.
21 *
22 * @APPLE_LICENSE_HEADER_END@
23 */
24
25#include <sys/types.h>
26#include <sys/stat.h>
27#include <sys/mman.h>
28#include <stdarg.h>
29#include <stdio.h>
30#include <fcntl.h>
31#include <unistd.h>
32
33#include <vector>
34#include <set>
35#include <ext/hash_set>
36
37#include "MachOFileAbstraction.hpp"
38#include "Architectures.hpp"
39
40
41 __attribute__((noreturn))
42void throwf(const char* format, ...)
43{
44 va_list list;
45 char* p;
46 va_start(list, format);
47 vasprintf(&p, format, list);
48 va_end(list);
49
50 const char* t = p;
51 throw t;
52}
53
54
55template <typename A>
56class MachOChecker
57{
58public:
59 static bool validFile(const uint8_t* fileContent);
60 static MachOChecker<A>* make(const uint8_t* fileContent, uint32_t fileLength, const char* path)
61 { return new MachOChecker<A>(fileContent, fileLength, path); }
62 virtual ~MachOChecker() {}
63
64
65private:
66 typedef typename A::P P;
67 typedef typename A::P::E E;
68 typedef typename A::P::uint_t pint_t;
69
70 class CStringEquals
71 {
72 public:
73 bool operator()(const char* left, const char* right) const { return (strcmp(left, right) == 0); }
74 };
75
76 typedef __gnu_cxx::hash_set<const char*, __gnu_cxx::hash<const char*>, CStringEquals> StringSet;
77
78 MachOChecker(const uint8_t* fileContent, uint32_t fileLength, const char* path);
79 void checkMachHeader();
80 void checkLoadCommands();
81 void checkSection(const macho_segment_command<P>* segCmd, const macho_section<P>* sect);
82 uint8_t loadCommandSizeMask();
83 void checkSymbolTable();
84 void checkIndirectSymbolTable();
85 void checkRelocations();
86 void checkExternalReloation(const macho_relocation_info<P>* reloc);
87 void checkLocalReloation(const macho_relocation_info<P>* reloc);
88 pint_t relocBase();
89 bool addressInWritableSegment(pint_t address);
90
91 const char* fPath;
92 const macho_header<P>* fHeader;
93 uint32_t fLength;
94 const char* fStrings;
95 const char* fStringsEnd;
96 const macho_nlist<P>* fSymbols;
97 uint32_t fSymbolCount;
98 const macho_dysymtab_command<P>* fDynamicSymbolTable;
99 const uint32_t* fIndirectTable;
100 uint32_t fIndirectTableCount;
101 const macho_relocation_info<P>* fLocalRelocations;
102 uint32_t fLocalRelocationsCount;
103 const macho_relocation_info<P>* fExternalRelocations;
104 uint32_t fExternalRelocationsCount;
105 bool fWriteableSegmentWithAddrOver4G;
106 const macho_segment_command<P>* fFirstSegment;
107 const macho_segment_command<P>* fFirstWritableSegment;
108};
109
110
111
112template <>
113bool MachOChecker<ppc>::validFile(const uint8_t* fileContent)
114{
115 const macho_header<P>* header = (const macho_header<P>*)fileContent;
116 if ( header->magic() != MH_MAGIC )
117 return false;
118 if ( header->cputype() != CPU_TYPE_POWERPC )
119 return false;
120 switch (header->filetype()) {
121 case MH_EXECUTE:
122 case MH_DYLIB:
123 case MH_BUNDLE:
124 case MH_DYLINKER:
125 return true;
126 }
127 return false;
128}
129
130template <>
131bool MachOChecker<ppc64>::validFile(const uint8_t* fileContent)
132{
133 const macho_header<P>* header = (const macho_header<P>*)fileContent;
134 if ( header->magic() != MH_MAGIC_64 )
135 return false;
136 if ( header->cputype() != CPU_TYPE_POWERPC64 )
137 return false;
138 switch (header->filetype()) {
139 case MH_EXECUTE:
140 case MH_DYLIB:
141 case MH_BUNDLE:
142 case MH_DYLINKER:
143 return true;
144 }
145 return false;
146}
147
148template <>
149bool MachOChecker<x86>::validFile(const uint8_t* fileContent)
150{
151 const macho_header<P>* header = (const macho_header<P>*)fileContent;
152 if ( header->magic() != MH_MAGIC )
153 return false;
154 if ( header->cputype() != CPU_TYPE_I386 )
155 return false;
156 switch (header->filetype()) {
157 case MH_EXECUTE:
158 case MH_DYLIB:
159 case MH_BUNDLE:
160 case MH_DYLINKER:
161 return true;
162 }
163 return false;
164}
165
166template <>
167bool MachOChecker<x86_64>::validFile(const uint8_t* fileContent)
168{
169 const macho_header<P>* header = (const macho_header<P>*)fileContent;
170 if ( header->magic() != MH_MAGIC_64 )
171 return false;
172 if ( header->cputype() != CPU_TYPE_X86_64 )
173 return false;
174 switch (header->filetype()) {
175 case MH_EXECUTE:
176 case MH_DYLIB:
177 case MH_BUNDLE:
178 case MH_DYLINKER:
179 return true;
180 }
181 return false;
182}
183
184template <>
185bool MachOChecker<arm>::validFile(const uint8_t* fileContent)
186{
187 const macho_header<P>* header = (const macho_header<P>*)fileContent;
188 if ( header->magic() != MH_MAGIC )
189 return false;
190 if ( header->cputype() != CPU_TYPE_ARM )
191 return false;
192 switch (header->filetype()) {
193 case MH_EXECUTE:
194 case MH_DYLIB:
195 case MH_BUNDLE:
196 case MH_DYLINKER:
197 return true;
198 }
199 return false;
200}
201
202template <> uint8_t MachOChecker<ppc>::loadCommandSizeMask() { return 0x03; }
203template <> uint8_t MachOChecker<ppc64>::loadCommandSizeMask() { return 0x07; }
204template <> uint8_t MachOChecker<x86>::loadCommandSizeMask() { return 0x03; }
205template <> uint8_t MachOChecker<x86_64>::loadCommandSizeMask() { return 0x07; }
206template <> uint8_t MachOChecker<arm>::loadCommandSizeMask() { return 0x03; }
207
208template <typename A>
209MachOChecker<A>::MachOChecker(const uint8_t* fileContent, uint32_t fileLength, const char* path)
210 : fHeader(NULL), fLength(fileLength), fStrings(NULL), fSymbols(NULL), fSymbolCount(0), fDynamicSymbolTable(NULL), fIndirectTableCount(0),
211 fLocalRelocations(NULL), fLocalRelocationsCount(0), fExternalRelocations(NULL), fExternalRelocationsCount(0),
212 fWriteableSegmentWithAddrOver4G(false), fFirstSegment(NULL), fFirstWritableSegment(NULL)
213{
214 // sanity check
215 if ( ! validFile(fileContent) )
216 throw "not a mach-o file that can be checked";
217
218 fPath = strdup(path);
219 fHeader = (const macho_header<P>*)fileContent;
220
221 // sanity check header
222 checkMachHeader();
223
224 // check load commands
225 checkLoadCommands();
226
227 checkIndirectSymbolTable();
228
229 checkRelocations();
230
231 checkSymbolTable();
232}
233
234
235template <typename A>
236void MachOChecker<A>::checkMachHeader()
237{
238 if ( (fHeader->sizeofcmds() + sizeof(macho_header<P>)) > fLength )
239 throw "sizeofcmds in mach_header is larger than file";
240
241 uint32_t flags = fHeader->flags();
242 const uint32_t invalidBits = MH_INCRLINK | MH_LAZY_INIT | 0xFFE00000;
243 if ( flags & invalidBits )
244 throw "invalid bits in mach_header flags";
245 if ( (flags & MH_NO_REEXPORTED_DYLIBS) && (fHeader->filetype() != MH_DYLIB) )
246 throw "MH_NO_REEXPORTED_DYLIBS bit of mach_header flags only valid for dylibs";
247}
248
249template <typename A>
250void MachOChecker<A>::checkLoadCommands()
251{
252 // check that all load commands fit within the load command space file
253 const macho_encryption_info_command<P>* encryption_info = NULL;
254 const uint8_t* const endOfFile = (uint8_t*)fHeader + fLength;
255 const uint8_t* const endOfLoadCommands = (uint8_t*)fHeader + sizeof(macho_header<P>) + fHeader->sizeofcmds();
256 const uint32_t cmd_count = fHeader->ncmds();
257 const macho_load_command<P>* const cmds = (macho_load_command<P>*)((uint8_t*)fHeader + sizeof(macho_header<P>));
258 const macho_load_command<P>* cmd = cmds;
259 for (uint32_t i = 0; i < cmd_count; ++i) {
260 uint32_t size = cmd->cmdsize();
261 if ( (size & this->loadCommandSizeMask()) != 0 )
262 throwf("load command #%d has a unaligned size", i);
263 const uint8_t* endOfCmd = ((uint8_t*)cmd)+cmd->cmdsize();
264 if ( endOfCmd > endOfLoadCommands )
265 throwf("load command #%d extends beyond the end of the load commands", i);
266 if ( endOfCmd > endOfFile )
267 throwf("load command #%d extends beyond the end of the file", i);
268 switch ( cmd->cmd() ) {
269 case macho_segment_command<P>::CMD:
270 case LC_SYMTAB:
271 case LC_UNIXTHREAD:
272 case LC_DYSYMTAB:
273 case LC_LOAD_DYLIB:
274 case LC_ID_DYLIB:
275 case LC_LOAD_DYLINKER:
276 case LC_ID_DYLINKER:
277 case macho_routines_command<P>::CMD:
278 case LC_SUB_FRAMEWORK:
279 case LC_SUB_CLIENT:
280 case LC_TWOLEVEL_HINTS:
281 case LC_PREBIND_CKSUM:
282 case LC_LOAD_WEAK_DYLIB:
283 case LC_LAZY_LOAD_DYLIB:
284 case LC_UUID:
285 case LC_REEXPORT_DYLIB:
286 case LC_SEGMENT_SPLIT_INFO:
287 case LC_CODE_SIGNATURE:
288 break;
289 case LC_ENCRYPTION_INFO:
290 encryption_info = (macho_encryption_info_command<P>*)cmd;
291 break;
292 case LC_SUB_UMBRELLA:
293 case LC_SUB_LIBRARY:
294 if ( fHeader->flags() & MH_NO_REEXPORTED_DYLIBS )
295 throw "MH_NO_REEXPORTED_DYLIBS bit of mach_header flags should not be set in an image with LC_SUB_LIBRARY or LC_SUB_UMBRELLA";
296 break;
297 default:
298 throwf("load command #%d is an unknown kind 0x%X", i, cmd->cmd());
299 }
300 cmd = (const macho_load_command<P>*)endOfCmd;
301 }
302
303 // check segments
304 cmd = cmds;
305 std::vector<std::pair<pint_t, pint_t> > segmentAddressRanges;
306 std::vector<std::pair<pint_t, pint_t> > segmentFileOffsetRanges;
307 const macho_segment_command<P>* linkEditSegment = NULL;
308 for (uint32_t i = 0; i < cmd_count; ++i) {
309 if ( cmd->cmd() == macho_segment_command<P>::CMD ) {
310 const macho_segment_command<P>* segCmd = (const macho_segment_command<P>*)cmd;
311 if ( segCmd->cmdsize() != (sizeof(macho_segment_command<P>) + segCmd->nsects() * sizeof(macho_section_content<P>)) )
312 throw "invalid segment load command size";
313
314 // see if this overlaps another segment address range
315 uint64_t startAddr = segCmd->vmaddr();
316 uint64_t endAddr = startAddr + segCmd->vmsize();
317 for (typename std::vector<std::pair<pint_t, pint_t> >::iterator it = segmentAddressRanges.begin(); it != segmentAddressRanges.end(); ++it) {
318 if ( it->first < startAddr ) {
319 if ( it->second > startAddr )
320 throw "overlapping segment vm addresses";
321 }
322 else if ( it->first > startAddr ) {
323 if ( it->first < endAddr )
324 throw "overlapping segment vm addresses";
325 }
326 else {
327 throw "overlapping segment vm addresses";
328 }
329 segmentAddressRanges.push_back(std::make_pair<pint_t, pint_t>(startAddr, endAddr));
330 }
331 // see if this overlaps another segment file offset range
332 uint64_t startOffset = segCmd->fileoff();
333 uint64_t endOffset = startOffset + segCmd->filesize();
334 for (typename std::vector<std::pair<pint_t, pint_t> >::iterator it = segmentFileOffsetRanges.begin(); it != segmentFileOffsetRanges.end(); ++it) {
335 if ( it->first < startOffset ) {
336 if ( it->second > startOffset )
337 throw "overlapping segment file data";
338 }
339 else if ( it->first > startOffset ) {
340 if ( it->first < endOffset )
341 throw "overlapping segment file data";
342 }
343 else {
344 throw "overlapping segment file data";
345 }
346 segmentFileOffsetRanges.push_back(std::make_pair<pint_t, pint_t>(startOffset, endOffset));
347 // check is within file bounds
348 if ( (startOffset > fLength) || (endOffset > fLength) )
349 throw "segment file data is past end of file";
350 }
351 // verify it fits in file
352 if ( startOffset > fLength )
353 throw "segment fileoff does not fit in file";
354 if ( endOffset > fLength )
355 throw "segment fileoff+filesize does not fit in file";
356
357 // keep LINKEDIT segment
358 if ( strcmp(segCmd->segname(), "__LINKEDIT") == 0 )
359 linkEditSegment = segCmd;
360
361 // cache interesting segments
362 if ( fFirstSegment == NULL )
363 fFirstSegment = segCmd;
364 if ( (segCmd->initprot() & VM_PROT_WRITE) != 0 ) {
365 if ( fFirstWritableSegment == NULL )
366 fFirstWritableSegment = segCmd;
367 if ( segCmd->vmaddr() > 0x100000000ULL )
368 fWriteableSegmentWithAddrOver4G = true;
369 }
370
371 // check section ranges
372 const macho_section<P>* const sectionsStart = (macho_section<P>*)((char*)segCmd + sizeof(macho_segment_command<P>));
373 const macho_section<P>* const sectionsEnd = &sectionsStart[segCmd->nsects()];
374 for(const macho_section<P>* sect = sectionsStart; sect < sectionsEnd; ++sect) {
375 // check all sections are within segment
376 if ( sect->addr() < startAddr )
377 throwf("section %s vm address not within segment", sect->sectname());
378 if ( (sect->addr()+sect->size()) > endAddr )
379 throwf("section %s vm address not within segment", sect->sectname());
380 if ( ((sect->flags() & SECTION_TYPE) != S_ZEROFILL) && (segCmd->filesize() != 0) ) {
381 if ( sect->offset() < startOffset )
382 throwf("section %s file offset not within segment", sect->sectname());
383 if ( (sect->offset()+sect->size()) > endOffset )
384 throwf("section %s file offset not within segment", sect->sectname());
385 }
386 checkSection(segCmd, sect);
387 }
388 }
389 cmd = (const macho_load_command<P>*)(((uint8_t*)cmd)+cmd->cmdsize());
390 }
391
392 // verify there was a LINKEDIT segment
393 if ( linkEditSegment == NULL )
394 throw "no __LINKEDIT segment";
395
396 // checks for executables
397 bool isStaticExecutable = false;
398 if ( fHeader->filetype() == MH_EXECUTE ) {
399 isStaticExecutable = true;
400 cmd = cmds;
401 for (uint32_t i = 0; i < cmd_count; ++i) {
402 switch ( cmd->cmd() ) {
403 case LC_LOAD_DYLINKER:
404 // the existence of a dyld load command makes a executable dynamic
405 isStaticExecutable = false;
406 break;
407 }
408 cmd = (const macho_load_command<P>*)(((uint8_t*)cmd)+cmd->cmdsize());
409 }
410 if ( isStaticExecutable ) {
411 if ( fHeader->flags() != MH_NOUNDEFS )
412 throw "invalid bits in mach_header flags for static executable";
413 }
414 }
415
416 // verify encryption info
417 if ( encryption_info != NULL ) {
418 if ( fHeader->filetype() != MH_EXECUTE )
419 throw "LC_ENCRYPTION_INFO load command is only legal in main executables";
420 if ( encryption_info->cryptoff() < (sizeof(macho_header<P>) + fHeader->sizeofcmds()) )
421 throw "LC_ENCRYPTION_INFO load command has cryptoff covers some load commands";
422 if ( (encryption_info->cryptoff() % 4096) != 0 )
423 throw "LC_ENCRYPTION_INFO load command has cryptoff which is not page aligned";
424 if ( (encryption_info->cryptsize() % 4096) != 0 )
425 throw "LC_ENCRYPTION_INFO load command has cryptsize which is not page sized";
426 for (typename std::vector<std::pair<pint_t, pint_t> >::iterator it = segmentFileOffsetRanges.begin();
427 it != segmentFileOffsetRanges.end(); ++it) {
428 if ( (it->first <= encryption_info->cryptoff()) && (encryption_info->cryptoff() < it->second) ) {
429 if ( (encryption_info->cryptoff() + encryption_info->cryptsize()) > it->second )
430 throw "LC_ENCRYPTION_INFO load command is not contained within one segment";
431 }
432 }
433 }
434
435 // check LC_SYMTAB, LC_DYSYMTAB, and LC_SEGMENT_SPLIT_INFO
436 cmd = cmds;
437 bool foundDynamicSymTab = false;
438 for (uint32_t i = 0; i < cmd_count; ++i) {
439 switch ( cmd->cmd() ) {
440 case LC_SYMTAB:
441 {
442 const macho_symtab_command<P>* symtab = (macho_symtab_command<P>*)cmd;
443 fSymbolCount = symtab->nsyms();
444 fSymbols = (const macho_nlist<P>*)((char*)fHeader + symtab->symoff());
445 if ( symtab->symoff() < linkEditSegment->fileoff() )
446 throw "symbol table not in __LINKEDIT";
447 if ( (symtab->symoff() + fSymbolCount*sizeof(macho_nlist<P>*)) > (linkEditSegment->fileoff()+linkEditSegment->filesize()) )
448 throw "symbol table end not in __LINKEDIT";
449 if ( (symtab->symoff() % sizeof(pint_t)) != 0 )
450 throw "symbol table start not pointer aligned";
451 fStrings = (char*)fHeader + symtab->stroff();
452 fStringsEnd = fStrings + symtab->strsize();
453 if ( symtab->stroff() < linkEditSegment->fileoff() )
454 throw "string pool not in __LINKEDIT";
455 if ( (symtab->stroff()+symtab->strsize()) > (linkEditSegment->fileoff()+linkEditSegment->filesize()) )
456 throw "string pool extends beyond __LINKEDIT";
457 if ( (symtab->stroff() % 4) != 0 ) // work around until rdar://problem/4737991 is fixed
458 throw "string pool start not pointer aligned";
459 if ( (symtab->strsize() % sizeof(pint_t)) != 0 )
460 throw "string pool size not a multiple of pointer size";
461 }
462 break;
463 case LC_DYSYMTAB:
464 {
465 if ( isStaticExecutable )
466 throw "LC_DYSYMTAB should not be used in static executable";
467 foundDynamicSymTab = true;
468 fDynamicSymbolTable = (struct macho_dysymtab_command<P>*)cmd;
469 fIndirectTable = (uint32_t*)((char*)fHeader + fDynamicSymbolTable->indirectsymoff());
470 fIndirectTableCount = fDynamicSymbolTable->nindirectsyms();
471 if ( fIndirectTableCount != 0 ) {
472 if ( fDynamicSymbolTable->indirectsymoff() < linkEditSegment->fileoff() )
473 throw "indirect symbol table not in __LINKEDIT";
474 if ( (fDynamicSymbolTable->indirectsymoff()+fIndirectTableCount*8) > (linkEditSegment->fileoff()+linkEditSegment->filesize()) )
475 throw "indirect symbol table not in __LINKEDIT";
476 if ( (fDynamicSymbolTable->indirectsymoff() % sizeof(pint_t)) != 0 )
477 throw "indirect symbol table not pointer aligned";
478 }
479 fLocalRelocationsCount = fDynamicSymbolTable->nlocrel();
480 if ( fLocalRelocationsCount != 0 ) {
481 fLocalRelocations = (const macho_relocation_info<P>*)((char*)fHeader + fDynamicSymbolTable->locreloff());
482 if ( fDynamicSymbolTable->locreloff() < linkEditSegment->fileoff() )
483 throw "local relocations not in __LINKEDIT";
484 if ( (fDynamicSymbolTable->locreloff()+fLocalRelocationsCount*sizeof(macho_relocation_info<P>)) > (linkEditSegment->fileoff()+linkEditSegment->filesize()) )
485 throw "local relocations not in __LINKEDIT";
486 if ( (fDynamicSymbolTable->locreloff() % sizeof(pint_t)) != 0 )
487 throw "local relocations table not pointer aligned";
488 }
489 fExternalRelocationsCount = fDynamicSymbolTable->nextrel();
490 if ( fExternalRelocationsCount != 0 ) {
491 fExternalRelocations = (const macho_relocation_info<P>*)((char*)fHeader + fDynamicSymbolTable->extreloff());
492 if ( fDynamicSymbolTable->extreloff() < linkEditSegment->fileoff() )
493 throw "external relocations not in __LINKEDIT";
494 if ( (fDynamicSymbolTable->extreloff()+fExternalRelocationsCount*sizeof(macho_relocation_info<P>)) > (linkEditSegment->fileoff()+linkEditSegment->filesize()) )
495 throw "external relocations not in __LINKEDIT";
496 if ( (fDynamicSymbolTable->extreloff() % sizeof(pint_t)) != 0 )
497 throw "external relocations table not pointer aligned";
498 }
499 }
500 break;
501 case LC_SEGMENT_SPLIT_INFO:
502 {
503 if ( isStaticExecutable )
504 throw "LC_SEGMENT_SPLIT_INFO should not be used in static executable";
505 const macho_linkedit_data_command<P>* info = (struct macho_linkedit_data_command<P>*)cmd;
506 if ( info->dataoff() < linkEditSegment->fileoff() )
507 throw "split seg info not in __LINKEDIT";
508 if ( (info->dataoff()+info->datasize()) > (linkEditSegment->fileoff()+linkEditSegment->filesize()) )
509 throw "split seg info not in __LINKEDIT";
510 if ( (info->dataoff() % sizeof(pint_t)) != 0 )
511 throw "split seg info table not pointer aligned";
512 if ( (info->datasize() % sizeof(pint_t)) != 0 )
513 throw "split seg info size not a multiple of pointer size";
514 }
515 break;
516 }
517 cmd = (const macho_load_command<P>*)(((uint8_t*)cmd)+cmd->cmdsize());
518 }
519 if ( !isStaticExecutable && !foundDynamicSymTab )
520 throw "missing dynamic symbol table";
521 if ( fStrings == NULL )
522 throw "missing symbol table";
523
524}
525
526template <typename A>
527void MachOChecker<A>::checkSection(const macho_segment_command<P>* segCmd, const macho_section<P>* sect)
528{
529 uint8_t sectionType = (sect->flags() & SECTION_TYPE);
530 if ( sectionType == S_ZEROFILL ) {
531 if ( sect->offset() != 0 )
532 throwf("section offset should be zero for zero-fill section %s", sect->sectname());
533 }
534
535 // more section tests here
536}
537
538template <typename A>
539void MachOChecker<A>::checkIndirectSymbolTable()
540{
541 const macho_load_command<P>* const cmds = (macho_load_command<P>*)((uint8_t*)fHeader + sizeof(macho_header<P>));
542 const uint32_t cmd_count = fHeader->ncmds();
543 const macho_load_command<P>* cmd = cmds;
544 for (uint32_t i = 0; i < cmd_count; ++i) {
545 if ( cmd->cmd() == macho_segment_command<P>::CMD ) {
546 const macho_segment_command<P>* segCmd = (const macho_segment_command<P>*)cmd;
547 const macho_section<P>* const sectionsStart = (macho_section<P>*)((char*)segCmd + sizeof(macho_segment_command<P>));
548 const macho_section<P>* const sectionsEnd = &sectionsStart[segCmd->nsects()];
549 for(const macho_section<P>* sect = sectionsStart; sect < sectionsEnd; ++sect) {
550 // make sure all magic sections that use indirect symbol table fit within it
551 uint32_t start = 0;
552 uint32_t elementSize = 0;
553 switch ( sect->flags() & SECTION_TYPE ) {
554 case S_SYMBOL_STUBS:
555 elementSize = sect->reserved2();
556 start = sect->reserved1();
557 break;
558 case S_LAZY_SYMBOL_POINTERS:
559 case S_NON_LAZY_SYMBOL_POINTERS:
560 elementSize = sizeof(pint_t);
561 start = sect->reserved1();
562 break;
563 }
564 if ( elementSize != 0 ) {
565 uint32_t count = sect->size() / elementSize;
566 if ( (count*elementSize) != sect->size() )
567 throwf("%s section size is not an even multiple of element size", sect->sectname());
568 if ( (start+count) > fIndirectTableCount )
569 throwf("%s section references beyond end of indirect symbol table (%d > %d)", sect->sectname(), start+count, fIndirectTableCount );
570 }
571 }
572 }
573 cmd = (const macho_load_command<P>*)(((uint8_t*)cmd)+cmd->cmdsize());
574 }
575}
576
577
578template <typename A>
579void MachOChecker<A>::checkSymbolTable()
580{
581 // verify no duplicate external symbol names
582 if ( fDynamicSymbolTable != NULL ) {
583 StringSet externalNames;
584 const macho_nlist<P>* const exportedStart = &fSymbols[fDynamicSymbolTable->iextdefsym()];
585 const macho_nlist<P>* const exportedEnd = &exportedStart[fDynamicSymbolTable->nextdefsym()];
586 for(const macho_nlist<P>* p = exportedStart; p < exportedEnd; ++p) {
587 const char* symName = &fStrings[p->n_strx()];
588 if ( externalNames.find(symName) != externalNames.end() )
589 throwf("duplicate external symbol: %s", symName);
590 externalNames.insert(symName);
591 }
592 }
593}
594
595
596template <>
597ppc::P::uint_t MachOChecker<ppc>::relocBase()
598{
599 if ( fHeader->flags() & MH_SPLIT_SEGS )
600 return fFirstWritableSegment->vmaddr();
601 else
602 return fFirstSegment->vmaddr();
603}
604
605template <>
606ppc64::P::uint_t MachOChecker<ppc64>::relocBase()
607{
608 if ( fWriteableSegmentWithAddrOver4G )
609 return fFirstWritableSegment->vmaddr();
610 else
611 return fFirstSegment->vmaddr();
612}
613
614template <>
615x86::P::uint_t MachOChecker<x86>::relocBase()
616{
617 if ( fHeader->flags() & MH_SPLIT_SEGS )
618 return fFirstWritableSegment->vmaddr();
619 else
620 return fFirstSegment->vmaddr();
621}
622
623template <>
624x86_64::P::uint_t MachOChecker<x86_64>::relocBase()
625{
626 // check for split-seg
627 return fFirstWritableSegment->vmaddr();
628}
629
630template <>
631arm::P::uint_t MachOChecker<arm>::relocBase()
632{
633 if ( fHeader->flags() & MH_SPLIT_SEGS )
634 return fFirstWritableSegment->vmaddr();
635 else
636 return fFirstSegment->vmaddr();
637}
638
639
640template <typename A>
641bool MachOChecker<A>::addressInWritableSegment(pint_t address)
642{
643 const macho_load_command<P>* const cmds = (macho_load_command<P>*)((uint8_t*)fHeader + sizeof(macho_header<P>));
644 const uint32_t cmd_count = fHeader->ncmds();
645 const macho_load_command<P>* cmd = cmds;
646 for (uint32_t i = 0; i < cmd_count; ++i) {
647 if ( cmd->cmd() == macho_segment_command<P>::CMD ) {
648 const macho_segment_command<P>* segCmd = (const macho_segment_command<P>*)cmd;
649 if ( (address >= segCmd->vmaddr()) && (address < segCmd->vmaddr()+segCmd->vmsize()) ) {
650 // if segment is writable, we are fine
651 if ( (segCmd->initprot() & VM_PROT_WRITE) != 0 )
652 return true;
653 // could be a text reloc, make sure section bit is set
654 const macho_section<P>* const sectionsStart = (macho_section<P>*)((char*)segCmd + sizeof(macho_segment_command<P>));
655 const macho_section<P>* const sectionsEnd = &sectionsStart[segCmd->nsects()];
656 for(const macho_section<P>* sect = sectionsStart; sect < sectionsEnd; ++sect) {
657 if ( (sect->addr() <= address) && (address < (sect->addr()+sect->size())) ) {
658 // found section for this address, if has relocs we are fine
659 return ( (sect->flags() & (S_ATTR_EXT_RELOC|S_ATTR_LOC_RELOC)) != 0 );
660 }
661 }
662 }
663 }
664 cmd = (const macho_load_command<P>*)(((uint8_t*)cmd)+cmd->cmdsize());
665 }
666 return false;
667}
668
669
670template <>
671void MachOChecker<ppc>::checkExternalReloation(const macho_relocation_info<P>* reloc)
672{
673 if ( reloc->r_length() != 2 )
674 throw "bad external relocation length";
675 if ( reloc->r_type() != GENERIC_RELOC_VANILLA )
676 throw "unknown external relocation type";
677 if ( reloc->r_pcrel() != 0 )
678 throw "bad external relocation pc_rel";
679 if ( reloc->r_extern() == 0 )
680 throw "local relocation found with external relocations";
681 if ( ! this->addressInWritableSegment(reloc->r_address() + this->relocBase()) )
682 throw "external relocation address not in writable segment";
683 // FIX: check r_symbol
684}
685
686template <>
687void MachOChecker<ppc64>::checkExternalReloation(const macho_relocation_info<P>* reloc)
688{
689 if ( reloc->r_length() != 3 )
690 throw "bad external relocation length";
691 if ( reloc->r_type() != GENERIC_RELOC_VANILLA )
692 throw "unknown external relocation type";
693 if ( reloc->r_pcrel() != 0 )
694 throw "bad external relocation pc_rel";
695 if ( reloc->r_extern() == 0 )
696 throw "local relocation found with external relocations";
697 if ( ! this->addressInWritableSegment(reloc->r_address() + this->relocBase()) )
698 throw "external relocation address not in writable segment";
699 // FIX: check r_symbol
700}
701
702template <>
703void MachOChecker<x86>::checkExternalReloation(const macho_relocation_info<P>* reloc)
704{
705 if ( reloc->r_length() != 2 )
706 throw "bad external relocation length";
707 if ( reloc->r_type() != GENERIC_RELOC_VANILLA )
708 throw "unknown external relocation type";
709 if ( reloc->r_pcrel() != 0 )
710 throw "bad external relocation pc_rel";
711 if ( reloc->r_extern() == 0 )
712 throw "local relocation found with external relocations";
713 if ( ! this->addressInWritableSegment(reloc->r_address() + this->relocBase()) )
714 throw "external relocation address not in writable segment";
715 // FIX: check r_symbol
716}
717
718
719template <>
720void MachOChecker<x86_64>::checkExternalReloation(const macho_relocation_info<P>* reloc)
721{
722 if ( reloc->r_length() != 3 )
723 throw "bad external relocation length";
724 if ( reloc->r_type() != X86_64_RELOC_UNSIGNED )
725 throw "unknown external relocation type";
726 if ( reloc->r_pcrel() != 0 )
727 throw "bad external relocation pc_rel";
728 if ( reloc->r_extern() == 0 )
729 throw "local relocation found with external relocations";
730 if ( ! this->addressInWritableSegment(reloc->r_address() + this->relocBase()) )
731 throw "exernal relocation address not in writable segment";
732 // FIX: check r_symbol
733}
734
735template <>
736void MachOChecker<arm>::checkExternalReloation(const macho_relocation_info<P>* reloc)
737{
738 if ( reloc->r_length() != 2 )
739 throw "bad external relocation length";
740 if ( reloc->r_type() != ARM_RELOC_VANILLA )
741 throw "unknown external relocation type";
742 if ( reloc->r_pcrel() != 0 )
743 throw "bad external relocation pc_rel";
744 if ( reloc->r_extern() == 0 )
745 throw "local relocation found with external relocations";
746 if ( ! this->addressInWritableSegment(reloc->r_address() + this->relocBase()) )
747 throw "external relocation address not in writable segment";
748 // FIX: check r_symbol
749}
750
751
752template <>
753void MachOChecker<ppc>::checkLocalReloation(const macho_relocation_info<P>* reloc)
754{
755 if ( reloc->r_address() & R_SCATTERED ) {
756 // scattered
757 const macho_scattered_relocation_info<P>* sreloc = (const macho_scattered_relocation_info<P>*)reloc;
758 // FIX
759
760 }
761 else {
762 // ignore pair relocs
763 if ( reloc->r_type() == PPC_RELOC_PAIR )
764 return;
765 // FIX
766 if ( ! this->addressInWritableSegment(reloc->r_address() + this->relocBase()) )
767 throwf("local relocation address 0x%08X not in writable segment", reloc->r_address());
768 }
769}
770
771
772template <>
773void MachOChecker<ppc64>::checkLocalReloation(const macho_relocation_info<P>* reloc)
774{
775 if ( reloc->r_length() != 3 )
776 throw "bad local relocation length";
777 if ( reloc->r_type() != GENERIC_RELOC_VANILLA )
778 throw "unknown local relocation type";
779 if ( reloc->r_pcrel() != 0 )
780 throw "bad local relocation pc_rel";
781 if ( reloc->r_extern() != 0 )
782 throw "external relocation found with local relocations";
783 if ( ! this->addressInWritableSegment(reloc->r_address() + this->relocBase()) )
784 throw "local relocation address not in writable segment";
785}
786
787template <>
788void MachOChecker<x86>::checkLocalReloation(const macho_relocation_info<P>* reloc)
789{
790 // FIX
791}
792
793template <>
794void MachOChecker<x86_64>::checkLocalReloation(const macho_relocation_info<P>* reloc)
795{
796 if ( reloc->r_length() != 3 )
797 throw "bad local relocation length";
798 if ( reloc->r_type() != X86_64_RELOC_UNSIGNED )
799 throw "unknown local relocation type";
800 if ( reloc->r_pcrel() != 0 )
801 throw "bad local relocation pc_rel";
802 if ( reloc->r_extern() != 0 )
803 throw "external relocation found with local relocations";
804 if ( ! this->addressInWritableSegment(reloc->r_address() + this->relocBase()) )
805 throw "local relocation address not in writable segment";
806}
807
808template <>
809void MachOChecker<arm>::checkLocalReloation(const macho_relocation_info<P>* reloc)
810{
811 if ( reloc->r_address() & R_SCATTERED ) {
812 // scattered
813 const macho_scattered_relocation_info<P>* sreloc = (const macho_scattered_relocation_info<P>*)reloc;
814 if ( sreloc->r_length() != 2 )
815 throw "bad local scattered relocation length";
816 if ( sreloc->r_type() != ARM_RELOC_PB_LA_PTR )
817 throw "bad local scattered relocation type";
818 }
819 else {
820 if ( reloc->r_length() != 2 )
821 throw "bad local relocation length";
822 if ( reloc->r_extern() != 0 )
823 throw "external relocation found with local relocations";
824 if ( ! this->addressInWritableSegment(reloc->r_address() + this->relocBase()) )
825 throw "local relocation address not in writable segment";
826 }
827}
828
829template <typename A>
830void MachOChecker<A>::checkRelocations()
831{
832 // external relocations should be sorted to minimize dyld symbol lookups
833 // therefore every reloc with the same r_symbolnum value should be contiguous
834 std::set<uint32_t> previouslySeenSymbolIndexes;
835 uint32_t lastSymbolIndex = 0xFFFFFFFF;
836 const macho_relocation_info<P>* const externRelocsEnd = &fExternalRelocations[fExternalRelocationsCount];
837 for (const macho_relocation_info<P>* reloc = fExternalRelocations; reloc < externRelocsEnd; ++reloc) {
838 this->checkExternalReloation(reloc);
839 if ( reloc->r_symbolnum() != lastSymbolIndex ) {
840 if ( previouslySeenSymbolIndexes.count(reloc->r_symbolnum()) != 0 )
841 throw "external relocations not sorted";
842 previouslySeenSymbolIndexes.insert(lastSymbolIndex);
843 lastSymbolIndex = reloc->r_symbolnum();
844 }
845 }
846
847 const macho_relocation_info<P>* const localRelocsEnd = &fLocalRelocations[fLocalRelocationsCount];
848 for (const macho_relocation_info<P>* reloc = fLocalRelocations; reloc < localRelocsEnd; ++reloc) {
849 this->checkLocalReloation(reloc);
850 }
851}
852
853
854static void check(const char* path)
855{
856 struct stat stat_buf;
857
858 try {
859 int fd = ::open(path, O_RDONLY, 0);
860 if ( fd == -1 )
861 throw "cannot open file";
862 ::fstat(fd, &stat_buf);
863 uint32_t length = stat_buf.st_size;
864 uint8_t* p = (uint8_t*)::mmap(NULL, stat_buf.st_size, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0);
865 if ( p == ((uint8_t*)(-1)) )
866 throw "cannot map file";
867 ::close(fd);
868 const mach_header* mh = (mach_header*)p;
869 if ( mh->magic == OSSwapBigToHostInt32(FAT_MAGIC) ) {
870 const struct fat_header* fh = (struct fat_header*)p;
871 const struct fat_arch* archs = (struct fat_arch*)(p + sizeof(struct fat_header));
872 for (unsigned long i=0; i < OSSwapBigToHostInt32(fh->nfat_arch); ++i) {
873 size_t offset = OSSwapBigToHostInt32(archs[i].offset);
874 size_t size = OSSwapBigToHostInt32(archs[i].size);
875 unsigned int cputype = OSSwapBigToHostInt32(archs[i].cputype);
876
877 switch(cputype) {
878 case CPU_TYPE_POWERPC:
879 if ( MachOChecker<ppc>::validFile(p + offset) )
880 MachOChecker<ppc>::make(p + offset, size, path);
881 else
882 throw "in universal file, ppc slice does not contain ppc mach-o";
883 break;
884 case CPU_TYPE_I386:
885 if ( MachOChecker<x86>::validFile(p + offset) )
886 MachOChecker<x86>::make(p + offset, size, path);
887 else
888 throw "in universal file, i386 slice does not contain i386 mach-o";
889 break;
890 case CPU_TYPE_POWERPC64:
891 if ( MachOChecker<ppc64>::validFile(p + offset) )
892 MachOChecker<ppc64>::make(p + offset, size, path);
893 else
894 throw "in universal file, ppc64 slice does not contain ppc64 mach-o";
895 break;
896 case CPU_TYPE_X86_64:
897 if ( MachOChecker<x86_64>::validFile(p + offset) )
898 MachOChecker<x86_64>::make(p + offset, size, path);
899 else
900 throw "in universal file, x86_64 slice does not contain x86_64 mach-o";
901 break;
902 case CPU_TYPE_ARM:
903 if ( MachOChecker<arm>::validFile(p + offset) )
904 MachOChecker<arm>::make(p + offset, size, path);
905 else
906 throw "in universal file, arm slice does not contain arm mach-o";
907 break;
908 default:
909 throwf("in universal file, unknown architecture slice 0x%x\n", cputype);
910 }
911 }
912 }
913 else if ( MachOChecker<x86>::validFile(p) ) {
914 MachOChecker<x86>::make(p, length, path);
915 }
916 else if ( MachOChecker<ppc>::validFile(p) ) {
917 MachOChecker<ppc>::make(p, length, path);
918 }
919 else if ( MachOChecker<ppc64>::validFile(p) ) {
920 MachOChecker<ppc64>::make(p, length, path);
921 }
922 else if ( MachOChecker<x86_64>::validFile(p) ) {
923 MachOChecker<x86_64>::make(p, length, path);
924 }
925 else if ( MachOChecker<arm>::validFile(p) ) {
926 MachOChecker<arm>::make(p, length, path);
927 }
928 else {
929 throw "not a known file type";
930 }
931 }
932 catch (const char* msg) {
933 throwf("%s in %s", msg, path);
934 }
935}
936
937
938int main(int argc, const char* argv[])
939{
940 try {
941 for(int i=1; i < argc; ++i) {
942 const char* arg = argv[i];
943 if ( arg[0] == '-' ) {
944 if ( strcmp(arg, "-no_content") == 0 ) {
945
946 }
947 else {
948 throwf("unknown option: %s\n", arg);
949 }
950 }
951 else {
952 check(arg);
953 }
954 }
955 }
956 catch (const char* msg) {
957 fprintf(stderr, "machocheck failed: %s\n", msg);
958 return 1;
959 }
960
961 return 0;
962}
963
964
965