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 <System/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        CFErrorRef  error;
793        kextPlist = CFPropertyListCreateWithData(kCFAllocatorDefault,
794                                                 kextPlistDataObject,
795                                                 kCFPropertyListImmutable,
796                                                 NULL,
797                                                 &error);
798        if (!kextPlist) {
799            errorString = CFErrorCopyDescription(error);
800            CFRelease(error);
801            if (errorString) {
802                CFIndex bufsize = CFStringGetMaximumSizeForEncoding(
803                CFStringGetLength(errorString), kCFStringEncodingUTF8);
804                char * error_string = (char *)malloc((1+bufsize) * sizeof(char));
805                if (!error_string) {
806                    fprintf(stderr, "memory allocation failure\n");
807                    error = true;
808                    goto finish;
809                }
810                if (CFStringGetCString(errorString, error_string,
811                     bufsize, kCFStringEncodingUTF8)) {
812                    fprintf(stderr, "error reading plist: %s",
813                        error_string);
814                }
815                free(error_string);
816            } else {
817                fprintf(stderr, "error reading plist");
818            }
819            goto finish;
820        }
821
822        entryName = createKextNameFromPlist(entries, kextPlist);
823        if (!entryName) {
824            fprintf(stderr, "internal error.\n");
825            error = true;
826            goto finish;
827        }
828
829        CFDictionarySetValue(entryDict, CFSTR("plistData"), kextPlistDataObject);
830        CFDictionarySetValue(entryDict, CFSTR("plist"), kextPlist);
831
832        if (gVerbose) {
833            char kext_name[PATH_MAX];
834            char * bundle_id = NULL;  // don't free
835            char * bundle_vers = NULL; // don't free
836
837            if (!CFStringGetCString(entryName, kext_name, sizeof(kext_name) - 1,
838                    kCFStringEncodingUTF8)) {
839                    fprintf(stderr, "memory or string conversion error\n");
840            } else {
841                switch (getBundleIDAndVersion(kextPlist, i, &bundle_id,
842                    &bundle_vers)) {
843
844                  case 1:
845                    fprintf(stdout, "%s - %s (%s)\n", kext_name, bundle_id,
846                        bundle_vers);
847                    break;
848                  case 0:
849                    continue;
850                  case -1:
851                  default:
852                    error = true;
853                    goto finish;
854                    break;
855                }
856            }
857        }
858
859       /*****
860        * Get the executable
861        */
862        if (OSSwapBigToHostInt32(module_file->offset) ||
863            OSSwapBigToHostInt32(module_file->compsize) ||
864            OSSwapBigToHostInt32(module_file->realsize) ||
865            OSSwapBigToHostInt32(module_file->modifiedsecs)) {
866
867            if (!uncompressMkext1Entry(mkextStart,
868                module_file, &kextExecutable)) {
869
870                fprintf(stderr, "couldn't uncompress executable at index %d.\n",
871                    i);
872                continue;
873            }
874
875            if (kextExecutable) {
876                CFDictionarySetValue(entryDict, CFSTR("executable"),
877                    kextExecutable);
878            }
879        }
880
881        CFDictionarySetValue(entries, entryName, entryDict);
882    }
883
884finish:
885    if (error) {
886        if (entries) {
887            CFRelease(entries);
888            entries = NULL;
889        }
890    }
891    if (entryName)       CFRelease(entryName);
892    if (entryDict)       CFRelease(entryDict);
893    if (kextPlistDataObject) CFRelease(kextPlistDataObject);
894    if (kextPlist)       CFRelease(kextPlist);
895    if (errorString)     CFRelease(errorString);
896    if (kextExecutable)  CFRelease(kextExecutable);
897
898    return entries;
899}
900
901/*******************************************************************************
902*******************************************************************************/
903Boolean uncompressMkext1Entry(
904    void * mkext_base_address,
905    mkext_file * entry_address,
906    CFDataRef * uncompressedEntry)
907{
908    Boolean result = true;
909
910    u_int8_t * uncompressed_data = NULL;    // must free
911    CFDataRef uncompressedData = NULL;      // returned
912    size_t uncompressed_size = 0;
913
914    size_t offset = OSSwapBigToHostInt32(entry_address->offset);
915    size_t compsize = OSSwapBigToHostInt32(entry_address->compsize);
916    size_t realsize = OSSwapBigToHostInt32(entry_address->realsize);
917    time_t modifiedsecs = OSSwapBigToHostInt32(entry_address->modifiedsecs);
918
919    *uncompressedEntry = NULL;
920
921   /* If realsize is 0 there is nothing to uncompress or if any of the other
922    * three fields are 0, but that isn't an error.
923    */
924    if (realsize == 0 ||
925        (offset == 0 && compsize == 0 && modifiedsecs == 0)) {
926        goto finish;
927    }
928
929    uncompressed_data = malloc(realsize);
930    if (!uncompressed_data) {
931        fprintf(stderr, "malloc failure\n");
932        result = false;
933        goto finish;
934    }
935
936    if (compsize != 0) {
937        uncompressed_size = decompress_lzss(uncompressed_data,
938            (u_int32_t)realsize,
939            mkext_base_address + offset,
940            (u_int32_t)compsize);
941        if (uncompressed_size != realsize) {
942            fprintf(stderr, "uncompressed file is not the length "
943                  "recorded.\n");
944            result = false;
945            goto finish;
946        }
947    } else {
948        bcopy(mkext_base_address + offset, uncompressed_data,
949            realsize);
950    }
951
952    uncompressedData = CFDataCreate(kCFAllocatorDefault,
953        (const UInt8 *)uncompressed_data, realsize);
954    if (!uncompressedData) {
955        fprintf(stderr, "malloc failure\n");
956        result = false;
957        goto finish;
958    }
959    *uncompressedEntry = uncompressedData;
960
961finish:
962    if (uncompressed_data) free(uncompressed_data);
963
964    return result;
965}
966
967/*******************************************************************************
968*******************************************************************************/
969Boolean writeMkext1EntriesToDirectory(CFDictionaryRef entryDict,
970    char * outputDirectory)
971{
972    Boolean           result          = false;
973    CFStringRef     * kextNames       = NULL;  // must free
974    CFDictionaryRef * entries         = NULL;  // must free
975    char            * kext_name       = NULL;  // must free
976    char            * executable_name = NULL;  // must free
977    char              subPath[PATH_MAX];
978    unsigned int      count, i;
979
980    count = (unsigned int)CFDictionaryGetCount(entryDict);
981
982    kextNames = (CFStringRef *)malloc(count * sizeof(CFStringRef));
983    entries = (CFDictionaryRef *)malloc(count * sizeof(CFDictionaryRef));
984    if (!kextNames || !entries) {
985        fprintf(stderr, "malloc failure\n");
986        result = false;
987        goto finish;
988    }
989
990    CFDictionaryGetKeysAndValues(entryDict, (const void **)kextNames,
991        (const void **)entries);
992
993    for (i = 0; i < count; i++) {
994        CFStringRef     kextName       = kextNames[i];
995        CFDictionaryRef kextEntry      = entries[i];
996        CFStringRef     executableName = NULL;  // do not release
997        CFDataRef       fileData       = NULL;  // do not release
998        CFDictionaryRef plist;
999
1000        const void    * file_data = NULL;
1001
1002        SAFE_FREE_NULL(kext_name);
1003        SAFE_FREE_NULL(executable_name);
1004
1005        kext_name = createUTF8CStringForCFString(kextName);
1006        if (!kext_name) {
1007            OSKextLogMemError();
1008            goto finish;
1009        }
1010
1011       /*****
1012        * Write the plist file.
1013        */
1014        fileData = CFDictionaryGetValue(kextEntry, CFSTR("plistData"));
1015        if (!fileData) {
1016            OSKextLog(/* kext */ NULL,
1017                kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
1018                "Kext entry %d has no plist.", i);
1019            continue;
1020        }
1021        file_data = (u_int8_t *)CFDataGetBytePtr(fileData);
1022        if (!file_data) {
1023            OSKextLog(/* kext */ NULL,
1024            kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
1025            "Kext %s has no plist.", kext_name);
1026            continue;
1027        }
1028
1029        if (snprintf(subPath, sizeof(subPath),
1030                "%s.kext/Contents", kext_name) >= sizeof(subPath) - 1) {
1031            OSKextLog(/* kext */ NULL,
1032                kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
1033                "Output path is too long - %s.", subPath);
1034            goto finish;
1035        }
1036        if (!writeFileInDirectory(outputDirectory, subPath, "Info.plist",
1037            file_data, CFDataGetLength(fileData))) {
1038
1039            goto finish;
1040        }
1041
1042       /*****
1043        * Write the executable file.
1044        */
1045        fileData = CFDictionaryGetValue(kextEntry, CFSTR("executable"));
1046        if (!fileData) {
1047            continue;
1048        }
1049        file_data = (u_int8_t *)CFDataGetBytePtr(fileData);
1050        if (!file_data) {
1051            OSKextLog(/* kext */ NULL,
1052                kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
1053                "Executable missing from kext %s.",
1054                kext_name);
1055            continue;
1056        }
1057
1058        if (snprintf(subPath, sizeof(subPath),
1059                "%s.kext/Contents/MacOS", kext_name) >= sizeof(subPath) - 1) {
1060            OSKextLog(/* kext */ NULL,
1061                kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
1062                "Output path is too long - %s.", subPath);
1063            goto finish;
1064        }
1065        plist = CFDictionaryGetValue(kextEntry, CFSTR("plist"));
1066        executableName = CFDictionaryGetValue(plist, CFSTR("CFBundleExecutable"));
1067        if (!executableName) {
1068            OSKextLog(/* kext */ NULL,
1069                kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
1070                "Kext %s has an executable but no CFBundleExecutable property.",
1071                kext_name);
1072            continue;
1073        }
1074        executable_name = createUTF8CStringForCFString(executableName);
1075        if (!executable_name) {
1076            OSKextLogMemError();
1077            goto finish;
1078        }
1079        if (!writeFileInDirectory(outputDirectory, subPath, executable_name,
1080            file_data, CFDataGetLength(fileData))) {
1081
1082            goto finish;
1083        }
1084    }
1085
1086    result = true;
1087
1088finish:
1089    if (kextNames) free(kextNames);
1090    if (entries)   free(entries);
1091    return result;
1092}
1093
1094/*******************************************************************************
1095*******************************************************************************/
1096CFStringRef createKextNameFromPlist(
1097    CFDictionaryRef entries, CFDictionaryRef kextPlist)
1098{
1099    CFStringRef result = NULL; // returned
1100    CFStringRef bundleName = NULL; // don't release
1101    CFStringRef bundleID = NULL; // don't release
1102    static unsigned int nameUnknownIndex = 1;
1103    unsigned int dupIndex = 1;
1104    CFArrayRef idParts = NULL; // must release
1105
1106   /* First see if there's a CFBundleExecutable.
1107    */
1108    bundleName = CFDictionaryGetValue(kextPlist, CFSTR("CFBundleExecutable"));
1109    if (!bundleName) {
1110
1111       /* No? Try for CFBundleIdentifier and get the last component.
1112        */
1113        bundleID = CFDictionaryGetValue(kextPlist, CFSTR("CFBundleIdentifier"));
1114        if (bundleID) {
1115            CFIndex length;
1116
1117            idParts = CFStringCreateArrayBySeparatingStrings(
1118            kCFAllocatorDefault, bundleID, CFSTR("."));
1119            length = CFArrayGetCount(idParts);
1120            bundleName = (CFStringRef)CFArrayGetValueAtIndex(idParts, length - 1);
1121
1122           /* Identifier ends with a period? We got no name, then.
1123            */
1124            if (!CFStringGetLength(bundleName)) {
1125                bundleName = NULL;
1126            }
1127        }
1128
1129       /* If we didn't find a name to use, conjure one up.
1130        */
1131        if (!bundleName) {
1132            result = CFStringCreateWithFormat(kCFAllocatorDefault,
1133                NULL, CFSTR("NameUnknown-%d"), nameUnknownIndex);
1134            nameUnknownIndex++;
1135            goto finish;
1136        }
1137    }
1138
1139   /* See if we already have the name we found based on executable/bundle ID
1140    * (as opposed to making up with NameUnknown); if so, add numbers until we get a unique.
1141    */
1142    if (bundleName) {
1143    if (CFDictionaryGetValue(entries, bundleName)) {
1144        for ( ; ; dupIndex++) {
1145            result = CFStringCreateWithFormat(kCFAllocatorDefault,
1146                NULL, CFSTR("%@-%d"), bundleName, dupIndex);
1147            if (!CFDictionaryGetValue(entries, result)) {
1148                goto finish;
1149            }
1150            CFRelease(result);
1151            result = NULL;
1152        }
1153    } else {
1154        result = CFRetain(bundleName);
1155        goto finish;
1156    }
1157    }
1158finish:
1159    if (idParts) CFRelease(idParts);
1160    return result;
1161}
1162
1163/*******************************************************************************
1164*******************************************************************************/
1165int getBundleIDAndVersion(CFDictionaryRef kextPlist, unsigned index,
1166    char ** bundle_id_out, char ** bundle_version_out)
1167{
1168    int result = 1;
1169
1170    CFStringRef bundleID = NULL; // don't release
1171    CFStringRef bundleVersion = NULL; // don't release
1172    static char bundle_id[KMOD_MAX_NAME];
1173    static char bundle_vers[KMOD_MAX_NAME];
1174
1175    bundleID = CFDictionaryGetValue(kextPlist, CFSTR("CFBundleIdentifier"));
1176    if (!bundleID) {
1177        fprintf(stderr, "kext entry %d has no CFBundleIdentifier\n", index);
1178        result = 0;
1179        goto finish;
1180    } else if (CFGetTypeID(bundleID) != CFStringGetTypeID()) {
1181        fprintf(stderr, "kext entry %d, CFBundleIdentifier is not a string\n", index);
1182        result = 0;
1183        goto finish;
1184    } else {
1185        if (!CFStringGetCString(bundleID, bundle_id, sizeof(bundle_id) - 1,
1186            kCFStringEncodingUTF8)) {
1187            fprintf(stderr, "memory or string conversion error\n");
1188            result = -1;
1189            goto finish;
1190        }
1191    }
1192
1193    bundleVersion = CFDictionaryGetValue(kextPlist,
1194        CFSTR("CFBundleVersion"));
1195    if (!bundleVersion) {
1196        fprintf(stderr, "kext entry %d has no CFBundleVersion\n", index);
1197        result = 0;
1198        goto finish;
1199    } else if (CFGetTypeID(bundleVersion) != CFStringGetTypeID()) {
1200        fprintf(stderr, "kext entry %d, CFBundleVersion is not a string\n", index);
1201        result = 0;
1202        goto finish;
1203    } else {
1204        if (!CFStringGetCString(bundleVersion, bundle_vers,
1205            sizeof(bundle_vers) - 1, kCFStringEncodingUTF8)) {
1206            fprintf(stderr, "memory or string conversion error\n");
1207            result = -1;
1208            goto finish;
1209        }
1210    }
1211
1212    if (bundle_id_out) {
1213        *bundle_id_out = bundle_id;
1214    }
1215
1216    if (bundle_version_out) {
1217        *bundle_version_out = bundle_vers;
1218    }
1219
1220
1221finish:
1222
1223    return result;
1224}
1225
1226/*******************************************************************************
1227*******************************************************************************/
1228Boolean writeFileInDirectory(
1229    const char * basePath,
1230    char       * subPath,
1231    const char * fileName,
1232    const char * fileData,
1233    size_t       fileLength)
1234{
1235    Boolean  result = false;
1236    char     path[PATH_MAX];
1237    char   * pathComponent = NULL;     // do not free
1238    char   * pathComponentEnd = NULL;  // do not free
1239    int      fd = -1;
1240    uint32_t bytesWritten;
1241
1242    if (strlcpy(path, basePath, sizeof(path)) >= sizeof(path)) {
1243        OSKextLog(/* kext */ NULL,
1244            kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
1245            "Output path is too long - %s.", basePath);
1246        goto finish;
1247    }
1248
1249    pathComponent = subPath;
1250    while (pathComponent) {
1251        pathComponentEnd = index(pathComponent, '/');
1252        if (pathComponentEnd) {
1253            *pathComponentEnd = '\0';
1254        }
1255        if (strlcat(path, "/", sizeof(path)) >= sizeof(path) ||
1256            strlcat(path, pathComponent, sizeof(path)) >= sizeof(path)) {
1257
1258            OSKextLog(/* kext */ NULL,
1259                kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
1260                "Output path is too long - %s.", path);
1261            goto finish;
1262        }
1263
1264        if ((mkdir(path, 0777) < 0) && (errno != EEXIST)) {
1265            OSKextLog(/* kext */ NULL,
1266                kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
1267                "Can't create directory %s - %s", path, strerror(errno));
1268            goto finish;
1269        }
1270        if (pathComponentEnd) {
1271            *pathComponentEnd = '/';
1272            pathComponent = pathComponentEnd + 1;
1273            pathComponentEnd = NULL;
1274        } else {
1275            break;
1276        }
1277    }
1278
1279    if (strlcat(path, "/", sizeof(path)) >= sizeof(path) ||
1280        strlcat(path, fileName, sizeof(path)) >= sizeof(path)) {
1281
1282        OSKextLog(/* kext */ NULL,
1283            kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
1284            "Output path is too long - %s.", path);
1285        goto finish;
1286    }
1287
1288    fd = open(path, O_WRONLY | O_CREAT, 0777);
1289    if (fd < 0) {
1290        OSKextLog(/* kext */ NULL,
1291            kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
1292            "Can't open %s for writing - %s.", path, strerror(errno));
1293        goto finish;
1294    }
1295
1296    bytesWritten = 0;
1297    while (bytesWritten < fileLength) {
1298        int writeResult;
1299        writeResult = (int)write(fd, fileData + bytesWritten,
1300            fileLength - bytesWritten);
1301        if (writeResult < 0) {
1302            OSKextLog(/* kext */ NULL,
1303                kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
1304                "Write failed for %s - %s.", path, strerror(errno));
1305            goto finish;
1306        }
1307        bytesWritten += writeResult;
1308    }
1309
1310    result = true;
1311
1312finish:
1313    if (fd != -1) {
1314        close(fd);
1315    }
1316    return result;
1317
1318}
1319