#include <CoreFoundation/CoreFoundation.h>
#include <libc.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOKitServer.h>
#include <IOKit/kext/OSKextPrivate.h>
#include "kextunload_main.h"
const char * progname = "(unknown)";
int main(int argc, char * const * argv)
{
ExitStatus result = EX_OK;
KextunloadArgs toolArgs;
ExitStatus scratchResult = EX_OK;
Boolean fatal = false;
progname = rindex(argv[0], '/');
if (progname) {
progname++; } else {
progname = (char *)argv[0];
}
OSKextSetLogOutputFunction(&tool_log);
result = readArgs(argc, argv, &toolArgs);
if (result != EX_OK) {
goto finish;
}
result = checkArgs(&toolArgs);
if (result != EX_OK) {
goto finish;
}
result = createKextsIfNecessary(&toolArgs);
if (result != EX_OK && result != kKextunloadExitNotFound) {
goto finish;
}
scratchResult = terminateKextClasses(&toolArgs, &fatal);
if (result == EX_OK && scratchResult != EX_OK) {
result = scratchResult;
}
if (fatal) {
result = scratchResult;
goto finish;
}
scratchResult = unloadKextsByIdentifier(&toolArgs, &fatal);
if (result == EX_OK && scratchResult != EX_OK) {
result = scratchResult;
}
if (fatal) {
result = scratchResult;
goto finish;
}
scratchResult = unloadKextsByURL(&toolArgs, &fatal);
if (result == EX_OK && scratchResult != EX_OK) {
result = scratchResult;
}
if (fatal) {
result = scratchResult;
goto finish;
}
finish:
if (result == kKextunloadExitHelp) {
result = EX_OK;
}
exit(result);
SAFE_RELEASE(toolArgs.kextURLs);
SAFE_RELEASE(toolArgs.kextClassNames);
SAFE_RELEASE(toolArgs.kextBundleIDs);
return result;
}
ExitStatus readArgs(int argc, char * const * argv, KextunloadArgs * toolArgs)
{
ExitStatus result = EX_USAGE;
ExitStatus scratchResult = EX_USAGE;
int optchar = 0;
int longindex = -1;
CFStringRef scratchString = NULL; CFNumberRef scratchNumber = NULL; CFURLRef scratchURL = NULL; CFIndex i;
bzero(toolArgs, sizeof(*toolArgs));
toolArgs->terminateOption = kIOCatalogModuleUnload;
if (!createCFMutableArray(&toolArgs->kextURLs, &kCFTypeArrayCallBacks) ||
!createCFMutableArray(&toolArgs->kextBundleIDs, NULL ) ||
!createCFMutableArray(&toolArgs->kextClassNames, NULL )) {
result = EX_OSERR;
OSKextLogMemError();
goto finish;
}
result = EX_USAGE;
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 = kKextunloadExitHelp;
goto finish;
break;
case kOptBundleIdentifier:
case kOptModule:
addToArrayIfAbsent(toolArgs->kextBundleIDs, optarg);
break;
case kOptClassName:
addToArrayIfAbsent(toolArgs->kextClassNames, optarg);
break;
break;
case kOptPersonalitiesOnly:
toolArgs->terminateOption = kIOCatalogModuleTerminate;
break;
case kOptQuiet:
beQuiet();
break;
case kOptVerbose:
scratchResult = setLogFilterForOpt(argc, argv,
kOSKextLogKextOrGlobalMask);
if (scratchResult != EX_OK) {
result = scratchResult;
goto finish;
}
break;
default:
goto finish;
break;
}
}
for (i = optind; i < argc; i++) {
SAFE_RELEASE_NULL(scratchURL);
scratchURL = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault,
(const UInt8 *)argv[i], strlen(argv[i]), true);
if (!scratchURL) {
result = EX_OSERR;
OSKextLogMemError();
goto finish;
}
addToArrayIfAbsent(toolArgs->kextURLs, scratchURL);
}
result = EX_OK;
finish:
SAFE_RELEASE(scratchString);
SAFE_RELEASE(scratchNumber);
SAFE_RELEASE(scratchURL);
return result;
}
ExitStatus
checkArgs(KextunloadArgs * toolArgs)
{
ExitStatus result = EX_USAGE;
if (geteuid() != 0) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"You must be running as root to unload kexts, "
"terminate services, or remove driver personalities.");
result = EX_NOPERM;
goto finish;
}
if (!CFArrayGetCount(toolArgs->kextURLs) &&
!CFArrayGetCount(toolArgs->kextBundleIDs) &&
!CFArrayGetCount(toolArgs->kextClassNames)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"No kernel extensions specified.");
usage(kUsageLevelBrief);
goto finish;
}
result = EX_OK;
finish:
return result;
}
ExitStatus
createKextsIfNecessary(KextunloadArgs * toolArgs)
{
ExitStatus result = EX_OK;
OSKextRef aKext = NULL; CFIndex count, i;
if (!CFArrayGetCount(toolArgs->kextURLs)) {
goto finish;
}
if (!createCFMutableArray(&toolArgs->kexts, &kCFTypeArrayCallBacks)) {
result = EX_OSERR;
goto finish;
}
count = CFArrayGetCount(toolArgs->kextURLs);
for (i = 0; i < count; i++) {
CFURLRef kextURL = CFArrayGetValueAtIndex(toolArgs->kextURLs, i);
char kextPath[PATH_MAX] = "(unknown)";
CFURLGetFileSystemRepresentation(kextURL,
false,
(UInt8 *)kextPath,
sizeof(kextPath));
SAFE_RELEASE_NULL(aKext);
aKext = OSKextCreate(kCFAllocatorDefault, kextURL);
if (!aKext) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't create %s.", kextPath);
result = kKextunloadExitNotFound;
continue; }
addToArrayIfAbsent(toolArgs->kexts, aKext);
}
finish:
SAFE_RELEASE(aKext);
return result;
}
ExitStatus
terminateKextClasses(KextunloadArgs * toolArgs, Boolean * fatal)
{
ExitStatus result = EX_OK;
kern_return_t kernResult;
CFIndex count, i;
count = CFArrayGetCount(toolArgs->kextClassNames);
for (i = 0; i < count; i++) {
char * className = NULL;
className = (char *)CFArrayGetValueAtIndex(toolArgs->kextClassNames, i);
kernResult = IOCatalogueTerminate(kIOMasterPortDefault,
kIOCatalogServiceTerminate,
className);
if (kernResult == kIOReturnNotPrivileged) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"You must be running as root to terminate IOService instances.");
result = kKextunloadExitNotPrivileged;
*fatal = true;
goto finish;
} else if (kernResult != KERN_SUCCESS) {
result = kKextunloadExitPartialFailure;
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Failed to terminate class %s - %s.",
className, safe_mach_error_string(kernResult));
} else {
OSKextLog( NULL,
kOSKextLogBasicLevel | kOSKextLogIPCFlag,
"All instances of class %s terminated.",
className);
}
}
finish:
if (result == kKextunloadExitPartialFailure) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Check the system/kernel logs for error messages from the I/O Kit.");
}
return result;
}
ExitStatus unloadKextsByIdentifier(KextunloadArgs * toolArgs, Boolean * fatal)
{
ExitStatus result = EX_OK;
CFStringRef kextIdentifier = NULL; CFIndex count, i;
count = CFArrayGetCount(toolArgs->kextBundleIDs);
for (i = 0; i < count; i++) {
char * kextIDCString = NULL; ExitStatus thisResult;
SAFE_RELEASE_NULL(kextIdentifier);
kextIDCString = (char *)CFArrayGetValueAtIndex(toolArgs->kextBundleIDs, i);
kextIdentifier = CFStringCreateWithCString(kCFAllocatorDefault,
kextIDCString, kCFStringEncodingUTF8);
thisResult = unloadKextWithIdentifier(kextIdentifier, toolArgs, fatal);
if (result == EX_OK && thisResult != EX_OK) {
result = thisResult;
}
if (*fatal) {
result = thisResult;
goto finish;
}
}
finish:
SAFE_RELEASE(kextIdentifier);
if (!*fatal &&
result != EX_OK &&
toolArgs->terminateOption == kIOCatalogModuleTerminate) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Check the system/kernel logs for error messages from the I/O Kit.");
}
return result;
}
ExitStatus unloadKextsByURL(KextunloadArgs * toolArgs, Boolean * fatal)
{
ExitStatus result = EX_OK;
CFIndex count, i;
if (!toolArgs->kexts) {
goto finish;
}
count = CFArrayGetCount(toolArgs->kexts);
for (i = 0; i < count; i++) {
ExitStatus thisResult = EX_OK;
OSKextRef aKext = NULL; CFURLRef kextURL = NULL; CFStringRef kextID = NULL; char kextPath[PATH_MAX];
aKext = (OSKextRef)CFArrayGetValueAtIndex(toolArgs->kexts, i);
kextURL = OSKextGetURL(aKext);
kextID = OSKextGetIdentifier(aKext);
if (!CFURLGetFileSystemRepresentation(kextURL,
false,
(UInt8 *)kextPath,
sizeof(kextPath))) {
memcpy(kextPath, "(unknown)", sizeof("(unknown)"));
continue;
}
thisResult = unloadKextWithIdentifier(kextID, toolArgs, fatal);
if (result == EX_OK && thisResult != EX_OK) {
result = thisResult;
}
if (*fatal) {
result = thisResult;
goto finish;
}
}
finish:
if (!*fatal &&
result != EX_OK &&
toolArgs->terminateOption == kIOCatalogModuleTerminate) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Check the system/kernel logs for error messages from the I/O Kit.");
}
return result;
}
ExitStatus unloadKextWithIdentifier(
CFStringRef kextIdentifier,
KextunloadArgs * toolArgs,
Boolean * fatal)
{
ExitStatus result = EX_OK;
char * kextIdentifierCString = NULL; kern_return_t kernResult;
if (!kextIdentifierCString) {
kextIdentifierCString = createUTF8CStringForCFString(kextIdentifier);
if (!kextIdentifierCString) {
OSKextLogMemError();
result = EX_OSERR;
*fatal = true;
goto finish;
}
}
if (toolArgs->terminateOption == kIOCatalogModuleTerminate) {
kernResult = IOCatalogueTerminate(kIOMasterPortDefault,
toolArgs->terminateOption, kextIdentifierCString);
} else {
kernResult = OSKextUnloadKextWithIdentifier(kextIdentifier,
true);
}
if (kernResult == kIOReturnNotPrivileged) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"You must be running as root to unload kexts.");
result = kKextunloadExitNotPrivileged;
*fatal = true;
goto finish;
} else if (kernResult != KERN_SUCCESS) {
result = kKextunloadExitPartialFailure;
if (toolArgs->terminateOption == kIOCatalogModuleTerminate) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Terminate for %s failed - %s.",
kextIdentifierCString, safe_mach_error_string(kernResult));
} else {
}
} else {
if (toolArgs->terminateOption == kIOCatalogModuleTerminate) {
OSKextLog( NULL,
kOSKextLogBasicLevel | kOSKextLogIPCFlag,
"%s: services terminated and personalities removed "
"(kext not unloaded).",
kextIdentifierCString);
} else {
OSKextLog( NULL,
kOSKextLogBasicLevel | kOSKextLogIPCFlag,
"%s unloaded and personalities removed.",
kextIdentifierCString);
}
}
finish:
SAFE_FREE(kextIdentifierCString);
return result;
}
void usage(UsageLevel usageLevel)
{
fprintf(stderr, "usage: %s [-h] [-v [0-6]]\n"
" [-p] [-c class_name] ... [-b bundle_id] ... [kext] ...\n",
progname);
if (usageLevel == kUsageLevelBrief) {
goto finish;
}
fprintf(stderr, "kext: unload the named kext and all personalities for it\n");
fprintf(stderr, "\n");
fprintf(stderr, "-%s <bundle_id> (-%c):\n"
" unload the kext and personalities for CFBundleIdentifier <bundle_id>\n",
kOptNameBundleIdentifier, kOptBundleIdentifier);
fprintf(stderr, "-%s <class_name> (-%c):\n"
" terminate all instances of IOService class <class_name> but do not\n"
" unload its kext or remove its personalities\n",
kOptNameClassName, kOptClassName);
fprintf(stderr, "\n");
fprintf(stderr,
"-%s (-%c):\n"
" terminate services and remove personalities only; do not unload kexts\n"
" (applies only to unload by bundle-id or kext)\n",
kOptNamePersonalitiesOnly, kOptPersonalitiesOnly);
fprintf(stderr, "-%s (-%c):\n"
" quiet mode: print no informational or error messages\n",
kOptNameQuiet, kOptQuiet);
fprintf(stderr, "-%s [ 0-6 | 0x<flags> ] (-%c):\n"
" verbose mode; print info about analysis & loading\n",
kOptNameVerbose, kOptVerbose);
fprintf(stderr, "\n");
fprintf(stderr, "-%s (-%c): print this message and exit\n",
kOptNameHelp, kOptHelp);
fprintf(stderr, "\n");
fprintf(stderr, "--: end of options\n");
finish:
return;
}