1/*
2 *  kextload_main.c
3 *  kext_tools
4 *
5 *  Created by Nik Gervae on 11/08/08.
6 *  Copyright 2008 Apple Inc. All rights reserved.
7 *
8 */
9#include "kextload_main.h"
10#include "kext_tools_util.h"
11
12#include <libc.h>
13#include <servers/bootstrap.h>
14#include <sysexits.h>
15
16#include <IOKit/kext/KextManager.h>
17#include <IOKit/kext/KextManagerPriv.h>
18#include <IOKit/kext/kextmanager_types.h>
19#include <IOKit/kext/OSKextPrivate.h>
20
21#pragma mark Constants
22/*******************************************************************************
23* Constants
24*******************************************************************************/
25
26#pragma mark Global/Static Variables
27/*******************************************************************************
28* Global/Static Variables
29*******************************************************************************/
30const char      * progname     = "(unknown)";
31static Boolean    sKextdActive = FALSE;
32
33#pragma mark Main Routine
34/*******************************************************************************
35* Global variables.
36*******************************************************************************/
37ExitStatus
38main(int argc, char * const * argv)
39{
40    ExitStatus   result = EX_SOFTWARE;
41    KextloadArgs toolArgs;
42
43   /*****
44    * Find out what the program was invoked as.
45    */
46    progname = rindex(argv[0], '/');
47    if (progname) {
48        progname++;   // go past the '/'
49    } else {
50        progname = (char *)argv[0];
51    }
52
53   /* Set the OSKext log callback right away.
54    */
55    OSKextSetLogOutputFunction(&tool_log);
56
57   /*****
58    * Process args & check for permission to load.
59    */
60    result = readArgs(argc, argv, &toolArgs);
61    if (result != EX_OK) {
62        if (result == kKextloadExitHelp) {
63            result = EX_OK;
64        }
65        goto finish;
66    }
67
68    result = checkArgs(&toolArgs);
69    if (result != EX_OK) {
70        goto finish;
71    }
72
73    result = checkAccess();
74    if (result != EX_OK) {
75        goto finish;
76    }
77
78   /*****
79    * Assemble the list of URLs to scan, in this order (the OSKext lib inverts it
80    * for last-opened-wins semantics):
81    * 1. System repository directories (if not asking kextd to load).
82    * 2. Named kexts (always given after -repository & -dependency on command line).
83    * 3. Named repository directories (-repository/-r).
84    * 4. Named dependencies get priority (-dependency/-d).
85    *
86    * #2 is necessary since one might try to run kextload on two kexts,
87    * one of which depends on the other.
88    */
89    if (!sKextdActive) {
90        CFArrayRef sysExtFolders = OSKextGetSystemExtensionsFolderURLs();
91        if (!sysExtFolders) {
92            OSKextLog(/* kext */ NULL,
93                kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
94                "Can't get system extensions folders.");
95            result = EX_OSERR;
96            goto finish;
97        }
98        CFArrayAppendArray(toolArgs.scanURLs,
99            sysExtFolders, RANGE_ALL(sysExtFolders));
100    }
101    CFArrayAppendArray(toolArgs.scanURLs, toolArgs.kextURLs,
102        RANGE_ALL(toolArgs.kextURLs));
103    CFArrayAppendArray(toolArgs.scanURLs, toolArgs.repositoryURLs,
104        RANGE_ALL(toolArgs.repositoryURLs));
105    CFArrayAppendArray(toolArgs.scanURLs, toolArgs.dependencyURLs,
106        RANGE_ALL(toolArgs.dependencyURLs));
107
108    if (sKextdActive) {
109        result = loadKextsViaKextd(&toolArgs);
110    } else {
111        result = loadKextsIntoKernel(&toolArgs);
112    }
113
114finish:
115
116   /* We're actually not going to free anything else because we're exiting!
117    */
118    exit(result);
119
120    SAFE_RELEASE(toolArgs.kextIDs);
121    SAFE_RELEASE(toolArgs.dependencyURLs);
122    SAFE_RELEASE(toolArgs.repositoryURLs);
123    SAFE_RELEASE(toolArgs.kextURLs);
124    SAFE_RELEASE(toolArgs.scanURLs);
125    SAFE_RELEASE(toolArgs.allKexts);
126
127    return result;
128}
129
130#pragma mark Major Subroutines
131
132/*******************************************************************************
133* Major Subroutines
134*******************************************************************************/
135ExitStatus
136readArgs(
137    int            argc,
138    char * const * argv,
139    KextloadArgs * toolArgs)
140{
141    ExitStatus   result          = EX_USAGE;
142    ExitStatus   scratchResult   = EX_USAGE;
143    int          optchar;
144    int          longindex;
145    CFStringRef  scratchString   = NULL;  // must release
146    CFURLRef     scratchURL      = NULL;  // must release
147    uint32_t     i;
148
149   /* Set up default arg values.
150    */
151    bzero(toolArgs, sizeof(*toolArgs));
152
153   /*****
154    * Allocate collection objects needed for reading args.
155    */
156    if (!createCFMutableArray(&toolArgs->kextIDs, &kCFTypeArrayCallBacks)         ||
157        !createCFMutableArray(&toolArgs->dependencyURLs, &kCFTypeArrayCallBacks)  ||
158        !createCFMutableArray(&toolArgs->repositoryURLs, &kCFTypeArrayCallBacks)  ||
159        !createCFMutableArray(&toolArgs->kextURLs, &kCFTypeArrayCallBacks)        ||
160        !createCFMutableArray(&toolArgs->scanURLs, &kCFTypeArrayCallBacks)) {
161
162        result = EX_OSERR;
163        OSKextLogMemError();
164        exit(result);
165    }
166
167    while ((optchar = getopt_long_only(argc, (char * const *)argv,
168        kOptChars, sOptInfo, &longindex)) != -1) {
169
170        SAFE_RELEASE_NULL(scratchString);
171        SAFE_RELEASE_NULL(scratchURL);
172
173        switch (optchar) {
174            case kOptHelp:
175                usage(kUsageLevelFull);
176                result = kKextloadExitHelp;
177                goto finish;
178                break;
179
180            case kOptBundleIdentifier:
181                scratchString = CFStringCreateWithCString(kCFAllocatorDefault,
182                    optarg, kCFStringEncodingUTF8);
183                if (!scratchString) {
184                    OSKextLogMemError();
185                    result = EX_OSERR;
186                    goto finish;
187                }
188                CFArrayAppendValue(toolArgs->kextIDs, scratchString);
189                break;
190
191            case kOptDependency:
192            case kOptRepository:
193                scratchURL = CFURLCreateFromFileSystemRepresentation(
194                    kCFAllocatorDefault,
195                    (const UInt8 *)optarg, strlen(optarg), true);
196                if (!scratchURL) {
197                    OSKextLogStringError(/* kext */ NULL);
198                    result = EX_OSERR;
199                    goto finish;
200                }
201                CFArrayAppendValue((optchar == kOptDependency) ?
202                    toolArgs->dependencyURLs : toolArgs->repositoryURLs,
203                    scratchURL);
204                break;
205
206            case kOptQuiet:
207                beQuiet();
208                break;
209
210            case kOptVerbose:
211                scratchResult = setLogFilterForOpt(argc, argv, /* forceOnFlags */ 0);
212                if (scratchResult != EX_OK) {
213                    result = scratchResult;
214                    goto finish;
215                }
216                break;
217
218            case kOptNoCaches:
219                OSKextLog(/* kext */ NULL,
220                    kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
221                    "Notice: -%s (-%c) ignored; use kextutil(8) to test kexts.",
222                    kOptNameNoCaches, kOptNoCaches);
223                break;
224
225            case kOptNoLoadedCheck:
226                OSKextLog(/* kext */ NULL,
227                    kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
228                    "Notice: -%s (-%c) ignored.",
229                    kOptNameNoLoadedCheck, kOptNoLoadedCheck);
230                break;
231
232            case kOptTests:
233                OSKextLog(/* kext */ NULL,
234                    kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
235                    "Notice: -%s (-%c) ignored; use kextutil(8) to test kexts.",
236                    kOptNameTests, kOptTests);
237                break;
238
239            case 0:
240                switch (longopt) {
241                   default:
242                        OSKextLog(/* kext */ NULL,
243                            kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
244                            "Use kextutil(8) for development loading of kexts.");
245                        goto finish;
246                        break;
247                }
248                break;
249
250            default:
251                OSKextLog(/* kext */ NULL,
252                    kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
253                    "Use kextutil(8) for development loading of kexts.");
254                goto finish;
255                break;
256
257        } /* switch (optchar) */
258    } /* while (optchar = getopt_long_only(...) */
259
260   /*****
261    * Record the kext names from the command line.
262    */
263    for (i = optind; (int)i < argc; i++) {
264        SAFE_RELEASE_NULL(scratchURL);
265        scratchURL = CFURLCreateFromFileSystemRepresentation(
266            kCFAllocatorDefault,
267            (const UInt8 *)argv[i], strlen(argv[i]), true);
268        if (!scratchURL) {
269            result = EX_OSERR;
270            OSKextLogMemError();
271            goto finish;
272        }
273        CFArrayAppendValue(toolArgs->kextURLs, scratchURL);
274    }
275
276    result = EX_OK;
277
278finish:
279    SAFE_RELEASE(scratchString);
280    SAFE_RELEASE(scratchURL);
281
282    if (result == EX_USAGE) {
283        usage(kUsageLevelBrief);
284    }
285    return result;
286}
287
288/*******************************************************************************
289*******************************************************************************/
290ExitStatus
291checkArgs(KextloadArgs * toolArgs)
292{
293    ExitStatus         result         = EX_USAGE;
294
295    if (!CFArrayGetCount(toolArgs->kextURLs) &&
296        !CFArrayGetCount(toolArgs->kextIDs)) {
297
298        OSKextLog(/* kext */ NULL,
299            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
300            "No kernel extensions specified; name kernel extension bundles\n"
301            "    following options, or use -%s (-%c).",
302            kOptNameBundleIdentifier, kOptBundleIdentifier);
303        goto finish;
304    }
305
306    result = EX_OK;
307
308finish:
309    if (result == EX_USAGE) {
310        usage(kUsageLevelBrief);
311    }
312    return result;
313}
314
315/*******************************************************************************
316*******************************************************************************/
317ExitStatus checkAccess(void)
318{
319    ExitStatus    result         = EX_OK;
320#if !TARGET_OS_EMBEDDED
321    kern_return_t kern_result    = kOSReturnError;
322    mach_port_t   kextd_port     = MACH_PORT_NULL;
323
324    kern_result = bootstrap_look_up(bootstrap_port,
325        (char *)KEXTD_SERVER_NAME, &kextd_port);
326
327    if (kern_result == kOSReturnSuccess) {
328        sKextdActive = TRUE;
329    } else {
330        if (geteuid() == 0) {
331            OSKextLog(/* kext */ NULL,
332                kOSKextLogBasicLevel | kOSKextLogGeneralFlag |
333                kOSKextLogLoadFlag | kOSKextLogIPCFlag,
334                "Can't contact kextd; attempting to load directly into kernel.");
335        } else {
336            OSKextLog(/* kext */ NULL,
337                kOSKextLogErrorLevel | kOSKextLogGeneralFlag |
338                kOSKextLogLoadFlag | kOSKextLogIPCFlag,
339                "Can't contact kextd; must run as root to load kexts.");
340            result = EX_NOPERM;
341            goto finish;
342        }
343    }
344
345#else
346
347    if (geteuid() != 0) {
348        OSKextLog(/* kext */ NULL,
349            kOSKextLogErrorLevel | kOSKextLogGeneralFlag |
350            kOSKextLogLoadFlag | kOSKextLogIPCFlag,
351            "You must be running as root to load kexts.");
352        result = EX_NOPERM;
353        goto finish;
354    }
355
356#endif /* !TARGET_OS_EMBEDDED */
357
358finish:
359
360#if !TARGET_OS_EMBEDDED
361    if (kextd_port != MACH_PORT_NULL) {
362        mach_port_deallocate(mach_task_self(), kextd_port);
363    }
364#endif /* !TARGET_OS_EMBEDDED */
365
366    return result;
367}
368
369/*******************************************************************************
370*******************************************************************************/
371ExitStatus loadKextsViaKextd(KextloadArgs * toolArgs)
372{
373    ExitStatus result     = EX_OK;
374    OSReturn   loadResult = kOSReturnError;
375    char       scratchCString[PATH_MAX];
376    CFIndex    count, index;
377
378    count = CFArrayGetCount(toolArgs->kextIDs);
379    for (index = 0; index < count; index++) {
380        CFStringRef kextID  = CFArrayGetValueAtIndex(toolArgs->kextIDs, index);
381
382        if (!CFStringGetCString(kextID, scratchCString, sizeof(scratchCString),
383            kCFStringEncodingUTF8)) {
384
385            strlcpy(scratchCString, "unknown", sizeof(scratchCString));
386        }
387
388        OSKextLog(/* kext */ NULL,
389            kOSKextLogBasicLevel | kOSKextLogGeneralFlag |
390            kOSKextLogLoadFlag | kOSKextLogIPCFlag,
391            "Requesting load of %s.",
392            scratchCString);
393
394        loadResult = KextManagerLoadKextWithIdentifier(kextID,
395            toolArgs->scanURLs);
396        if (loadResult != kOSReturnSuccess) {
397            OSKextLog(/* kext */ NULL,
398                kOSKextLogErrorLevel | kOSKextLogLoadFlag | kOSKextLogIPCFlag,
399                "%s failed to load - %s; "
400                "check the system/kernel logs for errors or try kextutil(8).",
401                scratchCString, safe_mach_error_string(loadResult));
402            if (result == EX_OK) {
403                result = exitStatusForOSReturn(loadResult);
404                // keep trying other kexts though
405            }
406        } else {
407            OSKextLog(/* kext */ NULL,
408                kOSKextLogBasicLevel | kOSKextLogGeneralFlag | kOSKextLogLoadFlag,
409                "%s loaded successfully (or already loaded).",
410                scratchCString);
411        }
412    }
413
414    count = CFArrayGetCount(toolArgs->kextURLs);
415    for (index = 0; index < count; index++) {
416        CFURLRef kextURL = CFArrayGetValueAtIndex(toolArgs->kextURLs, index);
417        if (!CFURLGetFileSystemRepresentation(kextURL, /* resolveToBase */ true,
418            (UInt8 *)scratchCString, sizeof(scratchCString))) {
419
420            strlcpy(scratchCString, "unknown", sizeof(scratchCString));
421        }
422
423        OSKextLog(/* kext */ NULL,
424            kOSKextLogBasicLevel | kOSKextLogGeneralFlag |
425            kOSKextLogLoadFlag | kOSKextLogIPCFlag,
426            "Requesting load of %s.",
427            scratchCString);
428
429        loadResult = KextManagerLoadKextWithURL(kextURL,
430            toolArgs->scanURLs);
431        if (loadResult != kOSReturnSuccess) {
432            OSKextLog(/* kext */ NULL,
433                kOSKextLogErrorLevel | kOSKextLogLoadFlag | kOSKextLogIPCFlag,
434                "%s failed to load - %s; "
435                "check the system/kernel logs for errors or try kextutil(8).",
436                scratchCString, safe_mach_error_string(loadResult));
437            if (result == EX_OK) {
438                result = exitStatusForOSReturn(loadResult);
439                // keep trying other kexts though
440            }
441        } else {
442            OSKextLog(/* kext */ NULL,
443                kOSKextLogBasicLevel | kOSKextLogGeneralFlag | kOSKextLogLoadFlag,
444                "%s loaded successfully (or already loaded).",
445                scratchCString);
446        }
447    }
448
449    return result;
450}
451
452/*******************************************************************************
453*******************************************************************************/
454ExitStatus loadKextsIntoKernel(KextloadArgs * toolArgs)
455{
456    ExitStatus result     = EX_OK;
457    OSReturn   loadResult = kOSReturnError;
458    char       scratchCString[PATH_MAX];
459    CFIndex    count, index;
460
461    OSKextLog(/* kext */ NULL,
462        kOSKextLogProgressLevel | kOSKextLogGeneralFlag,
463        "Reading extensions.");
464    toolArgs->allKexts = OSKextCreateKextsFromURLs(kCFAllocatorDefault,
465        toolArgs->scanURLs);
466    if (!toolArgs->allKexts) {
467        OSKextLog(/* kext */ NULL,
468            kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
469            "Can't read kexts from disk.");
470        result = EX_OSERR;
471        goto finish;
472    }
473
474    count = CFArrayGetCount(toolArgs->kextIDs);
475    for (index = 0; index < count; index++) {
476        OSKextRef     theKext = NULL;  // do not release
477        CFStringRef   kextID  = CFArrayGetValueAtIndex(
478            toolArgs->kextIDs,
479            index);
480
481        if (!CFStringGetCString(kextID, scratchCString, sizeof(scratchCString),
482            kCFStringEncodingUTF8)) {
483
484            strlcpy(scratchCString, "unknown", sizeof(scratchCString));
485        }
486
487        OSKextLog(/* kext */ NULL,
488            kOSKextLogBasicLevel | kOSKextLogGeneralFlag |
489            kOSKextLogLoadFlag | kOSKextLogIPCFlag,
490            "Loading %s.",
491            scratchCString);
492
493        theKext = OSKextGetKextWithIdentifier(kextID);
494        if (!theKext) {
495            OSKextLog(/* kext */ NULL,
496                kOSKextLogErrorLevel | kOSKextLogLoadFlag | kOSKextLogIPCFlag,
497                "Error: Kext %s - not found/unable to create.", scratchCString);
498            result = kOSKextReturnNotFound;
499            goto finish;
500        }
501
502       /* The codepath from this function will do any error logging
503        * and cleanup needed.
504        */
505        loadResult = OSKextLoadWithOptions(theKext,
506            /* statExclusion */ kOSKextExcludeNone,
507            /* addPersonalitiesExclusion */ kOSKextExcludeNone,
508            /* personalityNames */ NULL,
509            /* delayAutounloadFlag */ false);
510
511        if (loadResult != kOSReturnSuccess) {
512            OSKextLog(/* kext */ NULL,
513                kOSKextLogErrorLevel | kOSKextLogLoadFlag | kOSKextLogIPCFlag,
514                "%s failed to load - %s.",
515                scratchCString, safe_mach_error_string(loadResult));
516            if (result == EX_OK) {
517                result = exitStatusForOSReturn(loadResult);
518                // keep trying other kexts though
519            }
520        } else {
521            OSKextLog(/* kext */ NULL,
522                kOSKextLogBasicLevel | kOSKextLogGeneralFlag | kOSKextLogLoadFlag,
523                "%s loaded successfully (or already loaded).",
524                scratchCString);
525        }
526    }
527
528    count = CFArrayGetCount(toolArgs->kextURLs);
529    for (index = 0; index < count; index++) {
530        CFURLRef      kextURL        = CFArrayGetValueAtIndex(
531            toolArgs->kextURLs,
532            index);
533        if (!CFURLGetFileSystemRepresentation(kextURL, /* resolveToBase */ true,
534            (UInt8 *)scratchCString, sizeof(scratchCString))) {
535
536            strlcpy(scratchCString, "unknown", sizeof(scratchCString));
537        }
538
539        OSKextLog(/* kext */ NULL,
540            kOSKextLogBasicLevel | kOSKextLogGeneralFlag |
541            kOSKextLogLoadFlag | kOSKextLogIPCFlag,
542            "Loading %s.",
543            scratchCString);
544
545        OSKextRef theKext = NULL;  // do not release
546
547       /* Use OSKextGetKextWithURL() to avoid double open error messages,
548        * because we already tried to open all kexts above.
549        * That means we don't log here if we don't find the kext.
550        */
551        theKext = OSKextGetKextWithURL(kextURL);
552        if (!theKext) {
553            loadResult = kOSKextReturnNotFound;
554        } else {
555           /* The codepath from this function will do any error logging
556            * and cleanup needed.
557            */
558            loadResult = OSKextLoadWithOptions(theKext,
559                /* statExclusion */ kOSKextExcludeNone,
560                /* addPersonalitiesExclusion */ kOSKextExcludeNone,
561                /* personalityNames */ NULL,
562                /* delayAutounloadFlag */ false);
563        }
564        if (loadResult != kOSReturnSuccess) {
565            OSKextLog(/* kext */ NULL,
566                kOSKextLogErrorLevel | kOSKextLogLoadFlag | kOSKextLogIPCFlag,
567                "%s failed to load - %s.",
568                scratchCString, safe_mach_error_string(loadResult));
569            if (result == EX_OK) {
570                result = exitStatusForOSReturn(loadResult);
571                // keep trying other kexts though
572            }
573        } else {
574            OSKextLog(/* kext */ NULL,
575                kOSKextLogBasicLevel | kOSKextLogGeneralFlag | kOSKextLogLoadFlag,
576                "%s loaded successfully (or already loaded).",
577                scratchCString);
578        }
579    }
580
581finish:
582    return result;
583}
584
585/*******************************************************************************
586*******************************************************************************/
587ExitStatus exitStatusForOSReturn(OSReturn osReturn)
588{
589    ExitStatus result = EX_OSERR;
590
591    switch (osReturn) {
592    case kOSKextReturnNotPrivileged:
593        result = EX_NOPERM;
594        break;
595    default:
596        result = EX_OSERR;
597        break;
598    }
599    return result;
600}
601
602/*******************************************************************************
603* usage()
604*******************************************************************************/
605void usage(UsageLevel usageLevel)
606{
607    fprintf(stderr, "usage: %s [options] [--] [kext] ...\n"
608      "\n", progname);
609
610    if (usageLevel == kUsageLevelBrief) {
611        fprintf(stderr, "use %s -%s for an explanation of each option\n",
612            progname, kOptNameHelp);
613        return;
614    }
615
616    fprintf(stderr, "kext: a kext bundle to load or examine\n");
617    fprintf(stderr, "\n");
618
619    fprintf(stderr, "-%s <bundle_id> (-%c):\n"
620        "        load/use the kext whose CFBundleIdentifier is <bundle_id>\n",
621        kOptNameBundleIdentifier, kOptBundleIdentifier);
622    fprintf(stderr, "-%s <kext> (-%c):\n"
623        "        consider <kext> as a candidate dependency\n",
624        kOptNameDependency, kOptDependency);
625    fprintf(stderr, "-%s <directory> (-%c):\n"
626        "        look in <directory> for kexts\n",
627        kOptNameRepository, kOptRepository);
628    fprintf(stderr, "\n");
629
630    fprintf(stderr, "-%s (-%c):\n"
631        "        quiet mode: print no informational or error messages\n",
632        kOptNameQuiet, kOptQuiet);
633    fprintf(stderr, "-%s [ 0-6 | 0x<flags> ] (-%c):\n"
634        "        verbose mode; print info about analysis & loading\n",
635        kOptNameVerbose, kOptVerbose);
636    fprintf(stderr, "\n");
637
638    fprintf(stderr, "-%s (-%c): print this message and exit\n",
639        kOptNameHelp, kOptHelp);
640    fprintf(stderr, "\n");
641
642    fprintf(stderr, "--: end of options\n");
643    return;
644}
645