#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 <IOKit/storage/RAID/AppleRAIDUserLib.h>
#include <IOKit/kext/OSKext.h>
#include <mach/mach.h>
#include <mach/mach_host.h>
#include <mach/mach_error.h>
#include <mach-o/arch.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <libc.h>
#include <servers/bootstrap.h>
#include <signal.h>
#include <sysexits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/resource.h>
#include <unistd.h>
#include <paths.h>
#include <IOKit/kext/OSKext.h>
#include <IOKit/kext/OSKextPrivate.h>
#include <IOKit/kext/kextmanager_types.h>
#include <bootfiles.h>
#include "kextd_main.h"
#include "kext_tools_util.h"
#include "kextd_globals.h"
#include "kextd_personalities.h"
#include "kextd_mig_server.h"
#include "kextd_request.h"
#include "kextd_usernotification.h"
#include "kextd_watchvol.h"
#include "bootcaches.h"
const char * progname = "(unknown)";
struct option sOptInfo[] = {
{ kOptNameHelp, no_argument, NULL, kOptHelp },
{ kOptNameNoCaches, no_argument, NULL, kOptNoCaches },
{ kOptNameDebug, no_argument, NULL, kOptDebug },
{ kOptNameRepository, no_argument, NULL, kOptRepository },
{ kOptNameQuiet, required_argument, NULL, kOptQuiet },
{ kOptNameVerbose, optional_argument, NULL, kOptVerbose },
{ NULL, 0, NULL, 0 } };
KextdArgs sToolArgs;
static CFArrayRef sAllKexts = NULL;
Boolean gKernelRequestsPending = false;
static CFRunLoopTimerRef sReleaseKextsTimer = NULL;
static CFRunLoopSourceRef sClientRequestRunLoopSource = NULL;
static CFMachPortRef sKextdSignalMachPort = NULL;
static mach_port_t sKextSignalMachPortMachPort = MACH_PORT_NULL;
static CFRunLoopSourceRef sSignalRunLoopSource = NULL;
const NXArchInfo * gKernelArchInfo = NULL;
ExitStatus sKextdExitStatus = kKextdExitOK;
int main(int argc, char * const * argv)
{
char logSpecBuffer[16];
progname = rindex(argv[0], '/');
if (progname) {
progname++; } else {
progname = (char *)argv[0];
}
OSKextSetLogOutputFunction(&tool_log);
sKextdExitStatus = readArgs(argc, argv, &sToolArgs);
if (sKextdExitStatus != EX_OK) {
goto finish;
}
if (!sToolArgs.debugMode) {
tool_openlog("com.apple.kextd");
}
setenv("KEXTD_SPAWNED", "", 1);
snprintf(logSpecBuffer, sizeof(logSpecBuffer), "0x%x",
OSKextGetLogFilter( true));
setenv("KEXT_LOG_FILTER_KERNEL", logSpecBuffer, 1);
snprintf(logSpecBuffer, sizeof(logSpecBuffer), "0x%x",
OSKextGetLogFilter( false));
setenv("KEXT_LOG_FILTER_USER", logSpecBuffer, 1);
if (!gKernelArchInfo) {
gKernelArchInfo = OSKextGetRunningKernelArchitecture();
if (!gKernelArchInfo) {
goto finish;
}
}
if (OSKextGetActualSafeBoot()) {
sToolArgs.safeBootMode = true;
} else if (sToolArgs.safeBootMode) {
OSKextSetSimulatedSafeBoot(true);
}
if (sToolArgs.safeBootMode) {
sToolArgs.useRepositoryCaches = false; }
OSKextSetUsesCaches(sToolArgs.useRepositoryCaches);
if (sToolArgs.safeBootMode) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag | kOSKextLogLoadFlag,
"Safe boot mode detected; invalidating system extensions caches.");
utimes("/System/Library/Extensions", NULL);
}
checkStartupMkext(&sToolArgs);
#if 0
if (sToolArgs.staleStartupMkext) {
goto server_start;
}
#endif
OSKextSetRecordsDiagnostics(kOSKextDiagnosticsFlagNone);
readExtensions();
sKextdExitStatus = setUpServer(&sToolArgs);
if (sKextdExitStatus != EX_OK) {
goto finish;
}
sendActiveToKernel();
if (kOSReturnSuccess != sendPersonalitiesToKernel()) {
sKextdExitStatus = EX_OSERR;
goto finish;
}
sendFinishedToKernel();
CFRunLoopRun();
_exit(sKextdExitStatus);
finish:
#ifndef NO_CFUserNotification
stopMonitoringConsoleUser();
#endif
kextd_stop_volwatch();
if (sKextdExitStatus == kKextdExitHelp) {
sKextdExitStatus = kKextdExitOK;
}
exit(sKextdExitStatus);
SAFE_RELEASE(sToolArgs.repositoryURLs);
return sKextdExitStatus;
}
#pragma mark Major Subroutines
ExitStatus
readArgs(
int argc,
char * const * argv,
KextdArgs * toolArgs)
{
ExitStatus result = EX_USAGE;
CFArrayRef SystemExtensionsFolderURLs = NULL; struct stat stat_buf;
int optchar;
int longindex;
CFStringRef scratchString = NULL; CFNumberRef scratchNumber = NULL; CFURLRef scratchURL = NULL;
OSKextSetLogFilter(kDefaultServiceLogFilter, false);
OSKextSetLogFilter(kOSKextLogSilentFilter, true);
bzero(toolArgs, sizeof(*toolArgs));
toolArgs->debugMode = false;
toolArgs->useRepositoryCaches = true;
if (stat(kAppleSetupDonePath, &stat_buf) == -1 && errno == ENOENT) {
toolArgs->firstBoot = true;
} else {
toolArgs->firstBoot = false;
}
if (!createCFMutableArray(&toolArgs->repositoryURLs,
&kCFTypeArrayCallBacks)) {
result = EX_OSERR;
OSKextLogMemError();
exit(result);
}
SystemExtensionsFolderURLs = OSKextGetSystemExtensionsFolderURLs();
if (!SystemExtensionsFolderURLs) {
result = EX_OSERR;
OSKextLogMemError();
result = EX_OSERR;
goto finish;
}
CFArrayAppendArray(toolArgs->repositoryURLs, SystemExtensionsFolderURLs,
RANGE_ALL(SystemExtensionsFolderURLs));
while ((optchar = getopt_long_only(argc, (char * const *)argv,
kOptChars, sOptInfo, &longindex)) != -1) {
SAFE_RELEASE_NULL(scratchString);
SAFE_RELEASE_NULL(scratchNumber);
SAFE_RELEASE_NULL(scratchURL);
switch (optchar) {
case kOptHelp:
usage(kUsageLevelFull);
result = kKextdExitHelp;
goto finish;
break;
case kOptRepository:
scratchURL = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault,
(const UInt8 *)optarg, strlen(optarg), true);
if (!scratchURL) {
OSKextLogStringError( NULL);
result = EX_OSERR;
goto finish;
}
CFArrayAppendValue(toolArgs->repositoryURLs, scratchURL);
break;
case kOptNoCaches:
toolArgs->useRepositoryCaches = false;
break;
case kOptDebug:
toolArgs->debugMode = true;
break;
case kOptQuiet:
beQuiet();
break;
case kOptVerbose:
result = setLogFilterForOpt(argc, argv, 0);
OSKextSetLogFilter(kOSKextLogSilentFilter, true);
break;
case kOptSafeBoot:
toolArgs->safeBootMode = true;
toolArgs->useRepositoryCaches = false; break;
default:
goto finish;
break;
}
}
argc -= optind;
argv += optind;
if (argc) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Extra input on command line; %s....", argv[0]);
goto finish;
}
result = EX_OK;
finish:
SAFE_RELEASE(scratchString);
SAFE_RELEASE(scratchNumber);
SAFE_RELEASE(scratchURL);
if (result == EX_USAGE) {
usage(kUsageLevelBrief);
}
return result;
}
void checkStartupMkext(KextdArgs * toolArgs)
{
struct stat extensions_stat_buf;
struct stat mkext_stat_buf;
Boolean outOfDate;
if (isBootRootActive() && !isNetboot()) {
toolArgs->staleBootNotificationNeeded = bootedFromDifferentMkext();
if (toolArgs->staleBootNotificationNeeded) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Warning: mkext on root filesystem does not match "
"the one used for startup");
}
}
outOfDate = (0 != stat(_kOSKextStartupMkextPath, &mkext_stat_buf));
if (!outOfDate && (0 == stat(kSystemExtensionsDir, &extensions_stat_buf))) {
outOfDate = (mkext_stat_buf.st_mtime !=
(extensions_stat_buf.st_mtime + 1));
}
if (outOfDate) {
do {
if (isBootRootActive()) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Warning: mkext unexpectedly out of date relative "
"to Extensions folder.");
toolArgs->staleStartupMkext = true;
break; }
} while (false);
}
return;
}
Boolean isNetboot(void)
{
Boolean result = false;
int netboot_mib_name[] = { CTL_KERN, KERN_NETBOOT };
int netboot = 0;
size_t netboot_len = sizeof(netboot);
if (sysctl(netboot_mib_name, sizeof(netboot_mib_name) / sizeof(int),
&netboot, &netboot_len, NULL, 0) != 0) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Failed to detect netboot - %s.", strerror(errno));
goto finish;
}
result = netboot ? true : false;
finish:
return result;
}
void sendActiveToKernel(void)
{
kern_return_t kernelResult;
kernelResult = IOCatalogueSendData(kIOMasterPortDefault,
kIOCatalogKextdActive, 0, 0);
if (kernelResult != KERN_SUCCESS) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Failed to notify kernel that kextd is active - %s.",
safe_mach_error_string(kernelResult));
}
return;
}
void sendFinishedToKernel(void)
{
kern_return_t kernelResult;
kernelResult = IOCatalogueSendData(kIOMasterPortDefault,
kIOCatalogKextdFinishedLaunching, 0, 0);
if (kernelResult != KERN_SUCCESS) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Failed to notify kernel that kextd is finished launching - %s.",
safe_mach_error_string(kernelResult));
}
return;
}
ExitStatus setUpServer(KextdArgs * toolArgs)
{
ExitStatus result = EX_OSERR;
kern_return_t kernelResult = KERN_SUCCESS;
unsigned int sourcePriority = 1;
CFMachPortRef kextdMachPort = NULL; mach_port_limits_t limits; mach_port_t servicePort;
kernelResult = bootstrap_check_in(bootstrap_port,
KEXTD_SERVER_NAME, &servicePort);
if (kernelResult != BOOTSTRAP_SUCCESS) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag | kOSKextLogIPCFlag,
"Failed server check-in - %s", bootstrap_strerror(kernelResult));
exit(EX_OSERR);
}
if (!CFRunLoopGetCurrent()) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Failed to create run loop.");
goto finish;
}
kextdMachPort = CFMachPortCreateWithPort(kCFAllocatorDefault,
servicePort, kextd_mach_port_callback, NULL,
NULL);
sClientRequestRunLoopSource = CFMachPortCreateRunLoopSource(
kCFAllocatorDefault, kextdMachPort, sourcePriority++);
if (!sClientRequestRunLoopSource) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Failed to create client request run loop source.");
goto finish;
}
CFRunLoopAddSource(CFRunLoopGetCurrent(), sClientRequestRunLoopSource,
kCFRunLoopDefaultMode);
if (kextd_watch_volumes(sourcePriority++)) {
goto finish;
}
sKextdSignalMachPort = CFMachPortCreate(kCFAllocatorDefault,
handleSignalInRunloop, NULL, NULL);
if (!sKextdSignalMachPort) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Failed to create signal-handling Mach port.");
goto finish;
}
sKextSignalMachPortMachPort = CFMachPortGetPort(sKextdSignalMachPort);
limits.mpl_qlimit = 1;
kernelResult = mach_port_set_attributes(mach_task_self(),
sKextSignalMachPortMachPort,
MACH_PORT_LIMITS_INFO,
(mach_port_info_t)&limits,
MACH_PORT_LIMITS_INFO_COUNT);
if (kernelResult != KERN_SUCCESS) {
OSKextLog( NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Failed to set signal-handling port limits.");
}
sSignalRunLoopSource = CFMachPortCreateRunLoopSource(
kCFAllocatorDefault, sKextdSignalMachPort, sourcePriority++);
if (!sSignalRunLoopSource) {
OSKextLog( NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Failed to create signal-handling run loop source.");
goto finish;
}
CFRunLoopAddSource(CFRunLoopGetCurrent(), sSignalRunLoopSource,
kCFRunLoopDefaultMode);
CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(),
NULL, updateRAIDSet,
CFSTR(kAppleRAIDNotificationSetChanged),
NULL, CFNotificationSuspensionBehaviorHold);
kernelResult = AppleRAIDEnableNotifications();
if (kernelResult != KERN_SUCCESS) {
OSKextLog( NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Failed to register for RAID notifications.");
}
#ifndef NO_CFUserNotification
result = startMonitoringConsoleUser(toolArgs, &sourcePriority);
if (result != EX_OK) {
goto finish;
}
#endif
signal(SIGHUP, handleSignal);
signal(SIGTERM, handleSignal);
signal(SIGCHLD, handleSignal);
result = EX_OK;
finish:
SAFE_RELEASE(sClientRequestRunLoopSource);
SAFE_RELEASE(sKextdSignalMachPort);
SAFE_RELEASE(sSignalRunLoopSource);
SAFE_RELEASE(kextdMachPort);
return result;
}
bool isBootRootActive(void)
{
int result = false;
io_service_t chosen = 0; CFTypeRef bootrootProp = 0;
chosen = IORegistryEntryFromPath(kIOMasterPortDefault,
"IODeviceTree:/chosen");
if (!chosen) {
goto finish;
}
bootrootProp = IORegistryEntryCreateCFProperty(
chosen, CFSTR(kBootRootActiveKey), kCFAllocatorDefault,
0 );
if (bootrootProp) {
result = true;
}
finish:
if (chosen) IOObjectRelease(chosen);
SAFE_RELEASE(bootrootProp);
return result;
}
void handleSignal(int signum)
{
kextd_mach_msg_signal_t msg;
kern_return_t kernelResult;
msg.signum = signum;
msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
msg.header.msgh_size = sizeof(msg.header);
msg.header.msgh_remote_port = sKextSignalMachPortMachPort;
msg.header.msgh_local_port = MACH_PORT_NULL;
msg.header.msgh_id = 0;
kernelResult = mach_msg(
&msg.header,
MACH_SEND_MSG | MACH_SEND_TIMEOUT,
sizeof(msg),
0,
MACH_PORT_NULL,
0,
MACH_PORT_NULL);
return;
}
void handleSignalInRunloop(
CFMachPortRef port,
void * msg,
CFIndex size,
void * info)
{
kextd_mach_msg_signal_t * signal_msg = (kextd_mach_msg_signal_t *)msg;
int signum = signal_msg->signum;
if (signum == SIGHUP) {
rescanExtensions();
} else if (signum == SIGTERM) {
CFRunLoopStop(CFRunLoopGetCurrent());
sKextdExitStatus = kKextdExitSigterm;
} else if (signum == SIGCHLD) {
pid_t child_pid = -1;
int child_status = 0;
do {
child_pid = waitpid(-1 , &child_status, WNOHANG);
if (child_pid == -1) {
if (errno != ECHILD) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Error %s waiting on child processes.",
strerror(errno));
}
} else if (child_pid > 0) {
OSKextLogSpec logSpec;
if (WEXITSTATUS(child_status)==0 || child_status==EX_SOFTWARE) {
logSpec = kOSKextLogDetailLevel;
} else {
logSpec = kOSKextLogErrorLevel;
}
OSKextLog( NULL, logSpec,
"async child pid %d exited with status %d",
child_pid, WEXITSTATUS(child_status));
}
} while (child_pid > 0);
}
return;
}
void readExtensions(void)
{
static Boolean haveStattedExtensions = false;
static struct timespec lastTimespec;
struct stat statBuf;
Boolean needReread = false;
if (0 != stat(kSystemExtensionsDir, &statBuf)) {
needReread = true;
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Failed to stat extensions folder (%s); rereading.",
strerror(errno));
} else {
if (!haveStattedExtensions) {
haveStattedExtensions = true;
} else if ((lastTimespec.tv_sec != statBuf.st_mtimespec.tv_sec) ||
(lastTimespec.tv_nsec != statBuf.st_mtimespec.tv_nsec)) {
needReread = true;
OSKextLog( NULL,
kOSKextLogProgressLevel | kOSKextLogGeneralFlag,
"Extensions folder mod time has changed; rereading.");
}
lastTimespec = statBuf.st_mtimespec;
}
if (needReread) {
releaseExtensions( NULL, NULL);
}
if (!sAllKexts && sToolArgs.repositoryURLs) {
OSKextLog( NULL,
kOSKextLogProgressLevel | kOSKextLogGeneralFlag,
"Reading extensions.");
sAllKexts = OSKextCreateKextsFromURLs(kCFAllocatorDefault,
sToolArgs.repositoryURLs);
}
scheduleReleaseExtensions();
return;
}
void scheduleReleaseExtensions(void)
{
OSKextLog( NULL,
kOSKextLogProgressLevel | kOSKextLogGeneralFlag,
"%scheduling release of all kexts.", sReleaseKextsTimer ? "Res" : "S");
if (sReleaseKextsTimer) {
CFRunLoopTimerInvalidate(sReleaseKextsTimer);
SAFE_RELEASE_NULL(sReleaseKextsTimer);
}
sReleaseKextsTimer = CFRunLoopTimerCreate(kCFAllocatorDefault,
CFAbsoluteTimeGetCurrent() + kReleaseKextsDelay, 0,
0, 0, &releaseExtensions, NULL);
if (!sReleaseKextsTimer) {
OSKextLogMemError();
goto finish;
}
CFRunLoopAddTimer(CFRunLoopGetCurrent(), sReleaseKextsTimer,
kCFRunLoopDefaultMode);
finish:
return;
}
void releaseExtensions(
CFRunLoopTimerRef timer,
void * context __unused)
{
OSKextLog( NULL,
kOSKextLogProgressLevel | kOSKextLogGeneralFlag,
"Releasing all kexts.");
if (timer == sReleaseKextsTimer) {
SAFE_RELEASE_NULL(sReleaseKextsTimer);
}
SAFE_RELEASE_NULL(sAllKexts);
return;
}
void rescanExtensions(void)
{
CFIndex count, i;
OSKextLog( NULL,
kOSKextLogBasicLevel | kOSKextLogGeneralFlag,
"Rescanning kernel extensions.");
#ifndef NO_CFUserNotification
resetUserNotifications( false);
#endif
releaseExtensions( NULL, NULL);
readExtensions();
sendPersonalitiesToKernel();
count = CFArrayGetCount(sToolArgs.repositoryURLs);
for (i = 0; i < count; i++) {
CFURLRef directoryURL = CFArrayGetValueAtIndex(
sToolArgs.repositoryURLs, i);
readKextPropertyValuesForDirectory(directoryURL,
CFSTR(kOSBundleHelperKey), gKernelArchInfo,
true, NULL);
}
}
void usage(UsageLevel usageLevel)
{
fprintf(stderr,
"usage: %s [-c] [-d] [-f] [-h] [-j] [-r dir] ... [-v [1-6]] [-x]\n",
progname);
if (usageLevel == kUsageLevelBrief) {
goto finish;
}
fprintf(stderr, "\n");
fprintf(stderr, "Arguments and options\n");
fprintf(stderr, "\n");
fprintf(stderr, "-%s (-%c):\n"
" don't use repository caches; scan repository folders\n",
kOptNameNoCaches, kOptNoCaches);
fprintf(stderr, "-%s (-%c):\n"
" run in debug mode (log to stderr)\n",
kOptNameDebug, kOptDebug);
fprintf(stderr, "-%s <directory> (-%c):\n"
" look in <directory> for kexts in addition to system extensions folders\n",
kOptNameRepository, kOptRepository);
fprintf(stderr, "-%s (-%c):\n"
" run as if the system is in safe boot mode\n",
kOptNameSafeBoot, kOptSafeBoot);
fprintf(stderr, "\n");
fprintf(stderr, "-%s (-%c):\n"
" quiet mode: log/print no informational or error messages\n",
kOptNameQuiet, kOptQuiet);
fprintf(stderr, "-%s [ 0-6 | 0x<flags> ] (-%c):\n"
" verbose mode; log/print info about analysis & loading\n",
kOptNameVerbose, kOptVerbose);
fprintf(stderr, "\n");
fprintf(stderr, "-%s (-%c): print this message and exit\n",
kOptNameHelp, kOptHelp);
finish:
return;
}