#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFBundlePriv.h>
#include <errno.h>
#include <libc.h>
#include <libgen.h> // dirname()
#include <sys/types.h>
#include <sys/mman.h>
#include <fts.h>
#include <paths.h>
#include <mach-o/arch.h>
#include <mach-o/fat.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#include <mach-o/swap.h>
#include <mach/mach.h>
#include <mach/mach_error.h>
#include <mach/mach_types.h>
#include <mach/machine/vm_param.h>
#include <mach/kmod.h>
#include <notify.h>
#include <stdlib.h>
#include <unistd.h> // sleep(3)
#include <sys/types.h>
#include <sys/stat.h>
#include <Security/SecKeychainPriv.h>
#include <sandbox/rootless.h>
#include <sys/csr.h>
#include <sys/sysctl.h>
#include <DiskArbitration/DiskArbitrationPrivate.h>
#include <IOKit/IOTypes.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOKitServer.h>
#include <IOKit/IOCFUnserialize.h>
#include <IOKit/IOCFSerialize.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <libkern/OSByteOrder.h>
#include <IOKit/kext/OSKext.h>
#include <IOKit/kext/OSKextPrivate.h>
#include <IOKit/kext/macho_util.h>
#include <bootfiles.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#include "kextcache_main.h"
#include "kext_tools_util.h"
#include "fork_program.h"
#if !NO_BOOT_ROOT
#include "bootcaches.h"
#include "bootroot_internal.h"
#endif
#include "compression.h"
#include "security.h"
#include "signposts.h"
#include "staging.h"
#include "syspolicy.h"
#include "driverkit.h"
#include "rosp_staging.h"
#if __has_include(<prelink.h>)
#include <prelink.h>
#else
#include <System/libkern/prelink.h>
#endif
#define PRELINK_KERNEL_PERMS (0644)
#define kOSKextSystemLoadTimeout (8 * 60)
#define kOSKextSystemLoadPauseTime (30)
#define kOSKextPrelinkedKernelSuffixedName _kOSKextPrelinkedKernelFileName "."
#define kPLKDirSymlinkPrefix "../../../PrelinkedKernels/"
#define kPersonalizeMacOSTool "/usr/local/bin/personalize_macos"
#define kListCDHashesTool "/usr/sbin/klist_cdhashes"
const char * progname = "(unknown)";
CFMutableDictionaryRef sExcludedKextAlertDict = NULL;
static void waitForIOKitQuiescence(void);
static void waitForGreatSystemLoad(void);
#define kMaxArchs 64
#define kRootPathLen 256
static u_int usecs_from_timeval(struct timeval *t);
static void timeval_from_usecs(struct timeval *t, u_int usecs);
static void timeval_difference(struct timeval *dst,
struct timeval *a, struct timeval *b);
static Boolean isValidKextSigningTargetVolume(CFURLRef theURL);
static Boolean wantsFastLibCompressionForTargetVolume(CFURLRef theURL);
static void _appendIfNewest(CFMutableArrayRef theArray, OSKextRef theKext);
static void removeStalePrelinkedKernels(KextcacheArgs * toolArgs);
static Boolean isRootVolURL(CFURLRef theURL);
static bool isValidPLKFile(KextcacheArgs *toolArgs);
static bool isSystemPLKPath(KextcacheArgs *toolArgs);
static bool isSystemKernelPath(KextcacheArgs *toolArgs);
static bool isProbablyProtectedPLK(KextcacheArgs *toolArgs);
static bool isProtectedPLK(int prelinkedKernel_fd);
static bool isProtectedAction(KextcacheArgs *toolArgs);
static bool isSecureAuthentication(KextcacheArgs *toolArgs);
static ExitStatus buildImmutableKernelcache(KextcacheArgs *toolArgs, const char *plk_filename);
static ExitStatus updateKextAllowList(void);
int main(int argc, char * const * argv)
{
KextcacheArgs toolArgs;
ExitStatus result = EX_SOFTWARE;
progname = rindex(argv[0], '/');
if (progname) {
progname++; } else {
progname = (char *)argv[0];
}
OSKextSetLogOutputFunction(&tool_log);
if (getenv("KEXTD_SPAWNED")) {
OSKextSetLogFilter(kDefaultServiceLogFilter | kOSKextLogKextOrGlobalMask,
false);
OSKextSetLogFilter(kDefaultServiceLogFilter | kOSKextLogKextOrGlobalMask,
true);
tool_openlog("com.apple.kextcache");
} else {
tool_initlog();
}
if (isDebugSetInBootargs()) {
#if 0 // default to more logging when running with debug boot-args
OSKextLogSpec logFilter = kOSKextLogDetailLevel |
kOSKextLogVerboseFlagsMask |
kOSKextLogKextOrGlobalMask;
OSKextSetLogFilter(logFilter, false);
OSKextSetLogFilter(logFilter, true);
#endif
int i;
int myBufSize = 0;
char * mybuf;
for (i = 0; i < argc; i++) {
myBufSize += strlen(argv[i]) + 1;
}
mybuf = malloc(myBufSize);
if (mybuf) {
mybuf[0] = 0x00;
for (i = 0; i < argc; i++) {
if (strlcat(mybuf, argv[i], myBufSize) >= myBufSize) {
break;
}
if (strlcat(mybuf, " ", myBufSize) >= myBufSize) {
break;
}
}
OSKextLog(NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"%s",
mybuf);
free(mybuf);
}
}
result = readArgs(&argc, &argv, &toolArgs);
if (result != EX_OK) {
if (result == kKextcacheExitHelp) {
result = EX_OK;
}
goto finish;
}
checkKextdSpawnedFilter( false);
checkKextdSpawnedFilter( true);
result = checkArgs(&toolArgs);
if (result != EX_OK) {
goto finish;
}
bool protectedAction = isProtectedAction(&toolArgs);
toolArgs.authenticationOptions.allowNetwork = isKextdRunning();
toolArgs.authenticationOptions.isCacheLoad = true;
toolArgs.authenticationOptions.performFilesystemValidation = !toolArgs.skipAuthentication;
toolArgs.authenticationOptions.performSignatureValidation = !toolArgs.skipAuthentication && isValidKextSigningTargetVolume(toolArgs.volumeRootURL);
toolArgs.authenticationOptions.requireSecureLocation = !toolArgs.skipAuthentication && protectedAction;
toolArgs.authenticationOptions.respectSystemPolicy = !toolArgs.skipAuthentication && protectedAction;
toolArgs.authenticationOptions.checkDextApproval = !toolArgs.skipAuthentication && protectedAction;
toolArgs.authenticationOptions.is_kextcache = true;
_OSKextSetAuthenticationFunction(&authenticateKext, &toolArgs.authenticationOptions);
_OSKextSetStrictAuthentication(true);
_OSKextSetPersonalityPatcherFunction(&addCDHashToDextPersonality);
result = EX_OK;
if (toolArgs.lowPriorityFlag) {
OSKextLog( NULL,
kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
"Running in low-priority background mode.");
setpriority(PRIO_PROCESS, getpid(), 20); setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, IOPOL_UTILITY);
if (toolArgs.prelinkedKernelPath) {
waitForGreatSystemLoad();
}
}
OSKextSetUsesCaches(false);
if (toolArgs.clearStaging) {
OSKextLog( NULL,
kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
"Clearing staging directory.");
clearStagingDirectory();
goto finish;
} else if (toolArgs.pruneStaging) {
OSKextLog( NULL,
kOSKextLogDetailLevel | kOSKextLogGeneralFlag,
"Pruning staging directory.");
pruneStagingDirectory();
goto finish;
}
if ((toolArgs.updateOpts & kBRUExpectUpToDate) &&
(toolArgs.updateOpts & kBRUEarlyBoot) &&
toolArgs.updateVolumeURL) {
bool gotVolPath = false;
char volPath[PATH_MAX] = {};
gotVolPath = CFURLGetFileSystemRepresentation(toolArgs.updateVolumeURL,
true,
(UInt8*)volPath,
PATH_MAX);
bool targetingBootVol = (gotVolPath && strcmp(volPath, "/") == 0);
if (!targetingBootVol) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"-Boot -U {vol} only works on the root/boot volume");
goto finish;
}
takeVolumeForPath(volPath);
result = updateKextAllowList();
putVolumeForPath(volPath, result);
goto finish;
}
#if !NO_BOOT_ROOT
if (toolArgs.updateVolumeURL) {
Boolean gotVolPath = false;
Boolean targetingBootVol = false;
char volPath[PATH_MAX];
gotVolPath = CFURLGetFileSystemRepresentation(toolArgs.updateVolumeURL,
true,
(UInt8*)volPath,
PATH_MAX);
targetingBootVol = (gotVolPath && strcmp(volPath, "/") == 0);
takeVolumeForPath(volPath);
result = doUpdateVolume(&toolArgs);
if (targetingBootVol) {
pruneStagingDirectory();
if (result == 0 && toolArgs.updateOpts & kBRUInvalidateKextcache) {
updateSystemPlistCaches(&toolArgs);
}
}
putVolumeForPath(volPath, result);
goto finish;
}
#endif
if (toolArgs.prelinkedKernelPath && !CFArrayGetCount(toolArgs.argURLs) &&
(toolArgs.compress || toolArgs.uncompress))
{
result = compressPrelinkedKernel(toolArgs.volumeRootURL,
toolArgs.prelinkedKernelPath,
toolArgs.compress);
goto finish;
}
if (toolArgs.printTestResults) {
OSKextSetRecordsDiagnostics(kOSKextDiagnosticsFlagAll);
}
if (toolArgs.authenticationOptions.requireSecureLocation) {
toolArgs.allKexts = createStagedKextsFromURLs(toolArgs.argURLs, true);
toolArgs.repositoryKexts = createStagedKextsFromURLs(toolArgs.repositoryURLs, true);
toolArgs.namedKexts = createStagedKextsFromURLs(toolArgs.namedKextURLs, true);
} else {
toolArgs.allKexts = OSKextCreateKextsFromURLs(kCFAllocatorDefault,
toolArgs.argURLs);
toolArgs.repositoryKexts = OSKextCreateKextsFromURLs(kCFAllocatorDefault,
toolArgs.repositoryURLs);
toolArgs.namedKexts = OSKextCreateKextsFromURLs(kCFAllocatorDefault,
toolArgs.namedKextURLs);
}
if (!toolArgs.allKexts || !CFArrayGetCount(toolArgs.allKexts)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"No kernel extensions found.");
result = EX_SOFTWARE;
goto finish;
}
if (!toolArgs.repositoryKexts || !toolArgs.namedKexts) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Error reading extensions.");
result = EX_SOFTWARE;
goto finish;
}
if (result != EX_OK) {
goto finish;
}
if (toolArgs.needLoadedKextInfo) {
result = copyLoadedKextInfo(&toolArgs.loadedKexts, true);
if (result != EX_OK) {
goto finish;
}
}
if (toolArgs.updateSystemCaches) {
result = updateSystemPlistCaches(&toolArgs);
}
if (toolArgs.prelinkedKernelPath) {
if (toolArgs.needDefaultPrelinkedKernelInfo &&
OSKextGetActualSafeBoot()) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't update the system prelinked kernel during safe boot.");
result = EX_OSERR;
goto finish;
}
result = createPrelinkedKernel(&toolArgs);
if (result != EX_OK) {
goto finish;
}
}
finish:
exit(result);
SAFE_RELEASE(toolArgs.kextIDs);
SAFE_RELEASE(toolArgs.argURLs);
SAFE_RELEASE(toolArgs.repositoryURLs);
SAFE_RELEASE(toolArgs.namedKextURLs);
SAFE_RELEASE(toolArgs.allKexts);
SAFE_RELEASE(toolArgs.repositoryKexts);
SAFE_RELEASE(toolArgs.namedKexts);
SAFE_RELEASE(toolArgs.loadedKexts);
SAFE_RELEASE(toolArgs.symbolDirURL);
SAFE_FREE(toolArgs.prelinkedKernelPath);
SAFE_FREE(toolArgs.prelinkedKernelDirname);
SAFE_FREE(toolArgs.kernelPath);
return result;
}
ExitStatus readArgs(
int * argc,
char * const ** argv,
KextcacheArgs * toolArgs)
{
ExitStatus result = EX_USAGE;
ExitStatus scratchResult = EX_USAGE;
CFStringRef scratchString = NULL; CFNumberRef scratchNumber = NULL; CFURLRef scratchURL = NULL; uint32_t i = 0;
int optchar = 0;
int longindex = -1;
struct stat sb;
bzero(toolArgs, sizeof(*toolArgs));
toolArgs->kernel_fd = -1;
toolArgs->prelinkedKernel_fd = -1;
toolArgs->prelinkedKernelDir_fd = -1;
if (!createCFMutableSet(&toolArgs->kextIDs, &kCFTypeSetCallBacks) ||
!createCFMutableArray(&toolArgs->argURLs, &kCFTypeArrayCallBacks) ||
!createCFMutableArray(&toolArgs->repositoryURLs, &kCFTypeArrayCallBacks) ||
!createCFMutableArray(&toolArgs->namedKextURLs, &kCFTypeArrayCallBacks) ||
!createCFMutableArray(&toolArgs->targetArchs, NULL)) {
OSKextLogMemError();
result = EX_OSERR;
exit(result);
}
while ((optchar = getopt_long_only(*argc, *argv,
kOptChars, sOptInfo, &longindex)) != -1) {
SAFE_RELEASE_NULL(scratchString);
SAFE_RELEASE_NULL(scratchNumber);
SAFE_RELEASE_NULL(scratchURL);
if (optchar == ':') {
switch (optopt) {
case kOptPrelinkedKernel:
optchar = optopt;
break;
default:
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"%s: option requires an argument -- -%c.",
progname, optopt);
break;
}
}
if (optchar == kOptSystemMkext) {
optchar = 0;
longopt = kLongOptSystemPrelinkedKernel;
}
switch (optchar) {
case kOptArch:
if (!addArchForName(toolArgs, optarg)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Unknown architecture %s.", optarg);
goto finish;
}
toolArgs->explicitArch = true;
break;
case kOptBundleIdentifier:
scratchString = CFStringCreateWithCString(kCFAllocatorDefault,
optarg, kCFStringEncodingUTF8);
if (!scratchString) {
OSKextLogMemError();
result = EX_OSERR;
goto finish;
}
CFSetAddValue(toolArgs->kextIDs, scratchString);
break;
case kOptPrelinkedKernel:
scratchResult = readPrelinkedKernelArgs(toolArgs, *argc, *argv,
longindex != -1);
if (scratchResult != EX_OK) {
result = scratchResult;
goto finish;
}
break;
#if !NO_BOOT_ROOT
case kOptForce:
toolArgs->updateOpts |= kBRUForceUpdateHelpers;
break;
#endif
case kOptLowPriorityFork:
toolArgs->lowPriorityFlag = true;
break;
case kOptHelp:
usage(kUsageLevelFull);
result = kKextcacheExitHelp;
goto finish;
case kOptRepositoryCaches:
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"-%c is no longer used; ignoring.",
kOptRepositoryCaches);
break;
case kOptKernel:
if (toolArgs->kernelPath) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Warning: kernel file already specified; using last.");
} else {
toolArgs->kernelPath = malloc(PATH_MAX);
if (!toolArgs->kernelPath) {
OSKextLogMemError();
result = EX_OSERR;
goto finish;
}
}
char * resolved_path;
resolved_path = realpath(optarg, toolArgs->kernelPath);
if (resolved_path == NULL) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Error: kernel filename invalid");
goto finish;
}
break;
case kOptLocalRoot:
toolArgs->requiredFlagsRepositoriesOnly |=
kOSKextOSBundleRequiredLocalRootFlag;
break;
case kOptLocalRootAll:
toolArgs->requiredFlagsAll |=
kOSKextOSBundleRequiredLocalRootFlag;
break;
case kOptNetworkRoot:
toolArgs->requiredFlagsRepositoriesOnly |=
kOSKextOSBundleRequiredNetworkRootFlag;
break;
case kOptNetworkRootAll:
toolArgs->requiredFlagsAll |=
kOSKextOSBundleRequiredNetworkRootFlag;
break;
case kOptAllLoaded:
toolArgs->needLoadedKextInfo = true;
break;
case kOptSafeBoot:
toolArgs->requiredFlagsRepositoriesOnly |=
kOSKextOSBundleRequiredSafeBootFlag;
break;
case kOptSafeBootAll:
toolArgs->requiredFlagsAll |=
kOSKextOSBundleRequiredSafeBootFlag;
break;
case kOptTests:
toolArgs->printTestResults = true;
break;
case kOptTargetOverride:
toolArgs->targetForKextVariants = optarg;
break;
#if !NO_BOOT_ROOT
case kOptInvalidate:
if (toolArgs->updateVolumeURL) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Warning: invalidate volume already specified; using last.");
SAFE_RELEASE_NULL(toolArgs->updateVolumeURL);
}
if (stat(optarg, &sb)) {
OSKextLog(NULL,kOSKextLogWarningLevel|kOSKextLogFileAccessFlag,
"%s - %s.", optarg, strerror(errno));
result = EX_NOINPUT;
goto finish;
}
scratchURL = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault,
(const UInt8 *)optarg,
strlen(optarg),
true);
if (!scratchURL) {
OSKextLogStringError( NULL);
result = EX_OSERR;
goto finish;
}
toolArgs->updateVolumeURL = CFRetain(scratchURL);
toolArgs->updateOpts |= kBRUInvalidateKextcache;
break;
#endif
case kOptUpdate:
case kOptCheckUpdate:
if (toolArgs->updateVolumeURL) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Warning: update volume already specified; using last.");
SAFE_RELEASE_NULL(toolArgs->updateVolumeURL);
}
if (stat(optarg, &sb)) {
OSKextLog(NULL,kOSKextLogWarningLevel|kOSKextLogFileAccessFlag,
"%s - %s.", optarg, strerror(errno));
result = EX_NOINPUT;
goto finish;
}
scratchURL = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault,
(const UInt8 *)optarg, strlen(optarg), true);
if (!scratchURL) {
OSKextLogStringError( NULL);
result = EX_OSERR;
goto finish;
}
toolArgs->updateVolumeURL = CFRetain(scratchURL);
if (optchar == kOptCheckUpdate) {
toolArgs->updateOpts |= kBRUExpectUpToDate;
toolArgs->updateOpts |= kBRUCachesAnyRoot;
}
break;
case kOptQuiet:
beQuiet();
break;
case kOptVerbose:
scratchResult = setLogFilterForOpt(*argc, *argv,
kOSKextLogKextOrGlobalMask);
if (scratchResult != EX_OK) {
result = scratchResult;
goto finish;
}
break;
case kOptBuildImmutable:
if (access(kPersonalizeMacOSTool, R_OK|X_OK) == 0) {
toolArgs->buildImmutableKernel = true;
toolArgs->updateOpts |= kBRUImmutableKernel;
} else {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"WARNING: Cannot find personalization tool to build immutable kernel: skipping!");
}
break;
case kOptNoAuthentication:
toolArgs->skipAuthentication = true;
break;
case 0:
switch (longopt) {
case kLongOptVolumeRoot:
if (toolArgs->volumeRootURL) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Warning: volume root already specified; using last.");
SAFE_RELEASE_NULL(toolArgs->volumeRootURL);
}
scratchURL = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault,
(const UInt8 *)optarg, strlen(optarg), true);
if (!scratchURL) {
OSKextLogStringError( NULL);
result = EX_OSERR;
goto finish;
}
toolArgs->volumeRootURL = CFRetain(scratchURL);
break;
case kLongOptSystemCaches:
toolArgs->updateSystemCaches = true;
setSystemExtensionsFolders(toolArgs);
break;
case kLongOptCompressed:
toolArgs->compress = true;
break;
case kLongOptUncompressed:
toolArgs->uncompress = true;
break;
case kLongOptSymbols:
if (toolArgs->symbolDirURL) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Warning: symbol directory already specified; using last.");
SAFE_RELEASE_NULL(toolArgs->symbolDirURL);
}
scratchURL = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault,
(const UInt8 *)optarg, strlen(optarg), true);
if (!scratchURL) {
OSKextLogStringError( NULL);
result = EX_OSERR;
goto finish;
}
toolArgs->symbolDirURL = CFRetain(scratchURL);
toolArgs->generatePrelinkedSymbols = true;
break;
case kLongOptSystemPrelinkedKernel:
scratchResult = setPrelinkedKernelArgs(toolArgs,
NULL);
if (scratchResult != EX_OK) {
result = scratchResult;
goto finish;
}
toolArgs->needLoadedKextInfo = true;
toolArgs->requiredFlagsRepositoriesOnly |=
kOSKextOSBundleRequiredLocalRootFlag;
break;
case kLongOptAllPersonalities:
toolArgs->includeAllPersonalities = true;
break;
case kLongOptNoLinkFailures:
toolArgs->noLinkFailures = true;
break;
case kLongOptStripSymbols:
toolArgs->stripSymbols = true;
break;
#if !NO_BOOT_ROOT
case kLongOptInstaller:
toolArgs->updateOpts |= kBRUHelpersOptional;
toolArgs->updateOpts |= kBRUForceUpdateHelpers;
break;
case kLongOptCachesOnly:
toolArgs->updateOpts |= kBRUCachesOnly;
break;
#endif
case kLongOptEarlyBoot:
toolArgs->updateOpts |= kBRUEarlyBoot;
break;
case kLongOptImmutableKexts:
toolArgs->requiredFlagsRepositoriesOnly = kImmutableKernelKextFilter;
break;
case kLongOptImmutableKextsAll:
toolArgs->requiredFlagsAll = kImmutableKernelKextFilter;
break;
case kLongOptClearStaging:
toolArgs->clearStaging = true;
break;
case kLongOptPruneStaging:
toolArgs->pruneStaging = true;
break;
default:
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"unrecognized option %s", (*argv)[optind-1]);
goto finish;
break;
}
break;
default:
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"unrecognized option %s", (*argv)[optind-1]);
goto finish;
break;
}
longindex = -1;
}
*argc -= optind;
*argv += optind;
if (!toolArgs->updateVolumeURL) {
for (i = 0; i < *argc; i++) {
SAFE_RELEASE_NULL(scratchURL);
SAFE_RELEASE_NULL(scratchString);
scratchURL = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault,
(const UInt8 *)(*argv)[i], strlen((*argv)[i]), true);
if (!scratchURL) {
OSKextLogMemError();
result = EX_OSERR;
goto finish;
}
CFArrayAppendValue(toolArgs->argURLs, scratchURL);
scratchString = CFURLCopyPathExtension(scratchURL);
if (scratchString && CFEqual(scratchString, CFSTR("kext"))) {
CFArrayAppendValue(toolArgs->namedKextURLs, scratchURL);
} else {
CFArrayAppendValue(toolArgs->repositoryURLs, scratchURL);
}
}
}
result = EX_OK;
finish:
SAFE_RELEASE(scratchString);
SAFE_RELEASE(scratchNumber);
SAFE_RELEASE(scratchURL);
if (result == EX_USAGE) {
usage(kUsageLevelBrief);
}
return result;
}
ExitStatus readPrelinkedKernelArgs(
KextcacheArgs * toolArgs,
int argc,
char * const * argv,
Boolean isLongopt)
{
char * filename = NULL;
if (optarg) {
filename = optarg;
} else if (isLongopt && optind < argc) {
filename = argv[optind];
optind++;
}
if (filename && !filename[0]) {
filename = NULL;
}
return setPrelinkedKernelArgs(toolArgs, filename);
}
ExitStatus setPrelinkedKernelArgs(
KextcacheArgs * toolArgs,
char * filename)
{
ExitStatus result = EX_USAGE;
if (toolArgs->prelinkedKernelPath) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Warning: prelinked kernel already specified; using last.");
} else {
toolArgs->prelinkedKernelPath = malloc(PATH_MAX);
if (!toolArgs->prelinkedKernelPath) {
OSKextLogMemError();
result = EX_OSERR;
goto finish;
}
}
if (!filename) {
#if NO_BOOT_ROOT
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Error: prelinked kernel filename required");
goto finish;
#else
if (!setDefaultPrelinkedKernel(toolArgs)) {
goto finish;
}
toolArgs->needDefaultPrelinkedKernelInfo = true;
setSystemExtensionsFolders(toolArgs);
#endif
} else {
char *resolved_path;
size_t len;
resolved_path = realpath(filename, toolArgs->prelinkedKernelPath);
if (resolved_path) {
len = strlen(filename);
} else {
len = strlcpy(toolArgs->prelinkedKernelPath, filename, PATH_MAX);
}
if (len >= PATH_MAX) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Error: prelinked kernel filename length exceeds PATH_MAX");
goto finish;
}
toolArgs->prelinkedKernelDirname = malloc(PATH_MAX);
if (!toolArgs->prelinkedKernelDirname) {
OSKextLogMemError();
result = EX_OSERR;
goto finish;
}
(void)dirname_r(toolArgs->prelinkedKernelPath,
toolArgs->prelinkedKernelDirname);
}
result = EX_OK;
finish:
return result;
}
#define kSyspolicyDB "/var/db/SystemPolicyConfiguration/KextPolicy"
#define kKextAllowListTmpFilePfx "/tmp/kextcache.klist.tmp"
static ExitStatus
updateKextAllowList(void)
{
ExitStatus result = EX_USAGE;
char bootuuid[37] = {};
char tmpPath[PATH_MAX] = {};
int cdhashesListFD = -1;
int cacheDirFD = -1;
size_t len;
int rc;
CFDataRef cdhashData = NULL; CFStringRef hashStr = NULL;
len = sizeof(bootuuid);
if (sysctlbyname("kern.bootsessionuuid", bootuuid, &len, NULL, 0) < 0) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"ERROR getting kern.bootsessionuuid");
return EX_OSERR;
}
bootuuid[36] = 0;
if (readKextHashAllowList(false, &hashStr, NULL, NULL, NULL)) {
char str[37] = {};
const char *strptr = CFStringGetCStringPtr(hashStr, kCFStringEncodingUTF8);
if (!strptr) {
if (!CFStringGetCString(hashStr, str, sizeof(str), kCFStringEncodingUTF8)) {
result = EX_OSERR;
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Error handling boot session UUID string");
goto out;
} else {
strptr = (const char *)str;
}
}
if (strncmp(strptr, bootuuid, sizeof(bootuuid)) == 0) {
result = EX_NOPERM;
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"kext allow list already up-to-date for boot %s", bootuuid);
goto out;
} else {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Generating kext allow for boot %s (!= %s)", bootuuid, strptr);
}
}
strlcpy(tmpPath, _kOSKextCachesRootFolder, PATH_MAX);
cacheDirFD = open(tmpPath, O_RDONLY | O_DIRECTORY);
if (cacheDirFD < 0) {
result = EX_NOPERM;
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't open cache directory '%s/'", tmpPath);
goto out;
}
strlcpy(tmpPath, kKextAllowListTmpFilePfx, sizeof(tmpPath));
if (strlcat(tmpPath, ".XXXXXX", sizeof(tmpPath)) >= sizeof(tmpPath)) {
result = EX_OSERR;
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"path to temp file too long? '%s'", tmpPath);
goto out;
}
cdhashesListFD = mkstemp(tmpPath);
if (cdhashesListFD < 0) {
OSKextLog( NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
"ERROR opening tmp file at \"%s\"",
kKextAllowListTmpFilePfx);
result = EX_OSERR;
goto out;
}
char *listuuids_argv[] = {
kListCDHashesTool,
kSyspolicyDB,
tmpPath,
NULL
};
rc = fork_program(kListCDHashesTool, listuuids_argv, true );
unlink(tmpPath);
if (rc != 0) {
OSKextLog( NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
"ERROR(%d) gathering UUIDs from : \"%s : %s\"",
rc, kSyspolicyDB, tmpPath);
result = EX_OSERR;
goto out;
}
if (!createCFDataFromFD(cdhashesListFD, &cdhashData)) {
OSKextLog( NULL,
kOSKextLogGeneralFlag | kOSKextLogWarningLevel,
"ERROR reading cdhashes from : \"%s\": resetting to 0 allowed kexts!", tmpPath);
const UInt8 zero = 0;
cdhashData = CFDataCreate(kCFAllocatorDefault, &zero, 1);
}
result = writeKextAllowList(bootuuid, cdhashData, cacheDirFD, kThirdPartyKextAllowList);
out:
if (cdhashesListFD >= 0) {
close(cdhashesListFD);
}
if (cacheDirFD >= 0) {
close(cacheDirFD);
}
SAFE_RELEASE(cdhashData);
SAFE_RELEASE(hashStr);
return result;
}
#if !NO_BOOT_ROOT
#ifndef kIOPMAssertNoIdleSystemSleep
#define kIOPMAssertNoIdleSystemSleep \
kIOPMAssertionTypePreventUserIdleSystemSleep
#endif
ExitStatus doUpdateVolume(KextcacheArgs *toolArgs)
{
ExitStatus rval; int result; IOReturn pmres = kIOReturnError; IOPMAssertionID awakeForUpdate; os_signpost_id_t spid = generate_signpost_id();
os_signpost_interval_begin(get_signpost_log(), spid, SIGNPOST_KEXTCACHE_UPDATE_VOLUME);
if (toolArgs->lowPriorityFlag == false) {
pmres = IOPMAssertionCreateWithName(kIOPMAssertNoIdleSystemSleep,
kIOPMAssertionLevelOn,
CFSTR("com.apple.kextmanager.update"),
&awakeForUpdate);
if (pmres) {
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Warning: couldn't block sleep during cache update");
} else {
os_signpost_event_emit(get_signpost_log(), spid, SIGNPOST_EVENT_POWER_ASSERTION);
}
}
result = checkUpdateCachesAndBoots(toolArgs->updateVolumeURL,
toolArgs->updateOpts);
switch (result) {
case ENOENT:
case EFTYPE: rval = EX_OSFILE; break;
default: rval = result;
}
if (toolArgs->lowPriorityFlag == false && pmres == 0) {
if (IOPMAssertionRelease(awakeForUpdate))
OSKextLog(NULL, kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"Warning: error re-enabling sleep after cache update");
}
os_signpost_interval_end(get_signpost_log(), spid, SIGNPOST_KEXTCACHE_UPDATE_VOLUME);
return rval;
}
Boolean setDefaultKernel(KextcacheArgs * toolArgs)
{
#if DEV_KERNEL_SUPPORT
Boolean addSuffix = FALSE;
#endif
size_t length = 0;
struct stat statBuf;
if (!toolArgs->kernelPath) {
toolArgs->kernelPath = malloc(PATH_MAX);
if (!toolArgs->kernelPath) {
OSKextLogMemError();
return FALSE;
}
}
while( true ) {
if (getKernelPathForURL(toolArgs->volumeRootURL,
toolArgs->kernelPath,
PATH_MAX) == FALSE) {
strlcpy(toolArgs->kernelPath, "/System/Library/Kernels/kernel",
PATH_MAX);
}
#if DEV_KERNEL_SUPPORT
addSuffix = useDevelopmentKernel(toolArgs->kernelPath);
if (addSuffix) {
if (strlen(toolArgs->kernelPath) + strlen(kDefaultDevKernelSuffix) + 1 < PATH_MAX) {
strlcat(toolArgs->kernelPath,
kDefaultDevKernelSuffix,
PATH_MAX);
}
else {
addSuffix = FALSE;
}
}
#endif
char * resolved_path = NULL;
resolved_path = realpath(toolArgs->kernelPath, NULL);
if (resolved_path == NULL) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Error: invalid kernel path '%s'",
toolArgs->kernelPath);
return FALSE;
}
free(toolArgs->kernelPath);
toolArgs->kernelPath = resolved_path;
break;
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Error: invalid kernel path '%s'",
toolArgs->kernelPath);
return FALSE;
}
TIMESPEC_TO_TIMEVAL(&toolArgs->kernelTimes[0], &statBuf.st_atimespec);
TIMESPEC_TO_TIMEVAL(&toolArgs->kernelTimes[1], &statBuf.st_mtimespec);
#if DEV_KERNEL_SUPPORT
if (toolArgs->prelinkedKernelPath &&
toolArgs->needDefaultPrelinkedKernelInfo &&
addSuffix) {
length = strlcat(toolArgs->prelinkedKernelPath,
kDefaultDevKernelSuffix,
PATH_MAX);
if (length >= PATH_MAX) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Error: prelinkedkernel filename length exceeds PATH_MAX");
return FALSE;
}
}
#endif
return TRUE;
}
Boolean setDefaultPrelinkedKernel(KextcacheArgs * toolArgs)
{
Boolean result = FALSE;
const char * prelinkedKernelFile = NULL;
size_t length = 0;
prelinkedKernelFile =
_kOSKextTemporaryPrelinkedKernelsPath "/"
_kOSKextPrelinkedKernelFileName;
length = strlcpy(toolArgs->prelinkedKernelPath,
prelinkedKernelFile, PATH_MAX);
if (length >= PATH_MAX) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Error: prelinked kernel filename length exceeds PATH_MAX");
goto finish;
}
toolArgs->prelinkedKernelDirname = malloc(PATH_MAX);
if (!toolArgs->prelinkedKernelDirname) {
OSKextLogMemError();
goto finish;
}
strlcpy(toolArgs->prelinkedKernelDirname, _kOSKextTemporaryPrelinkedKernelsPath, PATH_MAX);
if (!toolArgs->volumeRootURL) {
toolArgs->volumeRootURL = CFURLCreateWithFileSystemPath(NULL, CFSTR("/"), kCFURLPOSIXPathStyle, true);
if (!toolArgs->volumeRootURL) {
OSKextLogMemError();
goto finish;
}
}
result = TRUE;
finish:
return result;
}
#endif
void setSystemExtensionsFolders(KextcacheArgs * toolArgs)
{
CFArrayRef sysExtensionsFolders = OSKextGetSystemExtensionsFolderURLs();
CFArrayAppendArray(toolArgs->argURLs,
sysExtensionsFolders, RANGE_ALL(sysExtensionsFolders));
CFArrayAppendArray(toolArgs->repositoryURLs,
sysExtensionsFolders, RANGE_ALL(sysExtensionsFolders));
return;
}
static void
waitForIOKitQuiescence(void)
{
kern_return_t kern_result = 0;
mach_timespec_t waitTime = { 40, 0 };
if ( isKextdRunning() == FALSE ) {
return;
}
OSKextLog( NULL,
kOSKextLogProgressLevel | kOSKextLogIPCFlag,
"Waiting for I/O Kit to quiesce.");
kern_result = IOKitWaitQuiet(kIOMasterPortDefault, &waitTime);
if (kern_result == kIOReturnTimeout) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"IOKitWaitQuiet() timed out.");
} else if (kern_result != kOSReturnSuccess) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"IOKitWaitQuiet() failed - %s.",
safe_mach_error_string(kern_result));
}
}
static void
waitForGreatSystemLoad(void)
{
struct timeval currenttime;
struct timeval endtime;
struct timeval timeout;
fd_set readfds;
fd_set tmpfds;
uint64_t systemLoadAdvisoryState = 0;
uint32_t notifyStatus = 0;
uint32_t usecs = 0;
int systemLoadAdvisoryFileDescriptor = 0; int systemLoadAdvisoryToken = 0; int currentToken = 0; int myResult;
bzero(¤ttime, sizeof(currenttime));
bzero(&endtime, sizeof(endtime));
bzero(&timeout, sizeof(timeout));
OSKextLog( NULL,
kOSKextLogProgressLevel | kOSKextLogGeneralFlag,
"Waiting for low system load.");
notifyStatus = notify_register_file_descriptor(kIOSystemLoadAdvisoryNotifyName,
&systemLoadAdvisoryFileDescriptor,
0, &systemLoadAdvisoryToken);
if (notifyStatus != NOTIFY_STATUS_OK) {
goto finish;
}
OSKextLog( NULL,
kOSKextLogDebugLevel | kOSKextLogGeneralFlag,
"Received initial system load status %llu", systemLoadAdvisoryState);
notifyStatus = notify_get_state(systemLoadAdvisoryToken, &systemLoadAdvisoryState);
if (notifyStatus != NOTIFY_STATUS_OK) {
goto finish;
}
if (systemLoadAdvisoryState == kIOSystemLoadAdvisoryLevelGreat) {
goto finish;
}
myResult = gettimeofday(¤ttime, NULL);
if (myResult < 0) {
goto finish;
}
endtime = currenttime;
endtime.tv_sec += kOSKextSystemLoadTimeout;
timeval_difference(&timeout, &endtime, ¤ttime);
usecs = usecs_from_timeval(&timeout);
FD_ZERO(&readfds);
FD_SET(systemLoadAdvisoryFileDescriptor, &readfds);
while (usecs) {
FD_COPY(&readfds, &tmpfds);
myResult = select(systemLoadAdvisoryFileDescriptor + 1,
&tmpfds, NULL, NULL, &timeout);
if (myResult < 0) {
goto finish;
}
myResult = gettimeofday(¤ttime, NULL);
if (myResult < 0) {
goto finish;
}
timeval_difference(&timeout, &endtime, ¤ttime);
usecs = usecs_from_timeval(&timeout);
if (!FD_ISSET(systemLoadAdvisoryFileDescriptor, &tmpfds)) {
continue;
}
myResult = (int)read(systemLoadAdvisoryFileDescriptor,
¤tToken, sizeof(currentToken));
if (myResult < 0) {
goto finish;
}
currentToken = ntohl(currentToken);
if (currentToken != systemLoadAdvisoryToken) {
continue;
}
notifyStatus = notify_get_state(systemLoadAdvisoryToken,
&systemLoadAdvisoryState);
if (notifyStatus != NOTIFY_STATUS_OK) {
goto finish;
}
OSKextLog( NULL,
kOSKextLogDebugLevel | kOSKextLogGeneralFlag,
"Received updated system load status %llu", systemLoadAdvisoryState);
if (systemLoadAdvisoryState == kIOSystemLoadAdvisoryLevelGreat) {
break;
}
}
OSKextLog( NULL,
kOSKextLogDebugLevel | kOSKextLogGeneralFlag,
"Pausing for another %d seconds to avoid work contention",
kOSKextSystemLoadPauseTime);
sleep(kOSKextSystemLoadPauseTime);
OSKextLog( NULL,
kOSKextLogDebugLevel | kOSKextLogGeneralFlag,
"System load is low. Proceeding.\n");
finish:
if (systemLoadAdvisoryToken) {
notify_cancel(systemLoadAdvisoryToken);
}
return;
}
static u_int
usecs_from_timeval(struct timeval *t)
{
u_int usecs = 0;
if (t) {
usecs = (unsigned int)((t->tv_sec * 1000) + t->tv_usec);
}
return usecs;
}
static void
timeval_from_usecs(struct timeval *t, u_int usecs)
{
if (t) {
if (usecs > 0) {
t->tv_sec = usecs / 1000;
t->tv_usec = usecs % 1000;
} else {
bzero(t, sizeof(*t));
}
}
}
static void
timeval_difference(struct timeval *dst, struct timeval *a, struct timeval *b)
{
u_int ausec = 0, busec = 0, dstusec = 0;
if (dst) {
ausec = usecs_from_timeval(a);
busec = usecs_from_timeval(b);
if (ausec > busec) {
dstusec = ausec - busec;
}
timeval_from_usecs(dst, dstusec);
}
}
#if !NO_BOOT_ROOT
void setDefaultArchesIfNeeded(KextcacheArgs * toolArgs)
{
if (toolArgs->explicitArch) {
return;
}
CFArrayRemoveAllValues(toolArgs->targetArchs);
addArch(toolArgs, OSKextGetRunningKernelArchitecture());
return;
}
#endif
void addArch(
KextcacheArgs * toolArgs,
const NXArchInfo * arch)
{
if (CFArrayContainsValue(toolArgs->targetArchs,
RANGE_ALL(toolArgs->targetArchs), arch))
{
return;
}
CFArrayAppendValue(toolArgs->targetArchs, arch);
}
const NXArchInfo * addArchForName(
KextcacheArgs * toolArgs,
const char * archname)
{
const NXArchInfo * result = NULL;
result = NXGetArchInfoFromName(archname);
if (!result) {
goto finish;
}
addArch(toolArgs, result);
finish:
return result;
}
void checkKextdSpawnedFilter(Boolean kernelFlag)
{
const char * environmentVariable = NULL; char * environmentLogFilterString = NULL;
if (kernelFlag) {
environmentVariable = "KEXT_LOG_FILTER_KERNEL";
} else {
environmentVariable = "KEXT_LOG_FILTER_USER";
}
environmentLogFilterString = getenv(environmentVariable);
if (environmentLogFilterString) {
OSKextLogSpec toolLogSpec = OSKextGetLogFilter(kernelFlag);
OSKextLogSpec kextdLogSpec = (unsigned int)strtoul(environmentLogFilterString, NULL, 16);
OSKextLogSpec toolLogLevel = toolLogSpec & kOSKextLogLevelMask;
OSKextLogSpec kextdLogLevel = kextdLogSpec & kOSKextLogLevelMask;
OSKextLogSpec comboLogLevel = MAX(toolLogLevel, kextdLogLevel);
OSKextLogSpec toolLogFlags = toolLogSpec & kOSKextLogFlagsMask;
OSKextLogSpec kextdLogFlags = kextdLogSpec & kOSKextLogFlagsMask;
OSKextLogSpec comboLogFlags = toolLogFlags | kextdLogFlags |
kOSKextLogKextOrGlobalMask;
OSKextSetLogFilter(comboLogLevel | comboLogFlags, kernelFlag);
} else {
char logSpecBuffer[16];
snprintf(logSpecBuffer, sizeof(logSpecBuffer), "0x%x",
OSKextGetLogFilter(kernelFlag));
setenv(environmentVariable, logSpecBuffer, 1);
}
return;
}
ExitStatus checkArgs(KextcacheArgs * toolArgs)
{
ExitStatus result = EX_USAGE;
Boolean expectUpToDate = toolArgs->updateOpts & kBRUExpectUpToDate;
if (!toolArgs->prelinkedKernelPath &&
!toolArgs->updateVolumeURL && !toolArgs->updateSystemCaches &&
!toolArgs->clearStaging && !toolArgs->pruneStaging)
{
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"No work to do; check options and try again.");
goto finish;
}
if (toolArgs->clearStaging || toolArgs->pruneStaging)
{
if (toolArgs->prelinkedKernelPath ||
toolArgs->updateVolumeURL ||
toolArgs->updateSystemCaches)
{
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Kernel extension staging functions must be used alone.");
goto finish;
}
if (geteuid() != 0) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"You must be running as root to manage the kernel extension staging area.");
result = EX_NOPERM;
goto finish;
}
}
if (toolArgs->volumeRootURL &&
!toolArgs->prelinkedKernelPath)
{
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Use -%s only when creating a prelinked kernel.",
kOptNameVolumeRoot);
goto finish;
}
if (!toolArgs->updateVolumeURL && !CFArrayGetCount(toolArgs->argURLs) &&
!toolArgs->compress && !toolArgs->uncompress &&
!toolArgs->clearStaging && !toolArgs->pruneStaging)
{
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"No kexts or directories specified.");
goto finish;
}
if (!toolArgs->compress && !toolArgs->uncompress) {
toolArgs->compress = true;
} else if (toolArgs->compress && toolArgs->uncompress) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Both -%s and -%s specified; using -%s.",
kOptNameCompressed, kOptNameUncompressed, kOptNameCompressed);
toolArgs->compress = true;
toolArgs->uncompress = false;
}
#if !NO_BOOT_ROOT
if ((toolArgs->updateOpts & kBRUForceUpdateHelpers)
&& (toolArgs->updateOpts & kBRUCachesOnly)) {
OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"-%s (%-c) and %-s are mutually exclusive",
kOptNameForce, kOptForce, kOptNameCachesOnly);
goto finish;
}
if (toolArgs->updateOpts & kBRUForceUpdateHelpers) {
if (expectUpToDate || !toolArgs->updateVolumeURL) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"-%s (-%c) is allowed only with -%s (-%c).",
kOptNameForce, kOptForce, kOptNameUpdate, kOptUpdate);
goto finish;
}
}
if (toolArgs->updateOpts & kBRUCachesOnly) {
if (expectUpToDate || !toolArgs->updateVolumeURL) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"-%s is allowed only with -%s (-%c).",
kOptNameCachesOnly, kOptNameUpdate, kOptUpdate);
goto finish;
}
}
#endif
if (toolArgs->updateOpts & kBRUEarlyBoot) {
if (!toolArgs->updateVolumeURL ||
!(toolArgs->updateOpts & kBRUExpectUpToDate) ||
!(toolArgs->updateOpts & kBRUCachesAnyRoot)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"-%s requires -%c.",
kOptNameEarlyBoot, kOptCheckUpdate);
goto finish;
}
}
if (toolArgs->updateVolumeURL) {
if (toolArgs->prelinkedKernelPath) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't create prelinked kernel when updating volumes.");
}
}
if (toolArgs->updateVolumeURL &&
toolArgs->updateOpts & kBRUInvalidateKextcache &&
geteuid() != 0) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"You must be running as root to update prelinked kernel.");
result = EX_NOPERM;
goto finish;
}
#if !NO_BOOT_ROOT
setDefaultArchesIfNeeded(toolArgs);
#endif
if (toolArgs->extensionsDirTimes[1].tv_sec == 0 &&
CFArrayGetCount(toolArgs->repositoryURLs)) {
result = getLatestTimesFromCFURLArray(toolArgs->repositoryURLs,
toolArgs->extensionsDirTimes);
if (result != EX_OK) {
OSKextLog(NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"%s: Can't get mod times", __FUNCTION__);
goto finish;
}
}
#if !NO_BOOT_ROOT
if (toolArgs->needDefaultPrelinkedKernelInfo && !toolArgs->kernelPath) {
if (!setDefaultKernel(toolArgs)) {
result = EX_USAGE;
goto finish;
}
}
#endif
if (toolArgs->prelinkedKernelPath && CFArrayGetCount(toolArgs->argURLs)) {
struct stat myStatBuf;
if (!toolArgs->kernelPath) {
if (!setDefaultKernel(toolArgs)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"No kernel specified for prelinked kernel generation.");
result = EX_USAGE;
goto finish;
}
}
result = statPath(toolArgs->kernelPath, &myStatBuf);
if (result != EX_OK) {
goto finish;
}
TIMESPEC_TO_TIMEVAL(&toolArgs->kernelTimes[0], &myStatBuf.st_atimespec);
TIMESPEC_TO_TIMEVAL(&toolArgs->kernelTimes[1], &myStatBuf.st_mtimespec);
}
if (toolArgs->needDefaultPrelinkedKernelInfo ||
toolArgs->updateSystemCaches) {
if (CFArrayGetCount(toolArgs->namedKextURLs) || CFSetGetCount(toolArgs->kextIDs) ||
!CFEqual(toolArgs->repositoryURLs, OSKextGetSystemExtensionsFolderURLs())) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Custom kexts and repository directories are not allowed "
"when updating system kext caches.");
result = EX_USAGE;
goto finish;
}
if (geteuid() != 0) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"You must be running as root to update system kext caches.");
result = EX_NOPERM;
goto finish;
}
}
if (toolArgs->buildImmutableKernel) {
#if DEV_KERNEL_SUPPORT
if (geteuid() != 0) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"You must be running as root to replace the immutable kernel.");
result = EX_NOPERM;
goto finish;
}
if (CFArrayGetCount(toolArgs->namedKextURLs) || CFSetGetCount(toolArgs->kextIDs) ||
((CFArrayGetCount(toolArgs->repositoryURLs) > 0) &&
!CFEqual(toolArgs->repositoryURLs, OSKextGetSystemExtensionsFolderURLs()))) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"WARNING: You have specified custom kexts or kext repository "
"directories, and you are replacing your immutable kernel. "
"The end result will *not* be a standard immutablekernel!");
}
if (!toolArgs->requiredFlagsRepositoriesOnly && !toolArgs->requiredFlagsAll) {
toolArgs->requiredFlagsRepositoriesOnly = kImmutableKernelKextFilter;
toolArgs->requiredFlagsAll = kImmutableKernelKextFilter;
}
if (toolArgs->requiredFlagsRepositoriesOnly != kImmutableKernelKextFilter ||
toolArgs->requiredFlagsAll != kImmutableKernelKextFilter) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"WARNING: You are building an immutable kernel with a non-standard filter!");
}
#else
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"No support for updating the immutable kernel!");
result = EX_NOPERM;
goto finish;
#endif
}
if (isProbablyProtectedPLK(toolArgs)) {
if (toolArgs->skipAuthentication) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Cannot create a prelinked kernel in a SIP protected location without authentication!");
result = EX_NOPERM;
goto finish;
}
}
result = EX_OK;
finish:
if (result == EX_USAGE) {
usage(kUsageLevelBrief);
}
return result;
}
#pragma mark System Plist Caches
ExitStatus
copyLoadedKextInfo(
CFArrayRef *loadedKexts,
bool waitToQuiesce)
{
ExitStatus result = EX_SOFTWARE;
CFArrayRef requestedIdentifiers = NULL;
if (waitToQuiesce) {
(void) waitForIOKitQuiescence();
}
requestedIdentifiers = OSKextCopyAllRequestedIdentifiers();
if (!requestedIdentifiers) {
goto finish;
}
if (loadedKexts) {
*loadedKexts = OSKextCopyKextsWithIdentifiers(requestedIdentifiers);
if (*loadedKexts == NULL) {
goto finish;
}
}
result = EX_OK;
finish:
SAFE_RELEASE(requestedIdentifiers);
return result;
}
ExitStatus updateSystemPlistCaches(KextcacheArgs * toolArgs)
{
ExitStatus result = EX_OSERR;
ExitStatus directoryResult = EX_OK; CFArrayRef systemExtensionsURLs = NULL; CFArrayRef unauthenticatedKexts = NULL; CFMutableArrayRef authenticKexts = NULL; CFURLRef folderURL = NULL; char folderPath[PATH_MAX] = "";
const NXArchInfo * startArch = OSKextGetArchitecture();
CFArrayRef directoryValues = NULL; CFArrayRef personalities = NULL; os_signpost_id_t spid = generate_signpost_id();
CFIndex count, i;
os_signpost_interval_begin(get_signpost_log(), spid, SIGNPOST_KEXTCACHE_UPDATE_PLISTS);
if (!isSecureAuthentication(toolArgs)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Unable to update system caches with current authentication options.");
result = EX_NOPERM;
goto finish;
}
systemExtensionsURLs = OSKextGetSystemExtensionsFolderURLs();
if (!systemExtensionsURLs) {
OSKextLogMemError();
result = EX_OSERR;
goto finish;
}
unauthenticatedKexts = createStagedKextsFromURLs(systemExtensionsURLs, false);
if (!unauthenticatedKexts) {
goto finish;
}
if (!createCFMutableArray(&authenticKexts, &kCFTypeArrayCallBacks)) {
OSKextLogMemError();
result = EX_OSERR;
goto finish;
}
toolArgs->authenticationOptions.isCacheLoad = false;
count = CFArrayGetCount(unauthenticatedKexts);
for (i = 0; i < count; i++) {
OSKextRef aKext = (OSKextRef)CFArrayGetValueAtIndex(unauthenticatedKexts, i);
if (OSKextIsAuthentic(aKext)) {
CFArrayAppendValue(authenticKexts, aKext);
}
}
for (i = 0; i < CFArrayGetCount(toolArgs->targetArchs); i++) {
const NXArchInfo * targetArch =
CFArrayGetValueAtIndex(toolArgs->targetArchs, i);
SAFE_RELEASE_NULL(personalities);
if (!OSKextSetArchitecture(targetArch)) {
goto finish;
}
personalities = OSKextCopyPersonalitiesOfKexts(authenticKexts);
if (!personalities) {
goto finish;
}
if (!_OSKextWriteCache(systemExtensionsURLs, CFSTR(kIOKitPersonalitiesKey),
targetArch, _kOSKextCacheFormatIOXML, personalities)) {
goto finish;
}
if (!readSystemKextPropertyValues(CFSTR(kOSBundleHelperKey), targetArch,
true, NULL)) {
goto finish;
}
if (!readSystemKextPropertyValues(CFSTR("PGO"), targetArch,
true, NULL)) {
goto finish;
}
}
count = CFArrayGetCount(systemExtensionsURLs);
for (i = 0; i < count; i++) {
folderURL = CFArrayGetValueAtIndex(systemExtensionsURLs, i);
if (!CFURLGetFileSystemRepresentation(folderURL, true,
(UInt8 *)folderPath, sizeof(folderPath))) {
OSKextLogStringError( NULL);
goto finish;
}
if (EX_OK != updateDirectoryCaches(toolArgs, folderURL)) {
directoryResult = EX_OSERR;
} else {
OSKextLog( NULL,
kOSKextLogBasicLevel | kOSKextLogGeneralFlag,
"Directory caches updated for %s.", folderPath);
}
}
if (directoryResult == EX_OK) {
result = EX_OK;
}
finish:
SAFE_RELEASE(unauthenticatedKexts);
SAFE_RELEASE(authenticKexts);
SAFE_RELEASE(directoryValues);
SAFE_RELEASE(personalities);
OSKextSetArchitecture(startArch);
toolArgs->authenticationOptions.isCacheLoad = true;
os_signpost_interval_end(get_signpost_log(), spid, SIGNPOST_KEXTCACHE_UPDATE_PLISTS);
return result;
}
ExitStatus updateDirectoryCaches(
KextcacheArgs * toolArgs,
CFURLRef folderURL)
{
ExitStatus result = EX_OK; CFArrayRef kexts = NULL;
kexts = OSKextCreateKextsFromURL(kCFAllocatorDefault, folderURL);
if (!kexts) {
result = EX_OSERR;
goto finish;
}
if (!_OSKextWriteIdentifierCacheForKextsInDirectory(
kexts, folderURL, true)) {
result = EX_OSERR;
goto finish;
}
result = EX_OK;
finish:
SAFE_RELEASE(kexts);
return result;
}
#pragma mark Misc Stuff
ExitStatus
getFileURLModTimePlusOne(
CFURLRef fileURL,
struct timeval *origModTime,
struct timeval cacheFileTimes[2])
{
ExitStatus result = EX_SOFTWARE;
char path[PATH_MAX];
if (!CFURLGetFileSystemRepresentation(fileURL, true,
(UInt8 *)path, sizeof(path)))
{
OSKextLogStringError( NULL);
goto finish;
}
result = getFilePathModTimePlusOne(path, origModTime, cacheFileTimes);
finish:
return result;
}
ExitStatus
getFileDescriptorModTimePlusOne(
int the_fd,
struct timeval * origModTime,
struct timeval cacheFileTimes[2])
{
ExitStatus result = EX_SOFTWARE;
result = getFileDescriptorTimes(the_fd, cacheFileTimes);
if (result != EX_OK) {
goto finish;
}
if (origModTime != NULL) {
if (timercmp(origModTime, &cacheFileTimes[1], !=)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag | kOSKextLogFileAccessFlag,
"Source item has changed since starting; "
"not saving cache file <%s %d>", __func__, __LINE__);
result = kKextcacheExitStale;
goto finish;
}
}
cacheFileTimes[1].tv_sec++;
result = EX_OK;
finish:
return result;
}
ExitStatus
getFilePathModTimePlusOne(
const char * filePath,
struct timeval * origModTime,
struct timeval cacheFileTimes[2])
{
ExitStatus result = EX_SOFTWARE;
result = getFilePathTimes(filePath, cacheFileTimes);
if (result != EX_OK) {
goto finish;
}
if (origModTime != NULL) {
if (timercmp(origModTime, &cacheFileTimes[1], !=)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag | kOSKextLogFileAccessFlag,
"Source item %s has changed since starting; "
"not saving cache file", filePath);
result = kKextcacheExitStale;
goto finish;
}
}
cacheFileTimes[1].tv_sec++;
result = EX_OK;
finish:
return result;
}
typedef struct {
KextcacheArgs * toolArgs;
CFMutableArrayRef kextArray;
Boolean error;
} FilterIDContext;
void filterKextID(const void * vValue, void * vContext)
{
CFStringRef kextID = (CFStringRef)vValue;
FilterIDContext * context = (FilterIDContext *)vContext;
OSKextRef theKext = OSKextGetKextWithIdentifier(kextID);
if (!theKext) {
char kextIDCString[KMOD_MAX_NAME];
CFStringGetCString(kextID, kextIDCString, sizeof(kextIDCString),
kCFStringEncodingUTF8);
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't find kext with optional identifier %s; skipping.", kextIDCString);
#if 0
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Error - can't find kext with identifier %s.", kextIDCString);
context->error = TRUE;
#endif
goto finish;
}
if (kextMatchesFilter(context->toolArgs, theKext,
context->toolArgs->requiredFlagsAll) &&
!CFArrayContainsValue(context->kextArray,
RANGE_ALL(context->kextArray), theKext))
{
CFArrayAppendValue(context->kextArray, theKext);
}
finish:
return;
}
ExitStatus filterKextsForCache(
KextcacheArgs * toolArgs,
CFMutableArrayRef kextArray,
const NXArchInfo * arch,
Boolean * fatalOut)
{
ExitStatus result = EX_SOFTWARE;
CFMutableArrayRef firstPassArray = NULL;
OSKextRequiredFlags requiredFlags;
CFIndex count, i;
Boolean earlyBoot = false;
if (!createCFMutableArray(&firstPassArray, &kCFTypeArrayCallBacks)) {
OSKextLogMemError();
goto finish;
}
if (CFSetGetCount(toolArgs->kextIDs)) {
FilterIDContext context;
context.toolArgs = toolArgs;
context.kextArray = firstPassArray;
context.error = FALSE;
CFSetApplyFunction(toolArgs->kextIDs, filterKextID, &context);
if (context.error) {
goto finish;
}
} else {
requiredFlags = toolArgs->requiredFlagsRepositoriesOnly |
toolArgs->requiredFlagsAll;
if (requiredFlags) {
requiredFlags |= kOSKextOSBundleRequiredRootFlag |
kOSKextOSBundleRequiredConsoleFlag |
kOSKextOSBundleRequiredDriverKitFlag;
}
count = CFArrayGetCount(toolArgs->repositoryKexts);
for (i = 0; i < count; i++) {
OSKextRef theKext = (OSKextRef)CFArrayGetValueAtIndex(
toolArgs->repositoryKexts, i);
if (!kextMatchesFilter(toolArgs, theKext, requiredFlags)) {
char kextPath[PATH_MAX];
if (!CFURLGetFileSystemRepresentation(OSKextGetURL(theKext),
false, (UInt8 *)kextPath, sizeof(kextPath)))
{
strlcpy(kextPath, "(unknown)", sizeof(kextPath));
}
if (toolArgs->prelinkedKernelPath) {
OSKextLog( NULL,
kOSKextLogStepLevel | kOSKextLogArchiveFlag,
"%s is not demanded by OSBundleRequired conditions.",
kextPath);
}
continue;
}
if (!CFArrayContainsValue(firstPassArray, RANGE_ALL(firstPassArray), theKext)) {
_appendIfNewest(firstPassArray, theKext);
}
}
requiredFlags = toolArgs->requiredFlagsAll;
if (requiredFlags) {
requiredFlags |= kOSKextOSBundleRequiredRootFlag |
kOSKextOSBundleRequiredConsoleFlag |
kOSKextOSBundleRequiredDriverKitFlag;
}
count = CFArrayGetCount(toolArgs->namedKexts);
for (i = 0; i < count; i++) {
OSKextRef theKext = (OSKextRef)CFArrayGetValueAtIndex(
toolArgs->namedKexts, i);
if (!kextMatchesFilter(toolArgs, theKext, requiredFlags)) {
char kextPath[PATH_MAX];
if (!CFURLGetFileSystemRepresentation(OSKextGetURL(theKext),
false, (UInt8 *)kextPath, sizeof(kextPath)))
{
strlcpy(kextPath, "(unknown)", sizeof(kextPath));
}
if (toolArgs->prelinkedKernelPath) {
OSKextLog( NULL,
kOSKextLogStepLevel | kOSKextLogArchiveFlag,
"%s is not demanded by OSBundleRequired conditions.",
kextPath);
}
continue;
}
if (!CFArrayContainsValue(firstPassArray, RANGE_ALL(firstPassArray), theKext)) {
_appendIfNewest(firstPassArray, theKext);
}
}
}
CFArrayRemoveAllValues(kextArray);
count = CFArrayGetCount(firstPassArray);
if (count) {
if (callSecKeychainMDSInstall() != 0) {
goto finish;
}
earlyBoot = (isKextdRunning() == false);
OSKextIsInExcludeList(NULL, false); isInStrictExceptionList(NULL, NULL, false); isInExceptionList(NULL, NULL, false); for (i = count - 1; i >= 0; i--) {
char kextPath[PATH_MAX];
OSKextRef ownedKext = NULL;
OSKextRef theKext = (OSKextRef)CFArrayGetValueAtIndex(
firstPassArray, i);
if (!CFURLGetFileSystemRepresentation(OSKextGetURL(theKext),
false, (UInt8 *)kextPath, sizeof(kextPath)))
{
strlcpy(kextPath, "(unknown)", sizeof(kextPath));
}
if (!OSKextSupportsArchitecture(theKext, arch)) {
OSKextLog( NULL,
kOSKextLogStepLevel | kOSKextLogArchiveFlag,
"%s doesn't support architecture '%s'; skipping.", kextPath,
arch->name);
goto loop_continue;
}
if (!OSKextIsValid(theKext)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag |
kOSKextLogValidationFlag | kOSKextLogGeneralFlag,
"%s is not valid; omitting.", kextPath);
if (toolArgs->printTestResults) {
OSKextLogDiagnostics(theKext, kOSKextDiagnosticsFlagAll);
}
goto loop_continue;
}
if (toolArgs->authenticationOptions.requireSecureLocation) {
theKext = createStagedKext(theKext);
if (!theKext) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag |
kOSKextLogValidationFlag | kOSKextLogGeneralFlag,
"%s could not be staged properly; omitting.", kextPath);
if (toolArgs->printTestResults) {
OSKextLogDiagnostics(theKext, kOSKextDiagnosticsFlagAll);
}
goto loop_continue;
}
ownedKext = theKext;
}
if (OSKextIsInExcludeList(theKext, true)) {
addKextToAlertDict(&sExcludedKextAlertDict, theKext);
messageTraceExcludedKext(theKext);
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag |
kOSKextLogValidationFlag | kOSKextLogGeneralFlag,
"%s is in the exclude list; omitting.", kextPath);
if (toolArgs->printTestResults) {
OSKextLogDiagnostics(theKext, kOSKextDiagnosticsFlagAll);
}
goto loop_continue;
}
if (!OSKextIsAuthentic(theKext)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag |
kOSKextLogAuthenticationFlag | kOSKextLogGeneralFlag,
"%s does not authenticate; omitting.", kextPath);
if (toolArgs->printTestResults) {
OSKextLogDiagnostics(theKext, kOSKextDiagnosticsFlagAll);
}
goto loop_continue;
}
if (!OSKextResolveDependencies(theKext)) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogArchiveFlag |
kOSKextLogDependenciesFlag | kOSKextLogGeneralFlag,
"%s is missing dependencies (including anyway; "
"dependencies may be available from elsewhere)", kextPath);
if (toolArgs->printTestResults) {
OSKextLogDiagnostics(theKext, kOSKextDiagnosticsFlagAll);
}
goto loop_continue;
}
if (!CFArrayContainsValue(kextArray, RANGE_ALL(kextArray), theKext)) {
CFArrayAppendValue(kextArray, theKext);
}
loop_continue:
SAFE_RELEASE(ownedKext);
} }
#if HAVE_DANGERZONE
if (isRootVolURL(toolArgs->volumeRootURL)) {
CFArrayRef loadList = OSKextCopyLoadListForKexts(kextArray, false);
count = CFArrayGetCount(loadList);
for (i = count - 1; i >= 0; i--) {
OSKextRef theKext = (OSKextRef)CFArrayGetValueAtIndex(loadList, i);
dzRecordKextCacheAdd(theKext, true);
}
count = CFArrayGetCount(firstPassArray);
CFRange loadListRangeAll = RANGE_ALL(loadList);
for (i = count - 1; i >= 0; i--) {
OSKextRef theKext = (OSKextRef)CFArrayGetValueAtIndex(firstPassArray, i);
if (!CFArrayContainsValue(loadList, loadListRangeAll, theKext)) {
dzRecordKextCacheAdd(theKext, false);
}
}
}
#endif // HAVE_DANGERZONE
if (CFArrayGetCount(kextArray)) {
if (earlyBoot == false) {
recordKextLoadListForMT(kextArray, false);
}
}
result = EX_OK;
finish:
return result;
}
static void _appendIfNewest(CFMutableArrayRef theArray, OSKextRef theKext)
{
CFStringRef theBundleID; CFStringRef theBundleVersion; OSKextVersion theKextVersion = -1;
CFIndex myCount, i;
theBundleID = OSKextGetIdentifier(theKext);
theBundleVersion = OSKextGetValueForInfoDictionaryKey(
theKext,
kCFBundleVersionKey );
if (theBundleVersion == NULL) {
return;
}
theKextVersion = OSKextParseVersionCFString(theBundleVersion);
if (theKextVersion == -1) {
return;
}
myCount = CFArrayGetCount(theArray);
for (i = 0; i < myCount; i++) {
OSKextRef myKext; CFStringRef myBundleID; CFStringRef myBundleVersion; OSKextVersion myKextVersion = -1;
myKext = (OSKextRef) CFArrayGetValueAtIndex(theArray, i);
myBundleID = OSKextGetIdentifier(myKext);
if ( CFStringCompare(myBundleID, theBundleID, 0) == kCFCompareEqualTo ) {
myBundleVersion = OSKextGetValueForInfoDictionaryKey(
myKext,
kCFBundleVersionKey );
if (myBundleVersion == NULL) continue;
myKextVersion = OSKextParseVersionCFString(myBundleVersion);
if (myKextVersion > 0 && myKextVersion > theKextVersion ) {
OSKextLogCFString(NULL,
kOSKextLogDebugLevel | kOSKextLogArchiveFlag,
CFSTR("%s: found newer, skipping %@"),
__func__, theKext);
return;
}
if (myKextVersion > 0 && myKextVersion == theKextVersion ) {
OSKextLogCFString(NULL,
kOSKextLogDebugLevel | kOSKextLogArchiveFlag,
CFSTR("%s: found dup, skipping %@"),
__func__, theKext);
return;
}
if (myKextVersion > 0 && myKextVersion < theKextVersion ) {
OSKextLogCFString(NULL,
kOSKextLogDebugLevel | kOSKextLogArchiveFlag,
CFSTR("%s: found older, removing %@"),
__func__, myKext);
CFArrayRemoveValueAtIndex(theArray, i);
break;
}
}
}
CFArrayAppendValue(theArray, theKext);
return;
}
static Boolean isValidKextSigningTargetVolume(CFURLRef theVolRootURL)
{
Boolean myResult = true; char volRoot[PATH_MAX];
struct bootCaches *caches = NULL; CFDictionaryRef myDict = NULL; CFDictionaryRef postBootPathsDict = NULL;
if (theVolRootURL) {
if (!CFURLGetFileSystemRepresentation(theVolRootURL, true,
(UInt8*)volRoot, sizeof(volRoot))) {
OSKextLogStringError(NULL);
goto finish;
}
} else {
strlcpy(volRoot, "/", PATH_MAX);
}
caches = readBootCaches(volRoot, kBROptsNone);
if (!caches) goto finish;
myDict = caches->cacheinfo;
if (myDict) {
myResult = false;
postBootPathsDict = (CFDictionaryRef)
CFDictionaryGetValue(myDict, kBCPostBootKey);
if (postBootPathsDict &&
CFGetTypeID(postBootPathsDict) == CFDictionaryGetTypeID()) {
if (CFDictionaryContainsKey(postBootPathsDict, kBCKernelcacheV5Key)) {
myResult = true;
} else if (CFDictionaryContainsKey(postBootPathsDict, kBCKernelcacheV4Key)) {
myResult = true;
} else if (CFDictionaryContainsKey(postBootPathsDict, kBCKernelcacheV3Key)) {
myResult = true;
}
}
}
finish:
if (caches) destroyCaches(caches);
return(myResult);
}
static bool isValidPLKFile(KextcacheArgs *toolArgs)
{
bool myResult = false;
CFDataRef plkRef = NULL; CFDataRef uncompressed_plkRef = NULL; const UInt8 * machoHeader = NULL;
const NXArchInfo * archInfo = NULL;
void * prelinkInfoSect = NULL;
while (true) {
if (CFArrayGetCount(toolArgs->targetArchs) == 0) {
break;
}
archInfo = CFArrayGetValueAtIndex(toolArgs->targetArchs, 0);
if (archInfo == NULL) {
break;
}
plkRef = readMachOSliceForArchWith_fd(toolArgs->prelinkedKernel_fd,
archInfo,
TRUE);
if (plkRef == NULL) {
break;
}
if (MAGIC32(CFDataGetBytePtr(plkRef)) == OSSwapHostToBigInt32('comp')) {
uncompressed_plkRef = uncompressPrelinkedSlice(plkRef);
if (uncompressed_plkRef == NULL) {
break;
}
} else {
uncompressed_plkRef = CFRetain(plkRef);
}
machoHeader = CFDataGetBytePtr(uncompressed_plkRef);
if (ISMACHO64(MAGIC32(machoHeader))) {
prelinkInfoSect = (void *)
macho_get_section_by_name_64((struct mach_header_64 *)machoHeader,
kPrelinkInfoSegment,
kPrelinkInfoSection);
} else {
prelinkInfoSect = (void *)
macho_get_section_by_name((struct mach_header *)machoHeader,
kPrelinkInfoSegment,
kPrelinkInfoSection);
}
if (prelinkInfoSect == NULL) {
break;
}
myResult = true;
break;
}
SAFE_RELEASE(plkRef);
SAFE_RELEASE(uncompressed_plkRef);
return(myResult);
}
static bool isSystemKernelPath(KextcacheArgs *toolArgs)
{
char *kpath = "/System/Library/Kernels/kernel";
size_t kpath_len = 0;
struct bootCaches *bc = NULL;
char volRootPath[PATH_MAX] = { 0, };
if (CFURLGetFileSystemRepresentation(toolArgs->volumeRootURL, true,
(UInt8 *)volRootPath, PATH_MAX)) {
bc = readBootCaches(volRootPath, 0);
}
if (bc) {
kpath = &bc->kernelpath[0];
}
kpath_len = strnlen(kpath, PATH_MAX);
bool isvalid = (strncmp(kpath, toolArgs->kernelPath, kpath_len) == 0);
if (bc) {
destroyCaches(bc);
}
return isvalid;
}
static bool isSystemPLKPath(KextcacheArgs *toolArgs)
{
CFDictionaryRef bcDict = NULL;
CFDictionaryRef postBootPathsDict = NULL;
CFDictionaryRef kcDict = NULL;
CFStringRef tmpStr;
char plkpath[PATH_MAX] = { 0, };
size_t plkpath_len = 0;
bcDict = copyBootCachesDictForURL(toolArgs->volumeRootURL);
if (bcDict != NULL) {
postBootPathsDict = (CFDictionaryRef)CFDictionaryGetValue(bcDict, kBCPostBootKey);
if (postBootPathsDict &&
CFGetTypeID(postBootPathsDict) == CFDictionaryGetTypeID()) {
kcDict = (CFDictionaryRef)CFDictionaryGetValue(postBootPathsDict,
kBCKernelcacheV5Key);
if (!kcDict) {
kcDict = (CFDictionaryRef)CFDictionaryGetValue(postBootPathsDict,
kBCKernelcacheV4Key);
}
if (!kcDict) {
kcDict = (CFDictionaryRef)CFDictionaryGetValue(postBootPathsDict,
kBCKernelcacheV3Key);
}
}
}
if (!kcDict || CFGetTypeID(kcDict) != CFDictionaryGetTypeID()) {
goto use_default_path;
}
tmpStr = (CFStringRef)CFDictionaryGetValue(kcDict, kBCPathKey);
if (tmpStr == NULL || CFGetTypeID(tmpStr) != CFStringGetTypeID()) {
goto use_default_path;
}
if (!CFStringGetFileSystemRepresentation(tmpStr, plkpath, PATH_MAX)) {
goto use_default_path;
}
goto compare_paths;
use_default_path:
strlcpy(plkpath, _kOSKextTemporaryPrelinkedKernelsPath "/" _kOSKextPrelinkedKernelFileName, PATH_MAX);
compare_paths:
plkpath_len = strnlen(plkpath, PATH_MAX);
if (strncmp(plkpath, toolArgs->prelinkedKernelPath, plkpath_len) == 0) {
SAFE_RELEASE(bcDict);
return true;
}
SAFE_RELEASE(bcDict);
return false;
}
static bool isProtectedPLK(int prelinkedKernel_fd)
{
bool sip_enabled = csr_check(CSR_ALLOW_UNRESTRICTED_FS) != 0;
bool protected_fd = rootless_check_trusted_fd(prelinkedKernel_fd) == 0;
return sip_enabled && protected_fd;
}
static bool isProbablyProtectedPLK(KextcacheArgs *toolArgs)
{
bool sip_enabled = csr_check(CSR_ALLOW_UNRESTRICTED_FS) != 0;
if (toolArgs->prelinkedKernelPath == NULL) {
return false;
}
if (sip_enabled) {
bool file_exists = access(toolArgs->prelinkedKernelPath, F_OK) == 0;
if (file_exists) {
return rootless_check_trusted(toolArgs->prelinkedKernelPath) == 0;
} else {
char *parent_directory = dirname(toolArgs->prelinkedKernelPath);
if (!parent_directory) {
return false;
}
return rootless_check_trusted(parent_directory) == 0;
}
}
return false;
}
static bool isProtectedAction(KextcacheArgs *toolArgs)
{
Boolean targetingBootVol = false;
if (toolArgs->updateVolumeURL) {
targetingBootVol = isRootVolURL(toolArgs->updateVolumeURL);
}
return (isProbablyProtectedPLK(toolArgs) ||
toolArgs->updateSystemCaches ||
targetingBootVol);
}
static bool isSecureAuthentication(KextcacheArgs *toolArgs)
{
bool sip_enabled = csr_check(CSR_ALLOW_UNRESTRICTED_FS) != 0;
return (toolArgs->authenticationOptions.performFilesystemValidation &&
toolArgs->authenticationOptions.performSignatureValidation &&
toolArgs->authenticationOptions.requireSecureLocation &&
toolArgs->authenticationOptions.respectSystemPolicy) || !sip_enabled;
}
static Boolean wantsFastLibCompressionForTargetVolume(CFURLRef theVolRootURL)
{
Boolean myResult = false;
char volRoot[PATH_MAX];
struct bootCaches *caches = NULL; CFDictionaryRef myDict = NULL; CFDictionaryRef postBootPathsDict = NULL; CFDictionaryRef kernelCacheDict = NULL;
if (theVolRootURL) {
if (!CFURLGetFileSystemRepresentation(theVolRootURL, true,
(UInt8*)volRoot, sizeof(volRoot))) {
OSKextLogStringError(NULL);
goto finish;
}
} else {
strlcpy(volRoot, "/", PATH_MAX);
}
caches = readBootCaches(volRoot, kBROptsNone);
if (!caches) goto finish;
myDict = caches->cacheinfo;
if (myDict) {
postBootPathsDict = (CFDictionaryRef)
CFDictionaryGetValue(myDict, kBCPostBootKey);
if (postBootPathsDict &&
CFGetTypeID(postBootPathsDict) == CFDictionaryGetTypeID()) {
kernelCacheDict = (CFDictionaryRef)
CFDictionaryGetValue(postBootPathsDict, kBCKernelcacheV5Key);
if (!kernelCacheDict) {
kernelCacheDict = (CFDictionaryRef)
CFDictionaryGetValue(postBootPathsDict, kBCKernelcacheV4Key);
}
if (!kernelCacheDict) {
kernelCacheDict = (CFDictionaryRef)
CFDictionaryGetValue(postBootPathsDict, kBCKernelcacheV3Key);
}
if (kernelCacheDict &&
CFGetTypeID(kernelCacheDict) == CFDictionaryGetTypeID()) {
CFStringRef myTempStr;
myTempStr = (CFStringRef)
CFDictionaryGetValue(kernelCacheDict,
kBCPreferredCompressionKey);
if (myTempStr && CFGetTypeID(myTempStr) == CFStringGetTypeID()) {
if (CFStringCompare(myTempStr, CFSTR("lzvn"), 0) == kCFCompareEqualTo) {
myResult = true;
}
}
} } }
if (myResult && !supportsFastLibCompression()) {
myResult = false;
}
finish:
if (caches) destroyCaches(caches);
return(myResult);
}
Boolean
kextMatchesFilter(
KextcacheArgs * toolArgs,
OSKextRef theKext,
OSKextRequiredFlags requiredFlags)
{
Boolean result = false;
Boolean needLoadedKextInfo = toolArgs->needLoadedKextInfo &&
(OSKextGetArchitecture() == OSKextGetRunningKernelArchitecture());
if (needLoadedKextInfo) {
result = (requiredFlags && OSKextMatchesRequiredFlags(theKext, requiredFlags)) ||
CFArrayContainsValue(toolArgs->loadedKexts, RANGE_ALL(toolArgs->loadedKexts), theKext);
} else {
result = OSKextMatchesRequiredFlags(theKext, requiredFlags);
}
return result;
}
ExitStatus
createPrelinkedKernelArchs(
KextcacheArgs * toolArgs,
CFMutableArrayRef * prelinkArchsOut)
{
ExitStatus result = EX_OSERR;
CFMutableArrayRef kernelArchs = NULL; CFMutableArrayRef prelinkArchs = NULL; const NXArchInfo * targetArch = NULL; u_int i = 0;
result = readFatFileArchsWith_fd(toolArgs->kernel_fd, &kernelArchs);
if (result != EX_OK) {
goto finish;
}
prelinkArchs = CFArrayCreateMutableCopy(kCFAllocatorDefault,
0, toolArgs->targetArchs);
if (!prelinkArchs) {
OSKextLogMemError();
result = EX_OSERR;
goto finish;
}
for (i = 0; i < CFArrayGetCount(prelinkArchs); ++i) {
targetArch = CFArrayGetValueAtIndex(prelinkArchs, i);
if (!CFArrayContainsValue(kernelArchs,
RANGE_ALL(kernelArchs), targetArch))
{
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogArchiveFlag,
"Kernel file %s does not contain requested arch: %s",
toolArgs->kernelPath, targetArch->name);
CFArrayRemoveValueAtIndex(prelinkArchs, i);
i--;
continue;
}
}
*prelinkArchsOut = (CFMutableArrayRef) CFRetain(prelinkArchs);
result = EX_OK;
finish:
SAFE_RELEASE(kernelArchs);
SAFE_RELEASE(prelinkArchs);
return result;
}
ExitStatus
createExistingPrelinkedSlices(
KextcacheArgs * toolArgs,
CFMutableArrayRef * existingSlicesOut,
CFMutableArrayRef * existingArchsOut)
{
struct timeval existingFileTimes[2];
struct timeval prelinkFileTimes[2];
ExitStatus result = EX_SOFTWARE;
if (!toolArgs->needDefaultPrelinkedKernelInfo) {
result = EX_OK;
goto finish;
}
bzero(&existingFileTimes, sizeof(existingFileTimes));
bzero(&prelinkFileTimes, sizeof(prelinkFileTimes));
result = getFileDescriptorTimes(toolArgs->prelinkedKernel_fd,
existingFileTimes);
if (result != EX_OK) {
goto finish;
}
result = getExpectedPrelinkedKernelModTime(toolArgs,
prelinkFileTimes, NULL);
if (result != EX_OK) {
goto finish;
}
if (!timevalcmp(&existingFileTimes[1], &prelinkFileTimes[1], ==)) {
result = EX_SOFTWARE;
goto finish;
}
result = readMachOSlicesWith_fd(toolArgs->prelinkedKernel_fd,
existingSlicesOut,
existingArchsOut, NULL, NULL);
if (result != EX_OK) {
existingSlicesOut = NULL;
existingArchsOut = NULL;
goto finish;
}
result = EX_OK;
finish:
return result;
}
ExitStatus
createPrelinkedKernel(
KextcacheArgs * toolArgs)
{
ExitStatus result = EX_OSERR;
struct timeval prelinkFileTimes[2];
CFMutableArrayRef generatedArchs = NULL; CFMutableArrayRef generatedSymbols = NULL; CFMutableArrayRef existingArchs = NULL; CFMutableArrayRef existingSlices = NULL; CFMutableArrayRef prelinkArchs = NULL; CFMutableArrayRef prelinkSlices = NULL; CFDataRef prelinkSlice = NULL; CFDictionaryRef sliceSymbols = NULL; const NXArchInfo * targetArch = NULL; Boolean updateModTime = false;
u_int numArchs = 0;
u_int i = 0;
int j = 0;
int plk_result = 0;
ino_t plk_ino_t = 0;
dev_t plk_dev_t = 0;
size_t plk_size_t = 0;
ino_t kern_ino_t = 0;
dev_t kern_dev_t = 0;
bool created_plk = false;
char *plk_filename = NULL;
os_signpost_id_t spid = generate_signpost_id();
bool deferred_update = false;
os_signpost_interval_begin(get_signpost_log(), spid, SIGNPOST_KEXTCACHE_BUILD_PRELINKED_KERNEL);
bzero(&prelinkFileTimes, sizeof(prelinkFileTimes));
plk_filename = toolArgs->prelinkedKernelPath + strnlen(toolArgs->prelinkedKernelDirname, PATH_MAX);
while (*plk_filename == '/') plk_filename++;
os_signpost_event_emit(get_signpost_log(), spid, SIGNPOST_EVENT_PRELINKED_KERNEL_PATH,
"%s", toolArgs->prelinkedKernelPath);
toolArgs->prelinkedKernelDir_fd = open(toolArgs->prelinkedKernelDirname,
O_RDONLY | O_DIRECTORY);
if (toolArgs->prelinkedKernelDir_fd < 0) {
result = EX_NOPERM;
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Prelinked kernel directory '%s/' cannot be used",
toolArgs->prelinkedKernelDirname);
goto finish;
}
toolArgs->prelinkedKernel_fd = openat(toolArgs->prelinkedKernelDir_fd,
plk_filename, O_RDONLY | O_SYMLINK);
if (toolArgs->prelinkedKernel_fd != -1) {
plk_result = getFileDevAndInoAndSizeWith_fd(toolArgs->prelinkedKernel_fd, &plk_dev_t, &plk_ino_t, &plk_size_t);
if (plk_result == 0) {
if (plk_size_t != 0 && !isValidPLKFile(toolArgs)) {
plk_result = -1; }
}
}
if (toolArgs->prelinkedKernel_fd != -1 && plk_result == 0 && plk_size_t == 0) {
created_plk = true;
} else if (toolArgs->prelinkedKernel_fd == -1 && errno == ENOENT) {
toolArgs->prelinkedKernel_fd = openat(toolArgs->prelinkedKernelDir_fd,
plk_filename, O_RDWR | O_CREAT | O_EXCL);
if (toolArgs->prelinkedKernel_fd != -1) {
created_plk = true;
plk_result = getFileDevAndInoWith_fd(toolArgs->prelinkedKernel_fd, &plk_dev_t, &plk_ino_t);
}
}
if ((toolArgs->prelinkedKernel_fd == -1 && errno != ENOENT) ||
plk_result == -1) {
result = EX_NOPERM;
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Bad prelinkedkernel path '%s/%s' cannot be used",
toolArgs->prelinkedKernelDirname, plk_filename);
goto finish;
}
toolArgs->kernel_fd = open(toolArgs->kernelPath, O_RDONLY);
if (toolArgs->kernel_fd < 0 ||
getFileDevAndInoWith_fd(toolArgs->kernel_fd, &kern_dev_t, &kern_ino_t) != 0) {
result = EX_NOPERM;
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Bad kernel path '%s' cannot be used",
toolArgs->kernelPath);
goto finish;
}
if (isProtectedPLK(toolArgs->prelinkedKernel_fd)) {
OSKextLog( NULL,
kOSKextLogDebugLevel | kOSKextLogGeneralFlag,
"Creating SIP-protected prelinked kernel: %s",
toolArgs->prelinkedKernelPath);
if (toolArgs->skipAuthentication ||
!toolArgs->authenticationOptions.respectSystemPolicy ||
!toolArgs->authenticationOptions.requireSecureLocation) {
result = EX_NOPERM;
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Invalid to create a prelinked kernel without authentication at path '%s'",
toolArgs->prelinkedKernelPath);
goto finish;
}
if (!toolArgs->authenticationOptions.performSignatureValidation) {
result = EX_NOPERM;
OSKextLogCFString( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
CFSTR("Invalid to create a prelinked kernel without signature enforcement on volume: %@"),
toolArgs->volumeRootURL);
goto finish;
}
if (!isSystemPLKPath(toolArgs)) {
result = EX_NOPERM;
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Invalid path '%s': SIP-protected prelinked kernels must be in /System/Library/PrelinkedKernels",
toolArgs->prelinkedKernelPath);
goto finish;
}
if (rootless_check_trusted_fd(toolArgs->kernel_fd) != 0) {
result = EX_NOPERM;
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Untrusted kernel '%s' cannot be used to create a SIP-protected prelinked kernel",
toolArgs->kernelPath);
goto finish;
}
if (!isSystemKernelPath(toolArgs)) {
result = EX_NOPERM;
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Invalid kernel path '%s': only kernels from /System/Library/Kernels can be used when writing SIP-protected prelinked kernels",
toolArgs->kernelPath);
goto finish;
}
} else {
if (toolArgs->buildImmutableKernel) {
OSKextLog( NULL,
kOSKextLogWarningLevel | kOSKextLogGeneralFlag,
"WARNING: building immutablekernel in a non-SIP protected path '%s'",
toolArgs->prelinkedKernelPath);
}
OSKextLog( NULL,
kOSKextLogDebugLevel | kOSKextLogGeneralFlag,
"Creating unprotected prelinked kernel: %s",
toolArgs->prelinkedKernelPath);
}
#if !NO_BOOT_ROOT
result = takeVolumeForPath(toolArgs->prelinkedKernelPath);
if (result != EX_OK) {
goto finish;
}
#endif
result = createPrelinkedKernelArchs(toolArgs, &prelinkArchs);
if (result != EX_OK) {
goto finish;
}
numArchs = (u_int)CFArrayGetCount(prelinkArchs);
if (!toolArgs->symbolDirURL) {
result = createExistingPrelinkedSlices(toolArgs,
&existingSlices, &existingArchs);
if (result != EX_OK) {
SAFE_RELEASE_NULL(existingSlices);
SAFE_RELEASE_NULL(existingArchs);
}
}
prelinkSlices = CFArrayCreateMutable(kCFAllocatorDefault,
numArchs, &kCFTypeArrayCallBacks);
generatedSymbols = CFArrayCreateMutable(kCFAllocatorDefault,
numArchs, &kCFTypeArrayCallBacks);
generatedArchs = CFArrayCreateMutable(kCFAllocatorDefault,
numArchs, NULL);
if (!prelinkSlices || !generatedSymbols || !generatedArchs) {
OSKextLogMemError();
result = EX_OSERR;
goto finish;
}
for (i = 0; i < numArchs; i++) {
targetArch = CFArrayGetValueAtIndex(prelinkArchs, i);
SAFE_RELEASE_NULL(prelinkSlice);
SAFE_RELEASE_NULL(sliceSymbols);
if (existingArchs &&
targetArch != OSKextGetRunningKernelArchitecture())
{
j = (int)CFArrayGetFirstIndexOfValue(existingArchs,
RANGE_ALL(existingArchs), targetArch);
if (j != -1) {
prelinkSlice = CFArrayGetValueAtIndex(existingSlices, j);
CFArrayAppendValue(prelinkSlices, prelinkSlice);
prelinkSlice = NULL;
OSKextLog( NULL,
kOSKextLogDebugLevel | kOSKextLogArchiveFlag,
"Using existing prelinked slice for arch %s",
targetArch->name);
continue;
}
}
OSKextLog( NULL,
kOSKextLogDebugLevel | kOSKextLogArchiveFlag,
"Generating a new prelinked slice for arch %s",
targetArch->name);
result = createPrelinkedKernelForArch(toolArgs, &prelinkSlice,
&sliceSymbols, targetArch);
if (result != EX_OK) {
goto finish;
}
CFArrayAppendValue(prelinkSlices, prelinkSlice);
CFArrayAppendValue(generatedSymbols, sliceSymbols);
CFArrayAppendValue(generatedArchs, targetArch);
}
result = getExpectedPrelinkedKernelModTime(toolArgs,
prelinkFileTimes, &updateModTime);
if (result != EX_OK) {
goto finish;
}
if (isSameFileDevAndInoWith_fd(toolArgs->kernel_fd,
kern_dev_t,
kern_ino_t) == FALSE) {
OSKextLog(NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"File at path '%s' changed, cannot be used",
toolArgs->kernelPath);
result = EX_NOPERM;
goto finish;
}
#if 0
OSKextLogCFString(NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
CFSTR("%s: writing to %s"),
__func__, toolArgs->prelinkedKernelPath);
if (updateModTime) {
OSKextLogCFString(NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
CFSTR("%s: setting mod time to %ld"),
__func__, prelinkFileTimes[1].tv_sec);
}
#endif
result = writeFatFileWithValidation(toolArgs->prelinkedKernelPath,
TRUE,
plk_dev_t,
plk_ino_t,
prelinkSlices,
prelinkArchs,
PRELINK_KERNEL_PERMS,
(updateModTime) ? prelinkFileTimes : NULL);
if (result != EX_OK) {
goto finish;
}
created_plk = false;
if (toolArgs->symbolDirURL) {
result = writePrelinkedSymbols(toolArgs->symbolDirURL,
generatedSymbols, generatedArchs);
if (result != EX_OK) {
goto finish;
}
}
OSKextLog( NULL,
kOSKextLogGeneralFlag | kOSKextLogBasicLevel,
"Created prelinked kernel \"%s\"",
toolArgs->prelinkedKernelPath);
if (toolArgs->kernelPath) {
OSKextLog( NULL,
kOSKextLogGeneralFlag | kOSKextLogBasicLevel,
"Created prelinked kernel using \"%s\"",
toolArgs->kernelPath);
}
if (toolArgs->buildImmutableKernel) {
result = buildImmutableKernelcache(toolArgs, plk_filename);
if (result != EX_OK) {
goto finish;
}
}
removeStalePrelinkedKernels(toolArgs);
if (requiresDeferredUpdate(toolArgs->prelinkedKernelDirname)) {
deferred_update = true;
char mntname[MNAMELEN];
if (findmnt(plk_dev_t, mntname, false) == 0) {
if (strcmp(mntname, _kOSKextReadOnlyDataVolumePath) == 0) {
createDeferredUpdateScript();
} else {
char volRootPath[PATH_MAX] = {};
if (!CFURLGetFileSystemRepresentation(toolArgs->volumeRootURL,
true, (UInt8 *)volRootPath, sizeof(volRootPath))) {
OSKextLog( NULL,
kOSKextLogFileAccessFlag | kOSKextLogErrorLevel,
"Error getting volume root path.");
goto finish;
}
int copyres = copyKernelsInVolume(volRootPath);
if (copyres != EX_OK) {
OSKextLog( NULL,
kOSKextLogFileAccessFlag | kOSKextLogErrorLevel,
"Error copying prelinked kernel: %d",
copyres);
}
}
}
}
char *sptr;
sptr = strnstr(toolArgs->prelinkedKernelPath, _kOSKextPrelinkedKernelsPath, strlen(toolArgs->prelinkedKernelPath));
if (sptr && !deferred_update) {
long prefixSize = 0;
char tmpBuffer[PATH_MAX];
char plkRelPath[PATH_MAX];
prefixSize = sptr - toolArgs->prelinkedKernelPath;
if (prefixSize >= sizeof(tmpBuffer))
goto finish;
strlcpy(tmpBuffer, toolArgs->prelinkedKernelPath, prefixSize + 1);
PATHCAT(tmpBuffer, _kOSKextCachesRootFolder "/Startup/kernelcache", dontlink);
sptr = strnstr(toolArgs->prelinkedKernelPath, _kOSKextPrelinkedKernelFileName,
strlen(toolArgs->prelinkedKernelPath));
if (!sptr) {
goto dontlink;
}
PATHCPY(plkRelPath, kPLKDirSymlinkPrefix, dontlink);
PATHCAT(plkRelPath, sptr, dontlink);
if (sptr && strlen(sptr) > strlen(_kOSKextPrelinkedKernelFileName)) {
PATHCAT(tmpBuffer, sptr + strlen(_kOSKextPrelinkedKernelFileName), dontlink);
}
OSKextLog(NULL, kOSKextLogGeneralFlag | kOSKextLogBasicLevel,
"Symlink \"%s\" -> \"%s\"",
tmpBuffer, plkRelPath);
struct stat sb;
if (lstat(tmpBuffer, &sb) == 0) {
if (S_ISLNK(sb.st_mode)) {
unlink(tmpBuffer);
}
}
if (symlink(plkRelPath, tmpBuffer) < 0) {
if (errno != EEXIST) {
OSKextLog(NULL, kOSKextLogGeneralFlag | kOSKextLogWarningLevel,
"symlink(\"%s\", \"%s\") failed %d (%s)",
plkRelPath, tmpBuffer,
errno, strerror(errno));
}
}
goto finish;
dontlink:
OSKextLog(NULL, kOSKextLogGeneralFlag | kOSKextLogBasicLevel,
"Skipping com.apple.kext.caches symlink to: \"%s\"",
toolArgs->prelinkedKernelPath);
}
finish:
if (isKextdRunning() && isRootVolURL(toolArgs->volumeRootURL)) {
if (sExcludedKextAlertDict) {
postNoteAboutKexts(CFSTR("Excluded Kext Notification"),
sExcludedKextAlertDict);
}
}
if (result != EX_OK && created_plk &&
toolArgs->prelinkedKernel_fd != -1 &&
toolArgs->prelinkedKernelDir_fd != -1) {
if (unlinkat(toolArgs->prelinkedKernelDir_fd, plk_filename, 0) < 0) {
OSKextLog(NULL, kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
"Error removing (now invalid) prelinked kernel: %s/%s",
toolArgs->prelinkedKernelDirname, plk_filename);
}
}
if (toolArgs->prelinkedKernelDir_fd != -1) {
close(toolArgs->prelinkedKernelDir_fd);
toolArgs->prelinkedKernelDir_fd = -1;
}
if (toolArgs->prelinkedKernel_fd != -1) {
close(toolArgs->prelinkedKernel_fd);
toolArgs->prelinkedKernel_fd = -1;
}
if (toolArgs->kernel_fd != -1) {
close(toolArgs->kernel_fd);
toolArgs->kernel_fd = -1;
}
SAFE_RELEASE(generatedArchs);
SAFE_RELEASE(generatedSymbols);
SAFE_RELEASE(existingArchs);
SAFE_RELEASE(existingSlices);
SAFE_RELEASE(prelinkArchs);
SAFE_RELEASE(prelinkSlices);
SAFE_RELEASE(prelinkSlice);
SAFE_RELEASE(sliceSymbols);
#if !NO_BOOT_ROOT
putVolumeForPath(toolArgs->prelinkedKernelPath, result);
#endif
os_signpost_event_emit(get_signpost_log(), spid, SIGNPOST_EVENT_RESULT, "%d", result);
os_signpost_interval_end(get_signpost_log(), spid, SIGNPOST_KEXTCACHE_BUILD_PRELINKED_KERNEL);
return result;
}
static Boolean isRootVolURL(CFURLRef theURL)
{
Boolean result = false;
char volRootBuf[PATH_MAX];
char realPathBuf[PATH_MAX];
if (theURL == NULL) {
result = true;
goto finish;
}
volRootBuf[0] = 0x00;
if (CFURLGetFileSystemRepresentation(theURL,
true,
(UInt8 *)volRootBuf,
sizeof(volRootBuf)) == false) {
volRootBuf[0] = 0x00;
}
if (volRootBuf[0] == 0x00) {
result = true;
} else {
if (realpath(volRootBuf, realPathBuf)) {
if (strlen(realPathBuf) == 1 && realPathBuf[0] == '/') {
result = true;
}
}
}
finish:
return(result);
}
ExitStatus createPrelinkedKernelForArch(
KextcacheArgs * toolArgs,
CFDataRef * prelinkedKernelOut,
CFDictionaryRef * prelinkedSymbolsOut,
const NXArchInfo * archInfo)
{
ExitStatus result = EX_OSERR;
CFMutableArrayRef prelinkKexts = NULL;
CFDataRef kernelImage = NULL;
CFDataRef prelinkedKernel = NULL;
uint32_t flags = 0;
Boolean fatalOut = false;
Boolean kernelSupportsKASLR = false;
macho_seek_result machoResult;
const UInt8 * kernelStart;
const UInt8 * kernelEnd;
kernelImage = readMachOSliceForArchWith_fd(toolArgs->kernel_fd,
archInfo,
TRUE);
if (!kernelImage) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag | kOSKextLogFileAccessFlag,
"Failed to read kernel file.");
goto finish;
}
OSKextSetExecutableSuffix(NULL, toolArgs->kernelPath);
if (toolArgs->targetForKextVariants) {
OSKextSetTargetString(toolArgs->targetForKextVariants);
}
if (!OSKextSetArchitecture(archInfo)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't set architecture %s to create prelinked kernel.",
archInfo->name);
result = EX_OSERR;
goto finish;
}
prelinkKexts = CFArrayCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeArrayCallBacks);
if (!prelinkKexts) {
OSKextLogMemError();
result = EX_OSERR;
goto finish;
}
result = filterKextsForCache(toolArgs, prelinkKexts,
archInfo, &fatalOut);
if (result != EX_OK || fatalOut) {
goto finish;
}
if (isRootVolURL(toolArgs->volumeRootURL)) {
for (int i = 0; i < CFArrayGetCount(prelinkKexts); i++) {
OSKextRef theKext = (OSKextRef)CFArrayGetValueAtIndex(prelinkKexts, i);
if (needsGPUBundlesStaged(theKext)) {
stageGPUBundles(theKext);
}
}
}
result = EX_OSERR;
if (!CFArrayGetCount(prelinkKexts)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"No kexts found for architecture %s.",
archInfo->name);
goto finish;
}
flags |= (toolArgs->noLinkFailures) ? kOSKextKernelcacheNeedAllFlag : 0;
flags |= (toolArgs->printTestResults) ? kOSKextKernelcachePrintDiagnosticsFlag : 0;
flags |= (toolArgs->includeAllPersonalities) ? kOSKextKernelcacheIncludeAllPersonalitiesFlag : 0;
flags |= (toolArgs->stripSymbols) ? kOSKextKernelcacheStripSymbolsFlag : 0;
kernelStart = CFDataGetBytePtr(kernelImage);
kernelEnd = kernelStart + CFDataGetLength(kernelImage) - 1;
machoResult = macho_find_dysymtab(kernelStart, kernelEnd, NULL);
kernelSupportsKASLR = (machoResult == macho_seek_result_found);
if (kernelSupportsKASLR) {
flags |= kOSKextKernelcacheKASLRFlag;
}
prelinkedKernel = OSKextCreatePrelinkedKernel(kernelImage, prelinkKexts,
toolArgs->volumeRootURL, flags, prelinkedSymbolsOut);
if (!prelinkedKernel) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Failed to generate prelinked kernel.");
result = EX_OSERR;
goto finish;
}
if (toolArgs->compress) {
Boolean wantsFastLib = wantsFastLibCompressionForTargetVolume(toolArgs->volumeRootURL);
uint32_t compressionType = wantsFastLib ? COMP_TYPE_FASTLIB : COMP_TYPE_LZSS;
*prelinkedKernelOut = compressPrelinkedSlice(compressionType,
prelinkedKernel,
kernelSupportsKASLR);
} else {
*prelinkedKernelOut = CFRetain(prelinkedKernel);
}
if (!*prelinkedKernelOut) {
goto finish;
}
result = EX_OK;
finish:
SAFE_RELEASE(kernelImage);
SAFE_RELEASE(prelinkKexts);
SAFE_RELEASE(prelinkedKernel);
return result;
}
ExitStatus
getExpectedPrelinkedKernelModTime(
KextcacheArgs * toolArgs,
struct timeval cacheFileTimes[2],
Boolean * updateModTimeOut)
{
struct timeval kextTimes[2];
struct timeval kernelTimes[2];
ExitStatus result = EX_SOFTWARE;
Boolean updateModTime = false;
if (toolArgs->extensionsDirTimes[1].tv_sec == 0 ||
toolArgs->kernelTimes[1].tv_sec == 0) {
result = EX_OK;
goto finish;
}
result = getLatestTimesFromCFURLArray(toolArgs->repositoryURLs,
kextTimes);
if (result != EX_OK) {
goto finish;
}
kextTimes[1].tv_sec++;
result = getFileDescriptorModTimePlusOne(toolArgs->kernel_fd,
&toolArgs->kernelTimes[1],
kernelTimes);
if (result != EX_OK) {
goto finish;
}
#if 0
OSKextLogCFString(NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
CFSTR("%s: kernelPath %s"),
__func__, toolArgs->kernelPath);
OSKextLogCFString(NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
CFSTR("%s: %ld <- latest kext mod time"),
__func__, kextTimes[1].tv_sec);
OSKextLogCFString(NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
CFSTR("%s: %ld <- latest kernels mod time"),
__func__, kernelTimes[1].tv_sec);
#endif
cacheFileTimes[0].tv_sec = kextTimes[0].tv_sec; cacheFileTimes[0].tv_usec = kextTimes[0].tv_usec;
cacheFileTimes[1].tv_sec = kextTimes[1].tv_sec; cacheFileTimes[1].tv_usec = kextTimes[1].tv_usec;
if (timercmp(&kernelTimes[1], &kextTimes[1], >)) {
cacheFileTimes[0].tv_sec = kernelTimes[0].tv_sec; cacheFileTimes[0].tv_usec = kernelTimes[0].tv_usec;
cacheFileTimes[1].tv_sec = kernelTimes[1].tv_sec; cacheFileTimes[1].tv_usec = kernelTimes[1].tv_usec;
}
#if 0
OSKextLogCFString(NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
CFSTR("%s: %ld <- using this mod time"),
__func__, cacheFileTimes[1].tv_sec);
#endif
updateModTime = true;
result = EX_OK;
finish:
if (updateModTimeOut) *updateModTimeOut = updateModTime;
return result;
}
ExitStatus
compressPrelinkedKernel(
CFURLRef volumeRootURL,
const char * prelinkPath,
Boolean compress)
{
ExitStatus result = EX_SOFTWARE;
struct timeval prelinkedKernelTimes[2];
CFMutableArrayRef prelinkedSlices = NULL; CFMutableArrayRef prelinkedArchs = NULL; CFDataRef prelinkedSlice = NULL; const NXArchInfo * archInfo = NULL; const u_char * sliceBytes = NULL; mode_t fileMode = 0;
int i = 0;
result = readMachOSlices(prelinkPath, &prelinkedSlices,
&prelinkedArchs, &fileMode, prelinkedKernelTimes);
if (result != EX_OK) {
goto finish;
}
for (i = 0; i < CFArrayGetCount(prelinkedSlices); ++i) {
SAFE_RELEASE_NULL(prelinkedSlice);
prelinkedSlice = CFArrayGetValueAtIndex(prelinkedSlices, i);
if (compress) {
const PrelinkedKernelHeader *header = (const PrelinkedKernelHeader *)
CFDataGetBytePtr(prelinkedSlice);
Boolean wantsFastLib = wantsFastLibCompressionForTargetVolume(volumeRootURL);
uint32_t compressionType = wantsFastLib ? COMP_TYPE_FASTLIB : COMP_TYPE_LZSS;
prelinkedSlice = compressPrelinkedSlice(compressionType,
prelinkedSlice,
(OSSwapHostToBigInt32(header->prelinkVersion) == 1));
if (!prelinkedSlice) {
result = EX_DATAERR;
goto finish;
}
} else {
prelinkedSlice = uncompressPrelinkedSlice(prelinkedSlice);
if (!prelinkedSlice) {
result = EX_DATAERR;
goto finish;
}
}
CFArraySetValueAtIndex(prelinkedSlices, i, prelinkedSlice);
}
SAFE_RELEASE_NULL(prelinkedSlice);
if (!prelinkedArchs && CFArrayGetCount(prelinkedSlices) == 1) {
if (!createCFMutableArray(&prelinkedArchs, NULL)) {
OSKextLogMemError();
result = EX_OSERR;
goto finish;
}
sliceBytes = CFDataGetBytePtr(
CFArrayGetValueAtIndex(prelinkedSlices, 0));
archInfo = getThinHeaderPageArch(sliceBytes);
if (archInfo) {
CFArrayAppendValue(prelinkedArchs, archInfo);
} else {
SAFE_RELEASE_NULL(prelinkedArchs);
}
}
if (!prelinkedArchs) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Couldn't determine prelinked kernel's architecture");
result = EX_SOFTWARE;
goto finish;
}
result = writeFatFile(prelinkPath, prelinkedSlices,
prelinkedArchs, fileMode, prelinkedKernelTimes);
if (result != EX_OK) {
goto finish;
}
result = EX_OK;
finish:
SAFE_RELEASE(prelinkedSlices);
SAFE_RELEASE(prelinkedArchs);
SAFE_RELEASE(prelinkedSlice);
return result;
}
static ExitStatus
buildImmutableKernelcache(KextcacheArgs *toolArgs, const char *plk_filename)
{
ExitStatus result = EX_OK;
int dirfd = toolArgs->prelinkedKernelDir_fd;
bool have_backup = false;
char imk_filename[256] = {};
char imk_backupname[256] = {};
if (dirfd < 0) {
OSKextLog( NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
"Prelinked kernel path \"%s\" no longer seems to be open.",
toolArgs->prelinkedKernelDirname);
result = EX_OSERR;
goto finish;
}
if (!translatePrelinkedToImmutablePath(plk_filename, imk_filename, sizeof(imk_filename))) {
result = EX_OSERR;
goto finish;
}
OSKextLog( NULL,
kOSKextLogGeneralFlag | kOSKextLogBasicLevel,
"Rebuilding immutable kernel: \"%s/%s\"",
toolArgs->prelinkedKernelDirname, imk_filename);
if (strlcpy(imk_backupname, imk_filename, sizeof(imk_backupname)) > sizeof(imk_backupname) ||
strlcat(imk_backupname, ".bak", sizeof(imk_backupname)) > sizeof(imk_backupname)) {
result = EX_SOFTWARE;
goto finish;
}
if (renameat(dirfd, imk_filename, dirfd, imk_backupname) != 0 && errno != ENOENT) {
OSKextLog( NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
"ERROR: failed to backup immutable kernel to: \"%s/%s\"",
toolArgs->prelinkedKernelDirname, imk_backupname);
result = EX_OSERR;
goto finish;
}
have_backup = true;
if (linkat(dirfd, plk_filename, dirfd, imk_filename, 0) != 0) {
OSKextLog( NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
"ERROR: link failed: \"%s/%s\" -> \"%s/%s\"",
toolArgs->prelinkedKernelDirname, imk_filename,
toolArgs->prelinkedKernelDirname, plk_filename);
result = EX_OSERR;
goto finish;
}
{
char root_volume[PATH_MAX] ={};
char imk_path[PATH_MAX] = {};
char *personalize_argv[] = {
kPersonalizeMacOSTool,
"--volume",
root_volume,
"--kernelCache",
imk_path,
NULL
};
if (toolArgs->volumeRootURL) {
if (CFURLGetFileSystemRepresentation(toolArgs->volumeRootURL, true,
(UInt8 *)root_volume,
sizeof(root_volume)) == false) {
result = EX_SOFTWARE;
goto finish;
}
} else {
root_volume[0] = '/';
root_volume[1] = 0;
}
if (strlcpy(imk_path, toolArgs->prelinkedKernelDirname, sizeof(imk_path)) > sizeof(imk_path) ||
strlcat(imk_path, "/", sizeof(imk_path)) > sizeof(imk_path) ||
strlcat(imk_path, imk_filename, sizeof(imk_path)) > sizeof(imk_path)) {
result = EX_SOFTWARE;
goto finish;
}
int rval = fork_program(kPersonalizeMacOSTool, personalize_argv, true );
if (rval != 0) {
OSKextLog( NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
"ERROR(%d): personalization failed for : \"%s/%s\"",
rval, toolArgs->prelinkedKernelDirname, imk_filename);
result = EX_OSERR;
goto finish;
}
}
finish:
if (have_backup && result != EX_OK) {
renameat(dirfd, imk_backupname, dirfd, imk_filename);
}
if (result != EX_OK) {
OSKextLog( NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
"ERROR(%d): building/personalizing the immutable kernel: \"%s/%s\"",
result, toolArgs->prelinkedKernelDirname, imk_filename);
}
return result;
}
#pragma mark Boot!=Root
void usage(UsageLevel usageLevel)
{
fprintf(stderr,
"usage: %1$s -prelinked-kernel <filename> [options] [--] [kext or directory]\n"
" %1$s -system-prelinked-kernel\n"
" %1$s [options] -prelinked-kernel\n"
#if !NO_BOOT_ROOT
" %1$s -invalidate <volume> \n"
" %1$s -update-volume <volume> [options]\n"
#endif
" %1$s -system-caches [options]\n"
"\n",
progname);
if (usageLevel == kUsageLevelBrief) {
fprintf(stderr, "use %s -%s for an explanation of each option\n",
progname, kOptNameHelp);
}
if (usageLevel == kUsageLevelBrief) {
return;
}
fprintf(stderr, "-%s [<filename>] (-%c):\n"
" create/update prelinked kernel (must be last if no filename given)\n",
kOptNamePrelinkedKernel, kOptPrelinkedKernel);
fprintf(stderr, "-%s:\n"
" create/update system prelinked kernel\n",
kOptNameSystemPrelinkedKernel);
#if !NO_BOOT_ROOT
fprintf(stderr, "-%s <volume> (-%c): invalidate system kext caches for <volume>\n",
kOptNameInvalidate, kOptInvalidate);
fprintf(stderr, "-%s <volume> (-%c): update system kext caches for <volume>\n",
kOptNameUpdate, kOptUpdate);
fprintf(stderr, "-%s called us, modify behavior appropriately\n",
kOptNameInstaller);
fprintf(stderr, "-%s skips updating any helper partitions even if they appear out of date\n",
kOptNameCachesOnly);
#endif
#if 0
fprintf(stderr, "-%c <volume>:\n"
" check system kext caches for <volume> (nonzero exit if out of date)\n",
kOptCheckUpdate);
#endif
fprintf(stderr, "-%s: update system kext info caches for the root volume\n",
kOptNameSystemCaches);
fprintf(stderr, "\n");
fprintf(stderr,
"kext or directory: Consider kext or all kexts in directory for inclusion\n");
fprintf(stderr, "-%s <bundle_id> (-%c):\n"
" include the kext whose CFBundleIdentifier is <bundle_id>\n",
kOptNameBundleIdentifier, kOptBundleIdentifier);
fprintf(stderr, "-%s <volume>:\n"
" Save kext paths in a prelinked kernel "
" relative to <volume>\n",
kOptNameVolumeRoot);
fprintf(stderr, "-%s <kernel_filename> (-%c): Use kernel_filename for a prelinked kernel\n",
kOptNameKernel, kOptKernel);
fprintf(stderr, "-%s (-%c): Include all kexts ever loaded in prelinked kernel\n",
kOptNameAllLoaded, kOptAllLoaded);
#if !NO_BOOT_ROOT
fprintf(stderr, "-%s (-%c): Update volumes even if they look up to date\n",
kOptNameForce, kOptForce);
fprintf(stderr, "\n");
#endif
fprintf(stderr, "-%s <archname>:\n"
" include architecture <archname> in created cache(s)\n",
kOptNameArch);
fprintf(stderr, "-%c: run at low priority\n",
kOptLowPriorityFork);
fprintf(stderr, "\n");
fprintf(stderr, "-%s (-%c): 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):\n"
" print diagnostics for kexts with problems\n",
kOptNameTests, kOptTests);
fprintf(stderr, "-%s (-%c): don't authenticate kexts (for use during development)\n",
kOptNameNoAuthentication, kOptNoAuthentication);
fprintf(stderr, "\n");
fprintf(stderr, "-%s (-%c): print this message and exit\n",
kOptNameHelp, kOptHelp);
if (access(kPersonalizeMacOSTool, R_OK|X_OK) == 0) {
fprintf(stderr, "-%s (-%c): rebuild and personalize an immutablekernel (hardlinked to the new prelinkedkernel)\n",
kOptNameBuildImmutable, kOptBuildImmutable);
}
return;
}
#include "safecalls.h"
static void removeStalePrelinkedKernels(KextcacheArgs * toolArgs)
{
int my_fd;
struct stat statBuf;
char * tmpPath = NULL; char * volRootPath = NULL; char * suffixPtr = NULL; CFURLRef myURL = NULL; CFURLEnumeratorRef myEnumerator = NULL; CFURLRef enumURL = NULL; CFStringRef tmpCFString = NULL; CFArrayRef resultArray = NULL;
while (statPath(kAppleInternalPath, &statBuf) == EX_OK) {
tmpPath = malloc(PATH_MAX);
volRootPath = malloc(PATH_MAX);
if (tmpPath == NULL || volRootPath == NULL) {
break;
};
volRootPath[0] = 0x00;
if (toolArgs->volumeRootURL) {
if (CFURLGetFileSystemRepresentation(toolArgs->volumeRootURL,
true,
(UInt8 *)volRootPath,
PATH_MAX) == false) {
volRootPath[0] = 0x00;
}
}
if (strlen(volRootPath) > 1) {
if (strlcpy(tmpPath, volRootPath, PATH_MAX) >= PATH_MAX) break;
if (strlcat(tmpPath, _kOSKextPrelinkedKernelsPath, PATH_MAX) >= PATH_MAX) break;
}
else {
if (strlcpy(tmpPath, _kOSKextPrelinkedKernelsPath, PATH_MAX) >= PATH_MAX) break;
}
myURL = CFURLCreateFromFileSystemRepresentation(NULL,
(const UInt8 *)tmpPath,
strlen(tmpPath),
true);
if (myURL) {
myEnumerator = CFURLEnumeratorCreateForDirectoryURL(
NULL,
myURL,
kCFURLEnumeratorDefaultBehavior,
NULL);
}
while (myEnumerator &&
CFURLEnumeratorGetNextURL(myEnumerator,
&enumURL,
NULL) == kCFURLEnumeratorSuccess) {
SAFE_RELEASE_NULL(tmpCFString);
SAFE_FREE_NULL(suffixPtr);
tmpCFString = CFURLCopyLastPathComponent(enumURL);
if (tmpCFString == NULL) continue;
if (kCFCompareEqualTo == CFStringCompare(tmpCFString,
CFSTR("prelinkedkernel"),
kCFCompareAnchored)) {
continue;
}
if (CFStringHasPrefix(tmpCFString,
CFSTR("prelinkedkernel.")) == false) {
continue;
}
SAFE_RELEASE_NULL(resultArray);
resultArray = CFStringCreateArrayWithFindResults(
NULL,
tmpCFString,
CFSTR("."),
CFRangeMake(0, CFStringGetLength(tmpCFString)),
0);
if (resultArray && CFArrayGetCount(resultArray) > 1) {
continue;
}
SAFE_RELEASE(tmpCFString);
tmpCFString = CFURLCopyPathExtension(enumURL);
if (tmpCFString == NULL) continue;
suffixPtr = createUTF8CStringForCFString(tmpCFString);
if (suffixPtr == NULL) continue;
if (strlen(volRootPath) > 1) {
if (strlcpy(tmpPath, volRootPath, PATH_MAX) >= PATH_MAX) continue;
if (strlcat(tmpPath, kDefaultKernelPath, PATH_MAX) >= PATH_MAX) continue;
}
else {
if (strlcpy(tmpPath, kDefaultKernelPath, PATH_MAX) >= PATH_MAX) continue;
}
if (strlcat(tmpPath, ".", PATH_MAX) >= PATH_MAX) continue;
if (strlcat(tmpPath, suffixPtr, PATH_MAX) >= PATH_MAX) continue;
if (statPath(tmpPath, &statBuf) == EX_OK) {
continue;
}
if (CFURLGetFileSystemRepresentation(enumURL,
true,
(UInt8 *)tmpPath,
PATH_MAX) == false) {
continue;
}
my_fd = open(tmpPath, O_RDONLY);
if (my_fd != -1) {
if (sunlink(my_fd, tmpPath) == 0) {
OSKextLogCFString(NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag | kOSKextLogFileAccessFlag,
CFSTR("stale prelinked kernel, removing '%s'"),
tmpPath);
}
else {
OSKextLogCFString(NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
CFSTR("%s: sunlink failed for '%s' "),
__func__, tmpPath);
}
close(my_fd);
}
}
SAFE_RELEASE_NULL(myURL);
SAFE_RELEASE_NULL(myEnumerator);
break;
}
SAFE_FREE(tmpPath);
SAFE_FREE(volRootPath);
SAFE_FREE(suffixPtr);
SAFE_RELEASE(myURL);
SAFE_RELEASE(myEnumerator);
SAFE_RELEASE(tmpCFString);
SAFE_RELEASE(resultArray);
return;
}