#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFPriv.h> // for _CFRunLoopSetCurrent()
#include <IOKit/IOKitLib.h>
#include <IOKit/IOKitServer.h>
#include <IOKit/IOCFURLAccess.h>
#include <IOKit/IOCFUnserialize.h>
#include <mach/mach.h>
#include <mach/mach_host.h>
#include <mach/mach_error.h>
#include <libc.h>
#include <servers/bootstrap.h>
#include <sysexits.h>
#include <IOKit/kext/KXKextManager.h>
#include "globals.h"
#include "request.h"
#include "logging.h"
#include "queue.h"
#include "PTLock.h"
#include "paths.h"
static const char * KEXTD_SERVER_NAME = "com.apple.KernelExtensionServer";
#define kKXROMExtensionsFolder "/System/Library/Caches/com.apple.romextensions/"
char * progname = "(unknown)"; Boolean use_repository_caches = true;
Boolean debug = false;
Boolean load_in_task = false;
Boolean jettison_kernel_linker = true;
int g_verbose_level = 0; Boolean safe_boot_mode = false;
char * g_kernel_file = NULL; char * g_patch_dir = NULL; char * g_symbol_dir = NULL; Boolean gOverwrite_symbols = true;
mach_port_t g_io_master_port;
KXKextManagerRef gKextManager = NULL; CFRunLoopRef gMainRunLoop = NULL; CFRunLoopSourceRef gRescanRunLoopSource = NULL; CFRunLoopSourceRef gKernelRequestRunLoopSource = NULL; CFRunLoopSourceRef gClientRequestRunLoopSource = NULL; #ifndef NO_CFUserNotification
CFRunLoopSourceRef gNotificationQueueRunLoopSource = NULL; #endif
const char * default_kernel_file = "/mach";
queue_head_t g_request_queue;
PTLockRef gKernelRequestQueueLock = NULL;
PTLockRef gRunLoopSourceLock = NULL;
static Boolean kextd_is_running(mach_port_t * bootstrap_port_ref);
static int kextd_get_mach_ports(void);
static int kextd_fork(void);
static Boolean kextd_set_up_server(void);
static void kextd_release_parent_task(void);
void kextd_register_signals(void);
void kextd_handle_sigterm(int signum);
void kextd_handle_sighup(int signum);
void kextd_handle_sighup_in_runloop(void * info);
static Boolean kextd_find_rom_mkexts(void);
static Boolean kextd_download_personalities(void);
static void usage(int level);
char * CFURLCopyCString(CFURLRef anURL);
int main (int argc, const char * argv[]) {
int exit_status = 0;
KXKextManagerError result = kKXKextManagerErrorNone;
int optchar;
CFIndex count, i, rom_repository_idx = -1;
Boolean have_rom_mkexts = FALSE;
CFMutableArrayRef repositoryDirectories = NULL;
progname = rindex(argv[0], '/');
if (progname) {
progname++; } else {
progname = (char *)argv[0];
}
if (kextd_is_running(NULL)) {
exit_status = EX_UNAVAILABLE;
goto finish;
}
repositoryDirectories = CFArrayCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeArrayCallBacks);
if (!repositoryDirectories) {
fprintf(stderr, "%s: memory allocation failure\n", progname);
exit_status = 1;
goto finish;
}
#ifndef NO_CFUserNotification
gPendedNonsecureKextPaths = CFArrayCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeArrayCallBacks);
if (!gPendedNonsecureKextPaths) {
fprintf(stderr, "%s: memory allocation failure\n", progname);
exit_status = 1;
goto finish;
}
gNotifiedNonsecureKextPaths = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!gNotifiedNonsecureKextPaths) {
fprintf(stderr, "%s: memory allocation failure\n", progname);
exit_status = 1;
goto finish;
}
#endif
while ((optchar = getopt(argc, (char * const *)argv, "bcdfhjf:r:vx")) !=
-1) {
CFStringRef optArg = NULL;
switch (optchar) {
case 'b':
fprintf(stderr, "%s: -b is unused; ignoring", progname);
break;
case 'c':
use_repository_caches = false;
break;
case 'd':
debug = true;
break;
case 'f':
load_in_task = true;
break;
case 'h':
usage(1);
exit_status = 1;
goto finish;
break;
case 'j':
jettison_kernel_linker = false;
break;
case 'r':
if (!optarg) {
kextd_error_log("%s: no argument for -f", progname);
usage(0);
exit_status = 1;
goto finish;
}
optArg = CFStringCreateWithCString(kCFAllocatorDefault,
optarg, kCFStringEncodingMacRoman);
if (!optArg) {
fprintf(stderr, "%s: memory allocation failure\n", progname);
exit_status = 1;
goto finish;
}
CFArrayAppendValue(repositoryDirectories, optArg);
CFRelease(optArg);
optArg = NULL;
break;
case 'v':
{
const char * next;
if (optind >= argc) {
g_verbose_level = 1;
} else {
next = argv[optind];
if ((next[0] == '1' || next[0] == '2' || next[0] == '3' ||
next[0] == '4' || next[0] == '5' || next[0] == '6') &&
next[1] == '\0') {
g_verbose_level = atoi(next);
optind++;
} else if (next[0] == '-') {
g_verbose_level = 1;
} else if (optind < (argc - 1)) {
fprintf(stderr,"%s: invalid argument to -v option",
progname);
usage(0);
exit_status = 1;
goto finish;
} else {
g_verbose_level = 1;
}
}
}
break;
case 'x':
safe_boot_mode = true;
use_repository_caches = false; break;
default:
usage(0);
exit_status = 1;
goto finish;
}
}
argc -= optind;
argv += optind;
if (argc != 0) {
usage(0);
exit_status = 1;
goto finish;
}
if (!kextd_get_mach_ports()) {
exit_status = 1;
goto finish;
}
if (!debug && jettison_kernel_linker) {
if (!kextd_fork()) {
exit_status = 1;
goto finish;
}
kextd_openlog("kextd"); }
kextd_register_signals();
if (jettison_kernel_linker) {
kern_return_t kern_result;
kern_result = IOCatalogueSendData(g_io_master_port,
kIOCatalogRemoveKernelLinker, 0, 0);
if (kern_result != KERN_SUCCESS) {
kextd_error_log(
"couldn't remove linker from kernel; error %d "
"(may have been removed already)", kern_result);
}
have_rom_mkexts = kextd_find_rom_mkexts();
}
CFArrayInsertValueAtIndex(repositoryDirectories, 0,
kKXSystemExtensionsFolder);
if (have_rom_mkexts)
{
rom_repository_idx = 1;
CFArrayInsertValueAtIndex(repositoryDirectories, rom_repository_idx,
CFSTR(kKXROMExtensionsFolder));
}
if (!jettison_kernel_linker) {
goto finish;
}
gKextManager = KXKextManagerCreate(kCFAllocatorDefault);
if (!gKextManager) {
kextd_error_log("can't allocate kext manager");
exit_status = 1;
goto finish;
}
result = KXKextManagerInit(gKextManager,
false, safe_boot_mode);
if (result != kKXKextManagerErrorNone) {
kextd_error_log("can't initialize manager (%s)",
KXKextManagerErrorStaticCStringForError(result));
exit_status = 1;
goto finish;
}
KXKextManagerSetPerformLoadsInTask(gKextManager, load_in_task);
KXKextManagerSetPerformsStrictAuthentication(gKextManager, true);
KXKextManagerSetPerformsFullTests(gKextManager, false);
KXKextManagerSetLogLevel(gKextManager, g_verbose_level);
KXKextManagerSetLogFunction(gKextManager, kextd_log);
KXKextManagerSetErrorLogFunction(gKextManager, kextd_error_log);
KXKextManagerDisableClearRelationships(gKextManager);
count = CFArrayGetCount(repositoryDirectories);
for (i = 0; i < count; i++) {
CFStringRef directory = (CFStringRef)CFArrayGetValueAtIndex(
repositoryDirectories, i);
CFURLRef directoryURL =
CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
directory, kCFURLPOSIXPathStyle, true);
if (!directoryURL) {
kextd_error_log("memory allocation failure");
exit_status = 1;
goto finish;
}
result = KXKextManagerAddRepositoryDirectory(gKextManager,
directoryURL, true ,
(use_repository_caches && (i != rom_repository_idx)),
NULL);
if (result != kKXKextManagerErrorNone) {
kextd_error_log("can't add repository (%s).",
KXKextManagerErrorStaticCStringForError(result));
}
CFRelease(directoryURL);
directoryURL = NULL;
}
CFRelease(repositoryDirectories);
repositoryDirectories = NULL;
KXKextManagerEnableClearRelationships(gKextManager);
if (!kextd_set_up_server()) {
exit_status = 1;
goto finish;
}
if (!kextd_launch_kernel_request_thread()) {
exit_status = 1;
goto finish;
}
if (!kextd_download_personalities()) {
exit_status = 1;
goto finish;
}
if (!debug) {
kextd_release_parent_task();
}
CFRunLoopRun();
finish:
if (gKextManager) CFRelease(gKextManager);
if (gMainRunLoop) CFRelease(gMainRunLoop);
#ifndef NO_CFUserNotification
if (gPendedNonsecureKextPaths) CFRelease(gPendedNonsecureKextPaths);
if (gNotifiedNonsecureKextPaths) CFRelease(gNotifiedNonsecureKextPaths);
#endif
exit(exit_status);
return exit_status;
}
#define TEMP_FILE "/tmp/com.apple.iokit.kextd.XX"
#define MKEXTUNPACK_COMMAND "/usr/sbin/mkextunpack " \
"-d "kKXROMExtensionsFolder" "
static kern_return_t process_mkext(const UInt8 * bytes, CFIndex length)
{
kern_return_t err;
char temp_file[1 + strlen(TEMP_FILE)];
char mkextunpack_cmd[1 + strlen(TEMP_FILE) + strlen(MKEXTUNPACK_COMMAND)];
const char * rom_ext_dir = kKXROMExtensionsFolder;
int outfd = -1;
struct stat stat_buf;
strcpy(temp_file, TEMP_FILE);
mktemp(temp_file);
outfd = open(temp_file, O_WRONLY|O_CREAT|O_TRUNC, 0666);
if (-1 == outfd) {
kextd_error_log("can't create %s - %s\n", temp_file,
strerror(errno));
err = kKXKextManagerErrorFileAccess;
goto finish;
}
if (length != write(outfd, bytes, length))
err = kKXKextManagerErrorDiskFull;
else
err = kKXKextManagerErrorNone;
if (kKXKextManagerErrorNone != err) {
kextd_error_log("couldn't write output");
goto finish;
}
close(outfd);
outfd = -1;
if (-1 == stat(rom_ext_dir, &stat_buf))
{
if (0 != mkdir(rom_ext_dir, 0755))
{
kextd_error_log("mkdir(%s) failed: %s\n", rom_ext_dir, strerror(errno));
err = kKXKextManagerErrorFileAccess;
goto finish;
}
}
strcpy(mkextunpack_cmd, MKEXTUNPACK_COMMAND);
strcat(mkextunpack_cmd, temp_file);
if (0 != system(mkextunpack_cmd))
{
kextd_error_log(mkextunpack_cmd);
kextd_error_log("failed");
err = kKXKextManagerErrorChildTask;
goto finish;
}
finish:
if (-1 != outfd)
close(outfd);
unlink(temp_file);
return err;
}
static Boolean kextd_find_rom_mkexts(void)
{
kern_return_t kr;
CFSetRef set = NULL;
CFDataRef * mkexts = NULL;
CFIndex count, idx;
char * propertiesBuffer;
int loaded_bytecount;
enum { _kIOCatalogGetROMMkextList = 4 };
kr = IOCatalogueGetData(MACH_PORT_NULL, _kIOCatalogGetROMMkextList,
&propertiesBuffer, &loaded_bytecount);
if (kIOReturnSuccess == kr)
{
set = (CFSetRef)
IOCFUnserialize(propertiesBuffer, kCFAllocatorDefault, 0, 0);
vm_deallocate(mach_task_self(), (vm_address_t) propertiesBuffer, loaded_bytecount);
}
if (!set)
return false;
count = CFSetGetCount(set);
if (count)
{
mkexts = (CFDataRef *) calloc(count, sizeof(CFDataRef));
CFSetGetValues(set, (const void **) mkexts);
for (idx = 0; idx < count; idx++)
{
process_mkext(CFDataGetBytePtr(mkexts[idx]), CFDataGetLength(mkexts[idx]));
}
free(mkexts);
}
CFRelease(set);
return (count > 0);
}
static Boolean kextd_is_running(mach_port_t * bootstrap_port_ref)
{
boolean_t active = FALSE;
Boolean result = false;
kern_return_t kern_result = KERN_SUCCESS;
mach_port_t bootstrap_port;
if (bootstrap_port_ref && (*bootstrap_port_ref != PORT_NULL)) {
bootstrap_port = *bootstrap_port_ref;
} else {
kern_result = task_get_bootstrap_port(mach_task_self(),
&bootstrap_port);
if (kern_result != KERN_SUCCESS) {
kextd_error_log("task_get_bootstrap_port(): %s\n",
mach_error_string(kern_result));
exit (EX_UNAVAILABLE);
}
if (bootstrap_port_ref) {
*bootstrap_port_ref = bootstrap_port;
}
}
kern_result = bootstrap_status(bootstrap_port,
(char *)KEXTD_SERVER_NAME, &active);
switch (kern_result) {
case BOOTSTRAP_SUCCESS:
if (active) {
kextd_error_log("kextd: '%s' is already active\n",
KEXTD_SERVER_NAME);
result = true;
goto finish;
}
break;
case BOOTSTRAP_UNKNOWN_SERVICE:
result = false;
goto finish;
break;
default:
kextd_error_log("bootstrap_status(): %s\n",
mach_error_string(kern_result));
exit(EX_UNAVAILABLE);
}
finish:
return result;
}
static int kextd_get_mach_ports(void)
{
kern_return_t kern_result;
kern_result = IOMasterPort(NULL, &g_io_master_port);
if (kern_result != KERN_SUCCESS) {
kextd_error_log("couldn't get catalog port");
return 0;
}
return 1;
}
int kextd_fork(void)
{
uid_t pid;
signal(SIGTERM, kextd_handle_sigterm);
pid = fork();
switch (pid) {
case -1:
return 0;
break;
case 0: if (!kextd_get_mach_ports()) {
exit(1);
}
signal(SIGTERM, SIG_DFL);
_CFRunLoopSetCurrent(NULL);
break;
default: {
int status;
kextd_openlog("kextd-parent");
wait4(pid, (int *)&status, 0, 0);
if (WIFEXITED(status)) {
kextd_error_log(
"*** %s (daemon) failed to start, exit status=%d",
progname, WEXITSTATUS(status));
} else {
kextd_error_log(
"*** %s (daemon) failed to start, received signal=%d",
progname, WTERMSIG(status));
}
fflush (stderr);
exit(1);
}
break;
}
if (setsid() == -1) {
return 0;
}
if (chdir("/") == -1) {
return 0;
}
return 1;
}
extern void kextd_mach_port_callback(
CFMachPortRef port,
void *msg,
CFIndex size,
void *info);
static Boolean kextd_set_up_server(void)
{
Boolean result = true;
kern_return_t kern_result = KERN_SUCCESS;
CFRunLoopSourceContext sourceContext;
unsigned int sourcePriority = 1;
CFMachPortRef kextdMachPort = NULL;
if (kextd_is_running(&bootstrap_port)) {
result = false;
goto finish;
}
gMainRunLoop = CFRunLoopGetCurrent();
if (!gMainRunLoop) {
kextd_error_log("couldn't create run loop");
result = false;
goto finish;
}
bzero(&sourceContext, sizeof(CFRunLoopSourceContext));
sourceContext.version = 0;
sourceContext.perform = kextd_handle_sighup_in_runloop;
gRescanRunLoopSource = CFRunLoopSourceCreate(kCFAllocatorDefault,
sourcePriority++, &sourceContext);
if (!gRescanRunLoopSource) {
kextd_error_log("couldn't create signal-handling run loop source");
result = false;
goto finish;
}
CFRunLoopAddSource(gMainRunLoop, gRescanRunLoopSource,
kCFRunLoopDefaultMode);
sourceContext.perform = kextd_handle_kernel_request;
gKernelRequestRunLoopSource = CFRunLoopSourceCreate(kCFAllocatorDefault,
sourcePriority++, &sourceContext);
if (!gKernelRequestRunLoopSource) {
kextd_error_log("couldn't create kernel request run loop source");
result = false;
goto finish;
}
CFRunLoopAddSource(gMainRunLoop, gKernelRequestRunLoopSource,
kCFRunLoopDefaultMode);
kextdMachPort = CFMachPortCreate(kCFAllocatorDefault,
kextd_mach_port_callback, NULL, NULL);
gClientRequestRunLoopSource = CFMachPortCreateRunLoopSource(
kCFAllocatorDefault, kextdMachPort, sourcePriority++);
if (!gClientRequestRunLoopSource) {
kextd_error_log("couldn't create client request run loop source");
result = false;
goto finish;
}
CFRunLoopAddSource(gMainRunLoop, gClientRequestRunLoopSource,
kCFRunLoopDefaultMode);
#ifndef NO_CFUserNotification
sourceContext.perform = kextd_check_notification_queue;
gNotificationQueueRunLoopSource = CFRunLoopSourceCreate(kCFAllocatorDefault,
sourcePriority++, &sourceContext);
if (!gNotificationQueueRunLoopSource) {
kextd_error_log("couldn't create alert run loop source");
result = false;
goto finish;
}
CFRunLoopAddSource(gMainRunLoop, gNotificationQueueRunLoopSource,
kCFRunLoopDefaultMode);
#endif
kextd_log("registering service \"%s\"", KEXTD_SERVER_NAME);
kern_result = bootstrap_register(bootstrap_port,
(char *)KEXTD_SERVER_NAME, CFMachPortGetPort(kextdMachPort));
switch (kern_result) {
case BOOTSTRAP_SUCCESS:
break;
case BOOTSTRAP_NOT_PRIVILEGED:
kextd_error_log("bootstrap_register(): bootstrap not privileged");
exit(EX_OSERR);
case BOOTSTRAP_SERVICE_ACTIVE:
kextd_error_log("bootstrap_register(): bootstrap service active");
exit(EX_OSERR);
default:
kextd_error_log("bootstrap_register(): %s",
mach_error_string(kern_result));
exit(EX_OSERR);
}
finish:
if (gRescanRunLoopSource) CFRelease(gRescanRunLoopSource);
if (gKernelRequestRunLoopSource) CFRelease(gKernelRequestRunLoopSource);
if (gClientRequestRunLoopSource) CFRelease(gClientRequestRunLoopSource);
#ifndef NO_CFUserNotification
if (gNotificationQueueRunLoopSource) CFRelease(gNotificationQueueRunLoopSource);
#endif
if (kextdMachPort) CFRelease(kextdMachPort);
return result;
}
void kextd_release_parent_task(void)
{
kill(getppid(), SIGTERM);
return;
}
void kextd_register_signals(void)
{
signal(SIGHUP, kextd_handle_sighup);
return;
}
void kextd_handle_sigterm(int signum)
{
kern_return_t kern_result;
mach_timespec_t waitTime = { 40, 0 };
kern_result = IOKitWaitQuiet(g_io_master_port, &waitTime);
if (kern_result == kIOReturnTimeout) {
kextd_error_log("IOKitWaitQuiet() timed out");
} else if (kern_result != kIOReturnSuccess) {
kextd_error_log("IOKitWaitQuiet() failed with result code %lx",
kern_result);
}
_exit(0);
return;
}
void kextd_handle_sighup(int signum)
{
if (gRescanRunLoopSource) {
PTLockTakeLock(gRunLoopSourceLock);
kextd_log("received SIGHUP; rescanning all kexts and resetting catalogue");
CFRunLoopSourceSignal(gRescanRunLoopSource);
CFRunLoopWakeUp(gMainRunLoop);
PTLockUnlock(gRunLoopSourceLock);
} else {
kextd_log("received SIGHUP before entering run loop; ignoring");
}
return;
}
#ifndef NO_CFUserNotification
void kextd_clear_all_notifications(void)
{
CFArrayRemoveAllValues(gPendedNonsecureKextPaths);
if (gCurrentNotification) {
CFUserNotificationCancel(gCurrentNotification);
CFRelease(gCurrentNotification);
gCurrentNotification = NULL;
}
if (gCurrentNotificationRunLoopSource) {
CFRunLoopRemoveSource(gMainRunLoop, gCurrentNotificationRunLoopSource,
kCFRunLoopDefaultMode);
CFRelease(gCurrentNotificationRunLoopSource);
gCurrentNotificationRunLoopSource = NULL;
}
CFDictionaryRemoveAllValues(gNotifiedNonsecureKextPaths);
return;
}
#endif
void kextd_rescan(void)
{
#ifndef NO_CFUserNotification
kextd_clear_all_notifications();
#endif
KXKextManagerResetAllRepositories(gKextManager);
kextd_download_personalities();
return;
}
void kextd_handle_sighup_in_runloop(void * info)
{
kextd_rescan();
return;
}
static Boolean kextd_download_personalities(void)
{
Boolean result = true;
CFArrayRef allKextPersonalities = NULL;
IOCatalogueReset(g_io_master_port, kIOCatalogResetDefault);
allKextPersonalities = KXKextManagerCopyAllKextPersonalities(gKextManager);
if (!allKextPersonalities) {
kextd_error_log("can't get kext personalities to send to kernel");
result = false;
goto finish;
}
if (KXKextManagerSendPersonalitiesToCatalog(gKextManager,
allKextPersonalities) != kKXKextManagerErrorNone) {
kextd_error_log("can't send kext personalities to kernel");
result = false;
goto finish;
}
finish:
if (allKextPersonalities) {
CFRelease(allKextPersonalities);
}
return result;
}
static void usage(int level)
{
fprintf(stderr,
"usage: %s [-c] [-d] [-f] [-h] [-j] [-r directory] ... [-v [1-6]] [-x]",
progname);
if (level > 1) {
kextd_error_log(" -c don't use repository caches; scan repository folders\n");
kextd_error_log(" -d run in debug mode (don't fork daemon)\n");
kextd_error_log(" -f don't fork when loading (for debugging only)\n");
kextd_error_log(" -h help; print this list\n");
kextd_error_log(" -j don't jettison kernel linker; "
"just load NDRVs and exit (for startup from install CD)\n");
kextd_error_log(" -r start up with kexts in directory in addition to "
"those in /System/Library/Extensions\n");
kextd_error_log(" -v verbose mode\n");
kextd_error_log(" -x run in safe boot mode.\n");
}
return;
}
char * CFURLCopyCString(CFURLRef anURL)
{
char * string = NULL; CFIndex bufferLength;
CFStringRef urlString = NULL; Boolean error = false;
urlString = CFURLCopyFileSystemPath(anURL, kCFURLPOSIXPathStyle);
if (!urlString) {
goto finish;
}
bufferLength = 1 + CFStringGetLength(urlString);
string = (char *)malloc(bufferLength * sizeof(char));
if (!string) {
goto finish;
}
if (!CFStringGetCString(urlString, string, bufferLength,
kCFStringEncodingMacRoman)) {
error = true;
goto finish;
}
finish:
if (error) {
free(string);
string = NULL;
}
if (urlString) CFRelease(urlString);
return string;
}