1/* 2 * Copyright (c) 2006 Apple Computer, Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23#include <CoreFoundation/CoreFoundation.h> 24#include <IOKit/kext/OSKext.h> 25#include <IOKit/kext/OSKextPrivate.h> 26#include <IOKit/kext/fat_util.h> 27 28#include <Kernel/libkern/mkext.h> 29#include <sys/types.h> 30#include <sys/stat.h> 31#include <sys/mman.h> 32#include <unistd.h> 33#include <libc.h> 34#include <mach-o/arch.h> 35#include <mach-o/fat.h> 36 37#include <mach/mach.h> 38#include <mach/mach_types.h> 39#include <mach/kmod.h> 40#include "kext_tools_util.h" 41 42// not a utility.[ch] customer yet 43static const char * progname = "mkextunpack"; 44static Boolean gVerbose = false; 45 46u_int32_t local_adler32(u_int8_t *buffer, int32_t length); 47 48Boolean getMkextDataForArch( 49 u_int8_t * fileData, 50 size_t fileSize, 51 const NXArchInfo * archInfo, 52 void ** mkextStart, 53 void ** mkextEnd, 54 uint32_t * mkextVersion); 55 56CFArrayRef copyKextsFromMkext2( 57 void * mkextStart, 58 void * mkextEnd, 59 const NXArchInfo * archInfo); 60Boolean writeMkext2EntriesToDirectory( 61 CFArrayRef kexts, 62 char * outputDirectory); 63Boolean writeMkext2ToDirectory( 64 OSKextRef aKext, 65 const char * outputDirectory, 66 CFMutableSetRef kextNames); 67 68CFDictionaryRef extractEntriesFromMkext1( 69 void * mkextStart, 70 void * mkextEnd); 71Boolean uncompressMkext1Entry( 72 void * mkext_base_address, 73 mkext_file * entry_address, 74 CFDataRef * uncompressedEntry); 75Boolean writeMkext1EntriesToDirectory(CFDictionaryRef entries, 76 char * outputDirectory); 77CFStringRef createKextNameFromPlist( 78 CFDictionaryRef entries, CFDictionaryRef kextPlist); 79int getBundleIDAndVersion(CFDictionaryRef kextPlist, unsigned index, 80 char ** bundle_id_out, char ** bundle_version_out); 81 82Boolean writeFileInDirectory( 83 const char * basePath, 84 char * subPath, 85 const char * fileName, 86 const char * fileData, 87 size_t fileLength); 88 89#if 0 90/******************************************************************************* 91* NOTES! 92*******************************************************************************/ 93How to unpack all arches from an mkext: 94 95Unpack each arch in a fat mkext to a separate array of kexts. 96For each arch: 97 - Get kext plist 98 - Check {kext path, id, vers} against assembled kexts: 99 - Already have? 100 - Plist identical: 101 - "lipo" executable into assembled 102 - Add all resources IF NOT PRESENT (check for different?) 103 - Plist different: 104 - cannot handle, error; or save to separate path 105 - Else add kext to assembled list 106 107 - Need to save infoDict, executable, resources 108#endif /* 0 */ 109 110/******************************************************************************* 111*******************************************************************************/ 112void usage(int num) { 113 fprintf(stderr, "usage: %s [-v] [-a arch] [-d output_dir] mkextfile\n", progname); 114 fprintf(stderr, " -d output_dir: where to put kexts (must exist)\n"); 115 fprintf(stderr, " -a arch: pick architecture from fat mkext file\n"); 116 fprintf(stderr, " -v: verbose output; list kexts in mkextfile\n"); 117 return; 118} 119 120/******************************************************************************* 121*******************************************************************************/ 122int main (int argc, const char * argv[]) { 123 int exit_code = 0; 124 125 char optchar; 126 char * outputDirectory = NULL; 127 const char * mkextFile = NULL; 128 int mkextFileFD; 129 struct stat stat_buf; 130 uint8_t * mkextFileContents = NULL; 131 void * mkextStart = NULL; 132 void * mkextEnd = NULL; 133 CFDictionaryRef entries = NULL; 134 CFArrayRef oskexts = NULL; 135 const NXArchInfo * archInfo = NULL; 136 uint32_t mkextVersion; 137 138 progname = argv[0]; 139 140 /* Set the OSKext log callback right away. 141 */ 142 OSKextSetLogOutputFunction(&tool_log); 143 144 while ((optchar = getopt(argc, (char * const *)argv, "a:d:hv")) != -1) { 145 switch (optchar) { 146 case 'd': 147 if (!optarg) { 148 fprintf(stderr, "no argument for -d\n"); 149 usage(0); 150 exit_code = 1; 151 goto finish; 152 } 153 outputDirectory = optarg; 154 break; 155 case 'h': 156 usage(1); 157 exit_code = 0; 158 goto finish; 159 break; 160 case 'v': 161 gVerbose = true; 162 break; 163 case 'a': 164 if (archInfo != NULL) { 165 fprintf(stderr, "architecture already specified; replacing\n"); 166 } 167 if (!optarg) { 168 fprintf(stderr, "no argument for -a\n"); 169 usage(0); 170 exit_code = 1; 171 goto finish; 172 } 173 archInfo = NXGetArchInfoFromName(optarg); 174 if (!archInfo) { 175 fprintf(stderr, "architecture '%s' not found\n", optarg); 176 exit_code = 1; 177 goto finish; 178 } 179 break; 180 } 181 } 182 183 /* Update argc, argv based on option processing. 184 */ 185 argc -= optind; 186 argv += optind; 187 188 if (argc == 0 || argc > 1) { 189 usage(0); 190 exit_code = 1; 191 goto finish; 192 } 193 194 mkextFile = argv[0]; 195 196 if (!outputDirectory && !gVerbose) { 197 fprintf(stderr, "no work to do; please specify -d or -v\n"); 198 usage(0); 199 exit_code = 1; 200 goto finish; 201 } 202 203 if (!mkextFile) { 204 fprintf(stderr, "no mkext file given\n"); 205 usage(0); 206 exit_code = 1; 207 goto finish; 208 } 209 210 if (outputDirectory) { 211 if (stat(outputDirectory, &stat_buf) < 0) { 212 fprintf(stderr, "can't stat directory %s\n", 213 outputDirectory); 214 exit_code = 1; 215 goto finish; 216 } 217 218 if ((stat_buf.st_mode & S_IFMT) != S_IFDIR) { 219 fprintf(stderr, "%s is not a directory\n", 220 outputDirectory); 221 exit_code = 1; 222 goto finish; 223 } 224 225 if (access(outputDirectory, W_OK) == -1) { 226 fprintf(stderr, "can't write into directory %s " 227 "(permission denied)\n", outputDirectory); 228 exit_code = 1; 229 goto finish; 230 } 231 } 232 233 if (stat(mkextFile, &stat_buf) < 0) { 234 fprintf(stderr, "can't stat file %s\n", mkextFile); 235 exit_code = 1; 236 goto finish; 237 } 238 239 if (access(mkextFile, R_OK) == -1) { 240 fprintf(stderr, "can't read file %s (permission denied)\n", 241 mkextFile); 242 exit_code = 1; 243 goto finish; 244 } 245 246 mkextFileFD = open(mkextFile, O_RDONLY, 0); 247 if (mkextFileFD < 0) { 248 fprintf(stderr, "can't open file %s\n", mkextFile); 249 exit_code = 1; 250 goto finish; 251 } 252 253 if ( !(stat_buf.st_mode & S_IFREG) ) { 254 fprintf(stderr, "%s is not a regular file\n", 255 mkextFile); 256 exit_code = 1; 257 goto finish; 258 } 259 260 if (!stat_buf.st_size) { 261 fprintf(stderr, "%s is an empty file\n", 262 mkextFile); 263 exit_code = 1; 264 goto finish; 265 } 266 267 mkextFileContents = mmap(0, (size_t)stat_buf.st_size, PROT_READ, 268 MAP_FILE|MAP_PRIVATE, mkextFileFD, 0); 269 if (mkextFileContents == (u_int8_t *)-1) { 270 fprintf(stderr, "can't map file %s\n", mkextFile); 271 exit_code = 1; 272 goto finish; 273 } 274 275 if (!getMkextDataForArch(mkextFileContents, (size_t)stat_buf.st_size, 276 archInfo, &mkextStart, &mkextEnd, &mkextVersion)) { 277 278 exit_code = 1; 279 goto finish; 280 } 281 282 if (mkextVersion == MKEXT_VERS_2) { 283 oskexts = copyKextsFromMkext2(mkextStart, mkextEnd, archInfo); 284 if (!oskexts) { 285 exit_code = 1; 286 goto finish; 287 } 288 289 if (outputDirectory && 290 !writeMkext2EntriesToDirectory(oskexts, outputDirectory)) { 291 exit_code = 1; 292 goto finish; 293 } 294 } else if (mkextVersion == MKEXT_VERS_1) { 295 entries = extractEntriesFromMkext1(mkextStart, mkextEnd); 296 if (!entries) { 297 fprintf(stderr, "can't unpack file %s\n", mkextFile); 298 exit_code = 1; 299 goto finish; 300 } 301 302 if (outputDirectory && 303 !writeMkext1EntriesToDirectory(entries, outputDirectory)) { 304 305 exit_code = 1; 306 goto finish; 307 } 308 } 309 310finish: 311 SAFE_RELEASE(oskexts); 312 exit(exit_code); 313 return exit_code; 314} 315 316/******************************************************************************* 317*******************************************************************************/ 318Boolean getMkextDataForArch( 319 u_int8_t * fileData, 320 size_t fileSize, 321 const NXArchInfo * archInfo, 322 void ** mkextStart, 323 void ** mkextEnd, 324 uint32_t * mkextVersion) 325{ 326 Boolean result = false; 327 uint32_t magic; 328 fat_iterator fatIterator = NULL; // must fat_iterator_close() 329 mkext_header * mkextHeader = NULL; // do not free 330 uint8_t * crc_address = NULL; // do not free 331 uint32_t checksum; 332 333 *mkextStart = *mkextEnd = NULL; 334 *mkextVersion = 0; 335 336 magic = MAGIC32(fileData); 337 if (ISFAT(magic)) { 338 if (!archInfo) { 339 archInfo = NXGetLocalArchInfo(); 340 } 341 fatIterator = fat_iterator_for_data(fileData, 342 fileData + fileSize, 343 1 /* mach-o only */); 344 if (!fatIterator) { 345 OSKextLog(/* kext */ NULL, 346 kOSKextLogErrorLevel | kOSKextLogArchiveFlag, 347 "Can't read mkext fat header."); 348 goto finish; 349 } 350 *mkextStart = fat_iterator_find_arch(fatIterator, 351 archInfo->cputype, archInfo->cpusubtype, mkextEnd); 352 if (!*mkextStart) { 353 OSKextLog(/* kext */ NULL, 354 kOSKextLogErrorLevel | kOSKextLogArchiveFlag, 355 "Architecture %s not found in mkext.", 356 archInfo->name); 357 goto finish; 358 } 359 } else { 360 *mkextStart = fileData; 361 *mkextEnd = fileData + fileSize; 362 } 363 364 mkextHeader = (mkext_header *)*mkextStart; 365 366 if ((MKEXT_GET_MAGIC(mkextHeader) != MKEXT_MAGIC) || 367 (MKEXT_GET_SIGNATURE(mkextHeader) != MKEXT_SIGN)) { 368 369 OSKextLog(/* kext */ NULL, 370 kOSKextLogErrorLevel | kOSKextLogArchiveFlag, 371 "Bad mkext magic/signature."); 372 goto finish; 373 } 374 if (MKEXT_GET_LENGTH(mkextHeader) != 375 (*mkextEnd - *mkextStart)) { 376 377 OSKextLog(/* kext */ NULL, 378 kOSKextLogErrorLevel | kOSKextLogArchiveFlag, 379 "Mkext length field %d does not match mkext actual size %d.", 380 MKEXT_GET_LENGTH(mkextHeader), 381 (int)(*mkextEnd - *mkextStart)); 382 goto finish; 383 } 384 if (archInfo && MKEXT_GET_CPUTYPE(mkextHeader) != archInfo->cputype) { 385 386 OSKextLog(/* kext */ NULL, 387 kOSKextLogErrorLevel | kOSKextLogArchiveFlag, 388 "Mkext cputype %d does not match requested type %d (%s).", 389 MKEXT_GET_CPUTYPE(mkextHeader), 390 archInfo->cputype, 391 archInfo->name); 392 goto finish; 393 } 394 395 crc_address = (uint8_t *)&mkextHeader->version; 396 checksum = local_adler32(crc_address, 397 (int32_t)((uintptr_t)mkextHeader + 398 MKEXT_GET_LENGTH(mkextHeader) - (uintptr_t)crc_address)); 399 400 if (MKEXT_GET_CHECKSUM(mkextHeader) != checksum) { 401 OSKextLog(/* kext */ NULL, 402 kOSKextLogErrorLevel | kOSKextLogArchiveFlag, 403 "Mkext checksum error."); 404 goto finish; 405 } 406 407 *mkextVersion = MKEXT_GET_VERSION(mkextHeader); 408 409 result = true; 410 411finish: 412 if (fatIterator) { 413 fat_iterator_close(fatIterator); 414 } 415 return result; 416} 417 418/******************************************************************************* 419*******************************************************************************/ 420CFArrayRef copyKextsFromMkext2( 421 void * mkextStart, 422 void * mkextEnd, 423 const NXArchInfo * archInfo) 424{ 425 CFArrayRef result = NULL; // release on error 426 Boolean ok = false; 427 CFDataRef mkextDataObject = NULL; // must release 428 char kextPath[PATH_MAX]; 429 char * kextIdentifier = NULL; // must free 430 char kextVersion[kOSKextVersionMaxLength]; 431 432 if (!archInfo) { 433 archInfo = NXGetLocalArchInfo(); 434 } 435 436 OSKextSetArchitecture(archInfo); 437 mkextDataObject = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, 438 (UInt8 *)mkextStart, mkextEnd - mkextStart, NULL); 439 if (!mkextDataObject) { 440 OSKextLogMemError(); 441 goto finish; 442 } 443 result = OSKextCreateKextsFromMkextData(kCFAllocatorDefault, mkextDataObject); 444 if (!result) { 445 OSKextLog(/* kext */ NULL, 446 kOSKextLogErrorLevel | kOSKextLogArchiveFlag, 447 "Can't read mkext2 archive."); 448 goto finish; 449 } 450 451 if (gVerbose) { 452 CFIndex count, i; 453 454 count = CFArrayGetCount(result); 455 fprintf(stdout, "Found %d kexts:\n", (int)count); 456 for (i = 0; i < count; i++) { 457 OSKextRef theKext = (OSKextRef)CFArrayGetValueAtIndex(result, i); 458 459 SAFE_FREE(kextIdentifier); 460 461 if (!CFURLGetFileSystemRepresentation(OSKextGetURL(theKext), 462 /* resolveToBase */ false, (UInt8 *)kextPath, sizeof(kextPath))) { 463 464 OSKextLogStringError(theKext); 465 goto finish; 466 } 467 kextIdentifier = createUTF8CStringForCFString(OSKextGetIdentifier(theKext)); 468 OSKextVersionGetString(OSKextGetVersion(theKext), kextVersion, 469 sizeof(kextVersion)); 470 fprintf(stdout, "%s - %s (%s)\n", kextPath, kextIdentifier, 471 kextVersion); 472 } 473 } 474 475 ok = true; 476 477finish: 478 if (!ok) { 479 SAFE_RELEASE_NULL(result); 480 } 481 SAFE_RELEASE(mkextDataObject); 482 return result; 483} 484 485/******************************************************************************* 486*******************************************************************************/ 487Boolean writeMkext2EntriesToDirectory( 488 CFArrayRef kexts, 489 char * outputDirectory) 490{ 491 Boolean result = false; 492 CFMutableSetRef kextNames = NULL; // must release 493 CFIndex count, i; 494 495 if (!createCFMutableSet(&kextNames, &kCFTypeSetCallBacks)) { 496 OSKextLogMemError(); 497 } 498 499 count = CFArrayGetCount(kexts); 500 for (i = 0; i < count; i++) { 501 OSKextRef theKext = (OSKextRef)CFArrayGetValueAtIndex(kexts, i); 502 503 if (!writeMkext2ToDirectory(theKext, outputDirectory, kextNames)) { 504 goto finish; 505 } 506 } 507 508finish: 509 return result; 510} 511 512/******************************************************************************* 513*******************************************************************************/ 514Boolean writeMkext2ToDirectory( 515 OSKextRef aKext, 516 const char * outputDirectory, 517 CFMutableSetRef kextNames) 518{ 519 Boolean result = false; 520 CFDictionaryRef infoDict = NULL; // must release 521 CFDataRef infoDictData = NULL; // must release 522 CFErrorRef error = NULL; // must release 523 CFDataRef executable = NULL; // must release 524 CFURLRef kextURL = NULL; // do not release 525 CFStringRef kextName = NULL; // must release 526 CFStringRef executableName = NULL; // do not release 527 uint32_t pathLength; 528 char kextPath[PATH_MAX]; // gets trashed during use 529 char * kextNameCString = NULL; // do not free 530 char * kextNameSuffix = NULL; // do not free 531 char * kextNameCStringAlloced = NULL; // must free 532 char * executableNameCStringAlloced = NULL; // must free 533 const void * file_data = NULL; // do not free 534 char subPath[PATH_MAX]; 535 CFIndex i; 536 537 infoDict = OSKextCopyInfoDictionary(aKext); 538 executable = OSKextCopyExecutableForArchitecture(aKext, NULL); 539 if (!infoDict) { 540 OSKextLogMemError(); 541 goto finish; 542 } 543 544 kextURL = OSKextGetURL(aKext); 545 if (!CFURLGetFileSystemRepresentation(kextURL, /* resolveToBase */ true, 546 (UInt8 *)kextPath, sizeof(kextPath))) { 547 548 OSKextLogStringError(aKext); 549 goto finish; 550 } 551 pathLength = (uint32_t)strlen(kextPath); 552 if (pathLength && kextPath[pathLength-1] == '/') { 553 kextPath[pathLength-1] = '\0'; 554 } 555 kextNameCString = rindex(kextPath, '/'); 556 if (kextNameCString) { 557 kextNameCString++; 558 } 559 560 SAFE_RELEASE_NULL(kextName); 561 kextName = CFStringCreateWithCString(kCFAllocatorDefault, 562 kextNameCString, kCFStringEncodingUTF8); 563 if (!kextName) { 564 OSKextLogMemError(); 565 goto finish; 566 } 567 568 /* Splat off the ".kext" suffix if needed so we can build 569 * numbered variants. 570 */ 571 if (CFSetContainsValue(kextNames, kextName)) { 572 kextNameSuffix = strstr(kextNameCString, ".kext"); 573 if (!kextNameSuffix) { 574 OSKextLog(/* kext */ NULL, 575 kOSKextLogErrorLevel | kOSKextLogArchiveFlag, 576 "Bad mkext data; kext missing suffix."); 577 goto finish; 578 } 579 *kextNameSuffix = '\0'; // truncate at the period 580 } 581 582 i = 1; 583 while (CFSetContainsValue(kextNames, kextName)) { 584 SAFE_RELEASE_NULL(kextName); 585 kextName = CFStringCreateWithFormat(kCFAllocatorDefault, 586 /* formatOptions */ NULL, CFSTR("%s-%zd.kext"), 587 kextNameCString, i); 588 i++; 589 } 590 CFSetAddValue(kextNames, kextName); 591 592 kextNameCStringAlloced = createUTF8CStringForCFString(kextName); 593 594 /***** 595 * Write the plist file. 596 */ 597 infoDictData = CFPropertyListCreateData(kCFAllocatorDefault, 598 infoDict, kCFPropertyListXMLFormat_v1_0, /* options */ 0, &error); 599 if (!infoDictData) { 600 OSKextLog(/* kext */ NULL, 601 kOSKextLogErrorLevel | kOSKextLogArchiveFlag, 602 "Can't serialize kext info dictionary."); 603 goto finish; 604 } 605 606 file_data = (u_int8_t *)CFDataGetBytePtr(infoDictData); 607 if (!file_data) { 608 OSKextLog(/* kext */ NULL, 609 kOSKextLogErrorLevel | kOSKextLogArchiveFlag, 610 "Internal error; info dictionary has no data."); 611 goto finish; 612 } 613 614 if (snprintf(subPath, sizeof(subPath), 615 "%s/Contents", kextNameCStringAlloced) >= sizeof(subPath) - 1) { 616 OSKextLog(/* kext */ NULL, 617 kOSKextLogErrorLevel | kOSKextLogArchiveFlag | kOSKextLogFileAccessFlag, 618 "Output path is too long - %s.", subPath); 619 goto finish; 620 } 621 if (!writeFileInDirectory(outputDirectory, subPath, "Info.plist", 622 file_data, CFDataGetLength(infoDictData))) { 623 624 goto finish; 625 } 626 627 if (!executable) { 628 result = true; 629 goto finish; 630 } 631 632 /***** 633 * Write the executable file. 634 */ 635 file_data = (u_int8_t *)CFDataGetBytePtr(executable); 636 if (!file_data) { 637 OSKextLog(/* kext */ NULL, 638 kOSKextLogErrorLevel | kOSKextLogArchiveFlag, 639 "Internal error; executable has no data."); 640 goto finish; 641 } 642 643 if (snprintf(subPath, sizeof(subPath), 644 "%s/Contents/MacOS", kextNameCStringAlloced) >= sizeof(subPath) - 1) { 645 OSKextLog(/* kext */ NULL, 646 kOSKextLogErrorLevel | kOSKextLogArchiveFlag | kOSKextLogFileAccessFlag, 647 "Output path is too long - %s.", subPath); 648 goto finish; 649 } 650 executableName = CFDictionaryGetValue(infoDict, CFSTR("CFBundleExecutable")); 651 if (!executableName) { 652 OSKextLog(/* kext */ NULL, 653 kOSKextLogErrorLevel | kOSKextLogArchiveFlag, 654 "Kext %s has an executable but no CFBundleExecutable property.", 655 kextNameCStringAlloced); 656 goto finish; 657 } 658 executableNameCStringAlloced = createUTF8CStringForCFString(executableName); 659 if (!executableNameCStringAlloced) { 660 OSKextLogMemError(); 661 goto finish; 662 } 663 if (!writeFileInDirectory(outputDirectory, subPath, 664 executableNameCStringAlloced, 665 file_data, CFDataGetLength(executable))) { 666 667 goto finish; 668 } 669 670 result = true; 671 672finish: 673 SAFE_RELEASE(infoDict); 674 SAFE_RELEASE(infoDictData); 675 SAFE_RELEASE(error); 676 SAFE_RELEASE(executable); 677 SAFE_RELEASE(kextName); 678 SAFE_FREE(kextNameCStringAlloced); 679 SAFE_FREE(executableNameCStringAlloced); 680 return result; 681} 682 683/******************************************************************************* 684*******************************************************************************/ 685static Boolean CaseInsensitiveEqual(CFTypeRef left, CFTypeRef right) 686{ 687 CFComparisonResult compResult; 688 689 compResult = CFStringCompare(left, right, kCFCompareCaseInsensitive); 690 if (compResult == kCFCompareEqualTo) { 691 return true; 692 } 693 return false; 694} 695 696/******************************************************************************* 697*******************************************************************************/ 698static CFHashCode CaseInsensitiveHash(const void *key) 699{ 700 return (CFStringGetLength(key)); 701} 702 703/******************************************************************************* 704*******************************************************************************/ 705CFDictionaryRef extractEntriesFromMkext1( 706 void * mkextStart, 707 void * mkextEnd) 708{ 709 CFMutableDictionaryRef entries = NULL; // returned 710 Boolean error = false; 711 712 unsigned int i; 713 714 mkext1_header * mkextHeader = NULL; // don't free 715 mkext_kext * onekext_data = 0; // don't free 716 mkext_file * plist_file = 0; // don't free 717 mkext_file * module_file = 0; // don't free 718 CFStringRef entryName = NULL; // must release 719 CFMutableDictionaryRef entryDict = NULL; // must release 720 CFDataRef kextPlistDataObject = 0; // must release 721 CFDictionaryRef kextPlist = 0; // must release 722 CFStringRef errorString = NULL; // must release 723 CFDataRef kextExecutable = 0; // must release 724 CFDictionaryKeyCallBacks keyCallBacks; 725 726 727 keyCallBacks = kCFTypeDictionaryKeyCallBacks; 728 keyCallBacks.equal = &CaseInsensitiveEqual; 729 keyCallBacks.hash = &CaseInsensitiveHash; 730 entries = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, 731 &keyCallBacks, 732 &kCFTypeDictionaryValueCallBacks); 733 if (!entries) { 734 goto finish; 735 } 736 737 mkextHeader = (mkext1_header *)mkextStart; 738 739 if (gVerbose) { 740 fprintf(stdout, "Found %u kexts:\n", MKEXT_GET_COUNT(mkextHeader)); 741 } 742 743 for (i = 0; i < MKEXT_GET_COUNT(mkextHeader); i++) { 744 if (entryName) { 745 CFRelease(entryName); 746 entryName = NULL; 747 } 748 if (entryDict) { 749 CFRelease(entryDict); 750 entryDict = NULL; 751 } 752 if (kextPlistDataObject) { 753 CFRelease(kextPlistDataObject); 754 kextPlistDataObject = NULL; 755 } 756 if (kextPlist) { 757 CFRelease(kextPlist); 758 kextPlist = NULL; 759 } 760 if (errorString) { 761 CFRelease(errorString); 762 errorString = NULL; 763 } 764 if (kextExecutable) { 765 CFRelease(kextExecutable); 766 kextExecutable = NULL; 767 } 768 769 entryDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, 770 &kCFTypeDictionaryKeyCallBacks, 771 &kCFTypeDictionaryValueCallBacks); 772 if (!entryDict) { 773 fprintf(stderr, "internal error.\n"); 774 error = true; 775 goto finish; 776 } 777 778 onekext_data = &mkextHeader->kext[i]; 779 plist_file = &onekext_data->plist; 780 module_file = &onekext_data->module; 781 782 /***** 783 * Get the plist 784 */ 785 if (!uncompressMkext1Entry(mkextStart, 786 plist_file, &kextPlistDataObject) || !kextPlistDataObject) { 787 788 fprintf(stderr, "couldn't uncompress plist at index %d.\n", i); 789 continue; 790 } 791 792 kextPlist = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, 793 kextPlistDataObject, kCFPropertyListImmutable, &errorString); 794 if (!kextPlist) { 795 if (errorString) { 796 CFIndex bufsize = CFStringGetMaximumSizeForEncoding( 797 CFStringGetLength(errorString), kCFStringEncodingUTF8); 798 char * error_string = (char *)malloc((1+bufsize) * sizeof(char)); 799 if (!error_string) { 800 fprintf(stderr, "memory allocation failure\n"); 801 error = true; 802 goto finish; 803 } 804 if (CFStringGetCString(errorString, error_string, 805 bufsize, kCFStringEncodingUTF8)) { 806 fprintf(stderr, "error reading plist: %s", 807 error_string); 808 } 809 free(error_string); 810 } else { 811 fprintf(stderr, "error reading plist"); 812 } 813 goto finish; 814 } 815 816 entryName = createKextNameFromPlist(entries, kextPlist); 817 if (!entryName) { 818 fprintf(stderr, "internal error.\n"); 819 error = true; 820 goto finish; 821 } 822 823 CFDictionarySetValue(entryDict, CFSTR("plistData"), kextPlistDataObject); 824 CFDictionarySetValue(entryDict, CFSTR("plist"), kextPlist); 825 826 if (gVerbose) { 827 char kext_name[PATH_MAX]; 828 char * bundle_id = NULL; // don't free 829 char * bundle_vers = NULL; // don't free 830 831 if (!CFStringGetCString(entryName, kext_name, sizeof(kext_name) - 1, 832 kCFStringEncodingUTF8)) { 833 fprintf(stderr, "memory or string conversion error\n"); 834 } else { 835 switch (getBundleIDAndVersion(kextPlist, i, &bundle_id, 836 &bundle_vers)) { 837 838 case 1: 839 fprintf(stdout, "%s - %s (%s)\n", kext_name, bundle_id, 840 bundle_vers); 841 break; 842 case 0: 843 continue; 844 case -1: 845 default: 846 error = true; 847 goto finish; 848 break; 849 } 850 } 851 } 852 853 /***** 854 * Get the executable 855 */ 856 if (OSSwapBigToHostInt32(module_file->offset) || 857 OSSwapBigToHostInt32(module_file->compsize) || 858 OSSwapBigToHostInt32(module_file->realsize) || 859 OSSwapBigToHostInt32(module_file->modifiedsecs)) { 860 861 if (!uncompressMkext1Entry(mkextStart, 862 module_file, &kextExecutable)) { 863 864 fprintf(stderr, "couldn't uncompress executable at index %d.\n", 865 i); 866 continue; 867 } 868 869 if (kextExecutable) { 870 CFDictionarySetValue(entryDict, CFSTR("executable"), 871 kextExecutable); 872 } 873 } 874 875 CFDictionarySetValue(entries, entryName, entryDict); 876 } 877 878finish: 879 if (error) { 880 if (entries) { 881 CFRelease(entries); 882 entries = NULL; 883 } 884 } 885 if (entryName) CFRelease(entryName); 886 if (entryDict) CFRelease(entryDict); 887 if (kextPlistDataObject) CFRelease(kextPlistDataObject); 888 if (kextPlist) CFRelease(kextPlist); 889 if (errorString) CFRelease(errorString); 890 if (kextExecutable) CFRelease(kextExecutable); 891 892 return entries; 893} 894 895/******************************************************************************* 896*******************************************************************************/ 897Boolean uncompressMkext1Entry( 898 void * mkext_base_address, 899 mkext_file * entry_address, 900 CFDataRef * uncompressedEntry) 901{ 902 Boolean result = true; 903 904 u_int8_t * uncompressed_data = NULL; // must free 905 CFDataRef uncompressedData = NULL; // returned 906 size_t uncompressed_size = 0; 907 908 size_t offset = OSSwapBigToHostInt32(entry_address->offset); 909 size_t compsize = OSSwapBigToHostInt32(entry_address->compsize); 910 size_t realsize = OSSwapBigToHostInt32(entry_address->realsize); 911 time_t modifiedsecs = OSSwapBigToHostInt32(entry_address->modifiedsecs); 912 913 *uncompressedEntry = NULL; 914 915 /* If realsize is 0 there is nothing to uncompress or if any of the other 916 * three fields are 0, but that isn't an error. 917 */ 918 if (realsize == 0 || 919 (offset == 0 && compsize == 0 && modifiedsecs == 0)) { 920 goto finish; 921 } 922 923 uncompressed_data = malloc(realsize); 924 if (!uncompressed_data) { 925 fprintf(stderr, "malloc failure\n"); 926 result = false; 927 goto finish; 928 } 929 930 if (compsize != 0) { 931 uncompressed_size = decompress_lzss(uncompressed_data, 932 (u_int32_t)realsize, 933 mkext_base_address + offset, 934 (u_int32_t)compsize); 935 if (uncompressed_size != realsize) { 936 fprintf(stderr, "uncompressed file is not the length " 937 "recorded.\n"); 938 result = false; 939 goto finish; 940 } 941 } else { 942 bcopy(mkext_base_address + offset, uncompressed_data, 943 realsize); 944 } 945 946 uncompressedData = CFDataCreate(kCFAllocatorDefault, 947 (const UInt8 *)uncompressed_data, realsize); 948 if (!uncompressedData) { 949 fprintf(stderr, "malloc failure\n"); 950 result = false; 951 goto finish; 952 } 953 *uncompressedEntry = uncompressedData; 954 955finish: 956 if (uncompressed_data) free(uncompressed_data); 957 958 return result; 959} 960 961/******************************************************************************* 962*******************************************************************************/ 963Boolean writeMkext1EntriesToDirectory(CFDictionaryRef entryDict, 964 char * outputDirectory) 965{ 966 Boolean result = false; 967 CFStringRef * kextNames = NULL; // must free 968 CFDictionaryRef * entries = NULL; // must free 969 char * kext_name = NULL; // must free 970 char * executable_name = NULL; // must free 971 char subPath[PATH_MAX]; 972 unsigned int count, i; 973 974 count = (unsigned int)CFDictionaryGetCount(entryDict); 975 976 kextNames = (CFStringRef *)malloc(count * sizeof(CFStringRef)); 977 entries = (CFDictionaryRef *)malloc(count * sizeof(CFDictionaryRef)); 978 if (!kextNames || !entries) { 979 fprintf(stderr, "malloc failure\n"); 980 result = false; 981 goto finish; 982 } 983 984 CFDictionaryGetKeysAndValues(entryDict, (const void **)kextNames, 985 (const void **)entries); 986 987 for (i = 0; i < count; i++) { 988 CFStringRef kextName = kextNames[i]; 989 CFDictionaryRef kextEntry = entries[i]; 990 CFStringRef executableName = NULL; // do not release 991 CFDataRef fileData = NULL; // do not release 992 CFDictionaryRef plist; 993 994 const void * file_data = NULL; 995 996 SAFE_FREE_NULL(kext_name); 997 SAFE_FREE_NULL(executable_name); 998 999 kext_name = createUTF8CStringForCFString(kextName); 1000 if (!kext_name) { 1001 OSKextLogMemError(); 1002 goto finish; 1003 } 1004 1005 /***** 1006 * Write the plist file. 1007 */ 1008 fileData = CFDictionaryGetValue(kextEntry, CFSTR("plistData")); 1009 if (!fileData) { 1010 OSKextLog(/* kext */ NULL, 1011 kOSKextLogErrorLevel | kOSKextLogArchiveFlag, 1012 "Kext entry %d has no plist.", i); 1013 continue; 1014 } 1015 file_data = (u_int8_t *)CFDataGetBytePtr(fileData); 1016 if (!file_data) { 1017 OSKextLog(/* kext */ NULL, 1018 kOSKextLogErrorLevel | kOSKextLogArchiveFlag, 1019 "Kext %s has no plist.", kext_name); 1020 continue; 1021 } 1022 1023 if (snprintf(subPath, sizeof(subPath), 1024 "%s.kext/Contents", kext_name) >= sizeof(subPath) - 1) { 1025 OSKextLog(/* kext */ NULL, 1026 kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, 1027 "Output path is too long - %s.", subPath); 1028 goto finish; 1029 } 1030 if (!writeFileInDirectory(outputDirectory, subPath, "Info.plist", 1031 file_data, CFDataGetLength(fileData))) { 1032 1033 goto finish; 1034 } 1035 1036 /***** 1037 * Write the executable file. 1038 */ 1039 fileData = CFDictionaryGetValue(kextEntry, CFSTR("executable")); 1040 if (!fileData) { 1041 continue; 1042 } 1043 file_data = (u_int8_t *)CFDataGetBytePtr(fileData); 1044 if (!file_data) { 1045 OSKextLog(/* kext */ NULL, 1046 kOSKextLogErrorLevel | kOSKextLogArchiveFlag, 1047 "Executable missing from kext %s.", 1048 kext_name); 1049 continue; 1050 } 1051 1052 if (snprintf(subPath, sizeof(subPath), 1053 "%s.kext/Contents/MacOS", kext_name) >= sizeof(subPath) - 1) { 1054 OSKextLog(/* kext */ NULL, 1055 kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, 1056 "Output path is too long - %s.", subPath); 1057 goto finish; 1058 } 1059 plist = CFDictionaryGetValue(kextEntry, CFSTR("plist")); 1060 executableName = CFDictionaryGetValue(plist, CFSTR("CFBundleExecutable")); 1061 if (!executableName) { 1062 OSKextLog(/* kext */ NULL, 1063 kOSKextLogErrorLevel | kOSKextLogArchiveFlag, 1064 "Kext %s has an executable but no CFBundleExecutable property.", 1065 kext_name); 1066 continue; 1067 } 1068 executable_name = createUTF8CStringForCFString(executableName); 1069 if (!executable_name) { 1070 OSKextLogMemError(); 1071 goto finish; 1072 } 1073 if (!writeFileInDirectory(outputDirectory, subPath, executable_name, 1074 file_data, CFDataGetLength(fileData))) { 1075 1076 goto finish; 1077 } 1078 } 1079 1080 result = true; 1081 1082finish: 1083 if (kextNames) free(kextNames); 1084 if (entries) free(entries); 1085 return result; 1086} 1087 1088/******************************************************************************* 1089*******************************************************************************/ 1090CFStringRef createKextNameFromPlist( 1091 CFDictionaryRef entries, CFDictionaryRef kextPlist) 1092{ 1093 CFStringRef result = NULL; // returned 1094 CFStringRef bundleName = NULL; // don't release 1095 CFStringRef bundleID = NULL; // don't release 1096 static unsigned int nameUnknownIndex = 1; 1097 unsigned int dupIndex = 1; 1098 CFArrayRef idParts = NULL; // must release 1099 1100 /* First see if there's a CFBundleExecutable. 1101 */ 1102 bundleName = CFDictionaryGetValue(kextPlist, CFSTR("CFBundleExecutable")); 1103 if (!bundleName) { 1104 1105 /* No? Try for CFBundleIdentifier and get the last component. 1106 */ 1107 bundleID = CFDictionaryGetValue(kextPlist, CFSTR("CFBundleIdentifier")); 1108 if (bundleID) { 1109 CFIndex length; 1110 1111 idParts = CFStringCreateArrayBySeparatingStrings( 1112 kCFAllocatorDefault, bundleID, CFSTR(".")); 1113 length = CFArrayGetCount(idParts); 1114 bundleName = (CFStringRef)CFArrayGetValueAtIndex(idParts, length - 1); 1115 1116 /* Identifier ends with a period? We got no name, then. 1117 */ 1118 if (!CFStringGetLength(bundleName)) { 1119 bundleName = NULL; 1120 } 1121 } 1122 1123 /* If we didn't find a name to use, conjure one up. 1124 */ 1125 if (!bundleName) { 1126 result = CFStringCreateWithFormat(kCFAllocatorDefault, 1127 NULL, CFSTR("NameUnknown-%d"), nameUnknownIndex); 1128 nameUnknownIndex++; 1129 goto finish; 1130 } 1131 } 1132 1133 /* See if we already have the name we found based on executable/bundle ID 1134 * (as opposed to making up with NameUnknown); if so, add numbers until we get a unique. 1135 */ 1136 if (bundleName) { 1137 if (CFDictionaryGetValue(entries, bundleName)) { 1138 for ( ; ; dupIndex++) { 1139 result = CFStringCreateWithFormat(kCFAllocatorDefault, 1140 NULL, CFSTR("%@-%d"), bundleName, dupIndex); 1141 if (!CFDictionaryGetValue(entries, result)) { 1142 goto finish; 1143 } 1144 CFRelease(result); 1145 result = NULL; 1146 } 1147 } else { 1148 result = CFRetain(bundleName); 1149 goto finish; 1150 } 1151 } 1152finish: 1153 if (idParts) CFRelease(idParts); 1154 return result; 1155} 1156 1157/******************************************************************************* 1158*******************************************************************************/ 1159int getBundleIDAndVersion(CFDictionaryRef kextPlist, unsigned index, 1160 char ** bundle_id_out, char ** bundle_version_out) 1161{ 1162 int result = 1; 1163 1164 CFStringRef bundleID = NULL; // don't release 1165 CFStringRef bundleVersion = NULL; // don't release 1166 static char bundle_id[KMOD_MAX_NAME]; 1167 static char bundle_vers[KMOD_MAX_NAME]; 1168 1169 bundleID = CFDictionaryGetValue(kextPlist, CFSTR("CFBundleIdentifier")); 1170 if (!bundleID) { 1171 fprintf(stderr, "kext entry %d has no CFBundleIdentifier\n", index); 1172 result = 0; 1173 goto finish; 1174 } else if (CFGetTypeID(bundleID) != CFStringGetTypeID()) { 1175 fprintf(stderr, "kext entry %d, CFBundleIdentifier is not a string\n", index); 1176 result = 0; 1177 goto finish; 1178 } else { 1179 if (!CFStringGetCString(bundleID, bundle_id, sizeof(bundle_id) - 1, 1180 kCFStringEncodingUTF8)) { 1181 fprintf(stderr, "memory or string conversion error\n"); 1182 result = -1; 1183 goto finish; 1184 } 1185 } 1186 1187 bundleVersion = CFDictionaryGetValue(kextPlist, 1188 CFSTR("CFBundleVersion")); 1189 if (!bundleVersion) { 1190 fprintf(stderr, "kext entry %d has no CFBundleVersion\n", index); 1191 result = 0; 1192 goto finish; 1193 } else if (CFGetTypeID(bundleVersion) != CFStringGetTypeID()) { 1194 fprintf(stderr, "kext entry %d, CFBundleVersion is not a string\n", index); 1195 result = 0; 1196 goto finish; 1197 } else { 1198 if (!CFStringGetCString(bundleVersion, bundle_vers, 1199 sizeof(bundle_vers) - 1, kCFStringEncodingUTF8)) { 1200 fprintf(stderr, "memory or string conversion error\n"); 1201 result = -1; 1202 goto finish; 1203 } 1204 } 1205 1206 if (bundle_id_out) { 1207 *bundle_id_out = bundle_id; 1208 } 1209 1210 if (bundle_version_out) { 1211 *bundle_version_out = bundle_vers; 1212 } 1213 1214 1215finish: 1216 1217 return result; 1218} 1219 1220/******************************************************************************* 1221*******************************************************************************/ 1222Boolean writeFileInDirectory( 1223 const char * basePath, 1224 char * subPath, 1225 const char * fileName, 1226 const char * fileData, 1227 size_t fileLength) 1228{ 1229 Boolean result = false; 1230 char path[PATH_MAX]; 1231 char * pathComponent = NULL; // do not free 1232 char * pathComponentEnd = NULL; // do not free 1233 int fd = -1; 1234 uint32_t bytesWritten; 1235 1236 if (strlcpy(path, basePath, sizeof(path)) >= sizeof(path)) { 1237 OSKextLog(/* kext */ NULL, 1238 kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, 1239 "Output path is too long - %s.", basePath); 1240 goto finish; 1241 } 1242 1243 pathComponent = subPath; 1244 while (pathComponent) { 1245 pathComponentEnd = index(pathComponent, '/'); 1246 if (pathComponentEnd) { 1247 *pathComponentEnd = '\0'; 1248 } 1249 if (strlcat(path, "/", sizeof(path)) >= sizeof(path) || 1250 strlcat(path, pathComponent, sizeof(path)) >= sizeof(path)) { 1251 1252 OSKextLog(/* kext */ NULL, 1253 kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, 1254 "Output path is too long - %s.", path); 1255 goto finish; 1256 } 1257 1258 if ((mkdir(path, 0777) < 0) && (errno != EEXIST)) { 1259 OSKextLog(/* kext */ NULL, 1260 kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, 1261 "Can't create directory %s - %s", path, strerror(errno)); 1262 goto finish; 1263 } 1264 if (pathComponentEnd) { 1265 *pathComponentEnd = '/'; 1266 pathComponent = pathComponentEnd + 1; 1267 pathComponentEnd = NULL; 1268 } else { 1269 break; 1270 } 1271 } 1272 1273 if (strlcat(path, "/", sizeof(path)) >= sizeof(path) || 1274 strlcat(path, fileName, sizeof(path)) >= sizeof(path)) { 1275 1276 OSKextLog(/* kext */ NULL, 1277 kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, 1278 "Output path is too long - %s.", path); 1279 goto finish; 1280 } 1281 1282 fd = open(path, O_WRONLY | O_CREAT, 0777); 1283 if (fd < 0) { 1284 OSKextLog(/* kext */ NULL, 1285 kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, 1286 "Can't open %s for writing - %s.", path, strerror(errno)); 1287 goto finish; 1288 } 1289 1290 bytesWritten = 0; 1291 while (bytesWritten < fileLength) { 1292 int writeResult; 1293 writeResult = (int)write(fd, fileData + bytesWritten, 1294 fileLength - bytesWritten); 1295 if (writeResult < 0) { 1296 OSKextLog(/* kext */ NULL, 1297 kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, 1298 "Write failed for %s - %s.", path, strerror(errno)); 1299 goto finish; 1300 } 1301 bytesWritten += writeResult; 1302 } 1303 1304 result = true; 1305 1306finish: 1307 if (fd != -1) { 1308 close(fd); 1309 } 1310 return result; 1311 1312} 1313