#define _KEXT_KEYS #include #include #include #include #include #include #include #include #include #include #include #include #include "KXKext.h" #include "KXKext_private.h" #include "paths.h" #include "vers_rsrc.h" #include "printPList.h" /******************************************************************************* * Internal declarations for error dictionary keys and values. *******************************************************************************/ CFStringRef kKXKextErrorKeyFileAccess = NULL; CFStringRef kKXKextErrorKeyBundleNotInRepository = NULL; CFStringRef kKXKextErrorKeyNotABundle = NULL; CFStringRef kKXKextErrorKeyNotAKextBundle = NULL; CFStringRef kKXKextErrorKeyBadPropertyList = NULL; CFStringRef kKXKextErrorKeyMissingProperty = NULL; CFStringRef kKXKextErrorKeyPropertyIsIllegalType = NULL; CFStringRef kKXKextErrorKeyPropertyIsIllegalValue = NULL; CFStringRef kKXKextErrorKeyIdentifierOrVersionTooLong = NULL; CFStringRef kKXKextErrorKeyPersonalitiesNotNested = NULL; CFStringRef kKXKextErrorKeyMissingExecutable = NULL; CFStringRef kKXKextErrorKeyCompatibleVersionLaterThanVersion = NULL; CFStringRef kKXKextErrorKeyExecutableBad = NULL; CFStringRef kKXKextErrorKeyBundleIdentifierMismatch = NULL; CFStringRef kKXKextErrorKeyBundleVersionMismatch = NULL; CFStringRef kKXKextErrorKeyExecutableBadArch = NULL; CFStringRef kKXKextErrorKeyStatFailure = NULL; CFStringRef kKXKextErrorKeyFileNotFound = NULL; CFStringRef kKXKextErrorKeyOwnerPermission = NULL; CFStringRef kKXKextErrorKeyChecksum = NULL; CFStringRef kKXKextErrorKeySignature = NULL; CFStringRef kKXKextErrorKeyDependenciesUnresolvable = NULL; CFStringRef kKXKextErrorKeyPossibleDependencyLoop = NULL; CFStringRef kKXKextDependencyUnavailable = NULL; CFStringRef kKXKextDependencyNoCompatibleVersion = NULL; CFStringRef kKXKextDependencyCompatibleVersionUndeclared = NULL; CFStringRef kKXKextIndirectDependencyUnresolvable = NULL; CFStringRef kKXKextDependencyCircularReference = NULL; /******************************************************************************* * The basic data structure for a kext. * * Loadability test order: * 1. Valid * 2. Elgigible for boot level * 3. Enabled -- not yet implemented (may never be) * 4. Authentic * 5. Has all dependencies *******************************************************************************/ typedef struct __KXKext { CFRuntimeBase cfBase; // base CFType information SInt32 logLevel; // taken from "OSBundleDebugLevel" property in infoDictionary KXKextManagerRef manager; KXKextRepositoryRef repository; // Gives absolute path up to kext bundle CFDictionaryRef infoDictionary; // May be from cache! // A kext plist cache always, always lists top-level kexts before // all plugin kexts. That way all of the relationships can be // resolved as the kexts are initialized from their cache entries. // CFMutableArrayRef plugins; KXKextRef container; // The POSIX pathname, relative to the repository, of the bundle. // For a plugin this will include the parent kext's name and subdirectories // to the plugins directory. // This cannot be a CFURLRef because that data type does filesystem // ops when created and we want to delay those until a request is // actually made against the kext (for kexts from a cache). // CFStringRef bundlePathInRepository; // May be from cache! // The basic bundle directory name; last part of bundlePathInRepository. // Held by kext to avoid having multiple copies made by clients, as // that seems to be the only CF way to get the last part of a path. // This may be from the cache, as it otherwise has to be created from // an URL and we don't want to hit the filesystem when using the cache. // CFStringRef bundleDirectoryName; // These properties only exist once a request has been made against // the kext and we hit the disk to verify that it's there. // // For kexts created from an info cache bundle will be NULL. // The necessary filesystem ops may not have been peformed yet, // so none of the validation etc. info is defined. When a // request is actually made against this kext, the filesystem // ops will be performed. // CFBundleRef bundle; // Only exists once we've hit the disk. CFURLRef bundleURL; struct { unsigned int declaresExecutable:1; unsigned int isKernelResource:1; unsigned int isValid:1; // valid bundle and all kext props legal unsigned int isEligibleDuringSafeBoot:1; unsigned int isEnabled:1; // persistent enabling not yet implemented // (need to determine on-disk indication) unsigned int canAuthenticate:1; unsigned int hasBeenAuthenticated:1; unsigned int isAuthentic:1; // plist file owned by root, signed(?) unsigned int canResolveDependencies:1; unsigned int hasAllDependencies:1; // this gets set for all kexts of a given {id, version} // so it doesn't necessarily mean this particular kext's // executable code is in the kernel! There's really no // way to know given the current architecture. unsigned int isLoaded:1; unsigned int otherVersionIsLoaded:1; /***** * This flag is set whenever a kext fails to load, for whatever reason. * The kext manager only includes in its database of candidate kexts * those that do not have this flag set. This flag is cleared whenever * a kext is added to the manager, in case an unresolved dependency has * now become resolvable, but not when a kext is removed. */ unsigned int loadFailed:1; unsigned int hasIOKitDebugProperty:1; unsigned int reserved:2; } flags; // These values are cached during validation. UInt32 version; UInt32 compatibleVersion; // These must be cleared/recalculated with any change to a repository! // KXKextRef priorVersion; // Next kext with same identifier but // earlier version. KXKextRef nextDuplicate; // Next kext with same identifier & version. CFMutableArrayRef directDependencies; // may have some missing // may be derived from cached infoDictionary data /***** * Any failed diagnotic tests get their results put in one of these * three dictionaries. See the header file for what keys and values * go in them. If the manager does not perform full tests, then the * first failure encountered will cease testing and any of * these dictionaries will have exactly one entry. If the manager * does perform full tests, then as many errors as are found will * be in each dictionary. */ CFMutableDictionaryRef validationFailures; CFMutableDictionaryRef authenticationFailures; CFMutableDictionaryRef missingDependencies; // whether direct or indirect! } __KXKext, * __KXKextRef; /******************************************************************************* * Private function declarations. Definitions at bottom of this file. *******************************************************************************/ static void __KXKextInitialize(void); static CFStringRef __KXKextCopyDebugDescription(CFTypeRef cf); static KXKextRef __KXKextCreatePrivate( CFAllocatorRef allocator, CFAllocatorContext * context); static void __KXKextResetTestFlags(KXKextRef aKext); static void __KXKextInitBlank(KXKextRef aKext); static void __KXKextReleaseContents(CFTypeRef cf); static Boolean __KXKextCheckLogLevel( KXKextRef aKext, SInt32 managerLogLevel, SInt32 kextLogLevel, Boolean exact); static KXKextManagerError __KXKextValidate(KXKextRef aKext); static KXKextManagerError __KXKextValidateExecutable(KXKextRef aKext, Boolean requiresNewKextManager); static CFMutableDictionaryRef __KXKextGetOrCreateValidationFailures(KXKextRef aKext); static CFMutableDictionaryRef __KXKextGetOrCreateAuthenticationFailures( KXKextRef aKext); // validation failure array values static CFMutableArrayRef __KXKextGetMissingProperties(KXKextRef aKext); static CFMutableArrayRef __KXKextGetIllegalTypeProperties(KXKextRef aKext); static CFMutableArrayRef __KXKextGetIllegalValueProperties(KXKextRef aKext); // authentication failure array values static CFMutableArrayRef __KXKextGetMissingFiles(KXKextRef aKext); static CFMutableArrayRef __KXKextGetBadOwnerPermsFiles(KXKextRef aKext); static Boolean __KXKextCheckPropertyType( KXKextRef aKext, CFDictionaryRef aDictionary, CFStringRef propKey, CFArrayRef propPathArray, Boolean isRequired, CFTypeID expectedType, CFTypeRef * rawValueOut); static Boolean __KXKextCheckStringProperty( KXKextRef aKext, CFDictionaryRef aDictionary, CFStringRef propKey, CFArrayRef propPathArray, Boolean isRequired, CFStringRef expectedValue, CFStringRef *stringValueOut); static Boolean __KXKextCheckVersionProperty( KXKextRef aKext, CFDictionaryRef aDictionary, CFStringRef propKey, CFArrayRef propPathArray, Boolean isRequired, UInt32 *versionOut); static Boolean __KXKextCheckPersonalityTypes(KXKextRef aKext, CFTypeRef cfObj, CFMutableArrayRef propPathArray); static KXKextManagerError __KXKextAuthenticateURLAndParents(KXKextRef aKext, CFURLRef anURL, CFURLRef topURL /* must be absolute */, CFMutableDictionaryRef checkedURLs); static KXKextManagerError __KXKextAuthenticateURL(KXKextRef aKext, CFURLRef anURL); static KXKextManagerError __KXKextMakeFTSEntrySecure(KXKextRef aKext, FTSENT * ftsentry); static KXKextManagerError __KXKextCheckKmod(KXKextRef aKext, CFURLRef kmodURL, Boolean requiresNewKextManager); static KXKextManagerError __KXKextResolveDependencies(KXKextRef aKext, unsigned int recursionDepth); static char * __KXKextCopyDgraphEntryName(KXKextRef aKext); static char * __KXKextCopyDgraphKmodName(KXKextRef aKext); static Boolean __KXKextAddDependenciesToDgraph(KXKextRef aKext, CFArrayRef dependencies, dgraph_t * dgraph); static void __KXKextAddDependenciesToArray(KXKextRef aKext, CFMutableArrayRef dependencyArray); KXKextManagerError __KXKextRealizeFromCache(KXKextRef aKext); CFDictionaryRef __KXKextCopyInfoDictionaryFromCache( KXKextRef aKext, CFDictionaryRef cDict, Boolean makeSubstitutions); CFDictionaryRef __KXKextCopyInfoDictionaryForCache( KXKextRef aKext, CFDictionaryRef infoDict, Boolean makeSubstitutions); CFTypeRef __KXKextCopyPListForCache( KXKextRef aKext, CFTypeRef pList, Boolean * error); /******************************************************************************* * Core Foundation Class Definition Stuff * * Private functions are at the bottom of this file with other module-internal * code. *******************************************************************************/ /* This gets set by __KXKextInitialize(). */ static CFTypeID __kKXKextTypeID = _kCFRuntimeNotATypeID; CFTypeID KXKextGetTypeID(void) { return __kKXKextTypeID; } /******************************************************************************* * *******************************************************************************/ CFBundleRef KXKextGetBundle(KXKextRef aKext) { if (__KXKextRealizeFromCache(aKext) != kKXKextManagerErrorNone) { return NULL; } return aKext->bundle; } /******************************************************************************* * *******************************************************************************/ CFDictionaryRef KXKextGetInfoDictionary(KXKextRef aKext) { return aKext->infoDictionary; } /******************************************************************************* * *******************************************************************************/ KXKextRef KXKextGetPriorVersionKext(KXKextRef aKext) { return aKext->priorVersion; } /******************************************************************************* * *******************************************************************************/ KXKextRef KXKextGetDuplicateVersionKext(KXKextRef aKext) { return aKext->nextDuplicate; } /******************************************************************************* * *******************************************************************************/ KXKextRepositoryRef KXKextGetRepository(KXKextRef aKext) { return aKext->repository; } /******************************************************************************* * *******************************************************************************/ KXKextManagerRef KXKextGetManager(KXKextRef aKext) { return KXKextRepositoryGetManager(aKext->repository); } /******************************************************************************* * *******************************************************************************/ CFStringRef KXKextGetBundlePathInRepository(KXKextRef aKext) { return aKext->bundlePathInRepository; } /******************************************************************************* * *******************************************************************************/ CFStringRef KXKextGetBundleDirectoryName(KXKextRef aKext) { return aKext->bundleDirectoryName; } /******************************************************************************* * *******************************************************************************/ CFStringRef KXKextCopyAbsolutePath(KXKextRef aKext) { CFStringRef absPath = NULL; CFStringRef repositoryPath = NULL; // don't release repositoryPath = KXKextRepositoryGetPath(aKext->repository); if (!repositoryPath) { goto finish; } absPath = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@/%@"), repositoryPath, aKext->bundlePathInRepository); finish: return absPath; } /******************************************************************************* * *******************************************************************************/ CFURLRef KXKextGetAbsoluteURL(KXKextRef aKext) { return aKext->bundleURL; } /******************************************************************************* * *******************************************************************************/ Boolean KXKextGetIsKernelResource(KXKextRef aKext) { return aKext->flags.isKernelResource ? true : false; } /******************************************************************************* * *******************************************************************************/ Boolean KXKextGetDeclaresExecutable(KXKextRef aKext) { return aKext->flags.declaresExecutable ? true : false; } /******************************************************************************* * *******************************************************************************/ Boolean KXKextIsAPlugin(KXKextRef aKext) { return (aKext->container != NULL); } /******************************************************************************* * *******************************************************************************/ KXKextRef KXKextGetContainerForPluginKext(KXKextRef aKext) { return aKext->container; } /******************************************************************************* * *******************************************************************************/ CFArrayRef KXKextGetPlugins(KXKextRef aKext) { return (CFArrayRef)aKext->plugins; } /******************************************************************************* * *******************************************************************************/ Boolean KXKextIsValid(KXKextRef aKext) { return aKext->flags.isValid ? true : false; } /******************************************************************************* * *******************************************************************************/ CFMutableDictionaryRef KXKextGetValidationFailures(KXKextRef aKext) { return aKext->validationFailures; } /******************************************************************************* * *******************************************************************************/ Boolean KXKextHasDebugProperties(KXKextRef aKext) { return (aKext->flags.hasIOKitDebugProperty || (aKext->logLevel > 0)) ? true : false; } /******************************************************************************* * *******************************************************************************/ Boolean KXKextIsEligibleDuringSafeBoot(KXKextRef aKext) { return aKext->flags.isEligibleDuringSafeBoot ? true : false; } /******************************************************************************* * *******************************************************************************/ Boolean KXKextIsEnabled(KXKextRef aKext) { return aKext->flags.isEnabled ? true : false; } /******************************************************************************* * *******************************************************************************/ CFStringRef KXKextGetBundleIdentifier(KXKextRef aKext) { if (!aKext->infoDictionary) return NULL; return (CFStringRef)CFDictionaryGetValue(aKext->infoDictionary, CFSTR("CFBundleIdentifier")); } /******************************************************************************* * *******************************************************************************/ CFDictionaryRef KXKextGetBundleLibraryVersions(KXKextRef aKext) { if (!aKext->infoDictionary) { return NULL; } return CFDictionaryGetValue(aKext->infoDictionary, CFSTR("OSBundleLibraries")); } /******************************************************************************* * *******************************************************************************/ Boolean KXKextIsCompatibleWithVersionNumber( KXKextRef aKext, UInt32 version) { if (!aKext->flags.isValid) { return false; } if (aKext->compatibleVersion == 0) { return false; } if (aKext->compatibleVersion <= version && version <= aKext->version) { return true; } return false; } /******************************************************************************* * *******************************************************************************/ Boolean KXKextIsCompatibleWithVersionString( KXKextRef aKext, CFStringRef aVersionString) { char vers_buffer[32]; // more than long enough for legal vers UInt32 version; if (!aKext->flags.isValid) { return false; } if (aKext->compatibleVersion == 0) { return false; } if (!CFStringGetCString(aVersionString, vers_buffer, sizeof(vers_buffer) - 1, kCFStringEncodingMacRoman)) { return false; } else { vers_buffer[sizeof(vers_buffer) - 1] = '\0'; if (!VERS_parse_string(vers_buffer, &version)) { return false; } } if (aKext->compatibleVersion <= version && version <= aKext->version) { return true; } return false; } /******************************************************************************* * *******************************************************************************/ Boolean KXKextHasPersonalities(KXKextRef aKext) { CFDictionaryRef pDict = NULL; // don't release pDict = (CFDictionaryRef)CFDictionaryGetValue(aKext->infoDictionary, CFSTR("IOKitPersonalities")); if (!pDict) { return false; } else if (CFDictionaryGetCount(pDict) == 0) { return false; } return true; } /******************************************************************************* * *******************************************************************************/ CFDictionaryRef KXKextCopyPersonalities(KXKextRef aKext) { Boolean error = false; CFDictionaryRef pDict = NULL; // don't release CFMutableDictionaryRef newPDict = NULL; // returned CFIndex count, i; CFStringRef * keys = NULL; // must free CFStringRef * values = NULL; // must free pDict = (CFDictionaryRef)CFDictionaryGetValue(aKext->infoDictionary, CFSTR("IOKitPersonalities")); if (!pDict) { // not an error to have no personalities goto finish; } newPDict = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!newPDict) { error = true; goto finish; } count = CFDictionaryGetCount(pDict); keys = (CFStringRef *)malloc(count * sizeof(CFStringRef)); values = (CFStringRef *)malloc(count * sizeof(CFTypeRef)); CFDictionaryGetKeysAndValues(pDict, (const void **)keys, (const void **)values); for (i = 0; i < count; i++) { CFDictionaryRef thisPersonality = (CFDictionaryRef)values[i]; if (CFDictionaryGetValue(thisPersonality, CFSTR("CFBundleIdentifier"))) { CFDictionarySetValue(newPDict, keys[i], thisPersonality); } else { CFMutableDictionaryRef personalityCopy = NULL; // must release personalityCopy = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, CFDictionaryGetCount(thisPersonality)+5, thisPersonality); if (!personalityCopy) { error = true; goto finish; } CFDictionarySetValue(personalityCopy, CFSTR("CFBundleIdentifier"), KXKextGetBundleIdentifier(aKext)); CFDictionarySetValue(newPDict, keys[i], personalityCopy); CFRelease(personalityCopy); } } finish: if (error) { if (newPDict) CFRelease(newPDict); newPDict = NULL; } if (keys) free(keys); if (values) free(values); return newPDict; } CFArrayRef KXKextCopyPersonalitiesArray(KXKextRef aKext) { Boolean error = false; CFDictionaryRef pDict = NULL; // don't release CFMutableArrayRef newPArray = NULL; // returned CFIndex count, i; CFStringRef * values = NULL; // must free pDict = (CFDictionaryRef)CFDictionaryGetValue(aKext->infoDictionary, CFSTR("IOKitPersonalities")); if (!pDict) { // not an error to have no personalities goto finish; } newPArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (!newPArray) { error = true; goto finish; } count = CFDictionaryGetCount(pDict); values = (CFStringRef *)malloc(count * sizeof(CFTypeRef)); CFDictionaryGetKeysAndValues(pDict, NULL, (const void **)values); for (i = 0; i < count; i++) { CFDictionaryRef thisPersonality = (CFDictionaryRef)values[i]; if (CFDictionaryGetValue(thisPersonality, CFSTR("CFBundleIdentifier"))) { CFArrayAppendValue(newPArray, thisPersonality); } else { CFMutableDictionaryRef personalityCopy = NULL; // must release personalityCopy = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, CFDictionaryGetCount(thisPersonality)+5, thisPersonality); if (!personalityCopy) { error = true; goto finish; } CFDictionarySetValue(personalityCopy, CFSTR("CFBundleIdentifier"), KXKextGetBundleIdentifier(aKext)); CFArrayAppendValue(newPArray, personalityCopy); CFRelease(personalityCopy); } } finish: if (error) { if (newPArray) CFRelease(newPArray); newPArray = NULL; } if (values) free(values); return newPArray; } /******************************************************************************* * *******************************************************************************/ Boolean KXKextHasBeenAuthenticated(KXKextRef aKext) { return aKext->flags.hasBeenAuthenticated ? true : false; } /******************************************************************************* * *******************************************************************************/ KXKextManagerError KXKextAuthenticate(KXKextRef aKext) { KXKextManagerError result = kKXKextManagerErrorNone; KXKextManagerError checkResult = kKXKextManagerErrorNone; CFMutableDictionaryRef checkedURLs = NULL; // must release CFStringRef bundlePath = NULL; // must release CFURLRef infoDictURL = NULL; // must release CFURLRef backscanURL = NULL; // must release CFURLRef executableURL = NULL; // must release aKext->flags.isAuthentic = 0; checkedURLs = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!checkedURLs) { result = kKXKextManagerErrorNoMemory; goto finish; } /***** * Pre-authentication checks. */ checkResult = __KXKextRealizeFromCache(aKext); if (checkResult != kKXKextManagerErrorNone) { result = checkResult; // there can be no further test at this point goto finish; } if (!aKext->flags.canAuthenticate) { result = kKXKextManagerErrorAuthentication; // there can be no further test at this point goto finish; } if (__KXKextCheckLogLevel(aKext, kKXKextManagerLogLevelKexts, kKXKextLogLevelBasic, false)) { const char * kext_name = _KXKextCopyCanonicalPathnameAsCString(aKext); if (kext_name) { (_KMLog(aKext->manager))("authenticating extension %s", kext_name); free((char *)kext_name); } } if (!__KXKextGetOrCreateAuthenticationFailures(aKext)) { if (!aKext->authenticationFailures) { result = kKXKextManagerErrorNoMemory; goto finish; } } CFDictionaryRemoveAllValues(aKext->authenticationFailures); /***** * Authenticate the bundle's URL. */ if (!aKext->bundleURL) { result = kKXKextManagerErrorInvalidArgument; goto finish; } if (__KXKextCheckLogLevel(aKext, kKXKextManagerLogLevelKextDetails, kKXKextLogLevelDetails, false)) { const char * bundle_path = PATH_CanonicalizedCStringForURL(aKext->bundleURL); if (bundle_path) { (_KMLog(aKext->manager))("authenticating bundle directory %s", bundle_path); free((char *)bundle_path); } } checkResult = __KXKextAuthenticateURL(aKext, aKext->bundleURL); if (checkResult != kKXKextManagerErrorNone) { result = checkResult; if (result == kKXKextManagerErrorNoMemory || !KXKextManagerPerformsFullTests(aKext->manager)) { goto finish; } } /* Note that we've checked the bundle URL. */ CFDictionarySetValue(checkedURLs, aKext->bundleURL, kCFBooleanTrue); /***** * Old-style authentication checked only the bundle directory. New-style * authentication checks the info dictionary file, the executable, and * every filesystem node from them up to the bundle. */ if (!KXKextManagerPerformsStrictAuthentication(aKext->manager)) { aKext->flags.isAuthentic = 1; goto finish; } /***** * Authenticate the bundle's info dictionary file and containing directory. */ infoDictURL = _CFBundleCopyInfoPlistURL(aKext->bundle); if (!infoDictURL) { result = kKXKextManagerErrorUnspecified; goto finish; } checkResult = __KXKextAuthenticateURLAndParents(aKext, infoDictURL, aKext->bundleURL, checkedURLs); if (checkResult != kKXKextManagerErrorNone) { result = checkResult; if (result == kKXKextManagerErrorNoMemory || !KXKextManagerPerformsFullTests(aKext->manager)) { goto finish; } } /***** * Authenticate the bundle's executable and containing directory. */ if (aKext->flags.declaresExecutable) { executableURL = CFBundleCopyExecutableURL(aKext->bundle); /***** * This is unfortunate, but necessary. In order to spare disk I/O and * file stats during validation, we may not know if the executable is * even present. So we have to check here and possible return a * VALIDATION error from the authentication function. */ if (!executableURL) { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyMissingExecutable, kCFBooleanTrue); result = kKXKextManagerErrorValidation; if (!KXKextManagerPerformsFullTests(aKext->manager)) { goto finish; } } else { checkResult = __KXKextAuthenticateURLAndParents(aKext, executableURL, aKext->bundleURL, checkedURLs); if (checkResult != kKXKextManagerErrorNone) { result = checkResult; if (result == kKXKextManagerErrorNoMemory || !KXKextManagerPerformsFullTests(aKext->manager)) { goto finish; } } } } /***** * All tests passed, yay. */ if (result == kKXKextManagerErrorNone) { aKext->flags.isAuthentic = 1; } finish: aKext->flags.hasBeenAuthenticated = 1; if (checkedURLs) CFRelease(checkedURLs); if (bundlePath) CFRelease(bundlePath); if (infoDictURL) CFRelease(infoDictURL); if (backscanURL) CFRelease(backscanURL); if (executableURL) CFRelease(executableURL); if (__KXKextCheckLogLevel(aKext, kKXKextManagerLogLevelKexts, kKXKextLogLevelBasic, false)) { const char * kext_name = _KXKextCopyCanonicalPathnameAsCString(aKext); if (kext_name) { (_KMLog(aKext->manager))("extension %s is%s authentic", kext_name, aKext->flags.isAuthentic ? "" : " not"); free((char *)kext_name); } } return result; } /******************************************************************************* * *******************************************************************************/ KXKextManagerError KXKextMarkAuthentic(KXKextRef aKext) { KXKextManagerError result = kKXKextManagerErrorNone; if (__KXKextCheckLogLevel(aKext, kKXKextManagerLogLevelKexts, kKXKextLogLevelBasic, false)) { const char * kext_name = _KXKextCopyCanonicalPathnameAsCString(aKext); if (kext_name) { (_KMLog(aKext->manager))("marking extension %s authentic", kext_name); free((char *)kext_name); } } aKext->flags.isAuthentic = 1; aKext->flags.hasBeenAuthenticated = 1; return result; } /******************************************************************************* * *******************************************************************************/ Boolean KXKextIsAuthentic(KXKextRef aKext) { return aKext->flags.isAuthentic ? true : false; } /******************************************************************************* * *******************************************************************************/ CFMutableDictionaryRef KXKextGetAuthenticationFailures(KXKextRef aKext) { return aKext->authenticationFailures; } /******************************************************************************* * *******************************************************************************/ KXKextManagerError KXKextResolveDependencies(KXKextRef aKext) { if (__KXKextCheckLogLevel(aKext, kKXKextManagerLogLevelKexts, kKXKextLogLevelBasic, false)) { const char * kext_name = _KXKextCopyCanonicalPathnameAsCString(aKext); if (kext_name) { (_KMLog(aKext->manager))("resolving dependencies for extension %s", kext_name); free((char *)kext_name); } } return __KXKextResolveDependencies(aKext, 0); } /******************************************************************************* * *******************************************************************************/ Boolean KXKextGetHasAllDependencies(KXKextRef aKext) { return aKext->flags.hasAllDependencies ? true : false; } /******************************************************************************* * *******************************************************************************/ CFArrayRef KXKextGetDirectDependencies(KXKextRef aKext) { return aKext->directDependencies; } /******************************************************************************* * *******************************************************************************/ CFMutableArrayRef KXKextCopyAllDependencies(KXKextRef aKext) { CFMutableArrayRef allDependencies = NULL; if (!aKext->flags.hasAllDependencies || !aKext->directDependencies) { goto finish; } allDependencies = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (!allDependencies) { goto finish; } __KXKextAddDependenciesToArray(aKext, allDependencies); finish: if (allDependencies && !CFArrayGetCount(allDependencies)) { CFRelease(allDependencies); allDependencies = NULL; } return allDependencies; } /******************************************************************************* * *******************************************************************************/ CFMutableArrayRef KXKextCopyIndirectDependencies(KXKextRef aKext) { CFMutableArrayRef indirectDependencies = NULL; CFIndex count, i; if (!aKext->flags.hasAllDependencies || !aKext->directDependencies) { goto finish; } indirectDependencies = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (!indirectDependencies) { goto finish; } count = CFArrayGetCount(aKext->directDependencies); for (i = 0; i < count; i++) { KXKextRef thisDependency = (KXKextRef)CFArrayGetValueAtIndex( aKext->directDependencies, i); __KXKextAddDependenciesToArray(thisDependency, indirectDependencies); } finish: if (indirectDependencies && !CFArrayGetCount(indirectDependencies)) { CFRelease(indirectDependencies); indirectDependencies = NULL; } return indirectDependencies; } /******************************************************************************* * *******************************************************************************/ CFMutableArrayRef KXKextCopyAllDependents(KXKextRef aKext) { CFMutableArrayRef allDependents = NULL; // returned CFArrayRef allKexts = NULL; // must release CFArrayRef kextDependencies = NULL; // must release CFIndex count, i; allDependents = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (!allDependents) { goto finish; } allKexts = KXKextManagerCopyAllKexts(KXKextGetManager(aKext)); if (!allKexts) { goto finish; } count = CFArrayGetCount(allKexts); for (i = 0; i < count; i++) { KXKextRef thisKext = (KXKextRef)CFArrayGetValueAtIndex(allKexts, i); if (kextDependencies) { CFRelease(kextDependencies); kextDependencies = NULL; } kextDependencies = KXKextCopyAllDependencies(thisKext); if (!kextDependencies) { continue; } if (kCFNotFound != CFArrayGetFirstIndexOfValue(kextDependencies, CFRangeMake(0, CFArrayGetCount(kextDependencies)), aKext)) { CFArrayAppendValue(allDependents, thisKext); } } finish: if (allKexts) CFRelease(allKexts); if (kextDependencies) CFRelease(kextDependencies); if (allDependents && !CFArrayGetCount(allDependents)) { CFRelease(allDependents); allDependents = NULL; } return allDependents; } /******************************************************************************* * *******************************************************************************/ CFDictionaryRef KXKextGetMissingDependencyErrors(KXKextRef aKext) { return aKext->missingDependencies; } /******************************************************************************* * *******************************************************************************/ Boolean KXKextIsLoadable(KXKextRef aKext, Boolean safeBoot) { Boolean result = ( (aKext->flags.isValid) && (aKext->flags.isEligibleDuringSafeBoot || !safeBoot) && (aKext->flags.hasBeenAuthenticated && aKext->flags.isAuthentic) && aKext->flags.isEnabled && aKext->flags.hasAllDependencies); return result; } /******************************************************************************* * *******************************************************************************/ Boolean KXKextIsLoaded(KXKextRef aKext) { return aKext->flags.isLoaded ? true : false; } /******************************************************************************* * *******************************************************************************/ Boolean KXKextOtherVersionIsLoaded(KXKextRef aKext) { return aKext->flags.otherVersionIsLoaded ? true : false; } /******************************************************************************* * *******************************************************************************/ Boolean KXKextIsFromCache(KXKextRef aKext) { if (aKext->bundle) return false; return true; } /******************************************************************************* * *******************************************************************************/ Boolean KXKextGetLoadFailed(KXKextRef aKext) { return aKext->flags.loadFailed ? true : false; } /******************************************************************************* * *******************************************************************************/ void KXKextSetLoadFailed(KXKextRef aKext, Boolean flag) { aKext->flags.loadFailed = (flag ? 1 : 0); return; } /******************************************************************************* * *******************************************************************************/ void KXKextPrintDiagnostics(KXKextRef aKext, FILE * stream) { CFDictionaryRef validationFailures = NULL; // don't release CFDictionaryRef authenticationFailures = NULL; // don't release CFDictionaryRef missingDependencies = NULL; // don't release if (!stream) { stream = stdin; } validationFailures = KXKextGetValidationFailures(aKext); authenticationFailures = KXKextGetAuthenticationFailures(aKext); missingDependencies = KXKextGetMissingDependencyErrors(aKext); if (validationFailures && CFDictionaryGetCount(validationFailures)) { fprintf(stream, "Validation failures\n");fflush(stdout); printPList(stream, validationFailures); } if (authenticationFailures && CFDictionaryGetCount(authenticationFailures)) { fprintf(stream, "Authentication failures\n");fflush(stdout); printPList(stream, authenticationFailures); } if (missingDependencies && CFDictionaryGetCount(missingDependencies)) { fprintf(stream, "Missing dependencies\n");fflush(stdout); printPList(stream, missingDependencies); } fprintf(stream, "\n"); fflush(stream); return; } /******************************************************************************* ******************************************************************************** * FRAMEWORK-PRIVATE API BELOW HERE ******************************************************************************** *******************************************************************************/ /******************************************************************************* * *******************************************************************************/ KXKextRef _KXKextCreate(CFAllocatorRef alloc) { KXKextRef newKext = NULL; newKext = __KXKextCreatePrivate(alloc, NULL); if (!newKext) { goto finish; } finish: return (KXKextRef)newKext; } /******************************************************************************* * *******************************************************************************/ KXKextManagerError _KXKextInitWithBundlePathInManager( KXKextRef aKext, CFStringRef aBundlePath, KXKextManagerRef aManager) { KXKextManagerError result = kKXKextManagerErrorNone; CFURLRef bundleURL = NULL; // must release bundleURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, aBundlePath, kCFURLPOSIXPathStyle, true); if (!bundleURL) { // If the bundleURL couldn't be created we don't know why // without further investigation. We could be out of memory; // the entry in the filesystem might not exist; we might not // have access to it. // result = kKXKextManagerErrorUnspecified; goto finish; } result = _KXKextInitWithURLInManager(aKext, bundleURL, aManager); finish: if (bundleURL) CFRelease(bundleURL); return result; } /******************************************************************************* * *******************************************************************************/ KXKextManagerError _KXKextInitWithURLInManager( KXKextRef aKext, CFURLRef anURL, KXKextManagerRef aManager) { KXKextManagerError result = kKXKextManagerErrorNone; CFURLRef scratchURL = NULL; // must release KXKextRepositoryRef theRepository = NULL; // don't release theRepository = KXKextManagerGetRepositoryForKextWithURL(aManager, anURL); if (!theRepository) { // drop the kext name at the end of the path to get the repository scratchURL = CFURLCreateCopyDeletingLastPathComponent( kCFAllocatorDefault, anURL); if (!scratchURL) { goto finish; } result = KXKextManagerAddRepositoryDirectory(aManager, scratchURL, false, false, &theRepository); if (result != kKXKextManagerErrorNone) { goto finish; } } result = _KXKextInitWithURLInRepository(aKext, anURL, theRepository); finish: if (scratchURL) CFRelease(scratchURL); return result; } /******************************************************************************* * *******************************************************************************/ KXKextManagerError _KXKextInitWithBundleInManager( KXKextRef aKext, CFBundleRef aBundle, KXKextManagerRef aManager) { KXKextManagerError result = kKXKextManagerErrorNone; CFURLRef bundleURL = NULL; // must release CFURLRef scratchURL = NULL; // must release KXKextRepositoryRef theRepository = NULL; // don't release bundleURL = CFBundleCopyBundleURL(aBundle); theRepository = KXKextManagerGetRepositoryForKextWithURL(aManager, bundleURL); if (!theRepository) { // drop the kext name at the end of the path to get the repository scratchURL = CFURLCreateCopyDeletingLastPathComponent( kCFAllocatorDefault, bundleURL); if (!scratchURL) { goto finish; } result = KXKextManagerAddRepositoryDirectory(aManager, scratchURL, false, false, &theRepository); if (result != kKXKextManagerErrorNone) { goto finish; } } result = _KXKextInitWithBundleInRepository(aKext, aBundle, theRepository); finish: if (bundleURL) CFRelease(bundleURL); if (scratchURL) CFRelease(scratchURL); return result; } /******************************************************************************* * *******************************************************************************/ KXKextManagerError _KXKextInitWithBundlePathInRepository( KXKextRef aKext, CFStringRef aBundlePath, KXKextRepositoryRef aRepository) { KXKextManagerError result = kKXKextManagerErrorNone; CFURLRef bundleURL = NULL; // must release bundleURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, aBundlePath, kCFURLPOSIXPathStyle, true); if (!bundleURL) { // If the bundleURL couldn't be created we don't know why // without further investigation. We could be out of memory; // the entry in the filesystem might not exist; we might not // have access to it. // result = kKXKextManagerErrorUnspecified; goto finish; } result = _KXKextInitWithURLInRepository(aKext, bundleURL, aRepository); finish: CFRelease(bundleURL); return result; } /******************************************************************************* * *******************************************************************************/ KXKextManagerError _KXKextInitWithURLInRepository( KXKextRef aKext, CFURLRef anURL, KXKextRepositoryRef aRepository) { KXKextManagerError result = kKXKextManagerErrorNone; CFBundleRef aBundle = NULL; // must release const char * bundle_path = NULL; // must free // FIXME: Check for existence of directory!!! aBundle = CFBundleCreate(kCFAllocatorDefault, anURL); if (!aBundle) { struct stat stat_buf; bundle_path = PATH_CanonicalizedCStringForURL(anURL); if (!bundle_path) { result = kKXKextManagerErrorNoMemory; goto finish; } if (stat(bundle_path, &stat_buf) != 0) { if (errno == ENOENT || errno == ENOTDIR) { _KMErrLog(KXKextRepositoryGetManager(aRepository))( "%s: no such bundle file exists", bundle_path); result = kKXKextManagerErrorFileAccess; goto finish; } else if (errno == EACCES) { _KMErrLog(KXKextRepositoryGetManager(aRepository))( "%s: permission denied", bundle_path); result = kKXKextManagerErrorFileAccess; goto finish; } else { _KMErrLog(KXKextRepositoryGetManager(aRepository))( "%s: can't create bundle", bundle_path); result = kKXKextManagerErrorUnspecified; goto finish; } } if ( !(stat_buf.st_mode & S_IFDIR) ) { _KMErrLog(KXKextRepositoryGetManager(aRepository))( "%s is not a directory", bundle_path); result = kKXKextManagerErrorNotADirectory; goto finish; } result = kKXKextManagerErrorUnspecified; goto finish; } result = _KXKextInitWithBundleInRepository(aKext, aBundle, aRepository); finish: if (aBundle) CFRelease(aBundle); if (bundle_path) free((char *)bundle_path); return result; } /******************************************************************************* * *******************************************************************************/ KXKextManagerError _KXKextInitWithBundleInRepository( KXKextRef aKext, CFBundleRef aBundle, KXKextRepositoryRef aRepository) { KXKextManagerError result = kKXKextManagerErrorNone; CFURLRef bundleRelativeURL = NULL; // must release CFStringRef repositoryPath = NULL; // must release CFStringRef bundlePath = NULL; // must release CFStringRef bundleExtension = NULL; // must release CFStringRef bundleRepositoryPath = NULL; // don't release if (!aKext || !aBundle || !aRepository) { result = kKXKextManagerErrorInvalidArgument; goto finish; } /***** * Blank out all the fields that haven't yet been set. */ __KXKextInitBlank(aKext); /***** * Now set all the ones that can have values. */ /* Save the repository and the bundle. */ aKext->manager = KXKextRepositoryGetManager(aRepository); aKext->repository = aRepository; // do not retain up pointers! aKext->bundle = (CFBundleRef)CFRetain(aBundle); /* Figure out the absolute URL for the bundle. */ bundleRelativeURL = CFBundleCopyBundleURL(aKext->bundle); if (!bundleRelativeURL) { result = kKXKextManagerErrorNoMemory; goto finish; } aKext->bundleURL = PATH_CopyCanonicalizedURL(bundleRelativeURL); if (!aKext->bundleURL) { result = kKXKextManagerErrorNoMemory; goto finish; } /* Get the kext's bare directory name and its path relative to the * repository. */ aKext->bundleDirectoryName = CFURLCopyLastPathComponent(aKext->bundleURL); if (!aKext->bundleDirectoryName) { result = kKXKextManagerErrorNoMemory; goto finish; } repositoryPath = KXKextRepositoryGetPath(aKext->repository); if (!repositoryPath) { result = kKXKextManagerErrorUnspecified; goto finish; } bundlePath = CFURLCopyFileSystemPath(aKext->bundleURL, kCFURLPOSIXPathStyle); if (!bundlePath) { result = kKXKextManagerErrorNoMemory; goto finish; } bundleRepositoryPath = CFStringCreateWithSubstring(kCFAllocatorDefault, bundlePath, CFRangeMake(0, CFStringGetLength(repositoryPath))); if (!bundleRepositoryPath) { result = kKXKextManagerErrorNoMemory; goto finish; } aKext->bundlePathInRepository = CFStringCreateWithSubstring(kCFAllocatorDefault, bundlePath, CFRangeMake(CFStringGetLength(repositoryPath) + 1, CFStringGetLength(bundlePath) - CFStringGetLength(repositoryPath) - 1)); if (!aKext->bundlePathInRepository) { result = kKXKextManagerErrorNoMemory; goto finish; } /* Check whether kext is in repository directory. */ if (CFStringCompare(repositoryPath, bundleRepositoryPath, 0) != kCFCompareEqualTo) { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyBundleNotInRepository, kCFBooleanTrue); result = kKXKextManagerErrorURLNotInRepository; goto finish; } /* Now check whether this thing is even a proper bundle. */ if (!CFBundleGetIdentifier(aKext->bundle)) { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyNotABundle, kCFBooleanTrue); result = kKXKextManagerErrorNotABundle; goto finish; } aKext->infoDictionary = CFBundleGetInfoDictionary(aKext->bundle); if (!aKext->infoDictionary) { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyNotABundle, kCFBooleanTrue); result = kKXKextManagerErrorNotABundle; goto finish; } else { CFRetain(aKext->infoDictionary); } bundleExtension = CFURLCopyPathExtension(aKext->bundleURL); // FIXME: Need to check for "kext/" too? Maybe check for a // FIXME: ...proper extension before doing all this work? if (!bundleExtension || CFStringCompare(CFSTR("kext"), bundleExtension, NULL) != kCFCompareEqualTo) { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyNotAKextBundle, kCFBooleanTrue); result = kKXKextManagerErrorNotAKext; goto finish; } result = __KXKextValidate(aKext); finish: if (bundleRelativeURL) CFRelease(bundleRelativeURL); if (bundlePath) CFRelease(bundlePath); if (bundleRepositoryPath) CFRelease(bundleRepositoryPath); if (bundleExtension) CFRelease(bundleExtension); return result; } /******************************************************************************* * *******************************************************************************/ KXKextManagerError _KXKextInitWithCacheDictionaryInRepository( KXKextRef aKext, CFDictionaryRef aDictionary, KXKextRepositoryRef aRepository) { KXKextManagerError result = kKXKextManagerErrorNone; CFStringRef repositoryPath = NULL; // don't release CFStringRef bundlePath = NULL; // must release CFStringRef bundleExtension = NULL; // must release CFDictionaryRef cacheInfoDictionary = NULL; // don't release CFArrayRef plugins = NULL; // don't release CFIndex count, i; if (!aKext || !aDictionary || !aRepository) { result = kKXKextManagerErrorInvalidArgument; goto finish; } /* Blank out all the fields that haven't yet been set. */ __KXKextInitBlank(aKext); /***** * Now set all the ones that can have values. */ /* Save the repository and the bundle. */ aKext->manager = KXKextRepositoryGetManager(aRepository); aKext->repository = aRepository; // do not retain up pointers! aKext->bundle = NULL; // created when needed repositoryPath = KXKextRepositoryGetPath(aKext->repository); aKext->bundlePathInRepository = (CFStringRef)CFDictionaryGetValue(aDictionary, CFSTR("bundlePathInRepository")); if (!aKext->bundlePathInRepository || CFGetTypeID(aKext->bundlePathInRepository) != CFStringGetTypeID()) { result = kKXKextManagerErrorInvalidArgument; goto finish; } CFRetain(aKext->bundlePathInRepository); cacheInfoDictionary = (CFDictionaryRef)CFDictionaryGetValue(aDictionary, CFSTR("infoDictionary")); if (!cacheInfoDictionary || CFGetTypeID(cacheInfoDictionary) != CFDictionaryGetTypeID()) { result = kKXKextManagerErrorInvalidArgument; goto finish; } aKext->infoDictionary = __KXKextCopyInfoDictionaryFromCache( aKext, cacheInfoDictionary, false /* make key subs */); if (!aKext->infoDictionary) { result = kKXKextManagerErrorCache; goto finish; } /* Figure out the absolute URL for the bundle. */ bundlePath = CFStringCreateWithFormat(kCFAllocatorDefault, NULL /* options */, CFSTR("%@/%@"), repositoryPath, aKext->bundlePathInRepository); aKext->bundleURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, bundlePath, kCFURLPOSIXPathStyle, true); if (!aKext->bundleURL) { result = kKXKextManagerErrorNoMemory; goto finish; } /* Get the kext's bare directory name and its path relative to the * repository. */ aKext->bundleDirectoryName = CFURLCopyLastPathComponent(aKext->bundleURL); if (!aKext->bundleDirectoryName) { result = kKXKextManagerErrorNoMemory; goto finish; } bundleExtension = CFURLCopyPathExtension(aKext->bundleURL); // FIXME: Need to check for "kext/" too? Maybe check for a // FIXME: ...proper extension before doing all this work? if (!bundleExtension || CFStringCompare(CFSTR("kext"), bundleExtension, NULL) != kCFCompareEqualTo) { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyNotAKextBundle, kCFBooleanTrue); result = kKXKextManagerErrorNotAKext; goto finish; } result = __KXKextValidate(aKext); if (result != kKXKextManagerErrorNone) { goto finish; } /* Plugins aren't allowed to have plugins! */ if (aKext->container) goto finish; /* Check for plugins. */ plugins = (CFArrayRef)CFDictionaryGetValue(aDictionary, CFSTR("plugins")); if (!plugins) { goto finish; } if (CFGetTypeID(plugins) != CFArrayGetTypeID()) { result = kKXKextManagerErrorInvalidArgument; goto finish; } /* Load up the plugins. */ count = CFArrayGetCount(plugins); for (i = 0; i < count; i++) { CFDictionaryRef pDict = (CFDictionaryRef)CFArrayGetValueAtIndex( plugins, i); KXKextRef newKext = NULL; // must release KXKextManagerError kextResult = kKXKextManagerErrorNone; CFURLRef kextURL = NULL; // must release char * kext_path = NULL; // must free newKext = _KXKextCreate(kCFAllocatorDefault); if (!newKext) { result = kKXKextManagerErrorNoMemory; goto finish; } kextResult = _KXKextInitWithCacheDictionaryInRepository(newKext, pDict, aRepository); kextURL = KXKextGetAbsoluteURL(newKext); // don't release kext_path = NULL; // must free if (kextURL) { kext_path = PATH_CanonicalizedCStringForURL(kextURL); } if (kextResult != kKXKextManagerErrorNone) { _KXKextRepositoryAddBadKext(aRepository, newKext); if (KXKextManagerGetLogLevel(KXKextGetManager(newKext)) >= kKXKextManagerLogLevelDetails) { _KMLog(KXKextGetManager(newKext))("added invalid cached kernel extension %s", kext_path ? kext_path : "(unknown)"); } } else { if (KXKextManagerGetLogLevel(KXKextGetManager(newKext)) >= kKXKextManagerLogLevelDetails) { _KMLog(KXKextGetManager(newKext))("added cached kernel extension %s", kext_path ? kext_path : "(unknown)"); } _KXKextRepositoryAddKext(aRepository, newKext); } _KXKextAddPlugin(aKext, newKext); CFRelease(newKext); newKext = NULL; if (kext_path) free(kext_path); } finish: if (bundlePath) CFRelease(bundlePath); if (bundleExtension) CFRelease(bundleExtension); return result; } /******************************************************************************* * *******************************************************************************/ void _KXKextSetPriorVersionKext(KXKextRef aKext, KXKextRef thatKext) { KXKextRef oldPriorVersion = NULL; oldPriorVersion = aKext->priorVersion; aKext->priorVersion = thatKext; if (aKext->priorVersion) CFRetain(aKext->priorVersion); if (oldPriorVersion) CFRelease(oldPriorVersion); return; } /******************************************************************************* * *******************************************************************************/ void _KXKextClearVersionRelationships(KXKextRef aKext) { if (aKext->priorVersion) { CFRelease(aKext->priorVersion); aKext->priorVersion = NULL; } if (aKext->nextDuplicate) { CFRelease(aKext->nextDuplicate); aKext->nextDuplicate = NULL; } return; } /******************************************************************************* * *******************************************************************************/ void _KXKextClearDependencies(KXKextRef aKext) { aKext->flags.hasAllDependencies = 0; if (aKext->directDependencies) { CFRelease(aKext->directDependencies); aKext->directDependencies = NULL; } if (aKext->missingDependencies) { CFRelease(aKext->missingDependencies); aKext->missingDependencies = NULL; } return; } /******************************************************************************* * *******************************************************************************/ const char * _KXKextCopyCanonicalPathnameAsCString(KXKextRef aKext) { char * abs_path = NULL; // returned CFStringRef absPath = NULL; // must release CFIndex pathSize; Boolean error = false; absPath = KXKextCopyAbsolutePath(aKext); if (!absPath) { goto finish; } pathSize = 1 + CFStringGetLength(absPath); abs_path = (char *)malloc(pathSize * sizeof(char)); if (!abs_path) { goto finish; } if (!CFStringGetCString(absPath, abs_path, pathSize, kCFStringEncodingMacRoman)) { error = true; } finish: if (absPath) CFRelease(absPath); if (error && abs_path) { free(abs_path); abs_path = NULL; } return abs_path; } /******************************************************************************* * *******************************************************************************/ const char * _KXKextCopyBundlePathInRepositoryAsCString(KXKextRef aKext) { char * path = NULL; // returned CFStringRef bundlePath = NULL; // don't release CFIndex pathSize; Boolean error = false; bundlePath = KXKextGetBundlePathInRepository(aKext); if (!bundlePath) { goto finish; } pathSize = 1 + CFStringGetLength(bundlePath); path = (char *)malloc(pathSize * sizeof(char)); if (!path) { goto finish; } if (!CFStringGetCString(bundlePath, path, pathSize, kCFStringEncodingMacRoman)) { error = true; } finish: if (error && path) { free(path); path = NULL; } return path; } /******************************************************************************* * *******************************************************************************/ UInt32 _KXKextGetVersion(KXKextRef aKext) { return aKext->version; } /******************************************************************************* * *******************************************************************************/ UInt32 _KXKextGetCompatibleVersion(KXKextRef aKext) { return aKext->compatibleVersion; } /******************************************************************************* * *******************************************************************************/ Boolean _KXKextAddPriorOrDuplicateVersionKext(KXKextRef aKext, KXKextRef priorKext) { KXKextRef peekingKext = NULL; KXKextRef insertionKext = NULL; // FIXME: If a kext's compatibility range overlaps another's, then // FIXME: ...toss the one with the older version (unless it's already // FIXME: ...loaded (but that's hard to determine)). // FIXME: ...Will also need a way to note this problem in the kext being // FIXME: ...disqualified. Consider a validation failure? /* Kext being added must not have any prior versions of its own. */ if (KXKextGetPriorVersionKext(priorKext)) { return false; } /* Refuse to add a kext whose version is later than aKext's, and * add as a duplicate one whose version is the same. */ if (_KXKextGetVersion(priorKext) > _KXKextGetVersion(aKext)) { return false; } else if (_KXKextGetVersion(aKext) == _KXKextGetVersion(priorKext)) { return _KXKextAddDuplicateVersionKext(aKext, priorKext); } /* Find where to put the requested kext. If we find one in the linked * list with the same version, tack it on the **END** of the found kext's * duplicate version list. If we find one whose version is earlier, * insert it before the found kext. Otherwise we'll end up tacking it * onto the end of the list. * * Duplicate kexts are treated on a first-come, first-eligible basis. * This means that kexts from repositories added earliest to the manager * have priority, and within a single repository the ordering of * duplicates is explicitly undefined and may change from invocation to * invocation. */ peekingKext = insertionKext = aKext; while (peekingKext) { peekingKext = KXKextGetPriorVersionKext(insertionKext); if (peekingKext) { if (_KXKextGetVersion(peekingKext) == _KXKextGetVersion(priorKext)) { return _KXKextAddDuplicateVersionKext(peekingKext, priorKext); } else if (_KXKextGetVersion(peekingKext) < _KXKextGetVersion(priorKext)) { break; } insertionKext = peekingKext; } } _KXKextSetPriorVersionKext(priorKext, peekingKext); _KXKextSetPriorVersionKext(insertionKext, priorKext); return true; } /******************************************************************************* * *******************************************************************************/ void _KXKextSetDuplicateVersionKext(KXKextRef aKext, KXKextRef thatKext) { KXKextRef oldNextDuplicate = NULL; oldNextDuplicate = aKext->nextDuplicate; aKext->nextDuplicate = thatKext; if (aKext->nextDuplicate) CFRetain(aKext->nextDuplicate); if (oldNextDuplicate) CFRelease(oldNextDuplicate); return; } /******************************************************************************* * *******************************************************************************/ Boolean _KXKextAddDuplicateVersionKext(KXKextRef aKext, KXKextRef dupVersKext) { KXKextRef peekingKext = NULL; KXKextRef insertionKext = NULL; // FIXME: What to do if a dup has a different compatible version??? // FIXME: Will also need a way to note this problem in the kext being // FIXME: ...disqualified. Consider a validation failure? if (_KXKextGetVersion(aKext) != _KXKextGetVersion(dupVersKext)) { return false; } if (KXKextGetDuplicateVersionKext(dupVersKext)) { return false; } peekingKext = insertionKext = aKext; /* Just find the end of the linked list. */ while (peekingKext) { peekingKext = KXKextGetDuplicateVersionKext(insertionKext); if (peekingKext) insertionKext = peekingKext; } _KXKextSetDuplicateVersionKext(insertionKext, dupVersKext); return true; } /******************************************************************************* * *******************************************************************************/ KXKextManagerError _KXKextScanPlugins(KXKextRef aKext, CFArrayRef * goodPlugins, CFArrayRef * badPlugins, CFArrayRef * removedPlugins) { KXKextManagerError result = kKXKextManagerErrorNone; KXKextManagerError checkResult = kKXKextManagerErrorNone; CFStringRef bundlePath = NULL; // must release CFURLRef pluginsRelURL = NULL; // must release CFStringRef pluginsString = NULL; // must release CFURLRef pluginsAbsURL = NULL; // must release CFStringRef pluginsAbsString = NULL; // must release CFArrayRef pluginArray = NULL; CFIndex count, i; KXKextRef thisPlugin = NULL; checkResult = __KXKextRealizeFromCache(aKext); if (checkResult != kKXKextManagerErrorNone) { result = checkResult; // there can be no further test at this point goto finish; } if (!aKext->infoDictionary) { result = kKXKextManagerErrorNotABundle; CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyNotABundle, kCFBooleanTrue); aKext->flags.canAuthenticate = 0; goto finish; } if (aKext->container) goto finish; // Plugins aren't allowed to have plugins! bundlePath = CFURLCopyFileSystemPath(aKext->bundleURL, kCFURLPOSIXPathStyle); if (!bundlePath) { result = kKXKextManagerErrorNoMemory; goto finish; } pluginsRelURL = CFBundleCopyBuiltInPlugInsURL(aKext->bundle); if (!pluginsRelURL) { result = kKXKextManagerErrorNoMemory; goto finish; } pluginsString = CFURLCopyFileSystemPath(pluginsRelURL, kCFURLPOSIXPathStyle); if (!pluginsString) { result = kKXKextManagerErrorNoMemory; goto finish; } pluginsAbsString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@/%@"), bundlePath, pluginsString); if (!pluginsAbsString) { result = kKXKextManagerErrorNoMemory; goto finish; } pluginsAbsURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, pluginsAbsString, kCFURLPOSIXPathStyle, true); if (!pluginsAbsURL) { result = kKXKextManagerErrorNoMemory; goto finish; } #if 0 pluginsAbsURL = CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault, aKext->bundleURL, pluginsString, true); if (!pluginsAbsURL) { result = kKXKextManagerErrorNoMemory; goto finish; } #endif 0 result = _KXKextRepositoryScanDirectoryForKexts( aKext->repository, pluginsAbsURL, aKext->plugins, goodPlugins, badPlugins, removedPlugins); /* It's okay for there to be no plugins directory. */ if (result == kKXKextManagerErrorNotADirectory) { result = kKXKextManagerErrorNone; } if (result != kKXKextManagerErrorNone) { goto finish; } if (goodPlugins && *goodPlugins) { pluginArray = *goodPlugins; count = CFArrayGetCount(pluginArray); for (i = 0; i < count; i++) { thisPlugin = (KXKextRef)CFArrayGetValueAtIndex(pluginArray, i); _KXKextAddPlugin(aKext, thisPlugin); } } if (removedPlugins && *removedPlugins) { pluginArray = *removedPlugins; count = CFArrayGetCount(pluginArray); for (i = 0; i < count; i++) { thisPlugin = (KXKextRef)CFArrayGetValueAtIndex(pluginArray, i); _KXKextAddPlugin(aKext, thisPlugin); } } finish: if (bundlePath) CFRelease(bundlePath); if (pluginsRelURL) CFRelease(pluginsRelURL); if (pluginsString) CFRelease(pluginsString); if (pluginsAbsString) CFRelease(pluginsAbsString); if (pluginsAbsURL) CFRelease(pluginsAbsURL); return result; } /******************************************************************************* * *******************************************************************************/ void _KXKextSetContainerForPluginKext( KXKextRef aKext, KXKextRef containerKext) { aKext->container = containerKext; return; } /******************************************************************************* * *******************************************************************************/ void _KXKextAddPlugin(KXKextRef aKext, KXKextRef pluginKext) { CFIndex i; if (!aKext->plugins) { aKext->plugins = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (!aKext->plugins) return; } i = CFArrayGetFirstIndexOfValue(aKext->plugins, CFRangeMake(0, CFArrayGetCount(aKext->plugins)), pluginKext); if (i == kCFNotFound) { CFArrayAppendValue(aKext->plugins, pluginKext); _KXKextSetContainerForPluginKext(pluginKext, aKext); } return; } /******************************************************************************* * *******************************************************************************/ void _KXKextRemovePlugin(KXKextRef aKext, KXKextRef pluginKext) { CFIndex i; if (!aKext->plugins) { goto finish; } _KXKextSetContainerForPluginKext(pluginKext, NULL); i = CFArrayGetFirstIndexOfValue(aKext->plugins, CFRangeMake(0, CFArrayGetCount(aKext->plugins)), pluginKext); if (i != kCFNotFound) { CFArrayRemoveValueAtIndex(aKext->plugins, i); } finish: return; } /******************************************************************************* * *******************************************************************************/ void _KXKextRemoveAllPlugins(KXKextRef aKext) { CFIndex count, i; if (!aKext->plugins) { goto finish; } count = CFArrayGetCount(aKext->plugins); for (i = 0; i < count; i++) { KXKextRef thisKext = (KXKextRef)CFArrayGetValueAtIndex(aKext->plugins, i); _KXKextSetContainerForPluginKext(thisKext, NULL); } CFArrayRemoveAllValues(aKext->plugins); finish: return; } /******************************************************************************* * *******************************************************************************/ void _KXKextSetIsLoaded(KXKextRef aKext, Boolean flag) { aKext->flags.isLoaded = (flag ? 1 : 0); return; } /******************************************************************************* * *******************************************************************************/ void _KXKextSetHasBeenAuthenticated(KXKextRef aKext, Boolean flag) { aKext->flags.hasBeenAuthenticated = (flag ? 1 : 0); return; } /******************************************************************************* * *******************************************************************************/ void _KXKextSetOtherVersionIsLoaded(KXKextRef aKext, Boolean flag) { aKext->flags.otherVersionIsLoaded = (flag ? 1 : 0); return; } /******************************************************************************* * *******************************************************************************/ dgraph_t * _KXKextCreateDgraph(KXKextRef aKext) { int error = 0; dgraph_t * dgraph = NULL; // don't free dgraph_error_t result = dgraph_valid; if (!KXKextGetHasAllDependencies(aKext)) { (_KMErrLog(aKext->manager))("kext doesn't have all dependencies"); goto finish; } dgraph = (dgraph_t *)malloc(sizeof(dgraph_t)); if (!dgraph) { goto finish; } result = dgraph_init(dgraph); if (result != dgraph_valid) { (_KMErrLog(aKext->manager))("new dgraph is invalid"); error = 1; goto finish; } if (!__KXKextAddDependenciesToDgraph(aKext, KXKextGetDirectDependencies(aKext), dgraph)) { (_KMErrLog(aKext->manager))("can't add dependencies to dgraph"); error = 1; goto finish; } dgraph->root = dgraph_find_root(dgraph); dgraph_establish_load_order(dgraph); if (!dgraph->root) { (_KMErrLog(aKext->manager))("dependency graph has no root"); error = 1; goto finish; } if (dgraph->root->is_kernel_component) { (_KMErrLog(aKext->manager))( "dependency graph root is a kernel component"); error = 1; goto finish; } finish: if (error) { dgraph_free(dgraph, 1); dgraph = NULL; } return dgraph; } /******************************************************************************* * *******************************************************************************/ CFDictionaryRef _KXKextCopyCacheDictionary(KXKextRef aKext) { Boolean error = false; CFMutableDictionaryRef theDictionary = NULL; // returned CFDictionaryRef cacheInfoDictionary = NULL; // must release CFMutableArrayRef plugins = NULL; // must release theDictionary = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!theDictionary) { error = true; goto finish; } CFDictionarySetValue(theDictionary, CFSTR("bundlePathInRepository"), aKext->bundlePathInRepository); /* We have to make sure the cache's info dictionary doesn't contain * any non-xml-able CF types. */ cacheInfoDictionary = __KXKextCopyInfoDictionaryForCache( aKext, aKext->infoDictionary, false /* make key subs */); if (!cacheInfoDictionary) { goto finish; } CFDictionarySetValue(theDictionary, CFSTR("infoDictionary"), cacheInfoDictionary); if (aKext->plugins) { CFIndex count, i; plugins = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (!plugins) { error = true; goto finish; } CFDictionarySetValue(theDictionary, CFSTR("plugins"), plugins); // do not release plugins here count = CFArrayGetCount(aKext->plugins); for (i = 0; i < count; i++) { KXKextRef plugin = (KXKextRef)CFArrayGetValueAtIndex( aKext->plugins, i); CFDictionaryRef pDict = _KXKextCopyCacheDictionary(plugin); if (!pDict) { error = true; goto finish; } CFArrayAppendValue(plugins, pDict); CFRelease(pDict); // clean up within loop pDict = NULL; } } finish: if (cacheInfoDictionary) CFRelease(cacheInfoDictionary); if (plugins) CFRelease(plugins); if (error) { if (theDictionary) CFRelease(theDictionary); theDictionary = NULL; } return theDictionary; } /******************************************************************************* * *******************************************************************************/ KXKextManagerError _KXKextMakeSecure(KXKextRef aKext) { KXKextManagerError result = kKXKextManagerErrorNone; KXKextManagerError checkResult = kKXKextManagerErrorNone; CFURLRef absURL = NULL; // must release CFStringRef urlPath = NULL; // must release char path[MAXPATHLEN] = ""; char * const paths[2] = { path, NULL }; FTS * ftscursor = NULL; // must fts_close() FTSENT * ftsentry = NULL; checkResult = __KXKextRealizeFromCache(aKext); if (checkResult != kKXKextManagerErrorNone) { result = checkResult; // there can be no further test at this point goto finish; } absURL = PATH_CopyCanonicalizedURL(aKext->bundleURL); if (!absURL) { result = kKXKextManagerErrorNoMemory; goto finish; } urlPath = CFURLCopyFileSystemPath(absURL, kCFURLPOSIXPathStyle); if (!urlPath) { result = kKXKextManagerErrorNoMemory; goto finish; } if (!CFStringGetCString(urlPath, path, sizeof(path), kCFStringEncodingMacRoman)) { result = kKXKextManagerErrorNoMemory; goto finish; } /* Touch the kext's enclosing folder to make sure the cache gets rebuilt. */ if (!_KXKextRepositoryInvalidateCaches(aKext->repository)) { (_KMErrLog(aKext->manager))("can't invalidate caches for %s", path); // don't bail on this; keep working on the kext } // drill down into kext and make each URL secure ftscursor = fts_open(paths, (FTS_NOCHDIR | FTS_PHYSICAL), NULL); if (!ftscursor) { (_KMErrLog(aKext->manager))("can't scan contents of %s", path); result = kKXKextManagerErrorFileAccess; goto finish; } while ( (ftsentry = fts_read(ftscursor))) { if (! (ftsentry->fts_info & FTS_DP)) { result = __KXKextMakeFTSEntrySecure(aKext, ftsentry); if (result != kKXKextManagerErrorNone) { goto finish; } } } aKext->flags.hasBeenAuthenticated = 0; result = KXKextAuthenticate(aKext); finish: if (ftscursor) fts_close(ftscursor); if (absURL) CFRelease(absURL); if (urlPath) CFRelease(urlPath); return result; } /******************************************************************************* ******************************************************************************** * MODULE-PRIVATE API BELOW HERE ******************************************************************************** *******************************************************************************/ /******************************************************************************* * *******************************************************************************/ static const CFRuntimeClass __KXKextClass = { 0, // version "KXKext", // className NULL, // init NULL, // copy __KXKextReleaseContents, // finalize NULL, // equal NULL, // hash NULL, // copyFormattingDesc __KXKextCopyDebugDescription // copyDebugDesc }; static void __KXKextInitialize(void) { __kKXKextTypeID = _CFRuntimeRegisterClass(&__KXKextClass); // FIXME: Need a way to automate setup of these. // FIXME: These should be localizable! kKXKextErrorKeyFileAccess = CFSTR("File access failure; no permission?"); kKXKextErrorKeyBundleNotInRepository = CFSTR("Bundle was created with an URL not in the requested repository"); kKXKextErrorKeyNotABundle = CFSTR("Not a bundle; file structure or property list error?"); kKXKextErrorKeyNotAKextBundle = CFSTR("Bundle is not a kext bundle"); kKXKextErrorKeyBadPropertyList = CFSTR("Info dictionary not valid for kext"); kKXKextErrorKeyMissingProperty = CFSTR("Info dictionary missing required property/value"); kKXKextErrorKeyPropertyIsIllegalType = CFSTR("Info dictionary property value is of illegal type"); kKXKextErrorKeyPropertyIsIllegalValue = CFSTR("Info dictionary property value is illegal"); kKXKextErrorKeyIdentifierOrVersionTooLong = CFSTR("CFBundleIdentifier or CFBundleVersion string is too long"); kKXKextErrorKeyPersonalitiesNotNested = CFSTR("Structure of IOKitPersonalities is incorrect"); kKXKextErrorKeyMissingExecutable = CFSTR("Kext claims an executable file but it doesn't exist"); kKXKextErrorKeyCompatibleVersionLaterThanVersion = CFSTR("Compatible version is later than current version"); kKXKextErrorKeyExecutableBad = CFSTR("Executable file doesn't contain kernel extension code"); kKXKextErrorKeyExecutableBadArch = CFSTR("Executable does not contain code for this architecture"); kKXKextErrorKeyBundleIdentifierMismatch = CFSTR("Kext has a kernel dependency prior to version 6.0 and " "CFBundleIdentifier does not match executable's MODULE_NAME"); kKXKextErrorKeyBundleVersionMismatch = CFSTR("Kext has a kernel dependency prior to version 6.0 and " "CFBundleVersion does not match executable's MODULE_VERSION"); kKXKextErrorKeyStatFailure = CFSTR("Failed to get authentication info"); kKXKextErrorKeyFileNotFound = CFSTR("File not found"); kKXKextErrorKeyOwnerPermission = CFSTR("File owner/permissions are incorrect"); kKXKextErrorKeyChecksum = CFSTR("Checksum does not match"); kKXKextErrorKeySignature = CFSTR("Signature cannot be authenticated"); kKXKextErrorKeyDependenciesUnresolvable = CFSTR("Cannot resolve all dependencies"); kKXKextErrorKeyPossibleDependencyLoop = CFSTR("Too many dependencies; possible loop?"); kKXKextDependencyUnavailable = CFSTR("No valid version of this dependency can be found"); kKXKextDependencyNoCompatibleVersion = CFSTR("A valid compatible version of this dependency cannot be found"); kKXKextDependencyCompatibleVersionUndeclared = CFSTR("No dependency could be found that declares a compatible version"); kKXKextIndirectDependencyUnresolvable = CFSTR("This dependency has dependencies that cannot be resolved"); kKXKextDependencyCircularReference = CFSTR("This dependency may be causing a circular reference"); return; } /******************************************************************************* * *******************************************************************************/ static pthread_once_t initialized = PTHREAD_ONCE_INIT; static KXKextRef __KXKextCreatePrivate( CFAllocatorRef allocator, CFAllocatorContext * context) { KXKextRef newKext = NULL; void * offset = NULL; UInt32 size; /* initialize runtime */ pthread_once(&initialized, __KXKextInitialize); /* allocate session */ size = sizeof(__KXKext) - sizeof(CFRuntimeBase); newKext = (KXKextRef)_CFRuntimeCreateInstance(allocator, __kKXKextTypeID, size, NULL); if (!newKext) { return NULL; } offset = newKext; bzero(offset + sizeof(CFRuntimeBase), size); return (KXKextRef)newKext; } /******************************************************************************* * *******************************************************************************/ static CFStringRef __KXKextCopyDebugDescription(CFTypeRef cf) { CFAllocatorRef allocator = CFGetAllocator(cf); CFMutableStringRef result; result = CFStringCreateMutable(allocator, 0); CFStringAppendFormat(result, NULL, CFSTR(" {\n"), cf, allocator); // add useful stuff here CFStringAppendFormat(result, NULL, CFSTR("}")); return result; } /******************************************************************************* * *******************************************************************************/ static void __KXKextResetTestFlags(KXKextRef aKext) { aKext->logLevel = 0; aKext->flags.declaresExecutable = 0; aKext->flags.isKernelResource = 0; aKext->flags.isValid = 0; aKext->flags.isEligibleDuringSafeBoot = 0; aKext->flags.isEnabled = 1; // note this is true by default aKext->flags.canAuthenticate = 1; // assume yes unless validation says no aKext->flags.hasBeenAuthenticated = 0; aKext->flags.isAuthentic = 0; aKext->flags.canResolveDependencies = 1; // assume yes unless validation says no aKext->flags.hasAllDependencies = 0; aKext->flags.isLoaded = 0; aKext->flags.otherVersionIsLoaded = 0; aKext->flags.loadFailed = 0; aKext->flags.hasIOKitDebugProperty = 0; return; } /******************************************************************************* * *******************************************************************************/ static void __KXKextInitBlank(KXKextRef aKext) { aKext->manager = NULL; aKext->repository = NULL; aKext->bundle = NULL; aKext->bundleURL = NULL; aKext->infoDictionary = NULL; aKext->bundleDirectoryName = NULL; aKext->bundlePathInRepository = NULL; aKext->plugins = NULL; aKext->container = NULL; __KXKextResetTestFlags(aKext); aKext->version = 0; aKext->compatibleVersion = 0; aKext->priorVersion = NULL; aKext->nextDuplicate = NULL; aKext->directDependencies = NULL; aKext->validationFailures = NULL; aKext->authenticationFailures = NULL; aKext->missingDependencies = NULL; return; } /******************************************************************************* * *******************************************************************************/ static void __KXKextReleaseContents(CFTypeRef cf) { KXKextRef aKext = (KXKextRef)cf; if (aKext->manager) { // manager is not retained aKext->manager = NULL; } if (aKext->repository) { // repository is not retained aKext->repository = NULL; } if (aKext->bundlePathInRepository) { CFRelease(aKext->bundlePathInRepository); aKext->bundlePathInRepository = NULL; } if (aKext->bundleDirectoryName) { CFRelease(aKext->bundleDirectoryName); aKext->bundleDirectoryName = NULL; } if (aKext->infoDictionary) { CFRelease(aKext->infoDictionary); aKext->infoDictionary = NULL; } if (aKext->container) { CFRelease(aKext->container); aKext->container = NULL; } _KXKextRemoveAllPlugins((KXKextRef)aKext); if (aKext->plugins) { CFRelease(aKext->plugins); aKext->plugins = NULL; } if (aKext->bundleURL) { CFRelease(aKext->bundleURL); aKext->bundleURL = NULL; } if (aKext->bundle) { CFRelease(aKext->bundle); aKext->bundle = NULL; } if (aKext->priorVersion) { CFRelease(aKext->priorVersion); aKext->priorVersion = NULL; } if (aKext->nextDuplicate) { CFRelease(aKext->nextDuplicate); aKext->nextDuplicate = NULL; } if (aKext->directDependencies) { CFRelease(aKext->directDependencies); aKext->directDependencies = NULL; } if (aKext->validationFailures) { CFRelease(aKext->validationFailures); aKext->validationFailures = NULL; } if (aKext->authenticationFailures) { CFRelease(aKext->authenticationFailures); aKext->authenticationFailures = NULL; } if (aKext->missingDependencies) { CFRelease(aKext->missingDependencies); aKext->missingDependencies = NULL; } return; } /******************************************************************************* * *******************************************************************************/ static Boolean __KXKextCheckLogLevel( KXKextRef aKext, SInt32 managerLogLevel, SInt32 kextLogLevel, Boolean exact) { int kmLogLevel = KXKextManagerGetLogLevel(aKext->manager); if (exact) { if (kmLogLevel == managerLogLevel || aKext->logLevel == kextLogLevel) { return true; } } else if (kmLogLevel >= managerLogLevel || aKext->logLevel >= kextLogLevel) { return true; } return false; } /******************************************************************************* * *******************************************************************************/ static KXKextManagerError __KXKextValidate(KXKextRef aKext) { KXKextManagerError result = kKXKextManagerErrorNone; CFStringRef propKey; // for HIGHLY LOCAL reuse (not more than 10 lines) CFStringRef stringValue; CFTypeRef rawValue; CFNumberRef numValue; CFMutableArrayRef propPathArray = NULL; // must release CFStringRef propPathString = NULL; // must release CFIndex numPersonalities = 0; CFIndex numPersonalitiesWithIOKitDebug = 0; CFTypeRef * personalityKeys = NULL; // must free CFTypeRef * personalityValues = NULL; // must free CFStringRef * keys = NULL; // must free CFMutableArrayRef illegalValueProperties = NULL; CFMutableArrayRef missingValueProperties = NULL; KXKextManagerError checkResult = kKXKextManagerErrorNone; volatile Boolean foundErrors = false; // only set to true below here UInt32 kernelLibVersion = 0; Boolean requiresNewKextManager = false; // FIXME: Error to have personality with bundle's CFBundleIdentifier // FIXME: ...but no executable in bundle. // clear these flags just in case __KXKextResetTestFlags(aKext); if (!__KXKextGetOrCreateValidationFailures(aKext)) { if (!aKext->validationFailures) { result = kKXKextManagerErrorNoMemory; goto finish; } } CFDictionaryRemoveAllValues(aKext->validationFailures); /***** * This is used to add nested property keys to the error dictionaries. */ propPathArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (!propPathArray) { result = kKXKextManagerErrorNoMemory; goto finish; // this is a fatal error } /***** * Validation of the info dictionary. No filesystem access! *****/ /***** * Make sure there's an info dictionary and that it is a dictionary. */ if (!aKext->infoDictionary || CFGetTypeID(aKext->infoDictionary) != CFDictionaryGetTypeID()) { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyNotABundle, kCFBooleanTrue); result = kKXKextManagerErrorNotABundle; aKext->flags.canAuthenticate = 0; foundErrors = true; // This is an immediate stop, so ignore // KXKextManagerPerformsFullTests(). goto finish; } /***** * Check that property CFBundleIdentifier is present and is a string. * It must also not be too long. */ if (!__KXKextCheckStringProperty(aKext, aKext->infoDictionary, CFSTR("CFBundleIdentifier"), NULL, true, NULL, &stringValue)) { result = kKXKextManagerErrorValidation; foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) goto finish; } if (stringValue && CFStringGetLength(stringValue) > KMOD_MAX_NAME - 1) { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyIdentifierOrVersionTooLong, kCFBooleanTrue); result = kKXKextManagerErrorValidation; foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) goto finish; } /***** * Check that property CFBundlePackageType is "KEXT", but only if we're * from a bundle. A kext from a cache omits this property. */ if (!KXKextIsFromCache(aKext) && !__KXKextCheckStringProperty(aKext, aKext->infoDictionary, CFSTR("CFBundlePackageType"), NULL, true, CFSTR("KEXT"), NULL)) { result = kKXKextManagerErrorNotAKext; foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) goto finish; } /***** * Look for the OSBundleDebugLevel flag and read it. */ rawValue = CFDictionaryGetValue(aKext->infoDictionary, CFSTR("OSBundleDebugLevel")); if (rawValue) { Boolean numError = false; if (CFGetTypeID(rawValue) == CFNumberGetTypeID()) { numValue = (CFNumberRef)rawValue; if (!CFNumberGetValue(numValue, kCFNumberSInt32Type, &aKext->logLevel)) { numError = true; } } else { numError = true; } if (numError) { CFMutableArrayRef illegalTypeProperties = __KXKextGetIllegalTypeProperties(aKext); if (!illegalTypeProperties) { result = kKXKextManagerErrorNoMemory; goto finish; } CFArrayAppendValue(illegalTypeProperties, CFSTR("OSBundleDebugLevel")); result = kKXKextManagerErrorValidation; foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) goto finish; } } #if 0 // This property is omitted from caches and cannot be validated. // CFBundle should be doing this anyhow, and it doesn't impact // any functionality of a kext. /***** * Check that property CFBundleSignature is a string. */ if (!__KXKextCheckStringProperty(aKext, aKext->infoDictionary, CFSTR("CFBundleSignature"), NULL, true, NULL, NULL)) { result = kKXKextManagerErrorValidation; foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) goto finish; } #endif 0 /***** * Check validity of property CFBundleVersion. */ propKey = CFSTR("CFBundleVersion"); if (!__KXKextCheckVersionProperty(aKext, aKext->infoDictionary, propKey, NULL, true, &aKext->version)) { result = kKXKextManagerErrorValidation; foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) goto finish; } /***** * Check property OSBundleCompatibleVersion. * This is not required, but if it is present, its value must be legal. */ propKey = CFSTR("OSBundleCompatibleVersion"); if (!__KXKextCheckVersionProperty(aKext, aKext->infoDictionary, propKey, NULL, false, &aKext->compatibleVersion)) { result = kKXKextManagerErrorValidation; foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) goto finish; } if (aKext->compatibleVersion > aKext->version) { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyCompatibleVersionLaterThanVersion, kCFBooleanTrue); result = kKXKextManagerErrorValidation; foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) goto finish; } /***** * Check that CFBundleExecutable is a string. */ propKey = CFSTR("CFBundleExecutable"); if (!__KXKextCheckStringProperty(aKext, aKext->infoDictionary, propKey, NULL, false, NULL, &stringValue)) { result = kKXKextManagerErrorValidation; foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) goto finish; } if (!stringValue) { aKext->flags.declaresExecutable = 0; } else { aKext->flags.declaresExecutable = 1; } /***** * Check for a kernel pseudo-kext. These don't have dependencies. */ propKey = CFSTR("OSKernelResource"); if (!__KXKextCheckPropertyType(aKext, aKext->infoDictionary, propKey, NULL, false, CFBooleanGetTypeID(), &rawValue)) { result = kKXKextManagerErrorValidation; foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) goto finish; } if (rawValue) { aKext->flags.isKernelResource = CFBooleanGetValue((CFBooleanRef)rawValue) ? 1 : 0; aKext->flags.hasAllDependencies = aKext->flags.isKernelResource; } /***** * Check property IOKitPersonalities. This is required for proper I/O * kit driver matching, but not for the kext itself to be valid. A * diagnostic tool will just have to check explicitly for the presence * of personalities and show a caution/warning if there are none. */ propKey = CFSTR("IOKitPersonalities"); /* Push the personalities key onto the end of the path. */ CFArrayAppendValue(propPathArray, propKey); if (!__KXKextCheckPropertyType(aKext, aKext->infoDictionary, propKey, NULL, false, CFDictionaryGetTypeID(), &rawValue)) { result = kKXKextManagerErrorValidation; foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) goto finish; } else if (rawValue) { /***** * Check that all values in IOKitPersonalities are Dicts. */ CFDictionaryRef personalitiesDict = (CFDictionaryRef)rawValue; CFIndex i; numPersonalities = CFDictionaryGetCount(personalitiesDict); personalityKeys = (CFTypeRef *)malloc(numPersonalities * sizeof(CFTypeRef)); if (! personalityKeys) { result = kKXKextManagerErrorNoMemory; goto finish; // this is a fatal error } personalityValues = (CFTypeRef *)malloc(numPersonalities * sizeof(CFTypeRef)); if (!personalityValues) { result = kKXKextManagerErrorNoMemory; goto finish; // this is a fatal error } CFDictionaryGetKeysAndValues(personalitiesDict, (const void **)personalityKeys, (const void **)personalityValues); for (i = 0; i < numPersonalities; i++) { CFStringRef thisPersonalityName = personalityKeys[i]; CFTypeRef thisPersonality = personalityValues[i]; CFDictionaryRef thisPersonalityDict; if (CFGetTypeID(thisPersonality) != CFDictionaryGetTypeID()) { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyPersonalitiesNotNested, kCFBooleanTrue); result = kKXKextManagerErrorValidation; foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) { goto finish; } } else { /* Push the personality name onto the end of the path. */ CFArrayAppendValue(propPathArray, thisPersonalityName); thisPersonalityDict = (CFDictionaryRef)thisPersonality; /***** * Make sure the personality has an IOClass * string property. */ propKey = CFSTR("IOClass"); CFArrayAppendValue(propPathArray, propKey); if (!__KXKextCheckStringProperty(aKext, thisPersonality, propKey, propPathArray, true, NULL, NULL)) { result = kKXKextManagerErrorValidation; foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) { goto finish; } } CFArrayRemoveValueAtIndex(propPathArray, CFArrayGetCount(propPathArray) - 1); /***** * Make sure the personality has an IOProviderClass * string property. */ propKey = CFSTR("IOProviderClass"); CFArrayAppendValue(propPathArray, propKey); if (!__KXKextCheckStringProperty(aKext, thisPersonality, propKey, propPathArray, true, NULL, NULL)) { result = kKXKextManagerErrorValidation; foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) { goto finish; } } CFArrayRemoveValueAtIndex(propPathArray, CFArrayGetCount(propPathArray) - 1); /***** * Make sure the personality has a CFBundleIdentifier * string property. */ // FIXME: Should just add bundle's own CFBundleIdentifier // FIXME: ...to this personality if the prop isn't there. // FIXME: ...See the fixme below. propKey = CFSTR("CFBundleIdentifier"); CFArrayAppendValue(propPathArray, propKey); if (!__KXKextCheckStringProperty(aKext, thisPersonality, propKey, propPathArray, false, NULL, NULL)) { result = kKXKextManagerErrorValidation; foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) { goto finish; } } CFArrayRemoveValueAtIndex(propPathArray, CFArrayGetCount(propPathArray) - 1); /***** * Check that the IOKitDebug property, if present, is a number and * is not a float type. Then, if its value is nonzero, increment * the number of personalities known to have debug set so that we can * check later whether the kext as a whole is eligible for safe boot. */ propKey = CFSTR("IOKitDebug"); CFArrayAppendValue(propPathArray, propKey); if (!__KXKextCheckPropertyType(aKext, thisPersonality, propKey, propPathArray, false, CFNumberGetTypeID(), &rawValue)) { result = kKXKextManagerErrorValidation; foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) goto finish; } else if (rawValue) { CFNumberRef number = (CFNumberRef)rawValue; /* The fact that this is blatantly invalid is checked below * by __KXKextCheckPersonalityTypes(). */ if (!CFNumberIsFloatType(rawValue)) { long long int numValue = 0; if (!CFNumberGetValue(number, kCFNumberLongLongType, &numValue)) { (_KMErrLog(aKext->manager))( "error reading IOKitDebug property"); foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) { goto finish; } } if (numValue != 0) { numPersonalitiesWithIOKitDebug++; aKext->flags.hasIOKitDebugProperty = 1; } } } /* Pop the personality name of the end of the path. */ CFArrayRemoveValueAtIndex(propPathArray, CFArrayGetCount(propPathArray) - 1); /****** * Make sure that only kernel-supported plist types are present * in the personality. */ if (!__KXKextCheckPersonalityTypes(aKext, thisPersonality, propPathArray)) { result = kKXKextManagerErrorValidation; foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) { goto finish; } } /* Pop the personality name of the end of the path. */ CFArrayRemoveValueAtIndex(propPathArray, CFArrayGetCount(propPathArray) - 1); } } } /* Empty the property path for subsequent uses. */ CFArrayRemoveAllValues(propPathArray); /***** * Check property OSBundleLibraries and the version values of each of * its entries. Libraries must be declared for any non-kernel kext that * declares an executable. */ propKey = CFSTR("OSBundleLibraries"); /* Push the libarries key onto the end of the path. */ CFArrayAppendValue(propPathArray, propKey); if (!__KXKextCheckPropertyType(aKext, aKext->infoDictionary, propKey, NULL, (!aKext->flags.isKernelResource && aKext->flags.declaresExecutable), CFDictionaryGetTypeID(), &rawValue)) { result = kKXKextManagerErrorValidation; foundErrors = true; aKext->flags.canResolveDependencies = 0; if (!KXKextManagerPerformsFullTests(aKext->manager)) goto finish; } else if (rawValue) { CFDictionaryRef bundleLibraries = (CFDictionaryRef)rawValue; CFIndex numLibraries, i; numLibraries = CFDictionaryGetCount(bundleLibraries); if (!numLibraries) { result = kKXKextManagerErrorValidation; foundErrors = true; aKext->flags.canResolveDependencies = 0; missingValueProperties = __KXKextGetMissingProperties(aKext); if (!missingValueProperties) { result = kKXKextManagerErrorNoMemory; goto finish; } CFArrayAppendValue(missingValueProperties, propKey); if (!KXKextManagerPerformsFullTests(aKext->manager)) { goto finish; } } keys = (CFStringRef *)malloc(numLibraries * sizeof(CFStringRef)); if (!keys) { result = kKXKextManagerErrorNoMemory; goto finish; } CFDictionaryGetKeysAndValues(bundleLibraries, (const void **)keys, NULL); /***** * Prep the value used to check whether this kext can be * used on versions of Mac OS X prior to Jaguar. * FIXME: Change that version reference. */ if (!VERS_parse_string("6.0", &kernelLibVersion)) { result = kKXKextManagerErrorUnspecified; goto finish; } for (i = 0; i < numLibraries; i++) { CFStringRef dependencyID = keys[i]; UInt32 version; CFArrayAppendValue(propPathArray, dependencyID); if (!__KXKextCheckVersionProperty(aKext, bundleLibraries, dependencyID, propPathArray, true, &version)) { result = kKXKextManagerErrorValidation; foundErrors = true; aKext->flags.canResolveDependencies = 0; if (!KXKextManagerPerformsFullTests(aKext->manager)) { goto finish; } } else if (CFStringHasPrefix(dependencyID, CFSTR("com.apple.kernel"))) { if (version >= kernelLibVersion) { requiresNewKextManager = true; } } CFArrayRemoveValueAtIndex(propPathArray, CFArrayGetCount(propPathArray) - 1); } } CFArrayRemoveAllValues(propPathArray); propKey = CFSTR("OSBundleRequired"); if (!__KXKextCheckStringProperty(aKext, aKext->infoDictionary, propKey, NULL, false, NULL, &stringValue)) { result = kKXKextManagerErrorValidation; foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) goto finish; } else if (stringValue) { CFStringRef str = stringValue; if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("Root"), 0) || kCFCompareEqualTo == CFStringCompare(str, CFSTR("Local-Root"), 0) || kCFCompareEqualTo == CFStringCompare(str, CFSTR("Network-Root"), 0) || kCFCompareEqualTo == CFStringCompare(str, CFSTR("Console"), 0) || kCFCompareEqualTo == CFStringCompare(str, CFSTR("Safe Boot"), 0) ) { aKext->flags.isEligibleDuringSafeBoot = 1; } else { illegalValueProperties = __KXKextGetIllegalValueProperties(aKext); if (!illegalValueProperties) { result = kKXKextManagerErrorNoMemory; goto finish; } CFArrayAppendValue(illegalValueProperties, propKey); result = kKXKextManagerErrorValidation; foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) goto finish; } } /***** * We just checked for OSBundleRequired, which can set the kext's * isEligibleDuringSafeBoot, however, if all personalities have the * IOKitDebug property set to nonzero, we turn it right back off. */ if (numPersonalities > 0 && numPersonalities == numPersonalitiesWithIOKitDebug) { aKext->flags.isEligibleDuringSafeBoot = 0; } /***** * Validation that may access the filesystem, *only* if doing * a full diagnostic. *****/ /***** * If doing a full diagnostic check that the kext's executable exists * and has the right kmod name and version within. * * This new kext manager's loading code writes into the kmod's kmod_info * struct the CFBundleIdentifier and CFBundleVersion from the info * dictionary, so this test is only for sanity checking kexts that * will be used on versions of Mac OS X that have the old kext manager. */ if (KXKextManagerPerformsFullTests(aKext->manager)) { checkResult = __KXKextRealizeFromCache(aKext); if (checkResult != kKXKextManagerErrorNone) { result = checkResult; foundErrors = true; goto finish; // can't guarantee we can check executable so stop } if (aKext->flags.declaresExecutable) { checkResult = __KXKextValidateExecutable(aKext, requiresNewKextManager); if (checkResult != kKXKextManagerErrorNone) { result = checkResult; foundErrors = true; if (!KXKextManagerPerformsFullTests(aKext->manager)) goto finish; } } } /***** * Final marking of validity. *****/ if (!foundErrors) { aKext->flags.isValid = 1; } finish: if (result != kKXKextManagerErrorNone) { if (__KXKextCheckLogLevel(aKext, kKXKextManagerLogLevelBasic, kKXKextLogLevelBasic, false)) { const char * kext_name = _KXKextCopyCanonicalPathnameAsCString(aKext); // must free (_KMErrLog(aKext->manager))( "kext %s is not valid", kext_name ? kext_name : "(unknown)"); if (kext_name) free((char *)kext_name); } } if (propPathArray) CFRelease(propPathArray); if (propPathString) CFRelease(propPathString); if (personalityKeys) free(personalityKeys); if (personalityValues) free(personalityValues); if (keys) free(keys); return result; } /******************************************************************************* * *******************************************************************************/ static KXKextManagerError __KXKextValidateExecutable(KXKextRef aKext, Boolean requiresNewKextManager) { KXKextManagerError result = kKXKextManagerErrorNone; KXKextManagerError checkResult = kKXKextManagerErrorNone; CFURLRef executableURL = NULL; // must release checkResult = __KXKextRealizeFromCache(aKext); if (checkResult != kKXKextManagerErrorNone) { result = checkResult; // there can be no further test at this point goto finish; } executableURL = CFBundleCopyExecutableURL(aKext->bundle); if (!executableURL) { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyMissingExecutable, kCFBooleanTrue); result = kKXKextManagerErrorValidation; if (!KXKextManagerPerformsFullTests(aKext->manager)) { goto finish; } } else { checkResult = __KXKextCheckKmod(aKext, executableURL, requiresNewKextManager); if (checkResult != kKXKextManagerErrorNone) { result = checkResult; if (!KXKextManagerPerformsFullTests(aKext->manager)) { goto finish; } } } finish: if (executableURL) CFRelease(executableURL); return result; } /******************************************************************************* * *******************************************************************************/ static CFMutableDictionaryRef __KXKextGetOrCreateValidationFailures(KXKextRef aKext) { if (!aKext->validationFailures) { aKext->validationFailures = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); } return aKext->validationFailures; } /******************************************************************************* * *******************************************************************************/ static CFMutableDictionaryRef __KXKextGetOrCreateAuthenticationFailures(KXKextRef aKext) { if (!aKext->authenticationFailures) { aKext->authenticationFailures = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); } return aKext->authenticationFailures; } /******************************************************************************* * *******************************************************************************/ static CFMutableArrayRef __KXKextGetMissingProperties(KXKextRef aKext) { CFMutableDictionaryRef failures = NULL; CFMutableArrayRef theMissingProperties = NULL; failures = __KXKextGetOrCreateValidationFailures(aKext); if (!failures) { return NULL; } theMissingProperties = (CFMutableArrayRef) CFDictionaryGetValue(failures, kKXKextErrorKeyMissingProperty); if (!theMissingProperties) { theMissingProperties = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (!theMissingProperties) { return NULL; } CFDictionarySetValue(failures, kKXKextErrorKeyMissingProperty, theMissingProperties); CFRelease(theMissingProperties); } return theMissingProperties; } /******************************************************************************* * *******************************************************************************/ static CFMutableArrayRef __KXKextGetIllegalTypeProperties(KXKextRef aKext) { CFMutableDictionaryRef failures = NULL; CFMutableArrayRef theIllegalTypeProperties = NULL; failures = __KXKextGetOrCreateValidationFailures(aKext); if (!failures) { return NULL; } theIllegalTypeProperties = (CFMutableArrayRef) CFDictionaryGetValue(failures, kKXKextErrorKeyPropertyIsIllegalType); if (!theIllegalTypeProperties) { theIllegalTypeProperties = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (!theIllegalTypeProperties) { return NULL; } CFDictionarySetValue(failures, kKXKextErrorKeyPropertyIsIllegalType, theIllegalTypeProperties); CFRelease(theIllegalTypeProperties); } return theIllegalTypeProperties; } /******************************************************************************* * *******************************************************************************/ static CFMutableArrayRef __KXKextGetIllegalValueProperties(KXKextRef aKext) { CFMutableDictionaryRef failures = NULL; CFMutableArrayRef theIllegalValueProperties = NULL; failures = __KXKextGetOrCreateValidationFailures(aKext); if (!failures) { return NULL; } theIllegalValueProperties = (CFMutableArrayRef) CFDictionaryGetValue(failures, kKXKextErrorKeyPropertyIsIllegalValue); if (!theIllegalValueProperties) { theIllegalValueProperties = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (!theIllegalValueProperties) { return NULL; } CFDictionarySetValue(failures, kKXKextErrorKeyPropertyIsIllegalValue, theIllegalValueProperties); CFRelease(theIllegalValueProperties); } return theIllegalValueProperties; } /******************************************************************************* * *******************************************************************************/ static CFMutableArrayRef __KXKextGetMissingFiles(KXKextRef aKext) { CFMutableDictionaryRef failures = NULL; CFMutableArrayRef theArray = NULL; failures = __KXKextGetOrCreateAuthenticationFailures(aKext); if (!failures) { return NULL; } theArray = (CFMutableArrayRef) CFDictionaryGetValue(failures, kKXKextErrorKeyFileNotFound); if (!theArray) { theArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (!theArray) { return NULL; } CFDictionarySetValue(failures, kKXKextErrorKeyFileNotFound, theArray); CFRelease(theArray); } return theArray; } /******************************************************************************* * *******************************************************************************/ static CFMutableArrayRef __KXKextGetBadOwnerPermsFiles(KXKextRef aKext) { CFMutableDictionaryRef failures = NULL; CFMutableArrayRef theArray = NULL; failures = __KXKextGetOrCreateAuthenticationFailures(aKext); if (!failures) { return NULL; } theArray = (CFMutableArrayRef) CFDictionaryGetValue(failures, kKXKextErrorKeyOwnerPermission); if (!theArray) { theArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (!theArray) { return NULL; } CFDictionarySetValue(failures, kKXKextErrorKeyOwnerPermission, theArray); CFRelease(theArray); } return theArray; } /******************************************************************************* * *******************************************************************************/ static Boolean __KXKextCheckPropertyType( KXKextRef aKext, CFDictionaryRef aDictionary, CFStringRef propKey, CFArrayRef propPathArray, Boolean isRequired, CFTypeID expectedType, CFTypeRef * rawValueOut) { CFTypeRef rawValue; CFMutableArrayRef missingProperties; CFMutableArrayRef illegalTypeProperties; CFStringRef propPathString = NULL; // must release // FIXME: Can't bail caller on allocation failure! However, another // FIXME: ...alloc failure is sure to happen soon after so the caller // FIXME: ...will bail then. if (rawValueOut) * rawValueOut = NULL; rawValue = CFDictionaryGetValue(aDictionary, propKey); if (!rawValue) { if (!isRequired) { return true; } else { missingProperties = __KXKextGetMissingProperties(aKext); if (!missingProperties) { return false; } if (!propPathArray) { CFArrayAppendValue(missingProperties, propKey); } else { propPathString = CFStringCreateByCombiningStrings( kCFAllocatorDefault, propPathArray, CFSTR(":")); if (!propPathString) return NULL; CFArrayAppendValue(missingProperties, propPathString); CFRelease(propPathString); } return false; } } if (rawValueOut) *rawValueOut = rawValue; if (CFGetTypeID(rawValue) != expectedType) { illegalTypeProperties = __KXKextGetIllegalTypeProperties(aKext); if (! illegalTypeProperties) { return false; } if (!propPathArray) { CFArrayAppendValue(illegalTypeProperties, propKey); } else { propPathString = CFStringCreateByCombiningStrings( kCFAllocatorDefault, propPathArray, CFSTR(":")); if (!propPathString) return NULL; CFArrayAppendValue(illegalTypeProperties, propPathString); CFRelease(propPathString); } return false; } return true; } /******************************************************************************* * *******************************************************************************/ static Boolean __KXKextCheckStringProperty( KXKextRef aKext, CFDictionaryRef aDictionary, CFStringRef propKey, CFArrayRef propPathArray, Boolean isRequired, CFStringRef expectedValue, CFStringRef *stringValueOut) { CFTypeRef rawValue = NULL; CFStringRef stringValue = NULL; CFMutableArrayRef missingProperties; CFMutableArrayRef illegalTypeProperties; CFMutableArrayRef illegalValueProperties; CFStringRef propPathString = NULL; // must release // FIXME: Can't bail caller on allocation failure! However, another // FIXME: ...alloc failure is sure to happen soon after so the caller // FIXME: ...will bail then. if (stringValueOut) *stringValueOut = NULL; rawValue = CFDictionaryGetValue(aDictionary, propKey); if (!rawValue) { if (!isRequired) { return true; } else { missingProperties = __KXKextGetMissingProperties(aKext); if (!missingProperties) { return false; } if (!propPathArray) { CFArrayAppendValue(missingProperties, propKey); } else { propPathString = CFStringCreateByCombiningStrings( kCFAllocatorDefault, propPathArray, CFSTR(":")); if (!propPathString) return false; CFArrayAppendValue(missingProperties, propPathString); CFRelease(propPathString); } return false; } } if (CFGetTypeID(rawValue) != CFStringGetTypeID()) { illegalTypeProperties = __KXKextGetIllegalTypeProperties(aKext); if (!illegalTypeProperties) { return false; } if (!propPathArray) { CFArrayAppendValue(illegalTypeProperties, propKey); } else { propPathString = CFStringCreateByCombiningStrings( kCFAllocatorDefault, propPathArray, CFSTR(":")); if (!propPathString) return NULL; CFArrayAppendValue(illegalTypeProperties, propPathString); CFRelease(propPathString); } return false; } stringValue = (CFStringRef)rawValue; if (stringValueOut) *stringValueOut = stringValue; if (expectedValue) { if (CFStringCompare(expectedValue, stringValue, 0) != kCFCompareEqualTo) { illegalValueProperties = __KXKextGetIllegalValueProperties(aKext); if (!illegalValueProperties) { return false; } if (!propPathArray) { CFArrayAppendValue(illegalValueProperties, propKey); } else { propPathString = CFStringCreateByCombiningStrings( kCFAllocatorDefault, propPathArray, CFSTR(":")); if (!propPathString) return false; CFArrayAppendValue(illegalValueProperties, propPathString); CFRelease(propPathString); } return false; } } return true; } /******************************************************************************* * *******************************************************************************/ static Boolean __KXKextCheckVersionProperty( KXKextRef aKext, CFDictionaryRef aDictionary, CFStringRef propKey, CFArrayRef propPathArray, Boolean isRequired, UInt32 *version) { char vers_buffer[32]; // more than long enough for legal vers CFStringRef stringValue = NULL; CFStringRef propPathString = NULL; // must release CFMutableArrayRef illegalValueProperties = NULL; if (!__KXKextCheckStringProperty(aKext, aDictionary, propKey, propPathArray, isRequired, NULL, &stringValue)) { if (isRequired) return false; else return true; } if (!isRequired && !stringValue) { return true; } if (!CFStringGetCString(stringValue, vers_buffer, sizeof(vers_buffer) - 1, kCFStringEncodingMacRoman)) { illegalValueProperties = __KXKextGetIllegalValueProperties(aKext); if (!illegalValueProperties) return false; if (!propPathArray) { CFArrayAppendValue(illegalValueProperties, propKey); } else { propPathString = CFStringCreateByCombiningStrings( kCFAllocatorDefault, propPathArray, CFSTR(":")); if (!propPathString) return false; CFArrayAppendValue(illegalValueProperties, propPathString); CFRelease(propPathString); } return false; } else { vers_buffer[sizeof(vers_buffer) - 1] = '\0'; if (!VERS_parse_string(vers_buffer, version)) { illegalValueProperties = __KXKextGetIllegalValueProperties(aKext); if (! illegalValueProperties) return false; if (!propPathArray) { CFArrayAppendValue(illegalValueProperties, propKey); } else { propPathString = CFStringCreateByCombiningStrings( kCFAllocatorDefault, propPathArray, CFSTR(":")); if (!propPathString) return false; CFArrayAppendValue(illegalValueProperties, propPathString); CFRelease(propPathString); } return false; } } if (CFEqual(propKey, CFSTR("CFBundleVersion"))) { if (CFStringGetLength(stringValue) > KMOD_MAX_NAME - 1) { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyIdentifierOrVersionTooLong, kCFBooleanTrue); return false; } } return true; } /******************************************************************************* * *******************************************************************************/ static Boolean __KXKextCheckPersonalityTypes(KXKextRef aKext, CFTypeRef cfObj, CFMutableArrayRef propPathArray) { Boolean result = true; Boolean localResult = true; Boolean foundInvalidType = false; CFTypeID typeID; CFStringRef arrayIndexString = NULL; // must release CFStringRef propPathString = NULL; // must release CFMutableArrayRef illegalTypeProperties = NULL; // don't release CFStringRef * keys = NULL; // must free CFStringRef * values = NULL; // must free typeID = CFGetTypeID(cfObj); if (typeID == CFDictionaryGetTypeID()) { CFDictionaryRef dict = (CFDictionaryRef)cfObj; CFIndex count, i; count = CFDictionaryGetCount(dict); keys = (CFStringRef *)malloc(count * sizeof(CFStringRef)); values = (CFStringRef *)malloc(count * sizeof(CFTypeRef)); CFDictionaryGetKeysAndValues(dict, (const void **)keys, (const void **)values); for (i = 0; i < count; i++) { CFArrayAppendValue(propPathArray, keys[i]); localResult = __KXKextCheckPersonalityTypes(aKext, values[i], propPathArray); CFArrayRemoveValueAtIndex(propPathArray, CFArrayGetCount(propPathArray) - 1); if (!localResult) { result = false; if (!KXKextManagerPerformsFullTests(aKext->manager)) { goto finish; } } } } else if (typeID == CFArrayGetTypeID()) { CFArrayRef array = (CFArrayRef)cfObj; CFIndex count, i; count = CFArrayGetCount(array); for (i = 0; i < count; i++) { arrayIndexString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("[%d]"), i); if (!arrayIndexString) { result = false; goto finish; } CFArrayAppendValue(propPathArray, arrayIndexString); CFRelease(arrayIndexString); arrayIndexString = NULL; localResult = __KXKextCheckPersonalityTypes(aKext, CFArrayGetValueAtIndex(array, i), propPathArray); CFArrayRemoveValueAtIndex(propPathArray, CFArrayGetCount(propPathArray) - 1); if (!localResult) { result = false; if (!KXKextManagerPerformsFullTests(aKext->manager)) { goto finish; } } } } else if (typeID == CFStringGetTypeID() || typeID == CFDataGetTypeID() || typeID == CFBooleanGetTypeID()) { // these types are all valid atomic types; do nothing } else if (typeID == CFNumberGetTypeID()) { CFNumberRef number = (CFNumberRef)cfObj; if (CFNumberIsFloatType(number)) { foundInvalidType = true; } } else { foundInvalidType = true; } if (foundInvalidType) { // add entry to error dict illegalTypeProperties = __KXKextGetIllegalTypeProperties(aKext); if (!illegalTypeProperties) return false; propPathString = CFStringCreateByCombiningStrings( kCFAllocatorDefault, propPathArray, CFSTR(":")); if (!propPathString) return false; CFArrayAppendValue(illegalTypeProperties, propPathString); CFRelease(propPathString); propPathString = NULL; result = false; if (!KXKextManagerPerformsFullTests(aKext->manager)) { goto finish; } } finish: if (keys) free(keys); if (values) free(values); if (arrayIndexString) CFRelease(arrayIndexString); if (propPathString) CFRelease(propPathString); return result; } /******************************************************************************* * *******************************************************************************/ static KXKextManagerError __KXKextAuthenticateURLAndParents(KXKextRef aKext, CFURLRef anURL, CFURLRef topURL /* must be absolute */, CFMutableDictionaryRef checkedURLs) { KXKextManagerError result = kKXKextManagerErrorNone; KXKextManagerError checkResult = kKXKextManagerErrorNone; CFURLRef backscanURL = NULL; // must release char * anURL_path = NULL; // must free char * topURL_path = NULL; // must free backscanURL = PATH_CopyCanonicalizedURL(anURL); if (!backscanURL) { result = kKXKextManagerErrorNoMemory; goto finish; } // CHECK THAT anURL is contained by topURL anURL_path = PATH_CanonicalizedCStringForURL(backscanURL); topURL_path = PATH_CanonicalizedCStringForURL(topURL); if (strncmp(anURL_path, topURL_path, strlen(topURL_path))) { (_KMErrLog(aKext->manager))("\"%s\" is not contained within \"%s\"", anURL_path, topURL_path); result = kKXKextManagerErrorUnspecified; goto finish; } while (!CFEqual(backscanURL, topURL)) { CFURLRef parentURL = NULL; // must release /* Only check this URL if we haven't already done so. */ if (!CFDictionaryGetValue(checkedURLs, backscanURL)) { if (__KXKextCheckLogLevel(aKext, kKXKextManagerLogLevelKextDetails, kKXKextLogLevelDetails, false)) { const char * file_path = PATH_CanonicalizedCStringForURL(backscanURL); if (file_path) { (_KMLog(aKext->manager))( "authenticating file/directory \"%s\"", file_path); free((char *)file_path); } } checkResult = __KXKextAuthenticateURL(aKext, backscanURL); if (checkResult != kKXKextManagerErrorNone) { result = checkResult; if (result == kKXKextManagerErrorNoMemory || !KXKextManagerPerformsFullTests(aKext->manager)) { goto finish; } } } /* Note that we've checked this URL. */ CFDictionarySetValue(checkedURLs, backscanURL, kCFBooleanTrue); parentURL = CFURLCreateCopyDeletingLastPathComponent( kCFAllocatorDefault, backscanURL); if (!parentURL) { result = kKXKextManagerErrorUnspecified; goto finish; } CFRelease(backscanURL); backscanURL = parentURL; parentURL = NULL; } finish: if (backscanURL) CFRelease(backscanURL); if (anURL_path) free(anURL_path); if (topURL_path) free(topURL_path); return result; } /******************************************************************************* * *******************************************************************************/ static KXKextManagerError __KXKextAuthenticateURL(KXKextRef aKext, CFURLRef anURL) { KXKextManagerError result = kKXKextManagerErrorNone; CFMutableArrayRef missingFiles = NULL; CFMutableArrayRef badOwnerPermsFiles = NULL; CFURLRef absURL = NULL; // must release CFStringRef urlPath = NULL; // must release char path[MAXPATHLEN]; struct stat stat_buf; absURL = PATH_CopyCanonicalizedURL(anURL); if (!absURL) { result = kKXKextManagerErrorNoMemory; goto finish; } urlPath = CFURLCopyFileSystemPath(absURL, kCFURLPOSIXPathStyle); if (!urlPath) { result = kKXKextManagerErrorNoMemory; goto finish; } if (!CFStringGetCString(urlPath, path, sizeof(path), kCFStringEncodingMacRoman)) { result = kKXKextManagerErrorNoMemory; goto finish; } if (stat(path, &stat_buf) != 0) { if (errno == ENOENT) { missingFiles = __KXKextGetMissingFiles(aKext); if (missingFiles && kCFNotFound == CFArrayGetFirstIndexOfValue(missingFiles, CFRangeMake(0, CFArrayGetCount(missingFiles)), urlPath)) { CFArrayAppendValue(missingFiles, urlPath); } result = kKXKextManagerErrorValidation; goto finish; } else { perror(path); result = kKXKextManagerErrorUnspecified; goto finish; } } /* File/dir must be owned by root and not writeable by others, * and if not owned by gid 0 then not group-writeable. */ if ( (stat_buf.st_uid != 0) || (stat_buf.st_gid != 0 ) || (stat_buf.st_mode & S_IWOTH) || (stat_buf.st_mode & S_IWGRP) ) { badOwnerPermsFiles = __KXKextGetBadOwnerPermsFiles(aKext); if (badOwnerPermsFiles && kCFNotFound == CFArrayGetFirstIndexOfValue(badOwnerPermsFiles, CFRangeMake(0, CFArrayGetCount(badOwnerPermsFiles)), urlPath)) { CFArrayAppendValue(badOwnerPermsFiles, urlPath); } result = kKXKextManagerErrorAuthentication; goto finish; } finish: if (absURL) CFRelease(absURL); if (urlPath) CFRelease(urlPath); return result; } /******************************************************************************* * *******************************************************************************/ static KXKextManagerError __KXKextMakeFTSEntrySecure( KXKextRef aKext, FTSENT * ftsentry) { KXKextManagerError result = kKXKextManagerErrorNone; int change_result = 0; mode_t fixed_mode = 0; change_result = chown(ftsentry->fts_path, 0, 0); // root, wheel if (change_result != 0) { (_KMErrLog(aKext->manager))("can't change ownership/group of %s", ftsentry->fts_path); result = kKXKextManagerErrorFileAccess; goto finish; } if (ftsentry->fts_statp->st_mode & S_IFDIR) { fixed_mode = 0755; // octal } else { fixed_mode = 0644; // octal } change_result = chmod(ftsentry->fts_path, fixed_mode); if (change_result != 0) { (_KMErrLog(aKext->manager))("can't change permissions on %s", ftsentry->fts_path); result = kKXKextManagerErrorFileAccess; goto finish; } finish: return result; } /******************************************************************************* * *******************************************************************************/ static KXKextManagerError __KXKextCheckKmod(KXKextRef aKext, CFURLRef kmodURL, Boolean requiresNewKextManager) { KXKextManagerError result = kKXKextManagerErrorNone; CFURLRef absoluteKmodURL = 0; // must release int fd = -1; mach_port_t host_port = PORT_NULL; struct stat stat_buf; long object_addr; long object_size; struct mach_header *mh; kmod_info_t * kmod_info; CFStringRef executablePath = 0; // must release char module_path[MAXPATHLEN]; UInt32 version; CFStringRef bundleIdentifier = 0; // don't release CFStringRef kmodName = 0; // must release struct nlist nl[] = { { "_kmod_info" }, { "" }, }; absoluteKmodURL = PATH_CopyCanonicalizedURL(kmodURL); if (!absoluteKmodURL) { result = kKXKextManagerErrorNoMemory; goto finish; } executablePath = CFURLCopyFileSystemPath(absoluteKmodURL, kCFURLPOSIXPathStyle); if (!executablePath) { result = kKXKextManagerErrorNoMemory; goto finish; } if (!CFStringGetCString(executablePath, module_path, sizeof(module_path) - 1, kCFStringEncodingMacRoman)) { result = kKXKextManagerErrorNoMemory; goto finish; } if ((fd = open(module_path, O_RDONLY)) == -1) { (_KMErrLog(aKext->manager))("can't open %s", module_path); CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyFileAccess, kCFBooleanTrue); result = kKXKextManagerErrorFileAccess; goto finish; } if (nlist(module_path, nl)) { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyExecutableBad, kCFBooleanTrue); result = kKXKextManagerErrorValidation; goto finish; } /***** * If the kext requires Jaguar or later, we don't have to inspect * the MODULE_NAME and MODULE_VERSION fields of the kmod_info struct. * FIXME: change version reference */ if (requiresNewKextManager) { goto finish; } if (fstat(fd, &stat_buf) == -1) { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyFileAccess, kCFBooleanTrue); result = kKXKextManagerErrorFileAccess; goto finish; // can't make any more checks } object_size = stat_buf.st_size; if (map_fd(fd, 0, (vm_offset_t *)&object_addr, TRUE, object_size) != KERN_SUCCESS) { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyFileAccess, kCFBooleanTrue); result = kKXKextManagerErrorFileAccess; goto finish; // can't make any more checks } if (NXSwapBigLongToHost(*((long *)object_addr)) == FAT_MAGIC) { struct host_basic_info hbi; struct fat_header *fh; struct fat_arch *fat_archs, *fap; unsigned i, nfat_arch; /* Get our host info */ i = HOST_BASIC_INFO_COUNT; host_port = mach_host_self(); if (host_info(host_port, HOST_BASIC_INFO, (host_info_t)(&hbi), &i) != KERN_SUCCESS) { result = kKXKextManagerErrorUnspecified; goto finish; // can't make any more checks } // get number of architectures fh = (struct fat_header *)object_addr; nfat_arch = NXSwapBigLongToHost(fh->nfat_arch); // find beginning of fat_arch struct fat_archs = (struct fat_arch *)((char *)fh + sizeof(struct fat_header)); /* * Convert archs to host byte ordering (a constraint of * cpusubtype_getbestarch() */ for (i = 0; i < nfat_arch; i++) { fat_archs[i].cputype = NXSwapBigLongToHost(fat_archs[i].cputype); fat_archs[i].cpusubtype = NXSwapBigLongToHost(fat_archs[i].cpusubtype); fat_archs[i].offset = NXSwapBigLongToHost(fat_archs[i].offset); fat_archs[i].size = NXSwapBigLongToHost(fat_archs[i].size); fat_archs[i].align = NXSwapBigLongToHost(fat_archs[i].align); } // this code was lifted from Darwin/Libraries/NeXT/libc/gen.subproj/nlist.c // when cpusubtype_getbestarch exists this code should also be changed. #define CPUSUBTYPE_SUPPORT 0 #if CPUSUBTYPE_SUPPORT fap = cpusubtype_getbestarch(hbi.cpu_type, hbi.cpu_subtype, fat_archs, nfat_arch); #else CPUSUBTYPE_SUPPORT #warning Use the cpusubtype functions!!! fap = NULL; for (i = 0; i < nfat_arch; i++) { if (fat_archs[i].cputype == hbi.cpu_type) { fap = &fat_archs[i]; break; } } #endif CPUSUBTYPE_SUPPORT if (!fap) { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyExecutableBadArch, kCFBooleanTrue); result = kKXKextManagerErrorValidation; goto finish; // no point checking a bad executable } object_addr += fap->offset; object_size = fap->size; } mh = (struct mach_header *)object_addr; if (*((long *)mh) != MH_MAGIC) { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyExecutableBad, kCFBooleanTrue); result = kKXKextManagerErrorValidation; goto finish; // no point checking a bad executable } kmod_info = (kmod_info_t *)(object_addr + sizeof(struct mach_header) + mh->sizeofcmds + nl->n_value); bundleIdentifier = (CFStringRef)CFDictionaryGetValue(aKext->infoDictionary, CFSTR("CFBundleIdentifier")); kmodName = CFStringCreateWithCString(kCFAllocatorDefault, kmod_info->name, kCFStringEncodingMacRoman); if (!kmodName) { result = kKXKextManagerErrorNoMemory; goto finish; } if (CFStringCompare(bundleIdentifier , kmodName, NULL) != kCFCompareEqualTo) { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyBundleIdentifierMismatch, kCFBooleanTrue); result = kKXKextManagerErrorValidation; if (!KXKextManagerPerformsFullTests(aKext->manager)) goto finish; } if (VERS_parse_string(kmod_info->version, &version)) { if (aKext->version != version) { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyBundleVersionMismatch, kCFBooleanTrue); result = kKXKextManagerErrorValidation; if (!KXKextManagerPerformsFullTests(aKext->manager)) goto finish; } } else { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyBundleVersionMismatch, kCFBooleanTrue); result = kKXKextManagerErrorValidation; goto finish; } finish: /* Dispose of the host port to prevent security breaches and port * leaks. We don't care about the kern_return_t value of this * call for now as there's nothing we can do if it fails. */ if (PORT_NULL != host_port) { mach_port_deallocate(mach_task_self(), host_port); } if (fd != -1) close(fd); if (absoluteKmodURL) CFRelease(absoluteKmodURL); if (executablePath) CFRelease(executablePath); if (kmodName) CFRelease(kmodName); return result; } /******************************************************************************* * *******************************************************************************/ static KXKextManagerError __KXKextResolveDependencies(KXKextRef aKext, unsigned int recursionDepth) { KXKextManagerError result = kKXKextManagerErrorNone; KXKextManagerRef manager = KXKextGetManager(aKext); CFIndex count, i; CFDictionaryRef libraries = NULL; CFStringRef * libraryIDs = NULL; // must free CFStringRef * libraryVersions = NULL; // must free KXKextManagerError localResult = kKXKextManagerErrorNone; if (!aKext->flags.canResolveDependencies) { const char * kext_name = _KXKextCopyCanonicalPathnameAsCString(aKext); // must free if (kext_name && __KXKextCheckLogLevel(aKext, kKXKextManagerLogLevelKextDetails, kKXKextLogLevelDetails, false)) { (_KMErrLog(aKext->manager))( "%s has validation failures that prevent resolution of dependencies", kext_name); } result = kKXKextManagerErrorUnspecified; goto finish; } /* A kext that's compiled into the kernel trivially has all its dependencies met. */ if (aKext->flags.isKernelResource) { if (__KXKextCheckLogLevel(aKext, kKXKextManagerLogLevelKextDetails, kKXKextLogLevelDetails, false)) { const char * kext_name = _KXKextCopyCanonicalPathnameAsCString(aKext); // must free const char * message; message = "%s is a kernel resource and thus has no dependencies"; if (kext_name) { (_KMLog(aKext->manager))(message, kext_name); } if (kext_name) free((char *)kext_name); } aKext->flags.hasAllDependencies = 1; result = kKXKextManagerErrorNone; goto finish; } /* If we've already done the work, return immediately. */ if (aKext->flags.hasAllDependencies) { if (__KXKextCheckLogLevel(aKext, kKXKextManagerLogLevelKextDetails, kKXKextLogLevelDetails, false)) { const char * kext_name = _KXKextCopyCanonicalPathnameAsCString(aKext); // must releast if (kext_name) { (_KMLog(aKext->manager))( "extension %s has already resolved its dependencies", kext_name); } if (kext_name) free((char *)kext_name); } result = kKXKextManagerErrorNone; goto finish; } /* If we hit a depth this big, there's probably a loop. Bail. */ if (recursionDepth > 255) { CFDictionarySetValue(__KXKextGetOrCreateValidationFailures(aKext), kKXKextErrorKeyPossibleDependencyLoop, kCFBooleanTrue); result = kKXKextManagerErrorDependencyLoop; goto finish; } /***** * Otherwise clear out any partial dependency information, * including info on missing dependencies. */ if (aKext->directDependencies) { CFArrayRemoveAllValues(aKext->directDependencies); } else { aKext->directDependencies = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (!aKext->directDependencies) { result = kKXKextManagerErrorNoMemory; goto finish; } } if (aKext->missingDependencies) { CFDictionaryRemoveAllValues(aKext->missingDependencies); } else { aKext->missingDependencies = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!aKext->missingDependencies) { result = kKXKextManagerErrorNoMemory; goto finish; } } /***** * Now get to work. Start with this kext's dependencies. */ libraries = CFDictionaryGetValue(aKext->infoDictionary, CFSTR("OSBundleLibraries")); if (!libraries) { result = kKXKextManagerErrorValidation; goto finish; } /* Prepare to iterate the dependecy entries. */ count = CFDictionaryGetCount(libraries); if (!count) { // this should of course never happen! result = kKXKextManagerErrorValidation; goto finish; } libraryIDs = (CFStringRef *)malloc(count * sizeof(CFStringRef)); libraryVersions = (CFStringRef *)malloc(count * sizeof(CFStringRef)); if (!libraryIDs || !libraryVersions) { result = kKXKextManagerErrorNoMemory; goto finish; } CFDictionaryGetKeysAndValues(libraries, (const void **)libraryIDs, (const void **)libraryVersions); for (i = 0; i < count; i++) { KXKextRef thisDependency; if (__KXKextCheckLogLevel(aKext, kKXKextManagerLogLevelKextDetails, kKXKextLogLevelDetails, false)) { Boolean got_id, got_vers; char dep_id[255]; char dep_vers[255]; const char * kext_name = _KXKextCopyCanonicalPathnameAsCString(aKext); // must releast got_id = CFStringGetCString(libraryIDs[i], dep_id, sizeof(dep_id) - 1, kCFStringEncodingMacRoman); got_vers = CFStringGetCString(libraryVersions[i], dep_vers, sizeof(dep_vers) - 1, kCFStringEncodingMacRoman); if (got_id && got_vers && kext_name) { (_KMLog(aKext->manager))( "looking for dependency of extension %s with ID %s, " "compatible with version %s", kext_name, dep_id, dep_vers); } if (kext_name) free((char *)kext_name); } /* Find a kext compatible with the needed version. */ thisDependency = KXKextManagerGetKextWithIdentifierCompatibleWithVersionString( manager, libraryIDs[i], libraryVersions[i]); if (thisDependency) { if (__KXKextCheckLogLevel(aKext, kKXKextManagerLogLevelKextDetails, kKXKextLogLevelDetails, false)) { const char * kext_name = _KXKextCopyCanonicalPathnameAsCString(aKext); const char * dep_name = _KXKextCopyCanonicalPathnameAsCString(thisDependency); if (kext_name && dep_name) { (_KMLog(aKext->manager))( "found compatible dependency from extension %s to %s; " "resolving its dependencies", kext_name, dep_name); } if (kext_name) free((char *)kext_name); if (dep_name) free((char *)dep_name); } /* Found the current one; have it resolve its own dependencies. */ localResult = __KXKextResolveDependencies(thisDependency, recursionDepth + 1); if (localResult == kKXKextManagerErrorNone) { /* No error; so add the dependency to the direct list. */ CFArrayAppendValue(aKext->directDependencies, thisDependency); } else { if (__KXKextCheckLogLevel(aKext, kKXKextManagerLogLevelKextDetails, kKXKextLogLevelDetails, false)) { const char * kext_name = _KXKextCopyCanonicalPathnameAsCString(aKext); const char * dep_name = _KXKextCopyCanonicalPathnameAsCString(thisDependency); if (kext_name && dep_name) { (_KMLog(aKext->manager))( "failed to resolve dependencies for extension %s", dep_name); } if (kext_name) free((char *)kext_name); if (dep_name) free((char *)dep_name); } /* Oops, couldn't do it. Although we did find this dependency, * add its ID to the list of missing dependencies as having * unresolvable dependencies of its own. */ CFDictionarySetValue(aKext->missingDependencies, libraryIDs[i], kKXKextIndirectDependencyUnresolvable); if (!KXKextManagerPerformsFullTests(aKext->manager)) { goto finish; } } } else { /* Didn't find a direct dependency, so the show's over for this kext * (unless doing a full diagnostic, in which case keep trying the others). */ KXKextRef incompatibleDependency = KXKextManagerGetKextWithIdentifier(manager, libraryIDs[i]); if (incompatibleDependency) { if (!_KXKextGetCompatibleVersion(incompatibleDependency)) { CFDictionarySetValue(aKext->missingDependencies, libraryIDs[i], kKXKextDependencyCompatibleVersionUndeclared); } else { CFDictionarySetValue(aKext->missingDependencies, libraryIDs[i], kKXKextDependencyNoCompatibleVersion); } if (__KXKextCheckLogLevel(aKext, kKXKextManagerLogLevelKextDetails, kKXKextLogLevelDetails, false)) { const char * kext_name = _KXKextCopyCanonicalPathnameAsCString(aKext); char dep_id[255]; if (kext_name && CFStringGetCString(libraryIDs[i], dep_id, sizeof(dep_id) - 1, kCFStringEncodingMacRoman)) { (_KMLog(aKext->manager))( "can't resolve dependency from %s to ID %s; " "no compatible version of ID %s is known", kext_name, dep_id, dep_id); } if (kext_name) free((char *)kext_name); } } else { // no dependency found at all CFDictionarySetValue(aKext->missingDependencies, libraryIDs[i], kKXKextDependencyUnavailable); if (__KXKextCheckLogLevel(aKext, kKXKextManagerLogLevelKextDetails, kKXKextLogLevelDetails, false)) { const char * kext_name = _KXKextCopyCanonicalPathnameAsCString(aKext); char dep_id[255]; if (kext_name && CFStringGetCString(libraryIDs[i], dep_id, sizeof(dep_id) - 1, kCFStringEncodingMacRoman)) { (_KMErrLog(aKext->manager))( "can't resolve dependency from extension %s to ID %s; " "no extension with ID %s is known", kext_name, dep_id, dep_id); } if (kext_name) free((char *)kext_name); } } result = kKXKextManagerErrorDependency; if (!KXKextManagerPerformsFullTests(aKext->manager)) { goto finish; } } } if (result == kKXKextManagerErrorNone) { aKext->flags.hasAllDependencies = 1; } finish: if (libraryIDs) free(libraryIDs); if (libraryVersions) free(libraryVersions); return result; } /******************************************************************************* * *******************************************************************************/ static char * __KXKextCopyDgraphEntryName(KXKextRef aKext) { KXKextManagerError checkResult = kKXKextManagerErrorNone; CFStringRef bundleID = NULL; // don't release CFURLRef executableURL = NULL; // must release char * kmod_path = NULL; // don't free (alias for entry_name) char * bundle_identifier = NULL; // don't free (alias for entry_name) char * entry_name = NULL; // returned size_t size; int error = 0; checkResult = __KXKextRealizeFromCache(aKext); if (checkResult != kKXKextManagerErrorNone) { // there can be no further test at this point error = 1; goto finish; } if (KXKextGetIsKernelResource(aKext)) { bundleID = KXKextGetBundleIdentifier(aKext); size = (sizeof(char) * MAXPATHLEN); bundle_identifier = (char *)malloc(size); if (!bundle_identifier) { error = 1; goto finish; } if (!CFStringGetCString(bundleID, bundle_identifier, size - 1, kCFStringEncodingMacRoman)) { error = 1; goto finish; } entry_name = bundle_identifier; } else { executableURL = CFBundleCopyExecutableURL(aKext->bundle); if (!executableURL) { error = 1; goto finish; } kmod_path = PATH_CanonicalizedCStringForURL(executableURL); if (!kmod_path) { error = 1; goto finish; } entry_name = kmod_path; } finish: if (error && entry_name) { free(entry_name); entry_name = NULL; } if (executableURL) CFRelease(executableURL); return entry_name; } /******************************************************************************* * *******************************************************************************/ static char * __KXKextCopyDgraphKmodName(KXKextRef aKext) { CFStringRef bundleID = NULL; // don't release char * bundle_identifier = NULL; // returned CFIndex length; int error = 0; bundleID = KXKextGetBundleIdentifier(aKext); length = 1 + CFStringGetLength(bundleID); bundle_identifier = (char *)malloc(length); if (!bundle_identifier) { goto finish; } if (!CFStringGetCString(bundleID, bundle_identifier, length, kCFStringEncodingMacRoman)) { error = 1; goto finish; } finish: if (error && bundle_identifier) { free(bundle_identifier); bundle_identifier = NULL; } return bundle_identifier; } /******************************************************************************* * *******************************************************************************/ static Boolean __KXKextAddDependenciesToDgraph(KXKextRef aKext, CFArrayRef dependencies, dgraph_t * dgraph) { Boolean result = true; char * entry_name = NULL; // must free char * expected_kmod_name = NULL; // must free dgraph_entry_t * aKext_entry = NULL; // don't free CFIndex count, i; if (KXKextGetDeclaresExecutable(aKext)) { entry_name = __KXKextCopyDgraphEntryName(aKext); if (!entry_name) { result = false; goto finish; } expected_kmod_name = __KXKextCopyDgraphKmodName(aKext); if (!expected_kmod_name) { result = false; goto finish; } // add aKext as dependent aKext_entry = dgraph_add_dependent(dgraph, entry_name, expected_kmod_name, aKext->version, /* load address */ 0, KXKextGetIsKernelResource(aKext)); if (!aKext_entry) { result = false; goto finish; } free(entry_name); entry_name = NULL; free(expected_kmod_name); expected_kmod_name = NULL; } if (!dependencies) { goto finish; } count = CFArrayGetCount(dependencies); for (i = 0; i < count; i++) { KXKextRef thisKext = (KXKextRef)CFArrayGetValueAtIndex( dependencies, i); /* If this kext has an executable, insert it into the dgraph * as a dependency of aKext. */ if (KXKextGetDeclaresExecutable(thisKext)) { entry_name = __KXKextCopyDgraphEntryName(thisKext); if (!entry_name) { result = false; goto finish; } if (expected_kmod_name) { free(expected_kmod_name); expected_kmod_name = NULL; } expected_kmod_name = __KXKextCopyDgraphKmodName(thisKext); if (!expected_kmod_name) { result = false; goto finish; } if (!dgraph_add_dependency(dgraph, aKext_entry, entry_name, expected_kmod_name, thisKext->version, /* load address */ 0, KXKextGetIsKernelResource(thisKext))) { result = false; goto finish; } /* Add the dependencies of a kext with an executable to the dgraph. */ if (!__KXKextAddDependenciesToDgraph(thisKext, KXKextGetDirectDependencies(thisKext), dgraph)) { result = false; goto finish; } free(entry_name); entry_name = NULL; } else { /* For a dependency kext that has no executable, add its dependencies * as if they were direct dependencies of aKext. */ if (!__KXKextAddDependenciesToDgraph(aKext, KXKextGetDirectDependencies(thisKext), dgraph)) { result = false; goto finish; } } } finish: if (expected_kmod_name) free(expected_kmod_name); if (entry_name) free(entry_name); return result; } /******************************************************************************* * *******************************************************************************/ static void __KXKextAddDependenciesToArray(KXKextRef aKext, CFMutableArrayRef dependencyArray) { if (aKext->directDependencies) { CFIndex dCount, dIndex; CFIndex aCount, aIndex; /***** * First have all dependencies add their own dependencies * to the array so the array is built in load order. */ dCount = CFArrayGetCount(aKext->directDependencies); for (dIndex = 0; dIndex < dCount; dIndex ++) { KXKextRef thisDependency = (KXKextRef)CFArrayGetValueAtIndex( aKext->directDependencies, dIndex); __KXKextAddDependenciesToArray(thisDependency, dependencyArray); } /***** * Now add this kext's direct dependencies, but only if they * aren't already in the list. */ aCount = CFArrayGetCount(dependencyArray); for (dIndex = 0; dIndex < dCount; dIndex ++) { Boolean gotItAlready = false; KXKextRef thisDependency = (KXKextRef)CFArrayGetValueAtIndex( aKext->directDependencies, dIndex); for (aIndex = 0; aIndex < aCount; aIndex ++) { KXKextRef checkDependency = (KXKextRef)CFArrayGetValueAtIndex( dependencyArray, aIndex); if (thisDependency == checkDependency) { gotItAlready = true; break; } } if (!gotItAlready) { CFArrayAppendValue(dependencyArray, thisDependency); } } } return; } /******************************************************************************* * *******************************************************************************/ KXKextManagerError __KXKextRealizeFromCache(KXKextRef aKext) { KXKextManagerError result = kKXKextManagerErrorNone; CFStringRef absBundlePath = NULL; // must release CFDictionaryRef bundleInfoDictionary = NULL; // don't release CFDictionaryRef comparisonInfoDictionary = NULL; // must release if (aKext->bundle) goto finish; aKext->bundle = CFBundleCreate(kCFAllocatorDefault, aKext->bundleURL); if (!aKext->bundle) { // If the bundle couldn't be created we don't know why // without further investigation. We could be out of memory; // the entry in the filesystem might not exist; we might not // have access to it; the URL given may not point at a proper // bundle. // // FIXME? error for "from cache, no bundle"? // result = kKXKextManagerErrorCache; goto finish; } bundleInfoDictionary = CFBundleGetInfoDictionary(aKext->bundle); comparisonInfoDictionary = __KXKextCopyInfoDictionaryForCache( aKext, bundleInfoDictionary, false /* sub keys */ ); if (!comparisonInfoDictionary) { result = kKXKextManagerErrorNoMemory; goto finish; } if (!CFEqual(aKext->infoDictionary, comparisonInfoDictionary)) { result = kKXKextManagerErrorCache; goto finish; } CFRelease(aKext->infoDictionary); // save a little memory aKext->infoDictionary = bundleInfoDictionary; // not the comparison dict! CFRetain(bundleInfoDictionary); /***** * Check that property CFBundlePackageType is "KEXT". This is the only * property that we validate that isn't in a cached infoDictionary. */ if (!KXKextIsFromCache(aKext) && !__KXKextCheckStringProperty(aKext, aKext->infoDictionary, CFSTR("CFBundlePackageType"), NULL, true, CFSTR("KEXT"), NULL)) { result = kKXKextManagerErrorCache; goto finish; } finish: if (absBundlePath) CFRelease(absBundlePath); if (comparisonInfoDictionary) CFRelease(comparisonInfoDictionary); return result; } /******************************************************************************* * *******************************************************************************/ typedef struct cacheKeySubs { CFStringRef longKey; CFStringRef shortKey; Boolean required; } __KXKextCacheKeySubs; #define NUM_INFO_DICT_SUB_KEYS (14) static __KXKextCacheKeySubs __gInfoDictSubKeys[NUM_INFO_DICT_SUB_KEYS]; #if 0 #define NUM_PERSONALITY_SUB_KEYS (4) static __KXKextCacheKeySubs __gPersonalitySubKeys[NUM_PERSONALITY_SUB_KEYS]; #endif 0 static void __initSubKeys(void) { static Boolean didIt = false; if (didIt) return; __gInfoDictSubKeys[0].longKey = CFSTR("CFBundleInfoDictionaryVersion"); __gInfoDictSubKeys[0].shortKey = CFSTR("d"); __gInfoDictSubKeys[0].required = true; __gInfoDictSubKeys[1].longKey = CFSTR("CFBundleGetInfoString"); __gInfoDictSubKeys[1].shortKey = CFSTR("g"); __gInfoDictSubKeys[1].required = false; __gInfoDictSubKeys[2].longKey = CFSTR("CFBundleIdentifier"); __gInfoDictSubKeys[2].shortKey = CFSTR("i"); __gInfoDictSubKeys[2].required = true; __gInfoDictSubKeys[3].longKey = CFSTR("CFBundleExecutable"); __gInfoDictSubKeys[3].shortKey = CFSTR("x"); __gInfoDictSubKeys[3].required = false; __gInfoDictSubKeys[4].longKey = CFSTR("CFBundleName"); __gInfoDictSubKeys[4].shortKey = CFSTR("n"); __gInfoDictSubKeys[4].required = false; __gInfoDictSubKeys[5].longKey = CFSTR("CFBundleShortVersionString"); __gInfoDictSubKeys[5].shortKey = CFSTR("s"); __gInfoDictSubKeys[5].required = false; __gInfoDictSubKeys[6].longKey = CFSTR("CFBundleVersion"); __gInfoDictSubKeys[6].shortKey = CFSTR("v"); __gInfoDictSubKeys[6].required = true; __gInfoDictSubKeys[7].longKey = CFSTR("NSHumanReadableCopyright"); __gInfoDictSubKeys[7].shortKey = CFSTR("c"); __gInfoDictSubKeys[7].required = false; __gInfoDictSubKeys[8].longKey = CFSTR("OSBundleCompatibleVersion"); __gInfoDictSubKeys[8].shortKey = CFSTR("cv"); __gInfoDictSubKeys[8].required = false; __gInfoDictSubKeys[9].longKey = CFSTR("OSBundleRequired"); __gInfoDictSubKeys[9].shortKey = CFSTR("r"); __gInfoDictSubKeys[9].required = false; __gInfoDictSubKeys[10].longKey = CFSTR("IOKitPersonalities"); __gInfoDictSubKeys[10].shortKey = CFSTR("p"); __gInfoDictSubKeys[10].required = false; __gInfoDictSubKeys[11].longKey = CFSTR("OSBundleLibraries"); __gInfoDictSubKeys[11].shortKey = CFSTR("l"); __gInfoDictSubKeys[11].required = false; __gInfoDictSubKeys[12].longKey = CFSTR("OSKernelResource"); __gInfoDictSubKeys[12].shortKey = CFSTR("k"); __gInfoDictSubKeys[12].required = false; __gInfoDictSubKeys[13].longKey = CFSTR("OSBundleDebugLevel"); __gInfoDictSubKeys[13].shortKey = CFSTR("db"); __gInfoDictSubKeys[13].required = false; #if 0 // Can't do this because there are other entries in a personality and // they can have just about any key. __gPersonalitySubKeys[0].longKey = CFSTR("CFBundleIdentifier"); __gPersonalitySubKeys[0].shortKey = CFSTR("i"); __gPersonalitySubKeys[0].required = false; __gPersonalitySubKeys[1].longKey = CFSTR("IOClass"); __gPersonalitySubKeys[1].shortKey = CFSTR("ioc"); __gPersonalitySubKeys[1].required = false; __gPersonalitySubKeys[2].longKey = CFSTR("IONameMatch"); __gPersonalitySubKeys[2].shortKey = CFSTR("ionm"); __gPersonalitySubKeys[2].required = false; __gPersonalitySubKeys[3].longKey = CFSTR("IOProviderClass"); __gPersonalitySubKeys[3].shortKey = CFSTR("iopc"); __gPersonalitySubKeys[3].required = false; #endif 0 didIt = true; return; } static Boolean __KXKextCacheEntry( KXKextRef aKext, CFDictionaryRef infoDict, CFMutableDictionaryRef cDict, unsigned subKeyIndex, Boolean makeSubstitutions) { Boolean result = true; Boolean error = false; CFTypeRef infoDictValue = NULL; // don't release CFTypeRef cDictEntry = NULL; // must release __initSubKeys(); infoDictValue = CFDictionaryGetValue(infoDict, __gInfoDictSubKeys[subKeyIndex].longKey); if (!infoDictValue) { if (__gInfoDictSubKeys[subKeyIndex].required) { result = false; } goto finish; } cDictEntry = __KXKextCopyPListForCache(aKext, infoDictValue, &error); if (error) { result = false; goto finish; } if (cDictEntry) { if (makeSubstitutions) { CFDictionarySetValue(cDict, __gInfoDictSubKeys[subKeyIndex].shortKey, cDictEntry); } else { CFDictionarySetValue(cDict, __gInfoDictSubKeys[subKeyIndex].longKey, cDictEntry); } CFRelease(cDictEntry); } finish: return result; } static Boolean __KXKextUncacheEntry( KXKextRef aKext, CFDictionaryRef cDict, CFMutableDictionaryRef infoDict, unsigned subKeyIndex, Boolean makeSubstitutions) { Boolean result = true; CFTypeRef infoDictValue = NULL; // don't release __initSubKeys(); if (makeSubstitutions) { infoDictValue = CFDictionaryGetValue(cDict, __gInfoDictSubKeys[subKeyIndex].shortKey); } else { infoDictValue = CFDictionaryGetValue(cDict, __gInfoDictSubKeys[subKeyIndex].longKey); } if (!infoDictValue) { if (__gInfoDictSubKeys[subKeyIndex].required) { result = false; } goto finish; } CFDictionarySetValue(infoDict, __gInfoDictSubKeys[subKeyIndex].longKey, infoDictValue); finish: return result; } CFDictionaryRef __KXKextCopyInfoDictionaryFromCache( KXKextRef aKext, CFDictionaryRef cDict, Boolean makeSubstitutions) { CFMutableDictionaryRef infoDict = NULL; // returned Boolean error = false; unsigned int keyIndex; __initSubKeys(); if (!cDict) { goto finish; } infoDict = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!infoDict) { goto finish; } for (keyIndex = 0; keyIndex < NUM_INFO_DICT_SUB_KEYS; keyIndex++) { if (!__KXKextUncacheEntry(aKext, cDict, infoDict, keyIndex, makeSubstitutions)) { error = true; goto finish; } } finish: if (error) { if (infoDict) CFRelease(infoDict); infoDict = NULL; } return infoDict; } CFDictionaryRef __KXKextCopyInfoDictionaryForCache( KXKextRef aKext, CFDictionaryRef infoDict, Boolean makeSubstitutions) { CFMutableDictionaryRef cDict = NULL; // returned Boolean error = false; unsigned int keyIndex; __initSubKeys(); if (!infoDict) { goto finish; } cDict = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!cDict) { goto finish; } for (keyIndex = 0; keyIndex < NUM_INFO_DICT_SUB_KEYS; keyIndex++) { if (!__KXKextCacheEntry(aKext, infoDict, cDict, keyIndex, makeSubstitutions)) { error = true; goto finish; } } finish: if (error) { if (cDict) CFRelease(cDict); cDict = NULL; } return cDict; } /******************************************************************************* * *******************************************************************************/ CFTypeRef __KXKextCopyPListForCache( KXKextRef aKext, CFTypeRef pList, Boolean * error) { CFTypeRef cList = NULL; // returned CFTypeID typeID = NULL; if (!pList) { goto finish; } if (error) { *error = false; } typeID = CFGetTypeID(pList); if (typeID == CFDictionaryGetTypeID()) { CFDictionaryRef dict = (CFDictionaryRef)pList; CFMutableDictionaryRef newDict = NULL; // returned CFIndex count, i; CFStringRef * keys = NULL; // must free CFStringRef * values = NULL; // must free count = CFDictionaryGetCount(dict); keys = (CFStringRef *)malloc(count * sizeof(CFStringRef)); values = (CFStringRef *)malloc(count * sizeof(CFTypeRef)); newDict = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!newDict) { if (error) { *error = true; } goto finish; } cList = newDict; CFDictionaryGetKeysAndValues(dict, (const void **)keys, (const void **)values); for (i = 0; i < count; i++) { CFTypeRef entry = __KXKextCopyPListForCache(aKext, values[i], error); if (error && *error) { CFRelease(newDict); cList = NULL; goto finish; } if (entry) { CFDictionarySetValue(newDict, keys[i], entry); CFRelease(entry); } } free(keys); free(values); goto finish; } else if (typeID == CFArrayGetTypeID()) { CFArrayRef array = (CFArrayRef)pList; CFMutableArrayRef newArray = NULL; // returned CFIndex count, i; count = CFArrayGetCount(array); newArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (!newArray) { if (error) { *error = true; } goto finish; } cList = newArray; for (i = 0; i < count; i++) { CFTypeRef entry = __KXKextCopyPListForCache(aKext, CFArrayGetValueAtIndex(array, i), error); if (error && *error) { CFRelease(newArray); cList = NULL; goto finish; } if (entry) { CFArrayAppendValue(newArray, entry); CFRelease(entry); } } goto finish; } else if (typeID == CFStringGetTypeID() || typeID == CFDataGetTypeID() || typeID == CFNumberGetTypeID() || typeID == CFBooleanGetTypeID() || typeID == CFDateGetTypeID()) { cList = pList; CFRetain(cList); // caller will have to release goto finish; } else { cList = NULL; goto finish; } finish: return cList; }