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