#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFPriv.h> // for _CFRunLoopSetCurrent()
#include <IOKit/IOKitLib.h>
#include <IOKit/IOKitServer.h>
#include <IOKit/IOCFURLAccess.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 = "Kernel Extension Server";
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 gNonsecureKextRunLoopSource = 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_download_personalities(void);
static Boolean kextd_process_ndrvs(CFArrayRef repositoryDirectories);
static void usage(int level);
char * CFURLCopyCString(CFURLRef anURL);
__private_extern__
void IOLoadPEFsFromURL( CFURLRef url, io_service_t service );
int main (int argc, const char * argv[]) {
int exit_status = 0;
KXKextManagerError result = kKXKextManagerErrorNone;
int optchar;
CFIndex count, i;
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
gPendedNonsecureKexts = CFArrayCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeArrayCallBacks);
if (!gPendedNonsecureKexts) {
fprintf(stderr, "%s: memory allocation failure\n", progname);
exit_status = 1;
goto finish;
}
gPendedKextloadOperations = CFArrayCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeArrayCallBacks);
if (!gPendedKextloadOperations) {
fprintf(stderr, "%s: memory allocation failure\n", progname);
exit_status = 1;
goto finish;
}
gScheduledNonsecureKexts = CFArrayCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeArrayCallBacks);
if (!gScheduledNonsecureKexts) {
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);
}
}
CFArrayInsertValueAtIndex(repositoryDirectories, 0,
kKXSystemExtensionsFolder);
if (!kextd_process_ndrvs(repositoryDirectories)) {
exit_status = 1;
goto finish;
}
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, NULL);
if (result != kKXKextManagerErrorNone) {
kextd_error_log("can't add repository (%s).",
KXKextManagerErrorStaticCStringForError(result));
exit_status = 1;
goto finish;
}
CFRelease(directoryURL);
directoryURL = 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 (gPendedNonsecureKexts) CFRelease(gPendedNonsecureKexts);
if (gPendedKextloadOperations) CFRelease(gPendedKextloadOperations);
if (gScheduledNonsecureKexts) CFRelease(gScheduledNonsecureKexts);
#endif
exit(exit_status);
return exit_status;
}
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;
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_handle_pended_kextload;
gNonsecureKextRunLoopSource = CFRunLoopSourceCreate(kCFAllocatorDefault,
sourcePriority++, &sourceContext);
if (!gNonsecureKextRunLoopSource) {
kextd_error_log("couldn't create pended kextload run loop source");
result = false;
goto finish;
}
CFRunLoopAddSource(gMainRunLoop, gNonsecureKextRunLoopSource,
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 (gNonsecureKextRunLoopSource) CFRelease(gNonsecureKextRunLoopSource);
#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",
kern_result);
} 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;
}
void kextd_rescan(void)
{
#ifndef NO_CFUserNotification
CFArrayRemoveAllValues(gPendedNonsecureKexts);
CFArrayRemoveAllValues(gScheduledNonsecureKexts);
if (gSecurityNotification) {
CFUserNotificationCancel(gSecurityNotification);
CFRelease(gSecurityNotification);
gSecurityNotification = NULL;
}
if (gFailureNotification) {
CFUserNotificationCancel(gFailureNotification);
CFRelease(gFailureNotification);
gFailureNotification = NULL;
}
gSecurityAlertKext = NULL;
gResendSecurityAlertKextPersonalities = false;
#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 Boolean kextd_process_ndrvs(CFArrayRef repositoryDirectories)
{
Boolean result = true;
CFIndex repositoryCount, r;
CFStringRef thisPath = NULL; CFURLRef repositoryURL = NULL; CFURLRef ndrvDirURL = NULL;
repositoryCount = CFArrayGetCount(repositoryDirectories);
for (r = 0; r < repositoryCount; r++) {
if (repositoryURL) {
CFRelease(repositoryURL);
repositoryURL = NULL;
}
if (ndrvDirURL) {
CFRelease(ndrvDirURL);
ndrvDirURL = NULL;
}
thisPath = (CFStringRef)CFArrayGetValueAtIndex(
repositoryDirectories, r);
repositoryURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
thisPath, kCFURLPOSIXPathStyle, true);
if (!repositoryURL) {
kextd_error_log("memory allocation failure");
result = 0;
goto finish;
}
ndrvDirURL = CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault,
repositoryURL, CFSTR("AppleNDRV"), true);
if (!ndrvDirURL) {
kextd_error_log("memory allocation failure");
result = 0;
goto finish;
}
IOLoadPEFsFromURL( ndrvDirURL, MACH_PORT_NULL );
}
finish:
if (repositoryURL) CFRelease(repositoryURL);
if (ndrvDirURL) CFRelease(ndrvDirURL);
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;
}