1/* 2 * Copyright (c) 2006 Apple Computer, Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23#include <CoreFoundation/CoreFoundation.h> 24#include <libc.h> 25 26#include <IOKit/IOKitLib.h> 27#include <IOKit/IOKitServer.h> 28#include <IOKit/kext/OSKextPrivate.h> 29 30#include "kextunload_main.h" 31 32const char * progname = "(unknown)"; 33 34/******************************************************************************* 35*******************************************************************************/ 36int main(int argc, char * const * argv) 37{ 38 ExitStatus result = EX_OK; 39 KextunloadArgs toolArgs; 40 ExitStatus scratchResult = EX_OK; 41 Boolean fatal = false; 42 43 44 /***** 45 * Find out what my name is. 46 */ 47 progname = rindex(argv[0], '/'); 48 if (progname) { 49 progname++; // go past the '/' 50 } else { 51 progname = (char *)argv[0]; 52 } 53 54 /* Set the OSKext log callback right away. 55 */ 56 OSKextSetLogOutputFunction(&tool_log); 57 58 result = readArgs(argc, argv, &toolArgs); 59 if (result != EX_OK) { 60 goto finish; 61 } 62 63 result = checkArgs(&toolArgs); 64 if (result != EX_OK) { 65 goto finish; 66 } 67 68 /* If given URLs, create OSKext objects for them so we can get 69 * bundle identifiers (that's what IOCatalogueTerminate() expects). 70 * If we failed to open one, keep going on but save the not-found 71 * error for our exit status. 72 */ 73 result = createKextsIfNecessary(&toolArgs); 74 if (result != EX_OK && result != kKextunloadExitNotFound) { 75 goto finish; 76 } 77 78 /* Do the terminates & unloads. Catch the first nonfatal error as our 79 * exit and don't overwrite it with later nonfatal errors. 80 * *Do* overwrite it with a fatal error if we got one. 81 */ 82 scratchResult = terminateKextClasses(&toolArgs, &fatal); 83 if (result == EX_OK && scratchResult != EX_OK) { 84 result = scratchResult; 85 } 86 if (fatal) { 87 result = scratchResult; 88 goto finish; 89 } 90 91 scratchResult = unloadKextsByIdentifier(&toolArgs, &fatal); 92 if (result == EX_OK && scratchResult != EX_OK) { 93 result = scratchResult; 94 } 95 if (fatal) { 96 result = scratchResult; 97 goto finish; 98 } 99 100 scratchResult = unloadKextsByURL(&toolArgs, &fatal); 101 if (result == EX_OK && scratchResult != EX_OK) { 102 result = scratchResult; 103 } 104 if (fatal) { 105 result = scratchResult; 106 goto finish; 107 } 108 109finish: 110 111 if (result == kKextunloadExitHelp) { 112 result = EX_OK; 113 } 114 115 exit(result); 116 117 /***** 118 * Clean everything up. 119 */ 120 SAFE_RELEASE(toolArgs.kextURLs); 121 SAFE_RELEASE(toolArgs.kextClassNames); 122 SAFE_RELEASE(toolArgs.kextBundleIDs); 123 124 return result; 125} 126 127/******************************************************************************* 128*******************************************************************************/ 129ExitStatus readArgs(int argc, char * const * argv, KextunloadArgs * toolArgs) 130{ 131 ExitStatus result = EX_USAGE; 132 ExitStatus scratchResult = EX_USAGE; 133 int optchar = 0; 134 int longindex = -1; 135 CFStringRef scratchString = NULL; // must release 136 CFNumberRef scratchNumber = NULL; // must release 137 CFURLRef scratchURL = NULL; // must release 138 CFIndex i; 139 140 bzero(toolArgs, sizeof(*toolArgs)); 141 142 /* Default is to unload both kext and driver personalities. 143 */ 144 toolArgs->terminateOption = kIOCatalogModuleUnload; 145 146 /***** 147 * Allocate collection objects needed for command line argument processing. 148 */ 149 if (!createCFMutableArray(&toolArgs->kextURLs, &kCFTypeArrayCallBacks) || 150 !createCFMutableArray(&toolArgs->kextBundleIDs, NULL /* C strings */) || 151 !createCFMutableArray(&toolArgs->kextClassNames, NULL /* C strings */)) { 152 153 result = EX_OSERR; 154 OSKextLogMemError(); 155 goto finish; 156 } 157 158 /***** 159 * Process command-line arguments. 160 */ 161 result = EX_USAGE; 162 163 /***** 164 * Process command line arguments. 165 */ 166 while ((optchar = getopt_long_only(argc, (char * const *)argv, 167 kOptChars, sOptInfo, &longindex)) != -1) { 168 169 SAFE_RELEASE_NULL(scratchString); 170 SAFE_RELEASE_NULL(scratchNumber); 171 SAFE_RELEASE_NULL(scratchURL); 172 173 switch (optchar) { 174 case kOptHelp: 175 usage(kUsageLevelFull); 176 result = kKextunloadExitHelp; 177 goto finish; 178 break; 179 180 case kOptBundleIdentifier: 181 case kOptModule: 182 addToArrayIfAbsent(toolArgs->kextBundleIDs, optarg); 183 break; 184 185 case kOptClassName: 186 addToArrayIfAbsent(toolArgs->kextClassNames, optarg); 187 break; 188 break; 189 190 case kOptPersonalitiesOnly: 191 toolArgs->terminateOption = kIOCatalogModuleTerminate; 192 break; 193 194 case kOptQuiet: 195 beQuiet(); 196 break; 197 198 case kOptVerbose: 199 scratchResult = setLogFilterForOpt(argc, argv, 200 /* forceOnFlags */ kOSKextLogKextOrGlobalMask); 201 if (scratchResult != EX_OK) { 202 result = scratchResult; 203 goto finish; 204 } 205 break; 206 207 default: 208 /* getopt_long_only() prints an error message for us. */ 209 goto finish; 210 break; 211 212 } /* switch (optchar) */ 213 } /* while (optchar = getopt_long_only(...) */ 214 215 /***** 216 * Record the kext names from the command line. 217 */ 218 for (i = optind; i < argc; i++) { 219 220 SAFE_RELEASE_NULL(scratchURL); 221 222 scratchURL = CFURLCreateFromFileSystemRepresentation( 223 kCFAllocatorDefault, 224 (const UInt8 *)argv[i], strlen(argv[i]), true); 225 if (!scratchURL) { 226 result = EX_OSERR; 227 OSKextLogMemError(); 228 goto finish; 229 } 230 addToArrayIfAbsent(toolArgs->kextURLs, scratchURL); 231 } 232 233 result = EX_OK; 234finish: 235 SAFE_RELEASE(scratchString); 236 SAFE_RELEASE(scratchNumber); 237 SAFE_RELEASE(scratchURL); 238 239 return result; 240} 241 242/******************************************************************************* 243*******************************************************************************/ 244ExitStatus 245checkArgs(KextunloadArgs * toolArgs) 246{ 247 ExitStatus result = EX_USAGE; 248 249 if (geteuid() != 0) { 250 OSKextLog(/* kext */ NULL, 251 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 252 "You must be running as root to unload kexts, " 253 "terminate services, or remove driver personalities."); 254 result = EX_NOPERM; 255 goto finish; 256 } 257 258 if (!CFArrayGetCount(toolArgs->kextURLs) && 259 !CFArrayGetCount(toolArgs->kextBundleIDs) && 260 !CFArrayGetCount(toolArgs->kextClassNames)) { 261 262 /* Put an extra newline for readability. 263 */ 264 OSKextLog(/* kext */ NULL, 265 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 266 "No kernel extensions specified."); 267 usage(kUsageLevelBrief); 268 goto finish; 269 } 270 271 result = EX_OK; 272 273finish: 274 return result; 275} 276 277/******************************************************************************* 278*******************************************************************************/ 279ExitStatus 280createKextsIfNecessary(KextunloadArgs * toolArgs) 281{ 282 ExitStatus result = EX_OK; 283 OSKextRef aKext = NULL; // must release 284 CFIndex count, i; 285 286 if (!CFArrayGetCount(toolArgs->kextURLs)) { 287 goto finish; 288 } 289 290 if (!createCFMutableArray(&toolArgs->kexts, &kCFTypeArrayCallBacks)) { 291 result = EX_OSERR; 292 goto finish; 293 } 294 295 count = CFArrayGetCount(toolArgs->kextURLs); 296 for (i = 0; i < count; i++) { 297 CFURLRef kextURL = CFArrayGetValueAtIndex(toolArgs->kextURLs, i); 298 char kextPath[PATH_MAX] = "(unknown)"; 299 300 CFURLGetFileSystemRepresentation(kextURL, 301 /* resolveToBase */ false, 302 (UInt8 *)kextPath, 303 sizeof(kextPath)); 304 305 SAFE_RELEASE_NULL(aKext); 306 aKext = OSKextCreate(kCFAllocatorDefault, kextURL); 307 if (!aKext) { 308 OSKextLog(/* kext */ NULL, 309 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 310 "Can't create %s.", kextPath); 311 result = kKextunloadExitNotFound; 312 continue; // not fatal! 313 } 314 315 addToArrayIfAbsent(toolArgs->kexts, aKext); 316 } 317 318finish: 319 SAFE_RELEASE(aKext); 320 return result; 321} 322 323/******************************************************************************* 324*******************************************************************************/ 325ExitStatus 326terminateKextClasses(KextunloadArgs * toolArgs, Boolean * fatal) 327{ 328 ExitStatus result = EX_OK; 329 kern_return_t kernResult; 330 CFIndex count, i; 331 332 count = CFArrayGetCount(toolArgs->kextClassNames); 333 for (i = 0; i < count; i++) { 334 char * className = NULL; // do not free 335 336 className = (char *)CFArrayGetValueAtIndex(toolArgs->kextClassNames, i); 337 338 kernResult = IOCatalogueTerminate(kIOMasterPortDefault, 339 kIOCatalogServiceTerminate, 340 className); 341 342 if (kernResult == kIOReturnNotPrivileged) { 343 OSKextLog(/* kext */ NULL, 344 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 345 "You must be running as root to terminate IOService instances."); 346 result = kKextunloadExitNotPrivileged; 347 *fatal = true; 348 goto finish; 349 350 } else if (kernResult != KERN_SUCCESS) { 351 result = kKextunloadExitPartialFailure; 352 OSKextLog(/* kext */ NULL, 353 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 354 "Failed to terminate class %s - %s.", 355 className, safe_mach_error_string(kernResult)); 356 } else { 357 OSKextLog(/* kext */ NULL, 358 kOSKextLogBasicLevel | kOSKextLogIPCFlag, 359 "All instances of class %s terminated.", 360 className); 361 } 362 } 363 364finish: 365 if (result == kKextunloadExitPartialFailure) { 366 OSKextLog(/* kext */ NULL, 367 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 368 "Check the system/kernel logs for error messages from the I/O Kit."); 369 } 370 371 return result; 372} 373 374/******************************************************************************* 375*******************************************************************************/ 376ExitStatus unloadKextsByIdentifier(KextunloadArgs * toolArgs, Boolean * fatal) 377{ 378 ExitStatus result = EX_OK; 379 CFStringRef kextIdentifier = NULL; // must release 380 CFIndex count, i; 381 382 count = CFArrayGetCount(toolArgs->kextBundleIDs); 383 for (i = 0; i < count; i++) { 384 char * kextIDCString = NULL; // do not free 385 ExitStatus thisResult; 386 387 SAFE_RELEASE_NULL(kextIdentifier); 388 kextIDCString = (char *)CFArrayGetValueAtIndex(toolArgs->kextBundleIDs, i); 389 kextIdentifier = CFStringCreateWithCString(kCFAllocatorDefault, 390 kextIDCString, kCFStringEncodingUTF8); 391 thisResult = unloadKextWithIdentifier(kextIdentifier, toolArgs, fatal); 392 393 /* Only nab the first nonfatal error. 394 */ 395 if (result == EX_OK && thisResult != EX_OK) { 396 result = thisResult; 397 } 398 if (*fatal) { 399 result = thisResult; 400 goto finish; 401 } 402 } 403 404finish: 405 SAFE_RELEASE(kextIdentifier); 406 407 /* If we didn't suffer a catastrophic error, but only a routine 408 * terminate failure, then recommend checking the system log. 409 * Note that for unloads we capture kernel error logs from the OSKext 410 * subsystem and print them to stderr. 411 */ 412 if (!*fatal && 413 result != EX_OK && 414 toolArgs->terminateOption == kIOCatalogModuleTerminate) { 415 416 OSKextLog(/* kext */ NULL, 417 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 418 "Check the system/kernel logs for error messages from the I/O Kit."); 419 } 420 return result; 421} 422 423/******************************************************************************* 424*******************************************************************************/ 425ExitStatus unloadKextsByURL(KextunloadArgs * toolArgs, Boolean * fatal) 426{ 427 ExitStatus result = EX_OK; 428 CFIndex count, i; 429 430 /* This is one CF collection we only allocate when needed. 431 */ 432 if (!toolArgs->kexts) { 433 goto finish; 434 } 435 436 count = CFArrayGetCount(toolArgs->kexts); 437 for (i = 0; i < count; i++) { 438 ExitStatus thisResult = EX_OK; 439 OSKextRef aKext = NULL; // do not release 440 CFURLRef kextURL = NULL; // do not release 441 CFStringRef kextID = NULL; // do not release 442 char kextPath[PATH_MAX]; 443 444 aKext = (OSKextRef)CFArrayGetValueAtIndex(toolArgs->kexts, i); 445 kextURL = OSKextGetURL(aKext); 446 kextID = OSKextGetIdentifier(aKext); 447 448 if (!CFURLGetFileSystemRepresentation(kextURL, 449 /* resolveToBase */ false, 450 (UInt8 *)kextPath, 451 sizeof(kextPath))) { 452 453 memcpy(kextPath, "(unknown)", sizeof("(unknown)")); 454 continue; 455 } 456 457 thisResult = unloadKextWithIdentifier(kextID, toolArgs, fatal); 458 459 /* Only nab the first nonfatal error. 460 */ 461 if (result == EX_OK && thisResult != EX_OK) { 462 result = thisResult; 463 } 464 if (*fatal) { 465 result = thisResult; 466 goto finish; 467 } 468 } 469 470finish: 471 /* If we didn't suffer a catastrophic error, but only a routine 472 * terminate failure, then recommend checking the system log. 473 * Note that for unloads we capture kernel error logs from the OSKext 474 * subsystem and print them to stderr. 475 */ 476 if (!*fatal && 477 result != EX_OK && 478 toolArgs->terminateOption == kIOCatalogModuleTerminate) { 479 480 OSKextLog(/* kext */ NULL, 481 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 482 "Check the system/kernel logs for error messages from the I/O Kit."); 483 } 484 return result; 485} 486 487/******************************************************************************* 488*******************************************************************************/ 489ExitStatus unloadKextWithIdentifier( 490 CFStringRef kextIdentifier, 491 KextunloadArgs * toolArgs, 492 Boolean * fatal) 493{ 494 ExitStatus result = EX_OK; 495 char * kextIdentifierCString = NULL; // must free 496 kern_return_t kernResult; 497 498 if (!kextIdentifierCString) { 499 kextIdentifierCString = createUTF8CStringForCFString(kextIdentifier); 500 if (!kextIdentifierCString) { 501 OSKextLogMemError(); 502 result = EX_OSERR; 503 *fatal = true; 504 goto finish; 505 } 506 } 507 508 if (toolArgs->terminateOption == kIOCatalogModuleTerminate) { 509 kernResult = IOCatalogueTerminate(kIOMasterPortDefault, 510 toolArgs->terminateOption, kextIdentifierCString); 511 } else { 512 kernResult = OSKextUnloadKextWithIdentifier(kextIdentifier, 513 /* terminateAndRemovePersonalities */ true); 514 } 515 516 if (kernResult == kIOReturnNotPrivileged) { 517 OSKextLog(/* kext */ NULL, 518 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 519 "You must be running as root to unload kexts."); 520 result = kKextunloadExitNotPrivileged; 521 *fatal = true; 522 goto finish; 523 524 } else if (kernResult != KERN_SUCCESS) { 525 result = kKextunloadExitPartialFailure; 526 if (toolArgs->terminateOption == kIOCatalogModuleTerminate) { 527 OSKextLog(/* kext */ NULL, 528 kOSKextLogErrorLevel | kOSKextLogIPCFlag, 529 "Terminate for %s failed - %s.", 530 kextIdentifierCString, safe_mach_error_string(kernResult)); 531 } else { 532 // OSKextUnloadKextWithIdentifier() logged an error 533 } 534 } else { 535 if (toolArgs->terminateOption == kIOCatalogModuleTerminate) { 536 OSKextLog(/* kext */ NULL, 537 kOSKextLogBasicLevel | kOSKextLogIPCFlag, 538 "%s: services terminated and personalities removed " 539 "(kext not unloaded).", 540 kextIdentifierCString); 541 } else { 542 OSKextLog(/* kext */ NULL, 543 kOSKextLogBasicLevel | kOSKextLogIPCFlag, 544 "%s unloaded and personalities removed.", 545 kextIdentifierCString); 546 } 547 } 548finish: 549 SAFE_FREE(kextIdentifierCString); 550 551 return result; 552} 553 554/******************************************************************************* 555* 556*******************************************************************************/ 557void usage(UsageLevel usageLevel) 558{ 559 fprintf(stderr, "usage: %s [-h] [-v [0-6]]\n" 560 " [-p] [-c class_name] ... [-b bundle_id] ... [kext] ...\n", 561 progname); 562 563 if (usageLevel == kUsageLevelBrief) { 564 goto finish; 565 } 566 567 fprintf(stderr, "kext: unload the named kext and all personalities for it\n"); 568 fprintf(stderr, "\n"); 569 570 fprintf(stderr, "-%s <bundle_id> (-%c):\n" 571 " unload the kext and personalities for CFBundleIdentifier <bundle_id>\n", 572 kOptNameBundleIdentifier, kOptBundleIdentifier); 573 fprintf(stderr, "-%s <class_name> (-%c):\n" 574 " terminate all instances of IOService class <class_name> but do not\n" 575 " unload its kext or remove its personalities\n", 576 kOptNameClassName, kOptClassName); 577 fprintf(stderr, "\n"); 578 fprintf(stderr, 579 "-%s (-%c):\n" 580 " terminate services and remove personalities only; do not unload kexts\n" 581 " (applies only to unload by bundle-id or kext)\n", 582 kOptNamePersonalitiesOnly, kOptPersonalitiesOnly); 583 584// :doc: meaning of -p inverted all these years 585 586 fprintf(stderr, "-%s (-%c):\n" 587 " quiet mode: print no informational or error messages\n", 588 kOptNameQuiet, kOptQuiet); 589 fprintf(stderr, "-%s [ 0-6 | 0x<flags> ] (-%c):\n" 590 " verbose mode; print info about analysis & loading\n", 591 kOptNameVerbose, kOptVerbose); 592 fprintf(stderr, "\n"); 593 594 fprintf(stderr, "-%s (-%c): print this message and exit\n", 595 kOptNameHelp, kOptHelp); 596 fprintf(stderr, "\n"); 597 598 fprintf(stderr, "--: end of options\n"); 599 600finish: 601 return; 602} 603