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