1/*
2 *  kclist_main.c
3 *  kext_tools
4 *
5 *  Created by Nik Gervae on 2010 10 04.
6 *  Copyright 2010 Apple Computer, Inc. All rights reserved.
7 *
8 */
9
10#include <CoreFoundation/CoreFoundation.h>
11#include <System/libkern/OSKextLibPrivate.h>
12#include <System/libkern/prelink.h>
13#include <IOKit/kext/OSKext.h>
14#include <IOKit/kext/OSKextPrivate.h>
15#include <IOKit/IOCFUnserialize.h>
16#include <IOKit/kext/macho_util.h>
17
18#include <architecture/byte_order.h>
19#include <errno.h>
20#include <libc.h>
21#include <mach-o/fat.h>
22#include <mach-o/loader.h>
23#include <sys/mman.h>
24#include <uuid/uuid.h>
25
26#include "kclist_main.h"
27#include "compression.h"
28
29/*******************************************************************************
30*******************************************************************************/
31extern void printPList_new(FILE * stream, CFPropertyListRef plist, int style);
32
33/*******************************************************************************
34* Program Globals
35*******************************************************************************/
36const char * progname = "(unknown)";
37
38/*******************************************************************************
39*******************************************************************************/
40int main(int argc, char * const argv[])
41{
42    ExitStatus           result             = EX_SOFTWARE;
43    KclistArgs           toolArgs;
44    int                  kernelcache_fd     = -1;  // must close()
45    void               * fat_header         = NULL;  // must unmapFatHeaderPage()
46    struct fat_arch    * fat_arch           = NULL;
47    CFDataRef            rawKernelcache     = NULL;  // must release
48    CFDataRef            kernelcacheImage   = NULL;  // must release
49    const UInt8        * kernelcacheStart   = NULL;
50
51    void               * prelinkInfoSect = NULL;
52    const char         * prelinkInfoBytes = NULL;
53    CFPropertyListRef    prelinkInfoPlist = NULL;  // must release
54
55    void               * prelinkTextSect = NULL;
56    const char         * prelinkTextBytes = NULL;
57    uint64_t             prelinkTextSourceAddress = 0;
58    uint64_t             prelinkTextSourceSize = 0;
59
60    bzero(&toolArgs, sizeof(toolArgs));
61
62   /*****
63    * Find out what the program was invoked as.
64    */
65    progname = rindex(argv[0], '/');
66    if (progname) {
67        progname++;   // go past the '/'
68    } else {
69        progname = (char *)argv[0];
70    }
71
72   /* Set the OSKext log callback right away.
73    */
74    OSKextSetLogOutputFunction(&tool_log);
75
76   /*****
77    * Process args & check for permission to load.
78    */
79    result = readArgs(&argc, &argv, &toolArgs);
80    if (result != EX_OK) {
81        if (result == kKclistExitHelp) {
82            result = EX_OK;
83        }
84        goto finish;
85    }
86
87    result = checkArgs(&toolArgs);
88    if (result != EX_OK) {
89        if (result == kKclistExitHelp) {
90            result = EX_OK;
91        }
92        goto finish;
93    }
94
95    kernelcache_fd = open(toolArgs.kernelcachePath, O_RDONLY);
96    if (kernelcache_fd == -1) {
97        OSKextLog(/* kext */ NULL,
98            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
99            "Can't open %s: %s.", toolArgs.kernelcachePath, strerror(errno));
100        result = EX_OSERR;
101        goto finish;
102    }
103    fat_header = mapAndSwapFatHeaderPage(kernelcache_fd);
104    if (!fat_header) {
105        OSKextLog(/* kext */ NULL,
106            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
107            "Can't map %s: %s.", toolArgs.kernelcachePath, strerror(errno));
108        result = EX_OSERR;
109        goto finish;
110    }
111
112    fat_arch = getFirstFatArch(fat_header);
113    if (fat_arch && !toolArgs.archInfo) {
114        toolArgs.archInfo = NXGetArchInfoFromCpuType(fat_arch->cputype, fat_arch->cpusubtype);
115    }
116
117    rawKernelcache = readMachOSliceForArch(toolArgs.kernelcachePath, toolArgs.archInfo,
118        /* checkArch */ FALSE);
119    if (!rawKernelcache) {
120        OSKextLog(/* kext */ NULL,
121            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
122            "Can't read arch %s from %s.", toolArgs.archInfo->name, toolArgs.kernelcachePath);
123        goto finish;
124    }
125
126    if (MAGIC32(CFDataGetBytePtr(rawKernelcache)) == OSSwapHostToBigInt32('comp')) {
127        kernelcacheImage = uncompressPrelinkedSlice(rawKernelcache);
128        if (!kernelcacheImage) {
129            OSKextLog(/* kext */ NULL,
130                kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
131                "Can't uncompress kernelcache slice.");
132            goto finish;
133        }
134    } else {
135        kernelcacheImage = CFRetain(rawKernelcache);
136    }
137
138    kernelcacheStart = CFDataGetBytePtr(kernelcacheImage);
139
140    if (ISMACHO64(MAGIC32(kernelcacheStart))) {
141        prelinkInfoSect = (void *)macho_get_section_by_name_64(
142            (struct mach_header_64 *)kernelcacheStart,
143            kPrelinkInfoSegment, kPrelinkInfoSection);
144        prelinkTextSect = (void *)macho_get_section_by_name_64(
145            (struct mach_header_64 *)kernelcacheStart,
146            kPrelinkTextSegment, kPrelinkTextSection);
147    } else {
148        prelinkInfoSect = (void *)macho_get_section_by_name(
149            (struct mach_header *)kernelcacheStart,
150            kPrelinkInfoSegment, kPrelinkInfoSection);
151        prelinkTextSect = (void *)macho_get_section_by_name(
152            (struct mach_header *)kernelcacheStart,
153            kPrelinkTextSegment, kPrelinkTextSection);
154    }
155
156    if (!prelinkInfoSect) {
157        OSKextLog(/* kext */ NULL,
158            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
159            "Can't find prelink info section.");
160        goto finish;
161    }
162
163    if (!prelinkTextSect) {
164        OSKextLog(/* kext */ NULL,
165            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
166            "Can't find prelink text section.");
167        goto finish;
168    }
169
170    if (ISMACHO64(MAGIC32(kernelcacheStart))) {
171        prelinkInfoBytes = ((char *)kernelcacheStart) +
172            ((struct section_64 *)prelinkInfoSect)->offset;
173        prelinkTextBytes = ((char *)kernelcacheStart) +
174            ((struct section_64 *)prelinkTextSect)->offset;
175        prelinkTextSourceAddress = ((struct section_64 *)prelinkTextSect)->addr;
176        prelinkTextSourceSize = ((struct section_64 *)prelinkTextSect)->size;
177    } else {
178        prelinkInfoBytes = ((char *)kernelcacheStart) +
179            ((struct section *)prelinkInfoSect)->offset;
180        prelinkTextBytes = ((char *)kernelcacheStart) +
181            ((struct section *)prelinkTextSect)->offset;
182        prelinkTextSourceAddress = ((struct section *)prelinkTextSect)->addr;
183        prelinkTextSourceSize = ((struct section *)prelinkTextSect)->size;
184    }
185
186    prelinkInfoPlist = (CFPropertyListRef)IOCFUnserialize(prelinkInfoBytes,
187        kCFAllocatorDefault, /* options */ 0, /* errorString */ NULL);
188    if (!prelinkInfoPlist) {
189        OSKextLog(/* kext */ NULL,
190            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
191            "Can't unserialize prelink info.");
192        goto finish;
193    }
194
195    listPrelinkedKexts(&toolArgs, prelinkInfoPlist, prelinkTextBytes, prelinkTextSourceAddress, prelinkTextSourceSize);
196
197    result = EX_OK;
198
199finish:
200
201    SAFE_RELEASE(prelinkInfoPlist);
202    SAFE_RELEASE(kernelcacheImage);
203    SAFE_RELEASE(rawKernelcache);
204
205    if (fat_header) {
206        unmapFatHeaderPage(fat_header);
207    }
208
209    if (kernelcache_fd != -1) {
210        close(kernelcache_fd);
211    }
212    return result;
213}
214
215/*******************************************************************************
216*******************************************************************************/
217ExitStatus readArgs(
218    int            * argc,
219    char * const  ** argv,
220    KclistArgs     * toolArgs)
221{
222    ExitStatus   result         = EX_USAGE;
223    ExitStatus   scratchResult  = EX_USAGE;
224    int          optchar        = 0;
225    int          longindex      = -1;
226
227    bzero(toolArgs, sizeof(*toolArgs));
228
229   /*****
230    * Allocate collection objects.
231    */
232    if (!createCFMutableSet(&toolArgs->kextIDs, &kCFTypeSetCallBacks)) {
233
234        OSKextLogMemError();
235        result = EX_OSERR;
236        exit(result);
237    }
238
239    /*****
240    * Process command line arguments.
241    */
242    while ((optchar = getopt_long_only(*argc, *argv,
243        kOptChars, sOptInfo, &longindex)) != -1) {
244
245        switch (optchar) {
246
247            case kOptArch:
248                toolArgs->archInfo = NXGetArchInfoFromName(optarg);
249                if (!toolArgs->archInfo) {
250                    OSKextLog(/* kext */ NULL,
251                        kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
252                        "Unknown architecture %s.", optarg);
253                    goto finish;
254                }
255                break;
256
257            case kOptHelp:
258                usage(kUsageLevelFull);
259                result = kKclistExitHelp;
260                goto finish;
261
262            case kOptUUID:
263                toolArgs->printUUIDs = true;
264                break;
265
266            case kOptVerbose:
267                toolArgs->verbose = true;
268                break;
269
270            default:
271                OSKextLog(/* kext */ NULL,
272                    kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
273                    "unrecognized option %s", (*argv)[optind-1]);
274                goto finish;
275                break;
276
277        }
278
279       /* Reset longindex, because getopt_long_only() is stupid and doesn't.
280        */
281        longindex = -1;
282    }
283
284   /*****
285    * Record remaining args from the command line.
286    */
287    for ( /* optind already set */ ; optind < *argc; optind++) {
288        if (!toolArgs->kernelcachePath) {
289            toolArgs->kernelcachePath = (*argv)[optind];
290        } else {
291            CFStringRef scratchString = CFStringCreateWithCString(kCFAllocatorDefault,
292                (*argv)[optind], kCFStringEncodingUTF8);
293            if (!scratchString) {
294                result = EX_OSERR;
295                OSKextLogMemError();
296                goto finish;
297            }
298            CFSetAddValue(toolArgs->kextIDs, scratchString);
299            CFRelease(scratchString);
300        }
301    }
302
303   /* Update the argc & argv seen by main() so that boot<>root calls
304    * handle remaining args.
305    */
306    *argc -= optind;
307    *argv += optind;
308
309    result = EX_OK;
310
311finish:
312    if (result == EX_USAGE) {
313        usage(kUsageLevelBrief);
314    }
315    return result;
316}
317
318/*******************************************************************************
319*******************************************************************************/
320ExitStatus checkArgs(KclistArgs * toolArgs)
321{
322    ExitStatus result = EX_USAGE;
323
324    if (!toolArgs->kernelcachePath) {
325
326        OSKextLog(/* kext */ NULL,
327            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
328            "No kernelcache file specified.");
329        goto finish;
330    }
331
332    result = EX_OK;
333
334finish:
335    if (result == EX_USAGE) {
336        usage(kUsageLevelBrief);
337    }
338    return result;
339}
340
341/*******************************************************************************
342*******************************************************************************/
343void listPrelinkedKexts(KclistArgs * toolArgs, CFPropertyListRef kcInfoPlist, const char *prelinkTextBytes, uint64_t prelinkTextSourceAddress, uint64_t prelinkTextSourceSize)
344{
345    CFIndex i, count;
346    Boolean haveIDs = CFSetGetCount(toolArgs->kextIDs) > 0 ? TRUE : FALSE;
347    CFArrayRef kextPlistArray = NULL;
348
349    if (CFArrayGetTypeID() == CFGetTypeID(kcInfoPlist)) {
350        kextPlistArray = (CFArrayRef)kcInfoPlist;
351    } else if (CFDictionaryGetTypeID() == CFGetTypeID(kcInfoPlist)){
352        kextPlistArray = (CFArrayRef)CFDictionaryGetValue(kcInfoPlist,
353            CFSTR("_PrelinkInfoDictionary"));
354    } else {
355        OSKextLog(/* kext */ NULL,
356            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
357            "Unrecognized kernelcache plist data.");
358        goto finish;
359    }
360
361    count = CFArrayGetCount(kextPlistArray);
362    for (i = 0; i < count; i++) {
363        CFDictionaryRef kextPlist = (CFDictionaryRef)CFArrayGetValueAtIndex(kextPlistArray, i);
364        CFStringRef kextIdentifier = (CFStringRef)CFDictionaryGetValue(kextPlist, kCFBundleIdentifierKey);
365        CFNumberRef kextSourceAddress = (CFNumberRef)CFDictionaryGetValue(kextPlist, CFSTR(kPrelinkExecutableSourceKey));
366        CFNumberRef kextSourceSize = (CFNumberRef)CFDictionaryGetValue(kextPlist, CFSTR(kPrelinkExecutableSizeKey));
367        const char *kextTextBytes = NULL;
368
369        if (haveIDs && !CFSetContainsValue(toolArgs->kextIDs, kextIdentifier)) {
370            continue;
371        }
372
373        if (kextSourceAddress && CFNumberGetTypeID() == CFGetTypeID(kextSourceAddress) &&
374            kextSourceSize && CFNumberGetTypeID() == CFGetTypeID(kextSourceSize)) {
375            uint64_t sourceAddress;
376            uint64_t sourceSize;
377
378            CFNumberGetValue(kextSourceAddress, kCFNumberSInt64Type, &sourceAddress);
379            CFNumberGetValue(kextSourceSize, kCFNumberSInt64Type, &sourceSize);
380            if ((sourceAddress >= prelinkTextSourceAddress) &&
381                ((sourceAddress+sourceSize) <= (prelinkTextSourceAddress + prelinkTextSourceSize))) {
382                kextTextBytes = prelinkTextBytes + (ptrdiff_t)(sourceAddress - prelinkTextSourceAddress);
383            }
384        }
385
386        printKextInfo(kextPlist, toolArgs->verbose, toolArgs->printUUIDs, kextTextBytes);
387    }
388
389finish:
390    return;
391}
392
393/*******************************************************************************
394*******************************************************************************/
395void printKextInfo(CFDictionaryRef kextPlist, Boolean beVerbose, Boolean printUUIDs, const char *kextTextBytes)
396{
397    CFStringRef kextIdentifier = (CFStringRef)CFDictionaryGetValue(kextPlist, kCFBundleIdentifierKey);
398    CFStringRef kextVersion = (CFStringRef)CFDictionaryGetValue(kextPlist, kCFBundleVersionKey);
399    CFStringRef kextPath = (CFStringRef)CFDictionaryGetValue(kextPlist, CFSTR("_PrelinkBundlePath"));
400    char idBuffer[KMOD_MAX_NAME];
401    char versionBuffer[KMOD_MAX_NAME];
402    char pathBuffer[PATH_MAX];
403
404    CFNumberRef cfNum;
405    uint64_t  kextLoadAddress = 0x0;
406    uint64_t  kextSourceAddress = 0x0;
407    uint64_t  kextExecutableSize = 0;
408    uint64_t  kextKmodInfoAddress = 0x0;
409
410    struct load_command *lcp;
411    struct uuid_command *uuid_cmd = NULL;
412    uint32_t ncmds, cmd_i;
413
414    if (!kextIdentifier || !kextVersion || !kextPath) {
415        OSKextLog(/* kext */ NULL,
416            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
417            "Missing identifier, version, or path.");
418        goto finish;
419    }
420    CFStringGetCString(kextIdentifier, idBuffer, sizeof(idBuffer), kCFStringEncodingUTF8);
421    CFStringGetCString(kextVersion, versionBuffer, sizeof(versionBuffer), kCFStringEncodingUTF8);
422    CFStringGetCString(kextPath, pathBuffer, sizeof(pathBuffer), kCFStringEncodingUTF8);
423
424    if (NULL != (cfNum = CFDictionaryGetValue(kextPlist, CFSTR(kPrelinkExecutableLoadKey))))
425        CFNumberGetValue(cfNum, kCFNumberSInt64Type, &kextLoadAddress);
426    if (NULL != (cfNum = CFDictionaryGetValue(kextPlist, CFSTR(kPrelinkExecutableSourceKey))))
427        CFNumberGetValue(cfNum, kCFNumberSInt64Type, &kextSourceAddress);
428    if (NULL != (cfNum = CFDictionaryGetValue(kextPlist, CFSTR(kPrelinkExecutableSizeKey))))
429        CFNumberGetValue(cfNum, kCFNumberSInt64Type, &kextExecutableSize);
430    if (NULL != (cfNum = CFDictionaryGetValue(kextPlist, CFSTR(kPrelinkKmodInfoKey))))
431        CFNumberGetValue(cfNum, kCFNumberSInt64Type, &kextKmodInfoAddress);
432
433    if (kextTextBytes) {
434        if (ISMACHO64(MAGIC32(kextTextBytes))) {
435            struct mach_header_64 *mhp64 = (struct mach_header_64 *)kextTextBytes;
436            ncmds = mhp64->ncmds;
437            lcp = (struct load_command *)(void *)(mhp64 + 1);
438        } else {
439            struct mach_header *mhp = (struct mach_header *)kextTextBytes;
440            ncmds = mhp->ncmds;
441            lcp = (struct load_command *)(void *)(mhp + 1);
442        }
443
444        for (cmd_i = 0; cmd_i < ncmds; cmd_i++) {
445            if (lcp->cmd == LC_UUID) {
446                uuid_cmd = (struct uuid_command *)lcp;
447                break;
448            }
449            lcp = (struct load_command *)((uintptr_t)lcp + lcp->cmdsize);
450        }
451    }
452
453    if (printUUIDs) {
454
455        if (uuid_cmd) {
456            uuid_string_t uuid_string;
457
458            uuid_unparse(*(uuid_t *)uuid_cmd->uuid, uuid_string);
459            printf("%s\t%s\t%s\t0x%llx\t0x%llx\t%s\n", idBuffer, versionBuffer, uuid_string, kextLoadAddress, kextExecutableSize, pathBuffer);
460        } else {
461            printf("%s\t%s\t\t\t\t%s\n", idBuffer, versionBuffer, pathBuffer);
462        }
463    } else {
464        printf("%s\t%s\t%s\n", idBuffer, versionBuffer, pathBuffer);
465    }
466
467    if (beVerbose) {
468        printf("\t-> load address:   0x%0.8llx, "
469               "size              = 0x%0.8llx,\n"
470               "\t-> source address: 0x%0.8llx, "
471               "kmod_info address = 0x%0.8llx\n",
472               kextLoadAddress, kextExecutableSize, kextSourceAddress, kextKmodInfoAddress);
473    }
474
475finish:
476    return;
477}
478
479/*******************************************************************************
480*******************************************************************************/
481CFComparisonResult compareIdentifiers(const void * val1, const void * val2, void * context __unused)
482{
483    CFDictionaryRef dict1 = (CFDictionaryRef)val1;
484    CFDictionaryRef dict2 = (CFDictionaryRef)val2;
485
486    CFStringRef id1 = CFDictionaryGetValue(dict1, kCFBundleIdentifierKey);
487    CFStringRef id2 = CFDictionaryGetValue(dict2, kCFBundleIdentifierKey);
488
489    return CFStringCompare(id1, id2, 0);
490}
491
492/*******************************************************************************
493* usage()
494*******************************************************************************/
495void usage(UsageLevel usageLevel)
496{
497    fprintf(stderr,
498      "usage: %1$s [-arch archname] [-u] [-v] [--] kernelcache [bundle-id ...]\n"
499      "usage: %1$s -help\n"
500      "\n",
501      progname);
502
503    if (usageLevel == kUsageLevelBrief) {
504        fprintf(stderr, "use %s -%s for an explanation of each option\n",
505            progname, kOptNameHelp);
506    }
507
508    if (usageLevel == kUsageLevelBrief) {
509        return;
510    }
511
512    fprintf(stderr, "-%s <archname>:\n"
513        "        list info for architecture <archname>\n",
514        kOptNameArch);
515    fprintf(stderr, "-%s (-%c):\n"
516        "        print kext load addresses and UUIDs\n",
517            kOptNameUUID, kOptUUID);
518    fprintf(stderr, "-%s (-%c):\n"
519        "        emit additional information about kext load addresses and sizes\n",
520            kOptNameVerbose, kOptVerbose);
521    fprintf(stderr, "\n");
522
523    fprintf(stderr, "-%s (-%c): print this message and exit\n",
524        kOptNameHelp, kOptHelp);
525
526    return;
527}
528