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