/* * Copyright (c) 2007 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * FILE: bootcaches.c * AUTH: Soren Spies (sspies) * DATE: "spring" 2006 * DESC: routines for bootcache data * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for UUID fetching #include // path -> DADisk #include #include #include "fork_program.h" #include "bootcaches.h" // includes CF #include #include // bad! don't use kextd files in shared source #include "kextd_globals.h" #include "safecalls.h" #include "kext_tools_util.h" static MkextCRCResult getMkextCRC(const char * file_path, uint32_t * crc_ptr); // X these could take a label/action as their third parameter #define pathcpy(dst, src) do { \ if (strlcpy(dst, src, PATH_MAX) >= PATH_MAX) goto finish; \ } while(0) #define pathcat(dst, src) do { \ if (strlcat(dst, src, PATH_MAX) >= PATH_MAX) goto finish; \ } while(0) /****************************************************************************** * destroyCaches cleans up a bootCaches structure ******************************************************************************/ void destroyCaches(struct bootCaches *caches) { if (caches->cachefd != -1) close(caches->cachefd); if (caches->cacheinfo) CFRelease(caches->cacheinfo); if (caches->miscpaths) free(caches->miscpaths); // free all strings if (caches->rpspaths) free(caches->rpspaths); free(caches); } /****************************************************************************** * readCaches checks for and reads bootcaches.plist ******************************************************************************/ // used for turning /foo/bar into :foo:bar for kTSCacheDir entries (see awk(1)) static void gsub(char old, char new, char *s) { char *p; while((p = s++) && *p) if (*p == old) *p = new; } // fillCachedPath not currently used beyond this module, but it is in the header int fillCachedPath(cachedPath *cpath, char *uuidchars, char *relpath) { int rval = ELAST + 1; if (strlcat(cpath->tspath, kTSCacheDir, PATH_MAX) >= PATH_MAX) goto finish; pathcat(cpath->tspath, uuidchars); pathcat(cpath->tspath, "/"); // now append the actual path and stamp name if (strlcat(cpath->rpath, relpath, PATH_MAX) >= PATH_MAX) goto finish; gsub('/', ':', relpath); if (strlcat(cpath->tspath, relpath, PATH_MAX) >= PATH_MAX) goto finish; rval = 0; finish: return rval; } // wrap fillCachedPath() with the local idiom #define str2cachedPath(cpath, caches, relstr) \ do { \ char relpath[PATH_MAX]; \ \ if (!CFStringGetFileSystemRepresentation(relstr, relpath, PATH_MAX)) \ goto finish; \ if (fillCachedPath(cpath, caches->uuid_str, relpath)) goto finish; \ } while(0) // parse bootcaches.plist and dadisk dictionaries into passed struct // caller populates fields it needed to load the plist // and properly frees the structure if we fail static int finishParse(struct bootCaches *caches, CFDictionaryRef bcDict, CFDictionaryRef ddesc, char **errmsg) { int rval = ELAST + 1; CFDictionaryRef dict; // don't release CFIndex keyCount; // track whether we've handled all keys CFStringRef str; // used to point to objects owned by others CFStringRef createdStr = NULL; CFUUIDRef uuid; *errmsg = "error getting disk metadata"; // volume UUID, name, bsdname if (!(uuid = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumeUUIDKey))) goto finish; if (!(createdStr = CFUUIDCreateString(nil, uuid))) goto finish; if (!CFStringGetFileSystemRepresentation(createdStr,caches->uuid_str,NCHARSUUID)) goto finish; if (!(str = CFDictionaryGetValue(ddesc, kDADiskDescriptionVolumeNameKey))) goto finish; if (!CFStringGetFileSystemRepresentation(str, caches->volname, NAME_MAX)) goto finish; // bsdname still needed for bless if (!(str = CFDictionaryGetValue(ddesc, kDADiskDescriptionMediaBSDNameKey))) goto finish; if (!CFStringGetFileSystemRepresentation(str, caches->bsdname, NAME_MAX)) goto finish; *errmsg = "invalid bootcaches.plist data"; // covers the rest keyCount = CFDictionaryGetCount(bcDict); // start with the top // process keys for paths read "before the booter" dict = (CFDictionaryRef)CFDictionaryGetValue(bcDict, kBCPreBootKey); if (dict) { CFArrayRef apaths; CFIndex miscindex = 0; if (CFGetTypeID(dict) != CFDictionaryGetTypeID()) goto finish; // only "Additional Paths" can contain > 1 path caches->nmisc = CFDictionaryGetCount(dict); // start with 1 path/key keyCount += CFDictionaryGetCount(dict); // look at variable-sized member first -> right size for miscpaths apaths = (CFArrayRef)CFDictionaryGetValue(dict, kBCAdditionalPathsKey); if (apaths) { CFIndex acount; if (CFArrayGetTypeID() != CFGetTypeID(apaths)) goto finish; acount = CFArrayGetCount(apaths); // total "misc" paths = # of keyed paths + # additional paths caches->nmisc += acount - 1; // kBCAdditionalPathsKey not a path if (caches->nmisc > INT_MAX/sizeof(*caches->miscpaths)) goto finish; caches->miscpaths = (cachedPath*)calloc(caches->nmisc, sizeof(*caches->miscpaths)); if (!caches->miscpaths) goto finish; for (/*miscindex = 0 (above)*/; miscindex < acount; miscindex++) { str = CFArrayGetValueAtIndex(apaths, miscindex); if (CFGetTypeID(str) != CFStringGetTypeID()) goto finish; str2cachedPath(&caches->miscpaths[miscindex], caches, str); // M } keyCount--; // AdditionalPaths sub-key } else { // allocate enough for the top-level keys (nothing variable-sized) if (caches->nmisc > INT_MAX/sizeof(*caches->miscpaths)) goto finish; caches->miscpaths = calloc(caches->nmisc, sizeof(cachedPath)); if (!caches->miscpaths) goto finish; } str = (CFStringRef)CFDictionaryGetValue(dict, kBCLabelKey); if (str) { if (CFGetTypeID(str) != CFStringGetTypeID()) goto finish; str2cachedPath(&caches->miscpaths[miscindex], caches, str); // macro caches->label = &caches->miscpaths[miscindex]; miscindex++; // get ready for the next guy keyCount--; // DiskLabel is dealt with } // add new keys here keyCount--; // preboot dict } // process booter keys dict = (CFDictionaryRef)CFDictionaryGetValue(bcDict, kBCBootersKey); if (dict) { if (CFGetTypeID(dict) != CFDictionaryGetTypeID()) goto finish; keyCount += CFDictionaryGetCount(dict); str = (CFStringRef)CFDictionaryGetValue(dict, kBCEFIBooterKey); if (str) { if (CFGetTypeID(str) != CFStringGetTypeID()) goto finish; str2cachedPath(&caches->efibooter, caches, str); // macro keyCount--; // EFIBooter is dealt with } str = (CFStringRef)CFDictionaryGetValue(dict, kBCOFBooterKey); if (str) { if (CFGetTypeID(str) != CFStringGetTypeID()) goto finish; str2cachedPath(&caches->ofbooter, caches, str); // macro keyCount--; // BootX, check } // add new booters here keyCount--; // booters dict } dict = (CFDictionaryRef)CFDictionaryGetValue(bcDict, kBCPostBootKey); if (dict) { CFDictionaryRef mkDict; CFArrayRef apaths; CFIndex rpsindex = 0; if (CFGetTypeID(dict) != CFDictionaryGetTypeID()) goto finish; keyCount += CFDictionaryGetCount(dict); caches->nrps = CFDictionaryGetCount(dict); // >= 1 path / key // variable-sized member first apaths = (CFArrayRef)CFDictionaryGetValue(dict, kBCAdditionalPathsKey); if (apaths) { CFIndex acount; if (CFArrayGetTypeID() != CFGetTypeID(apaths)) goto finish; acount = CFArrayGetCount(apaths); // total rps paths = # of keyed paths + # additional paths caches->nrps += acount - 1; // replace array w/contents in nrps if (caches->nrps > INT_MAX/sizeof(*caches->rpspaths)) goto finish; caches->rpspaths = (cachedPath*)calloc(caches->nrps, sizeof(*caches->rpspaths)); if (!caches->rpspaths) goto finish; for (; rpsindex < acount; rpsindex++) { str = CFArrayGetValueAtIndex(apaths, rpsindex); if (CFGetTypeID(str) != CFStringGetTypeID()) goto finish; str2cachedPath(&caches->rpspaths[rpsindex], caches, str); // M } keyCount--; // AdditionalPaths sub-key } else { // allocate enough for the top-level keys (nothing variable-sized) if (caches->nrps > INT_MAX/sizeof(*caches->rpspaths)) goto finish; caches->rpspaths = calloc(caches->nrps, sizeof(cachedPath)); if (!caches->rpspaths) goto finish; } str = (CFStringRef)CFDictionaryGetValue(dict, kBCBootConfigKey); if (str) { if (CFGetTypeID(str) != CFStringGetTypeID()) goto finish; str2cachedPath(&caches->rpspaths[rpsindex], caches, str); // M caches->bootconfig = &caches->rpspaths[rpsindex++]; keyCount--; // handled BootConfig } if (CFDictionaryGetValue(dict, kBCMKextKey) && CFDictionaryGetValue(dict, kBCMKext2Key)) { // big fat error *errmsg = "multiple mkext keys found"; goto finish; } /* Handle original "MKext" key for format-1 mkexts prior to SnowLeopard. */ mkDict = (CFDictionaryRef)CFDictionaryGetValue(dict, kBCMKextKey); if (mkDict) { if (CFGetTypeID(mkDict) != CFDictionaryGetTypeID()) { goto finish; } // path to mkext itself str = (CFStringRef)CFDictionaryGetValue(mkDict, kBCPathKey); if (CFGetTypeID(str) != CFStringGetTypeID()) goto finish; str2cachedPath(&caches->rpspaths[rpsindex], caches, str); // M // get the Extensions folder path and set up exts by hand str=(CFStringRef)CFDictionaryGetValue(mkDict, kBCExtensionsDirKey); if (str) { char path[PATH_MAX]; if (CFGetTypeID(str) != CFStringGetTypeID()) { goto finish; } if (!CFStringGetFileSystemRepresentation(str, path, PATH_MAX)) { goto finish; } if (strlcat(caches->exts, path, PATH_MAX) >= PATH_MAX) { goto finish; } } // Archs are fetched from the cacheinfo dictionary when needed caches->mkext = &caches->rpspaths[rpsindex++]; keyCount--; // mkext key handled } /* Handle "MKext2" key for format-2 mkexts on SnowLeopard and later. */ mkDict = (CFDictionaryRef)CFDictionaryGetValue(dict, kBCMKext2Key); if (mkDict) { if (CFGetTypeID(mkDict) != CFDictionaryGetTypeID()) { goto finish; } // path to mkext itself str = (CFStringRef)CFDictionaryGetValue(mkDict, kBCPathKey); if (CFGetTypeID(str) != CFStringGetTypeID()) goto finish; str2cachedPath(&caches->rpspaths[rpsindex], caches, str); // M // get the Extensions folder path and set up exts by hand str=(CFStringRef)CFDictionaryGetValue(mkDict, kBCExtensionsDirKey); if (str) { char path[PATH_MAX]; if (CFGetTypeID(str) != CFStringGetTypeID()) { goto finish; } if (!CFStringGetFileSystemRepresentation(str, path, PATH_MAX)) { goto finish; } if (strlcat(caches->exts, path, PATH_MAX) >= PATH_MAX) { goto finish; } } // Archs are fetched from the cacheinfo dictionary when needed caches->mkext = &caches->rpspaths[rpsindex++]; keyCount--; // mkext2 key handled } keyCount--; // postBootPaths handled } if (keyCount) { *errmsg = "unrecognized bootcaches.plist data; skipping"; } else { // hooray *errmsg = NULL; rval = 0; caches->cacheinfo = CFRetain(bcDict); // for archs, etc } finish: if (createdStr) CFRelease(createdStr); return rval; } struct bootCaches* readCaches(DADiskRef dadisk) { struct bootCaches *rval = NULL, *caches = NULL; char *errmsg; int errnum = 4; struct stat sb; CFDictionaryRef ddesc = NULL; CFURLRef volURL = NULL; // owned by dict; don't release int ntries = 0; char bcpath[PATH_MAX]; void *bcbuf = NULL; CFDataRef bcData = NULL; CFDictionaryRef bcDict = NULL; char bspath[PATH_MAX]; // bootstamps errmsg = "allocation failure"; caches = calloc(1, sizeof(*caches)); if (!caches) goto finish; caches->cachefd = -1; // set cardinal (fd 0 valid) strlcpy(caches->root, "", PATH_MAX); // 'kextcache -U /' needs this retry to work around 5454260 // kexd's vol_appeared filters volumes w/o mount points errmsg = "error copying disk description"; do { if (!(ddesc = DADiskCopyDescription(dadisk))) goto finish; if((volURL=CFDictionaryGetValue(ddesc,kDADiskDescriptionVolumePathKey))) break; else sleep(1); } while (++ntries < kKextdDiskArbMaxRetries); if (ntries == kKextdDiskArbMaxRetries) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag | kOSKextLogFileAccessFlag, "Disk description missing mount point for %d tries", ntries); } else if (ntries) { OSKextLog(/* kext */ NULL, kOSKextLogWarningLevel | kOSKextLogGeneralFlag | kOSKextLogFileAccessFlag, "Warning: readCaches got mount point after %d tries.", ntries); } if (!CFURLGetFileSystemRepresentation(volURL, false, (UInt8*)caches->root, PATH_MAX)) { goto finish; } errmsg = "error reading " kBootCachesPath; if (strlcpy(bcpath, caches->root, PATH_MAX) >= PATH_MAX) goto finish; if (strlcat(bcpath, kBootCachesPath, PATH_MAX) >= PATH_MAX) goto finish; // Sec: cachefd lets us validate data, operations if (-1 == (caches->cachefd = open(bcpath, O_RDONLY|O_EVTONLY))) { if (errno == ENOENT) { errmsg = NULL; } goto finish; } // check the owner and mode (fstat() to insure it's the same file) // w/Leopard, root can see all the way to the disk; 99 -> truly unknown // note: 'sudo cp mach_kernel /Volumes/disrespected/' should -> error if (fstat(caches->cachefd, &sb)) { goto finish; } caches->sb = sb; // stash so we can detect changes if (sb.st_uid!= 0) { // XXX is there a way we can return a specific error for kextcache -u? errmsg = kBootCachesPath " not owned by root; no rebuilds"; goto finish; } if (sb.st_mode & S_IWGRP || sb.st_mode & S_IWOTH) { errmsg = kBootCachesPath " writable by non-root"; goto finish; } // read the plist if (!(bcbuf = malloc(sb.st_size))) goto finish; if (read(caches->cachefd, bcbuf, sb.st_size)!=sb.st_size) goto finish; if (!(bcData = CFDataCreate(nil, bcbuf, sb.st_size))) goto finish; errmsg = kBootCachesPath " doesn't contain a dictionary"; // Sec: see 4623105 & related for an assessment of our XML parsers bcDict = (CFDictionaryRef)CFPropertyListCreateFromXMLData(nil, bcData, kCFPropertyListImmutable, NULL); if (!bcDict || CFGetTypeID(bcDict)!=CFDictionaryGetTypeID()) { goto finish; } // let finishParse() fill in the rest of the structure if (finishParse(caches, bcDict, ddesc, &errmsg)) { goto finish; } errmsg = "error creating "kTSCacheDir; if (strlcpy(bspath, caches->root, PATH_MAX) >= PATH_MAX) goto finish; if (strlcat(bspath, kTSCacheDir, PATH_MAX) >= PATH_MAX) goto finish; pathcat(bspath, caches->uuid_str); if ((errnum = stat(bspath, &sb))) { // create unless owners aren't enabled (6206867) if (errno == ENOENT) { struct statfs sfs; if (statfs(caches->root, &sfs) == 0 && (sfs.f_flags & MNT_IGNORE_OWNERSHIP) == 0) { // s..mkdir ensures the cache directory is on the same volume if ((errnum = sdeepmkdir(caches->cachefd, bspath, kTSCacheMask))) { goto finish; } } } else { goto finish; } } // success! rval = caches; errmsg = NULL; finish: // report any error message if (errmsg) { if (errnum == -1) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, "%s: %s: %s.", caches->root, errmsg, strerror(errno)); } else { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, "%s: %s.", caches->root, errmsg); } } // clean up (unwind in allocation order) if (ddesc) CFRelease(ddesc); if (bcDict) CFRelease(bcDict); // retained for struct by finishParse if (bcData) CFRelease(bcData); if (bcbuf) free(bcbuf); // if finishParse() failed, clean up our stuff if (!rval) { destroyCaches(caches); // closes cachefd if needed } return rval; } /******************************************************************************* * needsUpdate checks a single path and timestamp; populates path->tstamp * We compare/copy the *ctime* of the source file to the *mtime* of the bootstamp. *******************************************************************************/ int needsUpdate(char *root, cachedPath* cpath, Boolean *outofdate) { Boolean ood; int bsderr = -1; struct stat rsb, tsb; char fullrp[PATH_MAX], fulltsp[PATH_MAX]; // create full paths pathcpy(fullrp, root); pathcat(fullrp, cpath->rpath); pathcpy(fulltsp, root); pathcat(fulltsp, cpath->tspath); // stat resolved rpath -> tstamp if (stat(fullrp, &rsb)) { if (errno == ENOENT) { // if the file doesn't exist; it can't be out of date bsderr = 0; *outofdate = false; } else { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, "Cached file %s: %s.", fullrp, strerror(errno)); } goto finish; } cpath->tstamps[0].tv_sec = rsb.st_atimespec.tv_sec; // to apply later cpath->tstamps[0].tv_usec = rsb.st_atimespec.tv_nsec / 1000; cpath->tstamps[1].tv_sec = rsb.st_ctimespec.tv_sec; // don't ask ;p cpath->tstamps[1].tv_usec = rsb.st_ctimespec.tv_nsec / 1000; // stat tspath (in com.apple.bootstamps) // and compare as appropriate if (stat(fulltsp, &tsb) == 0) { ood = (tsb.st_mtimespec.tv_sec != rsb.st_ctimespec.tv_sec || tsb.st_mtimespec.tv_nsec != rsb.st_ctimespec.tv_nsec); } else { if (errno == ENOENT) { ood = true; // nothing to compare with } else { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, "Cached file %s: %s.", fulltsp, strerror(errno)); goto finish; } } *outofdate = ood; bsderr = 0; finish: return bsderr; } /******************************************************************************* * needUpdates checks all paths and returns details if you want them * expects callers only to call it on volumes that will have timestamp paths * (e.g. BootRoot volumes! ;) *******************************************************************************/ int needUpdates(struct bootCaches *caches, Boolean *any, Boolean *rps, Boolean *booters, Boolean *misc) { int rval = 0; // looking for problems (any one will cause failure) Boolean needsUp, rpsOOD, bootersOOD, miscOOD, anyOOD; cachedPath *cp; // assume nothing needs updating (caller may interpret error -> needsUpdate) rpsOOD = bootersOOD = miscOOD = anyOOD = false; // in theory, all we have to do is find one "problem" (out of date file) // but in practice, there could be real problems (like missing sources) // we also like populating the tstamps for (cp = caches->rpspaths; cp < &caches->rpspaths[caches->nrps]; cp++) { if ((rval = needsUpdate(caches->root, cp, &needsUp))) goto finish; if (needsUp) anyOOD = rpsOOD = true; // one is enough, but needsUpdate populates tstamps which we need later } if ((cp = &(caches->efibooter)), cp->rpath[0]) { if ((rval = needsUpdate(caches->root, cp, &needsUp))) goto finish; if (needsUp) anyOOD = bootersOOD = true; } if ((cp = &(caches->ofbooter)), cp->rpath[0]) { if ((rval = needsUpdate(caches->root, cp, &needsUp))) goto finish; if (needsUp) anyOOD = bootersOOD = true; } for (cp = caches->miscpaths; cp < &caches->miscpaths[caches->nmisc]; cp++){ (void)needsUpdate(caches->root, cp, &needsUp); // could emit warnings in an appropriate verbose mode // no one cares if .VolumeIcon.icns is missing // though evidently (4487046) the label file is important if (needsUp) anyOOD = miscOOD = true; } if (rps) *rps = rpsOOD; if (booters) *booters = bootersOOD; if (misc) *misc = miscOOD; if (any) *any = anyOOD; finish: return rval; } /******************************************************************************* * applyStamps runs through all of the cached paths in a struct bootCaches * and applies the timestamps captured before the update * not going to bother with a re-stat() of the sources for now *******************************************************************************/ // Sec review: no need to drop privs thanks to safecalls.[ch] static int applyStamp(char *root, cachedPath *cpath, int fdvol) { int bsderr = -1, fd; char tspath[PATH_MAX]; pathcpy(tspath, root); pathcat(tspath, cpath->tspath); (void)sunlink(fdvol, tspath); // since sopen passes O_EXCL if (-1 == (fd = sopen(fdvol, tspath, O_WRONLY|O_CREAT, kTSCacheMask))) goto finish; bsderr = futimes(fd, cpath->tstamps); finish: return bsderr; } int applyStamps(struct bootCaches *caches) { int rval = 0; cachedPath *cp; // run through all of the cached paths apply bootstamp for (cp = caches->rpspaths; cp < &caches->rpspaths[caches->nrps]; cp++) { rval |= applyStamp(caches->root, cp, caches->cachefd); } if ((cp = &(caches->efibooter)), cp->rpath[0]) { rval |= applyStamp(caches->root, cp, caches->cachefd); } if ((cp = &(caches->ofbooter)), cp->rpath[0]) { rval |= applyStamp(caches->root, cp, caches->cachefd); } for (cp = caches->miscpaths; cp < &caches->miscpaths[caches->nmisc]; cp++){ rval |= applyStamp(caches->root, cp, caches->cachefd); } return rval; } /******************************************************************************* * rebuild_mkext fires off kextcache on the given volume * XX there is a bug here that can mask a stale mkext in the Apple_Boot (4764605) *******************************************************************************/ int rebuild_mkext(struct bootCaches *caches, Boolean wait) { int rval = ELAST + 1; int pid = -1; CFIndex i, argi = 0, argc = 0, narchs = 0; CFDictionaryRef pbDict, mkDict; int mkextVersion = 2; // default to new format CFArrayRef archArray; char **kcargs = NULL, **archstrs = NULL; // no [ARCH_MAX] anywhere? char * lastslash = NULL; char tpath[PATH_MAX]; struct stat sb; char fullmkextp[PATH_MAX], fullmkextdirp[PATH_MAX], fullextsp[PATH_MAX]; // bootcaches.plist might not request mkext rebuilds if (!caches->mkext) goto finish; pbDict = CFDictionaryGetValue(caches->cacheinfo, kBCPostBootKey); if (!pbDict || CFGetTypeID(pbDict) != CFDictionaryGetTypeID()) goto finish; /* Try for an MKext2 key, and if there isn't one, look for an "MKext" key. */ mkDict = CFDictionaryGetValue(pbDict, kBCMKext2Key); if (!mkDict) { mkDict = CFDictionaryGetValue(pbDict, kBCMKextKey); mkextVersion = 1; } if (!mkDict || CFGetTypeID(mkDict) != CFDictionaryGetTypeID()) goto finish; archArray = CFDictionaryGetValue(mkDict, kBCArchsKey); if (archArray) { narchs = CFArrayGetCount(archArray); archstrs = calloc(narchs, sizeof(char*)); if (!archstrs) goto finish; } // argv[0] -a x -a y [-n] -l -m* -volume-root NULL argc = 1 + (narchs*2) + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1; kcargs = malloc(argc * sizeof(char*)); if (!kcargs) goto finish; kcargs[argi++] = "kextcache"; // convert each -arch argument into a char* and add to the vector for(i = 0; i < narchs; i++) { CFStringRef archStr; size_t archSize; // get arch archStr = CFArrayGetValueAtIndex(archArray, i); if (!archStr || CFGetTypeID(archStr)!=CFStringGetTypeID()) goto finish; // XX an arch is not a pathname; EncodingASCII might be more appropriate archSize = CFStringGetMaximumSizeOfFileSystemRepresentation(archStr); if (!archSize) goto finish; // X marks the spot: over 800 lines written before I realized that // there were some serious security implications archstrs[i] = malloc(archSize); if (!archstrs[i]) goto finish; if (!CFStringGetFileSystemRepresentation(archStr,archstrs[i],archSize)) goto finish; kcargs[argi++] = "-a"; kcargs[argi++] = archstrs[i]; } // BootRoot always includes local kexts kcargs[argi++] = "-l"; // 6413843 check if it's installation media (-> add -n) pathcpy(tpath, caches->root); pathcat(tpath, "/etc/rc.cdrom"); if (stat(tpath, &sb) == 0) kcargs[argi++] = "-n"; if (mkextVersion == 2) { kcargs[argi++] = "-mkext2"; } else if (mkextVersion == 1) { kcargs[argi++] = "-mkext1"; } else { // internal error! goto finish; } pathcpy(fullmkextp, caches->root); pathcat(fullmkextp, caches->mkext->rpath); kcargs[argi++] = fullmkextp; kcargs[argi++] = "-volume-root"; kcargs[argi++] = caches->root; pathcpy(fullextsp, caches->root); pathcat(fullextsp, caches->exts); kcargs[argi++] = fullextsp; kcargs[argi] = NULL; pathcpy(fullmkextdirp, fullmkextp); lastslash = rindex(fullmkextdirp, '/'); if (lastslash) { *lastslash = '\0'; /* Make sure we have a destination directory to write the new mkext * file into (people occasionally delete the caches folder). */ if ((rval = sdeepmkdir(caches->cachefd, fullmkextdirp, kSysCacheMask))) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, "failed to create mkext folder %s.", fullmkextdirp); // can't make dest directory, kextcache will fail, so don't bother goto finish; } } rval = 0; /* wait:false means the return value is <0 for fork/exec failures and * the pid of the forked process if >0. * * wait:true means the return value is <0 for fork/exec failures and * the exit status of the forked process (>=0) otherwise. */ pid = fork_program("/usr/sbin/kextcache", kcargs, wait); // logs its own errors finish: if (rval) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Data error before mkext rebuild."); } if (wait || pid < 0) { rval = pid; } if (archstrs) { for (i = 0; i < narchs; i++) { if (archstrs[i]) free(archstrs[i]); } free(archstrs); } if (kcargs) free(kcargs); return rval; } /******************************************************************************* * Check our various plist caches, for the current kernel arch only, to see if * they need to be rebuilt: * * id -> url index * iokit personalities * logindwindow prop/value cache for OSBundleHelper * * This should only be called for the root volume! *******************************************************************************/ Boolean directoryPlistCachesNeedRebuild( CFURLRef directoryURL, const NXArchInfo * kernelArchInfo) { Boolean result = true; CFStringRef cacheBasename = NULL; // must release /* Check the KextIdentifiers index. */ if (!_OSKextReadCache(directoryURL, CFSTR(_kOSKextIdentifierCacheBasename), /* arch */ NULL, _kOSKextCacheFormatCFBinary, /* parseXML? */ false, /* valuesOut*/ NULL)) { goto finish; } /* Check the IOKitPersonalities cache for the current kernel arch. */ if (!_OSKextReadCache(directoryURL, CFSTR(kIOKitPersonalitiesKey), kernelArchInfo, _kOSKextCacheFormatIOXML, /* parseXML? */ false, /* valuesOut*/ NULL)) { goto finish; } /* Check the KextPropertyValues_OSBundleHelper cache for the current kernel arch. */ cacheBasename = CFStringCreateWithFormat(kCFAllocatorDefault, /* formatOptions */ NULL, CFSTR("%s%s"), _kKextPropertyValuesCacheBasename, "OSBundleHelper"); if (!cacheBasename) { OSKextLogMemError(); result = false; // cause we don't be able to update goto finish; } if (!_OSKextReadCache(directoryURL, cacheBasename, kernelArchInfo, _kOSKextCacheFormatCFXML, /* parseXML? */ false, /* valuesOut*/ NULL)) { goto finish; } result = false; finish: SAFE_RELEASE(cacheBasename); return result; } Boolean plistCachesNeedRebuild(const NXArchInfo * kernelArchInfo) { Boolean result = true; CFArrayRef systemExtensionsFolderURLs = NULL; // need not release CFIndex count, i; systemExtensionsFolderURLs = OSKextGetSystemExtensionsFolderURLs(); if (!systemExtensionsFolderURLs || !CFArrayGetCount(systemExtensionsFolderURLs)) { result = false; goto finish; } count = CFArrayGetCount(systemExtensionsFolderURLs); for (i = 0; i < count; i++) { CFURLRef directoryURL = CFArrayGetValueAtIndex( systemExtensionsFolderURLs, i); if (directoryPlistCachesNeedRebuild(directoryURL, kernelArchInfo)) { goto finish; } } result = false; finish: return result; } Boolean check_mkext(struct bootCaches *caches) { Boolean needsrebuild = false; struct stat sb; char fullmkextp[PATH_MAX], fullextsp[PATH_MAX]; // struct bootCaches paths are all *relative* pathcpy(fullmkextp, caches->root); pathcat(fullmkextp, caches->mkext->rpath); pathcpy(fullextsp, caches->root); pathcat(fullextsp, caches->exts); // mkext implies exts if (caches->mkext) { struct stat extsb; if (stat(fullextsp, &extsb) == -1) { OSKextLog(/* kext */ NULL, kOSKextLogWarningLevel | kOSKextLogFileAccessFlag, "Warning: %s: %s", fullextsp, strerror(errno)); // assert(needsrebuild == false); // we can't build w/o exts goto finish; } // Extensions.mkext needsrebuild = true; // since this stat() will fail if mkext gone if (stat(fullmkextp, &sb) == -1) goto finish; needsrebuild = (sb.st_mtime != extsb.st_mtime + 1); } finish: return needsrebuild; } /******************************************************************************* * createDiskForMount creates a DADisk object given a mount point * session is optional; one is created and released if the caller can't supply *******************************************************************************/ DADiskRef createDiskForMount(DASessionRef session, const char *mount) { DADiskRef rval = NULL; DASessionRef dasession = NULL; CFURLRef volURL = NULL; if (session) { dasession = session; } else { dasession = DASessionCreate(nil); if (!dasession) goto finish; } volURL = CFURLCreateFromFileSystemRepresentation(nil, (UInt8*)mount, strlen(mount), 1 /*isDirectory*/); if (!volURL) goto finish; rval = DADiskCreateFromVolumePath(nil, dasession, volURL); finish: if (volURL) CFRelease(volURL); if (!session && dasession) CFRelease(dasession); return rval; } /******************************************************************************* * hasBoots lets you know if a volume has boot partitions and if it's on GPT *******************************************************************************/ Boolean hasBootRootBoots(struct bootCaches *caches, CFArrayRef *auxPartsCopy, Boolean *isAPM) { CFDictionaryRef binfo = NULL; Boolean rval = false, apm = false; int err; CFArrayRef ar; char * errmsg = NULL; char stack_bsdname[DEVMAXPATHSIZE]; char * lookup_bsdname = caches->bsdname; CFArrayRef dataPartitions = NULL; // do not release; char fulldevname[PATH_MAX]; char parentdevname[PATH_MAX]; uint32_t partitionNum; BLPartitionType partitionType; /* Get the BL info about partitions & such. */ if (BLCreateBooterInformationDictionary(NULL, lookup_bsdname, &binfo)) goto finish; /***** * Now, for a GPT check, use one of the data partitions given by the above * call to BLCreateBooterInformationDictionary(). */ dataPartitions = CFDictionaryGetValue(binfo, kBLDataPartitionsKey); if (dataPartitions && CFArrayGetCount(dataPartitions)) { CFStringRef dpBsdName = CFArrayGetValueAtIndex(dataPartitions, 0); if (dpBsdName) { errmsg = "String conversion failure for bsdname."; // I hate CFString if (!CFStringGetCString(dpBsdName, stack_bsdname, sizeof(stack_bsdname), kCFStringEncodingUTF8)) { goto finish; } lookup_bsdname = stack_bsdname; } } /* Get the BL info about the partition type (that's all we use, but * we have to pass in valid buffer pointers for all the rest). */ errmsg = "Internal error."; if (sizeof(fulldevname) <= snprintf(fulldevname, sizeof(fulldevname), "/dev/%s", lookup_bsdname)) { goto finish; } errmsg = "Can't get partition type."; if (err = BLGetParentDeviceAndPartitionType(NULL /* context */, fulldevname, parentdevname, &partitionNum, &partitionType)) { goto finish; } if (partitionType == kBLPartitionType_APM) { apm = true; } // 5158091 / 6413843: 10.4.x APM Apple_Boot's aren't BootRoot if (apm) { CFDictionaryRef pbDict, mk2Dict; errmsg = NULL; // 10.4.x lacks OFBooter (BootX) support and the mkext2 format. // i.e. Leopard had BootX; SnowLeopard has mkext2 pbDict = CFDictionaryGetValue(caches->cacheinfo, kBCPostBootKey); if (!pbDict || CFGetTypeID(pbDict) != CFDictionaryGetTypeID()) goto finish; mk2Dict = CFDictionaryGetValue(pbDict, kBCMKext2Key); // if neither of these indicate a more modern OS, we skip if (!mk2Dict && caches->ofbooter.rpath[0] == '\0') goto finish; } errmsg = "Can't get helper partitions."; // check for helper partitions ar = CFDictionaryGetValue(binfo, kBLAuxiliaryPartitionsKey); rval = (ar && CFArrayGetCount(ar) > 0); if (auxPartsCopy) *auxPartsCopy = CFRetain(ar); errmsg = NULL; finish: if (binfo) CFRelease(binfo); if (isAPM) *isAPM = apm; if (errmsg) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag, "%s: %s", caches->root, errmsg); } return rval; } /******************************************************************************* * *******************************************************************************/ Boolean bootedFromDifferentMkext(void) { Boolean result = true; MkextCRCResult startupCrcFound; MkextCRCResult onDiskCrcFound; uint32_t startupCrc; uint32_t onDiskCrc; startupCrcFound = getMkextCRC(NULL, &startupCrc); if (startupCrcFound != kMkextCRCFound) { result = false; goto finish; } onDiskCrcFound = getMkextCRC(_kOSKextStartupMkextPath, &onDiskCrc); if (onDiskCrcFound != kMkextCRCFound) { goto finish; } if (startupCrc == onDiskCrc) { result = false; } finish: return result; } /******************************************************************************* * *******************************************************************************/ MkextCRCResult getMkextCRC(const char * file_path, uint32_t * crc_ptr) { MkextCRCResult result = kMkextCRCError; fat_iterator iter = NULL; const void * file_start = NULL; const void * arch_start = NULL; void * arch_end = NULL; mkext_header * mkext_hdr; io_registry_entry_t ioRegRoot = MACH_PORT_NULL; CFTypeRef regObj = NULL; // must release CFDataRef dataObj = NULL; // do not release; alias of regObj CFIndex numBytes; uint32_t crc; if (!file_path) { ioRegRoot = IORegistryGetRootEntry(kIOMasterPortDefault); if (ioRegRoot != MACH_PORT_NULL) { regObj = IORegistryEntryCreateCFProperty(ioRegRoot, CFSTR(kOSStartupMkextCRC), kCFAllocatorDefault, kNilOptions); if (!regObj) { result = kMkextCRCNotFound; goto finish; } if (CFGetTypeID(regObj) != CFDataGetTypeID()) { goto finish; } } dataObj = (CFDataRef)regObj; numBytes = CFDataGetLength(dataObj); if (numBytes != sizeof(uint32_t)) { goto finish; } CFDataGetBytes(dataObj, CFRangeMake(0, numBytes), (void *)&crc); } else { iter = fat_iterator_open(file_path, /* mach-o only? */ 0); if (!iter) { goto finish; } file_start = fat_iterator_file_start(iter); if (!file_start) { goto finish; } if (*(uint32_t *)(file_start) == OSSwapHostToBigInt32(MKEXT_MAGIC)) { /* We don't support old-format non-fat mkexts any more. */ goto finish; } else { const NXArchInfo * runningKernelArch = OSKextGetRunningKernelArchitecture(); if (!runningKernelArch) { goto finish; } arch_start = fat_iterator_find_arch( iter, runningKernelArch->cputype, runningKernelArch->cpusubtype, &arch_end); if (!arch_start) { goto finish; } if (*(uint32_t *)(arch_start) != OSSwapHostToBigInt32(MKEXT_MAGIC)) { goto finish; } mkext_hdr = (struct mkext_header *)arch_start; } crc = MKEXT_GET_CHECKSUM(mkext_hdr); } *crc_ptr = crc; result = kMkextCRCFound; finish: if (ioRegRoot) IOObjectRelease(ioRegRoot); if (regObj) CFRelease(regObj); if (iter) fat_iterator_close(iter); return result; } void _daDone(DADiskRef disk, DADissenterRef dissenter, void *ctx) { if (dissenter) CFRetain(dissenter); *(DADissenterRef*)ctx = dissenter; CFRunLoopStop(CFRunLoopGetCurrent()); // assumed okay even if not running }