/* * Copyright (c) 2008 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ #include #include #include "kextd_usernotification.h" #include "security.h" //#include // xxx - do we want a new log activity flag? #ifdef NO_CFUserNotification void kextd_raise_notification( CFStringRef alertHeader, CFArrayRef alertMessageArray) { return; } #else SCDynamicStoreRef sSysConfigDynamicStore = NULL; uid_t sConsoleUser = (uid_t)-1; CFRunLoopSourceRef sNotificationQueueRunLoopSource = NULL; // must release CFMutableArrayRef sPendedNonsecureKextPaths = NULL; // must release CFUserNotificationRef sNonSecureNotification = NULL; // must release CFRunLoopSourceRef sNonSecureNotificationRunLoopSource = NULL; // must release CFMutableDictionaryRef sNotifiedNonsecureKextPaths = NULL; // must release CFMutableArrayRef sPendedRevokedCertKextPaths = NULL; // must release CFUserNotificationRef sRevokedCertNotification = NULL; // must release CFRunLoopSourceRef sRevokedCertNotificationRunLoopSource = NULL; // must release #if 0 // not yet CFMutableArrayRef sPendedUnsignedKextPaths = NULL; // must release CFUserNotificationRef sUnsignedKextNotification = NULL; // must release CFRunLoopSourceRef sUnsignedKextNotificationRunLoopSource = NULL; // must release #endif CFMutableArrayRef sPendedNoLoadKextPaths = NULL; // must release CFUserNotificationRef sNoLoadKextNotification = NULL; // must release CFRunLoopSourceRef sNoLoadKextNotificationRunLoopSource = NULL; // must release CFMutableArrayRef sPendedInvalidSignedKextPaths = NULL; // must release CFUserNotificationRef sInvalidSigNotification = NULL; // must release CFRunLoopSourceRef sInvalidSigNotificationRunLoopSource = NULL; // must release CFMutableArrayRef sPendedExcludedKextPaths = NULL; // must release CFUserNotificationRef sExcludedKextNotification = NULL; // must release CFRunLoopSourceRef sExcludedKextNotificationRunLoopSource = NULL; // must release CFDictionaryRef sKextTranslationsPlist = NULL; static void _sessionDidChange( SCDynamicStoreRef store, CFArrayRef changedKeys, void * info); void _checkNotificationQueue(void * info); void _notificationDismissed( CFUserNotificationRef userNotification, CFOptionFlags responseFlags); static CFStringRef createBundleMappingKey( CFStringRef theBundleID ); static Boolean isInAlertsSentArray( CFArrayRef theSentArray, CFDictionaryRef theDict, CFStringRef theMappingKey ); static CFStringRef getKextAlertMessage( CFDictionaryRef theDict, CFStringRef theMappingKey ); static Boolean sendKextAlertNotifications( CFMutableArrayRef * theSentAlertsArrayPtr, CFArrayRef theKextsArray, int theAlertType ); static CFStringRef createPathFromAlertType( CFStringRef theVolRoot, int theAlertType ); static Boolean doingSystemInstall(void); static void kextd_raise_nonsecure_notification( CFStringRef alertHeader, CFArrayRef alertMessageArray ); static void kextd_raise_noload_notification( CFStringRef alertHeader, CFArrayRef alertMessageArray ); static void kextd_raise_invalidsig_notification( CFStringRef alertHeader, CFArrayRef alertMessageArray ); static void kextd_raise_revokedcert_notification( CFStringRef alertHeader, CFArrayRef alertMessageArray ); static void revealInFinder( CFArrayRef theArray ); #if 0 // not yet static void kextd_raise_unsignedkext_notification( CFStringRef alertHeader, CFArrayRef alertMessageArray ); #endif static void kextd_raise_excludedkext_notification( CFStringRef alertHeader, CFArrayRef alertMessageArray ); /******************************************************************************* *******************************************************************************/ ExitStatus startMonitoringConsoleUser( KextdArgs * toolArgs, unsigned int * sourcePriority) { ExitStatus result = EX_OSERR; CFStringRef consoleUserName = NULL; // must release CFStringRef consoleUserKey = NULL; // must release CFMutableArrayRef keys = NULL; // must release CFRunLoopSourceRef sysConfigRunLoopSource = NULL; // must release CFRunLoopSourceContext sourceContext; sSysConfigDynamicStore = SCDynamicStoreCreate( kCFAllocatorDefault, CFSTR(KEXTD_SERVER_NAME), _sessionDidChange, /* context */ NULL); if (!sSysConfigDynamicStore) { OSKextLogMemError(); goto finish; } consoleUserName = SCDynamicStoreCopyConsoleUser(sSysConfigDynamicStore, &sConsoleUser, NULL); if (!consoleUserName) { sConsoleUser = (uid_t)-1; OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogGeneralFlag, "No user logged in at kextd startup."); } else { OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogGeneralFlag, "User %d logged in at kextd startup.", sConsoleUser); } consoleUserKey = SCDynamicStoreKeyCreateConsoleUser(kCFAllocatorDefault); if (!consoleUserKey) { OSKextLogMemError(); goto finish; } keys = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (!keys) { OSKextLogMemError(); goto finish; } CFArrayAppendValue(keys, consoleUserKey); SCDynamicStoreSetNotificationKeys(sSysConfigDynamicStore, keys, /* patterns */ NULL); sysConfigRunLoopSource = SCDynamicStoreCreateRunLoopSource( kCFAllocatorDefault, sSysConfigDynamicStore, 0); if (!sysConfigRunLoopSource) { OSKextLogMemError(); goto finish; } CFRunLoopAddSource(CFRunLoopGetCurrent(), sysConfigRunLoopSource, kCFRunLoopCommonModes); bzero(&sourceContext, sizeof(CFRunLoopSourceContext)); sourceContext.version = 0; sourceContext.perform = _checkNotificationQueue; sNotificationQueueRunLoopSource = CFRunLoopSourceCreate(kCFAllocatorDefault, (*sourcePriority)++, &sourceContext); if (!sNotificationQueueRunLoopSource) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Failed to create alert run loop source."); goto finish; } CFRunLoopAddSource(CFRunLoopGetCurrent(), sNotificationQueueRunLoopSource, kCFRunLoopDefaultMode); if (!createCFMutableArray(&sPendedNonsecureKextPaths, &kCFTypeArrayCallBacks)) { OSKextLogMemError(); goto finish; } if (!createCFMutableArray(&sPendedNoLoadKextPaths, &kCFTypeArrayCallBacks)) { OSKextLogMemError(); goto finish; } if (!createCFMutableArray(&sPendedRevokedCertKextPaths, &kCFTypeArrayCallBacks)) { OSKextLogMemError(); goto finish; } #if 0 // not yet if (!createCFMutableArray(&sPendedUnsignedKextPaths, &kCFTypeArrayCallBacks)) { OSKextLogMemError(); goto finish; } #endif if (!createCFMutableArray(&sPendedInvalidSignedKextPaths, &kCFTypeArrayCallBacks)) { OSKextLogMemError(); goto finish; } if (!createCFMutableArray(&sPendedExcludedKextPaths, &kCFTypeArrayCallBacks)) { OSKextLogMemError(); goto finish; } if (!createCFMutableDictionary(&sNotifiedNonsecureKextPaths)) { OSKextLogMemError(); goto finish; } result = EX_OK; finish: SAFE_RELEASE(consoleUserName); SAFE_RELEASE(consoleUserKey); SAFE_RELEASE(keys); SAFE_RELEASE(sysConfigRunLoopSource); return result; } /******************************************************************************* *******************************************************************************/ void stopMonitoringConsoleUser(void) { SAFE_RELEASE(sSysConfigDynamicStore); SAFE_RELEASE(sNotificationQueueRunLoopSource); SAFE_RELEASE(sPendedNonsecureKextPaths); SAFE_RELEASE(sPendedNoLoadKextPaths); SAFE_RELEASE(sPendedRevokedCertKextPaths); SAFE_RELEASE(sPendedInvalidSignedKextPaths); // SAFE_RELEASE(sPendedUnsignedKextPaths); SAFE_RELEASE(sPendedExcludedKextPaths); SAFE_RELEASE(sNotifiedNonsecureKextPaths); return; } /******************************************************************************* *******************************************************************************/ static void _sessionDidChange( SCDynamicStoreRef store, CFArrayRef changedKeys, void * info) { CFStringRef consoleUserName = NULL; // must release uid_t oldUser = sConsoleUser; /* If any users are logged on via fast user switching, logging out to * loginwindow sets the console user to 0 (root). We can't do a reset * until all users have fully logged out, in which case * SCDynamicStoreCopyConsoleUser() returns NULL. */ consoleUserName = SCDynamicStoreCopyConsoleUser(sSysConfigDynamicStore, &sConsoleUser, NULL); if (!consoleUserName) { if (oldUser != (uid_t)-1) { OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogGeneralFlag, "User %d logged out.", oldUser); } sConsoleUser = (uid_t)-1; resetUserNotifications(/* dismissAlert */ true); goto finish; } /* Sometimes we'll get >1 notification on a user login, so make sure * the old & new uid are different. */ if (sConsoleUser != (uid_t)-1 && oldUser != sConsoleUser) { OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogGeneralFlag, "User %d logged in.", sConsoleUser); CFRunLoopSourceSignal(sNotificationQueueRunLoopSource); CFRunLoopWakeUp(CFRunLoopGetCurrent()); } finish: SAFE_RELEASE(consoleUserName); return; } /******************************************************************************* *******************************************************************************/ void resetUserNotifications(Boolean dismissAlert) { OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogGeneralFlag, "Resetting user notifications."); if (dismissAlert) { /* Release any reference to any pending user notifications. */ if (sNonSecureNotification) { CFUserNotificationCancel(sNonSecureNotification); CFRelease(sNonSecureNotification); sNonSecureNotification = NULL; } if (sNonSecureNotificationRunLoopSource) { CFRunLoopRemoveSource(CFRunLoopGetCurrent(), sNonSecureNotificationRunLoopSource, kCFRunLoopDefaultMode); CFRelease(sNonSecureNotificationRunLoopSource); sNonSecureNotificationRunLoopSource = NULL; } if (sNoLoadKextNotification) { CFUserNotificationCancel(sNoLoadKextNotification); CFRelease(sNoLoadKextNotification); sNoLoadKextNotification = NULL; } if (sNoLoadKextNotificationRunLoopSource) { CFRunLoopRemoveSource(CFRunLoopGetCurrent(), sNoLoadKextNotificationRunLoopSource, kCFRunLoopDefaultMode); CFRelease(sNoLoadKextNotificationRunLoopSource); sNoLoadKextNotificationRunLoopSource = NULL; } if (sRevokedCertNotification) { CFUserNotificationCancel(sRevokedCertNotification); CFRelease(sRevokedCertNotification); sRevokedCertNotification = NULL; } if (sRevokedCertNotificationRunLoopSource) { CFRunLoopRemoveSource(CFRunLoopGetCurrent(), sRevokedCertNotificationRunLoopSource, kCFRunLoopDefaultMode); CFRelease(sRevokedCertNotificationRunLoopSource); sRevokedCertNotificationRunLoopSource = NULL; } #if 0 // not yet if (sUnsignedKextNotification) { CFUserNotificationCancel(sUnsignedKextNotification); CFRelease(sUnsignedKextNotification); sUnsignedKextNotification = NULL; } if (sUnsignedKextNotificationRunLoopSource) { CFRunLoopRemoveSource(CFRunLoopGetCurrent(), sUnsignedKextNotificationRunLoopSource, kCFRunLoopDefaultMode); CFRelease(sUnsignedKextNotificationRunLoopSource); sUnsignedKextNotificationRunLoopSource = NULL; } #endif if (sInvalidSigNotification) { CFUserNotificationCancel(sInvalidSigNotification); CFRelease(sInvalidSigNotification); sInvalidSigNotification = NULL; } if (sInvalidSigNotificationRunLoopSource) { CFRunLoopRemoveSource(CFRunLoopGetCurrent(), sInvalidSigNotificationRunLoopSource, kCFRunLoopDefaultMode); CFRelease(sInvalidSigNotificationRunLoopSource); sInvalidSigNotificationRunLoopSource = NULL; } if (sExcludedKextNotification) { CFUserNotificationCancel(sExcludedKextNotification); CFRelease(sExcludedKextNotification); sExcludedKextNotification = NULL; } if (sExcludedKextNotificationRunLoopSource) { CFRunLoopRemoveSource(CFRunLoopGetCurrent(), sExcludedKextNotificationRunLoopSource, kCFRunLoopDefaultMode); CFRelease(sExcludedKextNotificationRunLoopSource); sExcludedKextNotificationRunLoopSource = NULL; } } /* Clear the record of which kexts the user has been told are insecure. * If extensions folders have been modified, who knows which kexts are changed? * If user is logging out, logging back in will get the same alerts. */ CFArrayRemoveAllValues(sPendedNonsecureKextPaths); CFDictionaryRemoveAllValues(sNotifiedNonsecureKextPaths); /* clean up pending kext alerts too */ CFArrayRemoveAllValues(sPendedRevokedCertKextPaths); CFArrayRemoveAllValues(sPendedNoLoadKextPaths); CFArrayRemoveAllValues(sPendedInvalidSignedKextPaths); // CFArrayRemoveAllValues(sPendedUnsignedKextPaths); CFArrayRemoveAllValues(sPendedExcludedKextPaths); return; } /******************************************************************************* *******************************************************************************/ void _checkNotificationQueue(void * info __unused) { CFStringRef kextPath = NULL; // do not release CFMutableArrayRef nonsecureAlertMessageArray = NULL; // must release CFMutableArrayRef noLoadAlertMessageArray = NULL; // must release CFMutableArrayRef revokedCertAlertMessageArray = NULL; // must release CFMutableArrayRef invalidSigAlertMessageArray = NULL; // must release CFMutableArrayRef unsignedKextAlertMessageArray = NULL; // must release CFMutableArrayRef excludedAlertMessageArray = NULL; // must release CFMutableStringRef excludedAlertHeader = NULL; // must release CFIndex count, i; if (sConsoleUser == (uid_t)-1) { goto finish; } /* handle alerts for kexts that do not have the proper privs set */ if (CFArrayGetCount(sPendedNonsecureKextPaths) && sNonSecureNotificationRunLoopSource == NULL) { nonsecureAlertMessageArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (nonsecureAlertMessageArray == NULL) { goto finish; } kextPath = (CFStringRef) CFArrayGetValueAtIndex( sPendedNonsecureKextPaths, 0); if (!kextPath) { goto finish; } /* This is the localized format string for the alert message. */ CFArrayAppendValue(nonsecureAlertMessageArray, CFSTR("The system extension \"")); CFArrayAppendValue(nonsecureAlertMessageArray, kextPath); CFArrayAppendValue(nonsecureAlertMessageArray, CFSTR("\" was installed improperly and cannot be used. " "Please try reinstalling it, or contact the product's vendor " "for an update.")); CFArrayRemoveValueAtIndex(sPendedNonsecureKextPaths, 0); kextd_raise_nonsecure_notification(CFSTR("System extension cannot be used"), nonsecureAlertMessageArray); } /* handle alerts for kext signature verification errors that result * in a kext not loading (currently only kexts in /Library/Extensions/ */ count = CFArrayGetCount(sPendedNoLoadKextPaths); if (count > 0 && sNoLoadKextNotificationRunLoopSource == NULL) { noLoadAlertMessageArray = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks ); if (noLoadAlertMessageArray == NULL) { goto finish; } CFArrayAppendValue( noLoadAlertMessageArray, (count > 1 ? CFSTR("The following kernel extensions can't " "be loaded because they are from " "unidentified developers. Extensions " "loaded from /Library/Extensions must be " "signed by identified developers. \r") : CFSTR("The kernel extension at \"")) ); for (i = 0; i < count; i ++) { kextPath = (CFStringRef) CFArrayGetValueAtIndex( sPendedNoLoadKextPaths, i ); if (kextPath) { if (count > 1) { CFArrayAppendValue(noLoadAlertMessageArray, CFSTR("\r")); } CFArrayAppendValue(noLoadAlertMessageArray, kextPath); } } if (count == 1) { CFArrayAppendValue(noLoadAlertMessageArray, CFSTR("\" can't be loaded because it is from an " "unidentified developer. Extensions " "loaded from /Library/Extensions must be " "signed by identified developers.")); } CFArrayAppendValue(noLoadAlertMessageArray, CFSTR("\r\rPlease contact the kernel extension " "vendor for updated software.")); CFArrayRemoveAllValues(sPendedNoLoadKextPaths); kextd_raise_noload_notification( (count > 1 ? CFSTR("Kernel extensions could not be loaded") : CFSTR("Kernel extension could not be loaded")), noLoadAlertMessageArray); } /* handle alerts for kexts with a revoked cert */ count = CFArrayGetCount(sPendedRevokedCertKextPaths); if (count > 0 && sRevokedCertNotificationRunLoopSource == NULL) { revokedCertAlertMessageArray = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks ); if (revokedCertAlertMessageArray == NULL) { goto finish; } CFArrayAppendValue( revokedCertAlertMessageArray, (count > 1 ? CFSTR("The following kernel extensions " "are damaged and can't be " "loaded. \r") : CFSTR("The kernel extension at \"")) ); for (i = 0; i < count; i ++) { kextPath = (CFStringRef) CFArrayGetValueAtIndex( sPendedRevokedCertKextPaths, i ); if (kextPath) { if (count > 1) { CFArrayAppendValue(revokedCertAlertMessageArray, CFSTR("\r")); } CFArrayAppendValue(revokedCertAlertMessageArray, kextPath); } } if (count == 1) { CFArrayAppendValue(revokedCertAlertMessageArray, CFSTR("\" is damaged and can't be loaded.")); } CFArrayAppendValue(revokedCertAlertMessageArray, (count > 1 ? CFSTR("\r\rYou should move them to the Trash.") : CFSTR("\r\rYou should move it to the Trash.")) ); // CFArrayRemoveAllValues(sPendedRevokedCertKextPaths); do not remove here, need to use these to reveal in Finder kextd_raise_revokedcert_notification( (count > 1 ? CFSTR("Kernel extensions could not be loaded") : CFSTR("Kernel extension could not be loaded")), revokedCertAlertMessageArray); } #if 0 // not yet /* handle alerts for unsigned kexts */ count = CFArrayGetCount(sPendedUnsignedKextPaths); if (count > 0 && sUnsignedKextNotificationRunLoopSource == NULL) { unsignedKextAlertMessageArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (unsignedKextAlertMessageArray == NULL) { goto finish; } CFArrayAppendValue(unsignedKextAlertMessageArray, CFSTR("The following kernel extensions are not " "signed.\r")); for (i = 0; i < count; i ++) { kextPath = (CFStringRef) CFArrayGetValueAtIndex( sPendedUnsignedKextPaths, i); if (kextPath) { CFArrayAppendValue(unsignedKextAlertMessageArray, CFSTR("\r")); CFArrayAppendValue(unsignedKextAlertMessageArray, kextPath); } } CFArrayAppendValue(unsignedKextAlertMessageArray, CFSTR("\r\rPlease contact the vendor for each " "kernel extension for updated software.")); CFArrayRemoveAllValues(sPendedUnsignedKextPaths); kextd_raise_unsignedkext_notification(CFSTR("Kernel extensions are not signed"), unsignedKextAlertMessageArray); } #endif /* handle alerts for kext signature verification errors */ count = CFArrayGetCount(sPendedInvalidSignedKextPaths); if (count > 0 && sInvalidSigNotificationRunLoopSource == NULL) { invalidSigAlertMessageArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (invalidSigAlertMessageArray == NULL) { goto finish; } CFArrayAppendValue(invalidSigAlertMessageArray, (count > 1 ? CFSTR("The following kernel extensions are not " "from identified developers but will " "still be loaded. \r") : CFSTR("The kernel extension at \"")) ); for (i = 0; i < count; i ++) { kextPath = (CFStringRef) CFArrayGetValueAtIndex( sPendedInvalidSignedKextPaths, i); if (kextPath) { if (count > 1) { CFArrayAppendValue(invalidSigAlertMessageArray, CFSTR("\r")); } CFArrayAppendValue(invalidSigAlertMessageArray, kextPath); } } if (count == 1) { CFArrayAppendValue(invalidSigAlertMessageArray, CFSTR("\" is not from an identified developer " "but will still be loaded.")); } CFArrayAppendValue(invalidSigAlertMessageArray, CFSTR("\r\rPlease contact the kernel extension " "vendor for updated software.")); CFArrayRemoveAllValues(sPendedInvalidSignedKextPaths); kextd_raise_invalidsig_notification( (count > 1 ? CFSTR("Kernel extensions are not from identified " "developers") : CFSTR("Kernel extension is not from an identified " "developer")), invalidSigAlertMessageArray); } #if 1 // warn user about excluded kexts count = CFArrayGetCount(sPendedExcludedKextPaths); if (count > 0 && sExcludedKextNotificationRunLoopSource == NULL) { excludedAlertMessageArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (excludedAlertMessageArray == NULL) { goto finish; } excludedAlertHeader = CFStringCreateMutable(kCFAllocatorDefault, 0); if (excludedAlertHeader == NULL) { goto finish; } if (count > 1) { CFStringAppend(excludedAlertHeader, CFSTR("Some system extensions are not compatible with this version of OS X and can’t be used:")); for (i = 0; i < count; i ++) { kextPath = (CFStringRef) CFArrayGetValueAtIndex( sPendedExcludedKextPaths, i); if (kextPath) { CFRange myRange; myRange = CFStringFind(kextPath, CFSTR("/"), kCFCompareBackwards); // just get kext name if possible, not the full path if (myRange.length != 0 && myRange.location++ < CFStringGetLength(kextPath)) { CFStringRef myString; myRange.length = CFStringGetLength(kextPath) - myRange.location; myString = CFStringCreateWithSubstring(kCFAllocatorDefault, kextPath, myRange); if (myString) { CFArrayAppendValue(excludedAlertMessageArray, myString); SAFE_RELEASE(myString); } else { // fall back to full path CFArrayAppendValue(excludedAlertMessageArray, kextPath); } } else { // fall back to full path CFArrayAppendValue(excludedAlertMessageArray, kextPath); } CFArrayAppendValue(excludedAlertMessageArray, CFSTR("\r")); } } // one extra for "Please contact the developer..." to look good CFArrayAppendValue(excludedAlertMessageArray, CFSTR("\r")); } else { kextPath = (CFStringRef) CFArrayGetValueAtIndex( sPendedExcludedKextPaths, 0 ); if (kextPath == NULL) { goto finish; } CFStringAppend(excludedAlertHeader, CFSTR("The system extension \"")); CFRange myRange; myRange = CFStringFind(kextPath, CFSTR("/"), kCFCompareBackwards); // just get kext name if possible, not the full path if (myRange.length != 0 && myRange.location++ < CFStringGetLength(kextPath)) { CFStringRef myString; myRange.length = CFStringGetLength(kextPath) - myRange.location; myString = CFStringCreateWithSubstring(kCFAllocatorDefault, kextPath, myRange); if (myString) { CFStringAppend(excludedAlertHeader, myString); SAFE_RELEASE(myString); } else { // fall back to full path CFStringAppend(excludedAlertHeader, kextPath); } } else { // fall back to full path CFStringAppend(excludedAlertHeader, kextPath); } CFStringAppend(excludedAlertHeader, CFSTR("\" is not compatible with this version of OS X and can’t be used.")); } CFArrayAppendValue(excludedAlertMessageArray, CFSTR("Please contact the developer for updated software.")); CFArrayRemoveAllValues(sPendedExcludedKextPaths); kextd_raise_excludedkext_notification(excludedAlertHeader, excludedAlertMessageArray); } #endif // finish: SAFE_RELEASE(nonsecureAlertMessageArray); SAFE_RELEASE(noLoadAlertMessageArray); SAFE_RELEASE(excludedAlertMessageArray); SAFE_RELEASE(revokedCertAlertMessageArray); SAFE_RELEASE(invalidSigAlertMessageArray); SAFE_RELEASE(unsignedKextAlertMessageArray); SAFE_RELEASE(excludedAlertHeader); return; } /****************************************************************************** ******************************************************************************/ Boolean recordNonsecureKexts(CFArrayRef kextList) { Boolean result = false; CFStringRef nonsecureKextPath = NULL; // must release CFIndex count, i; if (kextList && (count = CFArrayGetCount(kextList))) { for (i = 0; i < count; i ++) { OSKextRef checkKext = (OSKextRef)CFArrayGetValueAtIndex(kextList, i); SAFE_RELEASE_NULL(nonsecureKextPath); if (OSKextIsAuthentic(checkKext)) { continue; } nonsecureKextPath = copyKextPath(checkKext); if (!nonsecureKextPath) { OSKextLogMemError(); goto finish; } if (!CFDictionaryGetValue(sNotifiedNonsecureKextPaths, nonsecureKextPath)) { CFArrayAppendValue(sPendedNonsecureKextPaths, nonsecureKextPath); CFDictionarySetValue(sNotifiedNonsecureKextPaths, nonsecureKextPath, kCFBooleanTrue); result = true; } } } finish: SAFE_RELEASE(nonsecureKextPath); if (result) { CFRunLoopSourceSignal(sNotificationQueueRunLoopSource); CFRunLoopWakeUp(CFRunLoopGetCurrent()); } return result; } /******************************************************************************* *******************************************************************************/ Boolean recordNoLoadKextPath(CFStringRef theKextPath) { Boolean result = false; if (theKextPath == NULL) { goto finish; } CFArrayAppendValue(sPendedNoLoadKextPaths, theKextPath); result = true; finish: return result; } /******************************************************************************* *******************************************************************************/ void sendNoLoadKextNotification(void) { if (CFArrayGetCount(sPendedNoLoadKextPaths)) { CFRunLoopSourceSignal(sNotificationQueueRunLoopSource); CFRunLoopWakeUp(CFRunLoopGetCurrent()); } return; } /******************************************************************************* *******************************************************************************/ void recordRevokedCertKextPath(CFStringRef theKextPath) { if (theKextPath == NULL) { goto finish; } CFArrayAppendValue(sPendedRevokedCertKextPaths, theKextPath); finish: return; } /******************************************************************************* *******************************************************************************/ void sendRevokedCertKextPath(void) { if (CFArrayGetCount(sPendedRevokedCertKextPaths)) { CFRunLoopSourceSignal(sNotificationQueueRunLoopSource); CFRunLoopWakeUp(CFRunLoopGetCurrent()); } return; } /******************************************************************************* *******************************************************************************/ Boolean recordInvalidSignedKextPath(CFStringRef theKextPath) { Boolean result = false; if (theKextPath == NULL) { goto finish; } CFArrayAppendValue(sPendedInvalidSignedKextPaths, theKextPath); result = true; finish: return result; } /******************************************************************************* *******************************************************************************/ void sendInvalidSignedKextNotification(void) { if (CFArrayGetCount(sPendedInvalidSignedKextPaths)) { CFRunLoopSourceSignal(sNotificationQueueRunLoopSource); CFRunLoopWakeUp(CFRunLoopGetCurrent()); } return; } #if 0 // not yet /******************************************************************************* *******************************************************************************/ Boolean recordUnsignedKextPath(CFStringRef theKextPath) { Boolean result = false; if (theKextPath == NULL) { goto finish; } CFArrayAppendValue(sPendedUnsignedKextPaths, theKextPath); result = true; finish: return result; } /******************************************************************************* *******************************************************************************/ void sendUnsignedKextNotification(void) { if (CFArrayGetCount(sPendedUnsignedKextPaths)) { CFRunLoopSourceSignal(sNotificationQueueRunLoopSource); CFRunLoopWakeUp(CFRunLoopGetCurrent()); } return; } #endif /******************************************************************************* *******************************************************************************/ Boolean recordExcludedKextPath(CFStringRef theKextPath) { Boolean result = false; if (theKextPath == NULL) { goto finish; } CFArrayAppendValue(sPendedExcludedKextPaths, theKextPath); result = true; finish: return result; } /******************************************************************************* *******************************************************************************/ void sendExcludedKextNotification(void) { if (CFArrayGetCount(sPendedExcludedKextPaths)) { CFRunLoopSourceSignal(sNotificationQueueRunLoopSource); CFRunLoopWakeUp(CFRunLoopGetCurrent()); } return; } /******************************************************************************* *******************************************************************************/ static CFMutableDictionaryRef createAlertDict( CFStringRef alertHeader, CFArrayRef alertMessageArray ) { CFMutableDictionaryRef alertDict = NULL; // do not release CFURLRef iokitFrameworkBundleURL = NULL; // must release /* Do not alert if we're doing a system install */ if ( doingSystemInstall() ) { goto finish; } OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogGeneralFlag, "Raising user notification."); if (sConsoleUser == (uid_t)-1) { OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogGeneralFlag, "No logged in user."); goto finish; } iokitFrameworkBundleURL = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, CFSTR("/System/Library/Frameworks/IOKit.framework"), kCFURLPOSIXPathStyle, true); if (!iokitFrameworkBundleURL) { goto finish; } alertDict = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!alertDict) { goto finish; } CFDictionarySetValue(alertDict, kCFUserNotificationLocalizationURLKey, iokitFrameworkBundleURL); CFDictionarySetValue(alertDict, kCFUserNotificationAlertHeaderKey, alertHeader); CFDictionarySetValue(alertDict, kCFUserNotificationDefaultButtonTitleKey, CFSTR("OK")); CFDictionarySetValue(alertDict, kCFUserNotificationAlertMessageKey, alertMessageArray); finish: SAFE_RELEASE(iokitFrameworkBundleURL); return(alertDict); } /******************************************************************************* *******************************************************************************/ static void kextd_raise_nonsecure_notification( CFStringRef alertHeader, CFArrayRef alertMessageArray ) { CFMutableDictionaryRef alertDict = NULL; // must release SInt32 userNotificationError = 0; alertDict = createAlertDict(alertHeader, alertMessageArray); if (alertDict == NULL) { goto finish; } sNonSecureNotification = CFUserNotificationCreate(kCFAllocatorDefault, 0 /* time interval */, kCFUserNotificationCautionAlertLevel, &userNotificationError, alertDict); if (!sNonSecureNotification) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Can't create user notification - %d", (int)userNotificationError); goto finish; } sNonSecureNotificationRunLoopSource = CFUserNotificationCreateRunLoopSource( kCFAllocatorDefault, sNonSecureNotification, &_notificationDismissed, /* order */ 5 /* xxx - cheesy! */ ); if (!sNonSecureNotificationRunLoopSource) { CFRelease(sNonSecureNotification); sNonSecureNotification = NULL; } CFRunLoopAddSource(CFRunLoopGetCurrent(), sNonSecureNotificationRunLoopSource, kCFRunLoopDefaultMode); finish: SAFE_RELEASE(alertDict); return; } /******************************************************************************* *******************************************************************************/ static void kextd_raise_noload_notification( CFStringRef alertHeader, CFArrayRef alertMessageArray ) { CFMutableDictionaryRef alertDict = NULL; // must release SInt32 userNotificationError = 0; alertDict = createAlertDict(alertHeader, alertMessageArray); if (alertDict == NULL) { goto finish; } sNoLoadKextNotification = CFUserNotificationCreate( kCFAllocatorDefault, 0 /* time interval */, kCFUserNotificationCautionAlertLevel, &userNotificationError, alertDict ); if (!sNoLoadKextNotification) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Can't create user notification - %d", (int)userNotificationError); goto finish; } sNoLoadKextNotificationRunLoopSource = CFUserNotificationCreateRunLoopSource( kCFAllocatorDefault, sNoLoadKextNotification, &_notificationDismissed, /* order */ 5 /* xxx - cheesy! */ ); if (!sNoLoadKextNotificationRunLoopSource) { CFRelease(sNoLoadKextNotification); sNoLoadKextNotification = NULL; } CFRunLoopAddSource(CFRunLoopGetCurrent(), sNoLoadKextNotificationRunLoopSource, kCFRunLoopDefaultMode); finish: SAFE_RELEASE(alertDict); return; } /******************************************************************************* *******************************************************************************/ static void kextd_raise_revokedcert_notification( CFStringRef alertHeader, CFArrayRef alertMessageArray ) { CFMutableDictionaryRef alertDict = NULL; // must release SInt32 userNotificationError = 0; alertDict = createAlertDict(alertHeader, alertMessageArray); if (alertDict == NULL) { goto finish; } /* retitle default button */ CFDictionarySetValue(alertDict, kCFUserNotificationDefaultButtonTitleKey, CFSTR("Reveal in Finder")); sRevokedCertNotification = CFUserNotificationCreate( kCFAllocatorDefault, 0 /* time interval */, kCFUserNotificationCautionAlertLevel, &userNotificationError, alertDict ); if (!sRevokedCertNotification) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Can't create user notification - %d", (int)userNotificationError); goto finish; } sRevokedCertNotificationRunLoopSource = CFUserNotificationCreateRunLoopSource( kCFAllocatorDefault, sRevokedCertNotification, &_notificationDismissed, /* order */ 5 /* xxx - cheesy! */ ); if (!sRevokedCertNotificationRunLoopSource) { CFRelease(sRevokedCertNotification); sRevokedCertNotification = NULL; } CFRunLoopAddSource(CFRunLoopGetCurrent(), sRevokedCertNotificationRunLoopSource, kCFRunLoopDefaultMode); finish: SAFE_RELEASE(alertDict); return; } /******************************************************************************* *******************************************************************************/ static void kextd_raise_invalidsig_notification( CFStringRef alertHeader, CFArrayRef alertMessageArray ) { CFMutableDictionaryRef alertDict = NULL; // must release SInt32 userNotificationError = 0; alertDict = createAlertDict(alertHeader, alertMessageArray); if (alertDict == NULL) { goto finish; } sInvalidSigNotification = CFUserNotificationCreate( kCFAllocatorDefault, 0 /* time interval */, kCFUserNotificationCautionAlertLevel, &userNotificationError, alertDict ); if (!sInvalidSigNotification) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Can't create user notification - %d", (int)userNotificationError); goto finish; } sInvalidSigNotificationRunLoopSource = CFUserNotificationCreateRunLoopSource( kCFAllocatorDefault, sInvalidSigNotification, &_notificationDismissed, /* order */ 5 /* xxx - cheesy! */ ); if (!sInvalidSigNotificationRunLoopSource) { CFRelease(sInvalidSigNotification); sInvalidSigNotification = NULL; } CFRunLoopAddSource(CFRunLoopGetCurrent(), sInvalidSigNotificationRunLoopSource, kCFRunLoopDefaultMode); finish: SAFE_RELEASE(alertDict); return; } #if 0 // not yet /******************************************************************************* *******************************************************************************/ static void kextd_raise_unsignedkext_notification( CFStringRef alertHeader, CFArrayRef alertMessageArray ) { CFMutableDictionaryRef alertDict = NULL; // must release SInt32 userNotificationError = 0; alertDict = createAlertDict(alertHeader, alertMessageArray); if (alertDict == NULL) { goto finish; } sUnsignedKextNotification = CFUserNotificationCreate( kCFAllocatorDefault, 0 /* time interval */, kCFUserNotificationCautionAlertLevel, &userNotificationError, alertDict ); if (!sUnsignedKextNotification) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Can't create user notification - %d", (int)userNotificationError); goto finish; } sUnsignedKextNotificationRunLoopSource = CFUserNotificationCreateRunLoopSource( kCFAllocatorDefault, sUnsignedKextNotification, &_notificationDismissed, /* order */ 5 /* xxx - cheesy! */ ); if (!sUnsignedKextNotificationRunLoopSource) { CFRelease(sUnsignedKextNotification); sUnsignedKextNotification = NULL; } CFRunLoopAddSource(CFRunLoopGetCurrent(), sUnsignedKextNotificationRunLoopSource, kCFRunLoopDefaultMode); finish: SAFE_RELEASE(alertDict); return; } #endif /******************************************************************************* *******************************************************************************/ static void kextd_raise_excludedkext_notification( CFStringRef alertHeader, CFArrayRef alertMessageArray ) { CFMutableDictionaryRef alertDict = NULL; // must release SInt32 userNotificationError = 0; alertDict = createAlertDict(alertHeader, alertMessageArray); if (alertDict == NULL) { goto finish; } sExcludedKextNotification = CFUserNotificationCreate( kCFAllocatorDefault, 0 /* time interval */, kCFUserNotificationCautionAlertLevel, &userNotificationError, alertDict ); if (!sExcludedKextNotification) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Can't create user notification - %d", (int)userNotificationError); goto finish; } sExcludedKextNotificationRunLoopSource = CFUserNotificationCreateRunLoopSource( kCFAllocatorDefault, sExcludedKextNotification, &_notificationDismissed, /* order */ 5 /* xxx - cheesy! */ ); if (!sExcludedKextNotificationRunLoopSource) { CFRelease(sExcludedKextNotification); sExcludedKextNotification = NULL; } CFRunLoopAddSource(CFRunLoopGetCurrent(), sExcludedKextNotificationRunLoopSource, kCFRunLoopDefaultMode); finish: SAFE_RELEASE(alertDict); return; } /******************************************************************************* *******************************************************************************/ void _notificationDismissed( CFUserNotificationRef userNotification, CFOptionFlags responseFlags) { if (sNonSecureNotification && sNonSecureNotification == userNotification) { CFRelease(sNonSecureNotification); sNonSecureNotification = NULL; if (sNonSecureNotificationRunLoopSource) { CFRunLoopRemoveSource(CFRunLoopGetCurrent(), sNonSecureNotificationRunLoopSource, kCFRunLoopDefaultMode); CFRelease(sNonSecureNotificationRunLoopSource); sNonSecureNotificationRunLoopSource = NULL; } } else if (sNoLoadKextNotification && sNoLoadKextNotification == userNotification) { CFRelease(sNoLoadKextNotification); sNoLoadKextNotification = NULL; if (sNoLoadKextNotificationRunLoopSource) { CFRunLoopRemoveSource(CFRunLoopGetCurrent(), sNoLoadKextNotificationRunLoopSource, kCFRunLoopDefaultMode); CFRelease(sNoLoadKextNotificationRunLoopSource); sNoLoadKextNotificationRunLoopSource = NULL; } } else if (sRevokedCertNotification && sRevokedCertNotification == userNotification) { CFRelease(sRevokedCertNotification); sRevokedCertNotification = NULL; if (sRevokedCertNotificationRunLoopSource) { CFRunLoopRemoveSource(CFRunLoopGetCurrent(), sRevokedCertNotificationRunLoopSource, kCFRunLoopDefaultMode); CFRelease(sRevokedCertNotificationRunLoopSource); sRevokedCertNotificationRunLoopSource = NULL; } // CFArrayRef CFArrayCreateCopy if (sPendedRevokedCertKextPaths) { revealInFinder(sPendedRevokedCertKextPaths); CFArrayRemoveAllValues(sPendedRevokedCertKextPaths); } // sPendedRevokedCertKextPaths } else if (sInvalidSigNotification && sInvalidSigNotification == userNotification) { CFRelease(sInvalidSigNotification); sInvalidSigNotification = NULL; if (sInvalidSigNotificationRunLoopSource) { CFRunLoopRemoveSource(CFRunLoopGetCurrent(), sInvalidSigNotificationRunLoopSource, kCFRunLoopDefaultMode); CFRelease(sInvalidSigNotificationRunLoopSource); sInvalidSigNotificationRunLoopSource = NULL; } } #if 0 // not yet else if (sUnsignedKextNotification && sUnsignedKextNotification == userNotification) { CFRelease(sUnsignedKextNotification); sUnsignedKextNotification = NULL; if (sUnsignedKextNotificationRunLoopSource) { CFRunLoopRemoveSource(CFRunLoopGetCurrent(), sUnsignedKextNotificationRunLoopSource, kCFRunLoopDefaultMode); CFRelease(sUnsignedKextNotificationRunLoopSource); sUnsignedKextNotificationRunLoopSource = NULL; } } #endif else if (sExcludedKextNotification && sExcludedKextNotification == userNotification) { CFRelease(sExcludedKextNotification); sExcludedKextNotification = NULL; if (sExcludedKextNotificationRunLoopSource) { CFRunLoopRemoveSource(CFRunLoopGetCurrent(), sExcludedKextNotificationRunLoopSource, kCFRunLoopDefaultMode); CFRelease(sExcludedKextNotificationRunLoopSource); sExcludedKextNotificationRunLoopSource = NULL; } } /* check to see if there are any other alerts pending */ CFRunLoopSourceSignal(sNotificationQueueRunLoopSource); CFRunLoopWakeUp(CFRunLoopGetCurrent()); return; } #include static const char kFinderBundleID[] = { "com.apple.finder" }; static void revealInFinder(CFArrayRef theArray) { CFIndex myCount, i; if (theArray == NULL) return; myCount = CFArrayGetCount(theArray); for (i = 0; i < myCount; i ++) { CFStringRef myKextPath = NULL; // do not release CFURLRef myURL = NULL; // must release OSErr myResult; AEDesc myTargetDesc = { typeNull, NULL }; AEDesc myFileDesc = { typeNull, NULL }; AEDescList myParmList = { typeNull, NULL }; AppleEvent myRevealEvent = { typeNull, NULL }; AppleEvent myActivateEvent = { typeNull, NULL }; char myPathString[2 * PATH_MAX]; myKextPath = (CFStringRef) CFArrayGetValueAtIndex(theArray, i); if (myKextPath == NULL) continue; /* NOTE - we create the URL from the path we are given then * extract the c string path from the URL in order to get the * correct URL prefix on the path. The AppleEvent system requires * this. Passing the full UNIX path does not work for AppleEvents */ myURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, myKextPath, kCFURLPOSIXPathStyle, true ); if (myURL == NULL) continue; myKextPath = CFURLGetString(myURL); if (myKextPath == NULL || CFStringGetCString(myKextPath, myPathString, sizeof(myPathString), kCFStringEncodingUTF8) == false) { SAFE_RELEASE_NULL(myURL); continue; } SAFE_RELEASE_NULL(myURL); myResult = AECreateDesc(typeApplicationBundleID, &kFinderBundleID, sizeof(kFinderBundleID), &myTargetDesc); if (myResult == noErr) { myResult = AECreateDesc(typeFileURL, myPathString, strlen(myPathString), &myFileDesc); } if (myResult == noErr) { myResult = AECreateList(NULL, 0, false, &myParmList); } if (myResult == noErr) { AEPutDesc(&myParmList, 1, &myFileDesc); } if (myResult == noErr) { myResult = AECreateAppleEvent(kAEMiscStandards, kAESelect, &myTargetDesc, kAutoGenerateReturnID, kAnyTransactionID, &myRevealEvent); } if (myResult == noErr) { myResult = AEPutParamDesc(&myRevealEvent, keyDirectObject, &myParmList); } if (myResult == noErr) { myResult = AECreateAppleEvent(kAEMiscStandards, kAEActivate, &myTargetDesc, kAutoGenerateReturnID, kAnyTransactionID, &myActivateEvent); } if (myResult == noErr) { AESendMessage(&myActivateEvent, NULL, kAENoReply, 0); AESendMessage(&myRevealEvent, NULL, kAENoReply, 0); } AEDisposeDesc(&myTargetDesc); AEDisposeDesc(&myFileDesc); AEDisposeDesc(&myParmList); AEDisposeDesc(&myRevealEvent); AEDisposeDesc(&myActivateEvent); } // for loop... return; } /******************************************************************************* * writeKextAlertPlist() - update or create one of our alert plist files: * invalidsignedkextalert.plist * noloadkextalert.plist * excludedkextalert.plist * with the given array of kext paths (CFStrings). * The key for the array in the plist dictionary is "Alerts sent". * * We use these plist files to control which kexts we have displayed an alert * dialog about. * Marketing only wanted us to alert once. Accees to this routine needs to be * synchronized (all callers use a dispatch queue). * * The plist files are located at: * /System/Library/Caches/com.apple.kext.caches/Startup/ * * The plist looks something like: Alerts sent CFBundleIdentifier com.foocompany.driver.foo CFBundleVersion 2.1 KextPathKey /System/Library/Extensions/foo.kext * * see addKextToAlertDict() for layout of theDict. * NOTE - this routine must drop reference to theDict. *******************************************************************************/ void writeKextAlertPlist( CFDictionaryRef theDict, int theAlertType ) { CFStringRef myVolRoot = NULL; // do NOT release CFArrayRef myKextArray; // do NOT release CFURLRef myURL = NULL; // must release CFStringRef myPath = NULL; // must release CFReadStreamRef readStream = NULL; // must release CFWriteStreamRef writeStream = NULL; // must release CFDictionaryRef alertPlist = NULL; // must release CFMutableDictionaryRef alertDict = NULL; // must release Boolean fileExists; Boolean closeReadStream = false; Boolean closeWriteStream = false; if (theDict == NULL) { goto finish; } myKextArray = (CFArrayRef) CFDictionaryGetValue(theDict, CFSTR("KextInfoArrayKey")); if (myKextArray == NULL || CFArrayGetCount(myKextArray) < 1) { goto finish; } /* note for the kextcache case we could target a volume other than the boot * volume. In that case we need to add the "/Volumes/XXXvol/" * to the path. */ myVolRoot = (CFStringRef) CFDictionaryGetValue( theDict, CFSTR("VolRootKey")); myPath = createPathFromAlertType(myVolRoot, theAlertType); if (myPath == NULL) { OSKextLogMemError(); goto finish; } myURL = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, myPath, kCFURLPOSIXPathStyle, false ); if (myURL == NULL) { OSKextLogMemError(); goto finish; } fileExists = CFURLResourceIsReachable(myURL, NULL); /* grab existing data and append to it */ if (fileExists) { readStream = CFReadStreamCreateWithFile(kCFAllocatorDefault, myURL); if (readStream == NULL) { OSKextLogMemError(); goto finish; } closeReadStream = CFReadStreamOpen(readStream); if (closeReadStream == false) { OSKextLogMemError(); goto finish; } /* read in the existing contents of alert plist file */ alertPlist = CFPropertyListCreateWithStream( kCFAllocatorDefault, readStream, 0, kCFPropertyListMutableContainersAndLeaves, NULL, NULL); if (alertPlist == NULL) { OSKextLogMemError(); goto finish; } CFMutableArrayRef sentArray = NULL; // do not release sentArray = (CFMutableArrayRef) CFDictionaryGetValue(alertPlist, CFSTR("Alerts sent")); if (sentArray == NULL) { OSKextLogMemError(); goto finish; } /* add any kext paths that are not already known */ Boolean didAppend = false; didAppend = sendKextAlertNotifications(&sentArray, myKextArray, theAlertType); /* now replace previous plist with our updated one */ if (didAppend) { writeStream = CFWriteStreamCreateWithFile(kCFAllocatorDefault, myURL); if (writeStream == NULL) { OSKextLogMemError(); goto finish; } closeWriteStream = CFWriteStreamOpen(writeStream); if (closeWriteStream == false) { OSKextLogMemError(); goto finish; } CFPropertyListWrite(alertPlist, writeStream, kCFPropertyListXMLFormat_v1_0, 0, NULL); } } else { /* plist does not exist, create one */ alertDict = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (alertDict == NULL) { OSKextLogMemError(); goto finish; } /* add our array to the dictionary */ CFDictionarySetValue(alertDict, CFSTR("Alerts sent"), myKextArray); alertPlist = CFPropertyListCreateDeepCopy( kCFAllocatorDefault, alertDict, kCFPropertyListMutableContainersAndLeaves ); if (alertPlist == NULL) { OSKextLogMemError(); goto finish; } writeStream = CFWriteStreamCreateWithFile(kCFAllocatorDefault, myURL); if (writeStream == NULL) { OSKextLogMemError(); goto finish; } closeWriteStream = CFWriteStreamOpen(writeStream); if (closeWriteStream == false) { OSKextLogMemError(); goto finish; } CFPropertyListWrite(alertPlist, writeStream, kCFPropertyListXMLFormat_v1_0, 0, NULL); sendKextAlertNotifications(NULL, myKextArray, theAlertType); } finish: #if 0 if (alertPlist) { OSKextLogCFString(NULL, kOSKextLogGeneralFlag | kOSKextLogErrorLevel, CFSTR("%s: alertPlist %@"), __func__, alertPlist); } #endif if (closeReadStream) CFReadStreamClose(readStream); if (closeWriteStream) CFWriteStreamClose(writeStream); SAFE_RELEASE(myURL); SAFE_RELEASE(readStream); SAFE_RELEASE(writeStream); SAFE_RELEASE(alertPlist); SAFE_RELEASE(alertDict); SAFE_RELEASE(myPath); SAFE_RELEASE(theDict); return; } /******************************************************************************* * sendRevokedCertAlert() - build an array of kexts with revoked certs then * send to put up an alert. * NOTE - this routine must drop reference to theDict. *******************************************************************************/ void sendRevokedCertAlert( CFDictionaryRef theDict ) { CFArrayRef myKextArray; // do NOT release CFIndex count, i; if (theDict == NULL) { goto finish; } myKextArray = (CFArrayRef) CFDictionaryGetValue(theDict, CFSTR("KextInfoArrayKey")); if (myKextArray == NULL || CFGetTypeID(myKextArray) != CFArrayGetTypeID() ) { goto finish; } count = CFArrayGetCount(myKextArray); for (i = 0; i < count; i++) { CFDictionaryRef myDict; // do NOT release CFStringRef myKextPath; // do NOT release myDict = (CFDictionaryRef) CFArrayGetValueAtIndex(myKextArray, i); if (myDict == NULL || CFGetTypeID(myDict) != CFDictionaryGetTypeID()) { continue; } myKextPath = CFDictionaryGetValue(myDict, CFSTR("KextPathKey")); recordRevokedCertKextPath(myKextPath); } // for loop... sendRevokedCertKextPath(); finish: SAFE_RELEASE(theDict); return; } /* theKextsArray is an array of dictionaries of kext info for kexts * we may want to send an alert about. Each kext info dictionary * contains bundle ID, bundle version and kext path * (all are CFStrings) * If we have already alerted before there will be a list of kexts we * brought to the attention of the user. We do not want to alert more than * once about the same kext or class of kexts when we have bundle ID to * mappings to a vendor or product. theSentAlertsArray is an array of the * kexts we have alerted. */ // NOTE - we have decided to not use bundle mappings for the alert messages // at this point. So for now myMappingKey will always be NULL. static Boolean sendKextAlertNotifications(CFMutableArrayRef *theSentAlertsArrayPtr, CFArrayRef theKextsArray, int theAlertType) { Boolean didAppend = false; CFIndex count, i; count = CFArrayGetCount(theKextsArray); for (i = 0; i < count; i++) { CFDictionaryRef myDict; // do NOT release CFStringRef myKextMessage; // do NOT release CFStringRef myBundleID; // do NOT release CFStringRef myMappingKey = NULL; // must release myDict = (CFDictionaryRef) CFArrayGetValueAtIndex(theKextsArray, i); if (myDict == NULL) continue; myBundleID = (CFStringRef) CFDictionaryGetValue(myDict, kCFBundleIdentifierKey); myMappingKey = createBundleMappingKey(myBundleID); if (theSentAlertsArrayPtr) { /* skip this one if we already have alerted for it */ if ( isInAlertsSentArray(*theSentAlertsArrayPtr, myDict, myMappingKey) ) { SAFE_RELEASE_NULL(myMappingKey); continue; } didAppend = true; CFArrayAppendValue(*theSentAlertsArrayPtr, myDict); } /* nag user about this kext */ myKextMessage = getKextAlertMessage(myDict, myMappingKey); if (theAlertType == INVALID_SIGNATURE_KEXT_ALERT) { recordInvalidSignedKextPath(myKextMessage); } else if (theAlertType == NO_LOAD_KEXT_ALERT) { recordNoLoadKextPath(myKextMessage); } else if (theAlertType == EXCLUDED_KEXT_ALERT) { recordExcludedKextPath(myKextMessage); } #if 0 // not yet else if (theAlertType == UNSIGNED_KEXT_ALERT) { recordUnsignedKextPath(myKextMessage); } #endif SAFE_RELEASE_NULL(myMappingKey); } // for loop... if (theAlertType == INVALID_SIGNATURE_KEXT_ALERT) { sendInvalidSignedKextNotification(); } else if (theAlertType == NO_LOAD_KEXT_ALERT) { sendNoLoadKextNotification(); } else if (theAlertType == EXCLUDED_KEXT_ALERT) { sendExcludedKextNotification(); } #if 0 // not yet else if (theAlertType == UNSIGNED_KEXT_ALERT) { sendUnsignedKextNotification(); } #endif return(didAppend); } /* The alert message is either going to be the kext path or a product * or vendor name mapped from the bundle ID. */ // NOTE - we have decided to not use bundle mappings for the alert messages // at this point. So for now theMappingKey will always be NULL and this routine // will always return the kext path. static CFStringRef getKextAlertMessage( CFDictionaryRef theDict, CFStringRef theMappingKey ) { CFStringRef myKextMessage = NULL; if (theMappingKey && sKextTranslationsPlist) { CFDictionaryRef myMappingDict = NULL; // do NOT release myMappingDict = (CFDictionaryRef) CFDictionaryGetValue(sKextTranslationsPlist, CFSTR("BundleMappings")); if (myMappingDict) { myKextMessage = (CFStringRef) CFDictionaryGetValue(myMappingDict, theMappingKey); } } if (myKextMessage == NULL) { /* fall back to kext path */ myKextMessage = CFDictionaryGetValue(theDict, CFSTR("KextPathKey")); } return(myKextMessage); } /******************************************************************************* * writeKextLoadPlist() - update or create the plist file tracking kexts we * have loaded and message traced. * The key for the array in the plist dictionary is "Alerts sent". >> todo change this key name? * * The plist files is located at: * /System/Library/Caches/com.apple.kext.caches/Startup/loadedkextmt.plist * * The plist looks something like: cat loadedkextmt.plist Alerts sent com.apple.message.bundleID com.softraid.driver.SoftRAID com.apple.message.hash 4567bcd500cf46ec773fc895902cfc33ebbaab00 com.apple.message.kextname SoftRAID.kext com.apple.message.version 4.4 com.apple.message.bundleID com.promise.driver.stex com.apple.message.hash 8d70fb592ec9e6581fbe7dc78b15e915fcbc0db8 com.apple.message.kextname PromiseSTEX.kext com.apple.message.version 5.1.62 * */ #define LOADED_KEXT_MT_ALERT_FULL_PATH \ _kOSKextCachesRootFolder "/" \ _kOSKextStartupCachesSubfolder "/" \ "loadedkextmt.plist" void writeKextLoadPlist( CFArrayRef theArray ) { CFURLRef myURL = NULL; // must release CFReadStreamRef readStream = NULL; // must release CFWriteStreamRef writeStream = NULL; // must release CFDictionaryRef alertPlist = NULL; // must release CFMutableDictionaryRef alertDict = NULL; // must release Boolean fileExists; Boolean closeReadStream = false; Boolean closeWriteStream = false; if (theArray == NULL || CFArrayGetCount(theArray) < 1) { goto finish; } myURL = CFURLCreateFromFileSystemRepresentation( kCFAllocatorDefault, (UInt8 *) LOADED_KEXT_MT_ALERT_FULL_PATH, strlen(LOADED_KEXT_MT_ALERT_FULL_PATH), false ); if (myURL == NULL) { OSKextLogMemError(); goto finish; } fileExists = CFURLResourceIsReachable(myURL, NULL); /* grab existing data and append to it */ if (fileExists) { readStream = CFReadStreamCreateWithFile(kCFAllocatorDefault, myURL); if (readStream == NULL) { OSKextLogMemError(); goto finish; } closeReadStream = CFReadStreamOpen(readStream); if (closeReadStream == false) { OSKextLogMemError(); goto finish; } /* read in the existing contents of plist */ alertPlist = CFPropertyListCreateWithStream( kCFAllocatorDefault, readStream, 0, kCFPropertyListMutableContainersAndLeaves, NULL, NULL); if (alertPlist == NULL) { OSKextLogMemError(); goto finish; } CFMutableArrayRef sentArray = NULL; // do not release sentArray = (CFMutableArrayRef) CFDictionaryGetValue(alertPlist, CFSTR("Alerts sent")); if (sentArray == NULL) { OSKextLogMemError(); goto finish; } /* add any kext paths that are not already known */ CFIndex count, i; Boolean didAppend = false; count = CFArrayGetCount(theArray); for (i = 0; i < count; i++) { // the information for each kext is stored as a dictionary CFDictionaryRef kextDict = CFArrayGetValueAtIndex(theArray, i); if (!CFArrayContainsValue(sentArray, RANGE_ALL(sentArray), kextDict)) { didAppend = true; CFArrayAppendValue(sentArray, kextDict); } } /* now replace previous plist with our updated one */ if (didAppend) { writeStream = CFWriteStreamCreateWithFile(kCFAllocatorDefault, myURL); if (writeStream == NULL) { OSKextLogMemError(); goto finish; } closeWriteStream = CFWriteStreamOpen(writeStream); if (closeWriteStream == false) { OSKextLogMemError(); goto finish; } CFPropertyListWrite(alertPlist, writeStream, kCFPropertyListXMLFormat_v1_0, 0, NULL); } } else { /* plist does not exist, create one */ alertDict = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (alertDict == NULL) { OSKextLogMemError(); goto finish; } /* add our array to the dictionary */ CFDictionarySetValue(alertDict, CFSTR("Alerts sent"), theArray); alertPlist = CFPropertyListCreateDeepCopy( kCFAllocatorDefault, alertDict, kCFPropertyListMutableContainersAndLeaves ); if (alertPlist == NULL) { OSKextLogMemError(); goto finish; } writeStream = CFWriteStreamCreateWithFile(kCFAllocatorDefault, myURL); if (writeStream == NULL) { OSKextLogMemError(); goto finish; } closeWriteStream = CFWriteStreamOpen(writeStream); if (closeWriteStream == false) { OSKextLogMemError(); goto finish; } CFPropertyListWrite(alertPlist, writeStream, kCFPropertyListXMLFormat_v1_0, 0, NULL); } finish: if (closeReadStream) CFReadStreamClose(readStream); if (closeWriteStream) CFWriteStreamClose(writeStream); SAFE_RELEASE(myURL); SAFE_RELEASE(readStream); SAFE_RELEASE(writeStream); SAFE_RELEASE(alertPlist); SAFE_RELEASE(alertDict); SAFE_RELEASE(theArray); return; } /* the BundleMappings dictionary contains key / values that map * bundle IDs to product / vendor names. The key is either a full * bundle ID or some partial bundle ID. The more of the bundle ID * for a key that matches means a more specific match. For example, * key com.foo.driver is a better match than a key with com.foo * Matching starts with a full bundle ID and works backwards to the * next '.'. So if we had bundle ID com.foo.driver.bar we look for * matches in this order: com.foo.driver.bar, com.foo.driver, * com.foo */ // NOTE - we have decided to not use bundle mappings for the alert messages // at this point. So for now this routine just bails out quickly because // sKextTranslationsPlist will always be NULL. static CFStringRef createBundleMappingKey( CFStringRef theBundleID ) { CFDictionaryRef myMappingDict = NULL; // do NOT release CFMutableStringRef myMatchKey = NULL; // do NOT release if (theBundleID == NULL || sKextTranslationsPlist == NULL) { goto finish; }; /* see if there are any BundleMappings for this bundle ID */ myMappingDict = (CFDictionaryRef) CFDictionaryGetValue(sKextTranslationsPlist, CFSTR("BundleMappings")); if (myMappingDict == NULL) { goto finish; } myMatchKey = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, theBundleID); while (myMatchKey) { CFStringRef myProductString = NULL; // do not release CFRange myRange; myProductString = CFDictionaryGetValue(myMappingDict, myMatchKey); if (myProductString) { /* found a product string with this key so we are done! */ break; } /* trim off anything from the last '.' including the '.' */ myRange = CFStringFind(myMatchKey, CFSTR("."), kCFCompareBackwards); if (myRange.length != 0) { myRange.length = CFStringGetLength(myMatchKey) - myRange.location; CFStringDelete(myMatchKey, myRange); myRange = CFStringFind(myMatchKey, CFSTR("."), 0); if (myRange.length == 0) { /* we are done if no more '.'s left. Clean up * myMatchKey so we know there was no match. */ SAFE_RELEASE_NULL(myMatchKey); } } } finish: return(myMatchKey); } /* This is the routine that controls our "alert only once" policy. * theSentArray is an array of kext info dictionaries for kexts we have * displayed an alert for. We use kext bundle ID and version to determine * if a kext has been alerted. */ static Boolean isInAlertsSentArray(CFArrayRef theSentArray, CFDictionaryRef theDict, CFStringRef theMappingKey) { Boolean myResult = false; CFIndex myCount, i; if (theSentArray == NULL || theDict == NULL) return(false); if (CFArrayContainsValue(theSentArray, RANGE_ALL(theSentArray), theDict)) { return(true); } myCount = CFArrayGetCount(theSentArray); if (theMappingKey && myCount > 0) { for (i = 0; i < myCount; i++) { CFDictionaryRef myKextAlertedDict; CFStringRef myAlertedBundleID; myKextAlertedDict = (CFDictionaryRef) CFArrayGetValueAtIndex(theSentArray, i); if (myKextAlertedDict == NULL || CFGetTypeID(myKextAlertedDict) != CFDictionaryGetTypeID()) { continue; } myAlertedBundleID = (CFStringRef) CFDictionaryGetValue(myKextAlertedDict, kCFBundleIdentifierKey); /* if our bundle mapping key matches any part of a bundle ID in * the sent alerts array then we have alreay messaged about * this class of kexts and should not alert again. */ if (myAlertedBundleID && CFGetTypeID(myAlertedBundleID) == CFStringGetTypeID()) { CFRange myRange; myRange = CFStringFind(myAlertedBundleID, theMappingKey, 0); if (myRange.length > 0) { myResult = true; break; } } } // for loop... } return( myResult); } /* The full paths to each of the alert plist files currently supported */ #define INVALIDSIGNED_KEXT_ALERT_FULL_PATH \ _kOSKextCachesRootFolder "/" \ _kOSKextStartupCachesSubfolder "/" \ "invalidsignedkextalert.plist" #if 0 // not yet #define UNSIGNED_KEXT_ALERT_FULL_PATH \ _kOSKextCachesRootFolder "/" \ _kOSKextStartupCachesSubfolder "/" \ "unsignedkextalert.plist" #endif #define NO_LOAD_KEXT_ALERT_FULL_PATH \ _kOSKextCachesRootFolder "/" \ _kOSKextStartupCachesSubfolder "/" \ "noloadkextalert.plist" #define EXCLUDED_KEXT_ALERT_FULL_PATH \ _kOSKextCachesRootFolder "/" \ _kOSKextStartupCachesSubfolder "/" \ "excludedkextalert.plist" #define TRANSLATIONS_FULL_PATH \ _kOSKextCachesRootFolder "/" \ _kOSKextStartupCachesSubfolder "/" \ "kexttranslation.plist" static CFStringRef createPathFromAlertType( CFStringRef theVolRoot, int theAlertType ) { CFStringRef myPath = NULL; // do NOT release if (theVolRoot) { if (theAlertType == INVALID_SIGNATURE_KEXT_ALERT) { myPath = CFStringCreateWithFormat( kCFAllocatorDefault, /* formatOptions */ NULL, CFSTR("%@%s"), theVolRoot, INVALIDSIGNED_KEXT_ALERT_FULL_PATH); } #if 0 // not yet else if (theAlertType == UNSIGNED_KEXT_ALERT) { myPath = CFStringCreateWithFormat( kCFAllocatorDefault, /* formatOptions */ NULL, CFSTR("%@%s"), theVolRoot, UNSIGNED_KEXT_ALERT_FULL_PATH); } #endif else if (theAlertType == NO_LOAD_KEXT_ALERT) { myPath = CFStringCreateWithFormat( kCFAllocatorDefault, /* formatOptions */ NULL, CFSTR("%@%s"), theVolRoot, NO_LOAD_KEXT_ALERT_FULL_PATH); } else if (theAlertType == EXCLUDED_KEXT_ALERT) { myPath = CFStringCreateWithFormat( kCFAllocatorDefault, /* formatOptions */ NULL, CFSTR("%@%s"), theVolRoot, EXCLUDED_KEXT_ALERT_FULL_PATH); } else { goto finish; } #if 0 // disable this for now. We will go with kext file name or path. // enable this if we decide to translate a kext bundle ID to a // product of vendor name. if (sKextTranslationsPlist == NULL) { myTranslatePath = CFStringCreateWithFormat( kCFAllocatorDefault, /* formatOptions */ NULL, CFSTR("%@%s"), theVolRoot, TRANSLATIONS_FULL_PATH); } #endif } else { if (theAlertType == INVALID_SIGNATURE_KEXT_ALERT) { myPath = CFStringCreateWithCString( kCFAllocatorDefault, INVALIDSIGNED_KEXT_ALERT_FULL_PATH, kCFStringEncodingUTF8 ); } #if 0 // not yet else if (theAlertType == UNSIGNED_KEXT_ALERT) { myPath = CFStringCreateWithCString( kCFAllocatorDefault, UNSIGNED_KEXT_ALERT_FULL_PATH, kCFStringEncodingUTF8 ); } #endif else if (theAlertType == NO_LOAD_KEXT_ALERT) { myPath = CFStringCreateWithCString( kCFAllocatorDefault, NO_LOAD_KEXT_ALERT_FULL_PATH, kCFStringEncodingUTF8 ); } else if (theAlertType == EXCLUDED_KEXT_ALERT) { myPath = CFStringCreateWithCString( kCFAllocatorDefault, EXCLUDED_KEXT_ALERT_FULL_PATH, kCFStringEncodingUTF8 ); } else { goto finish; } #if 0 // disable this for now. We will go with kext file name or path. // enable this if we decide to translate a kext bundle ID to a // product of vendor name. if (sKextTranslationsPlist == NULL) { myTranslatePath = CFStringCreateWithCString( kCFAllocatorDefault, TRANSLATIONS_FULL_PATH, kCFStringEncodingUTF8 ); } #endif } #if 0 // disable this for now. We will go with kext file name or path. if (myTranslatePath) { CFURLRef myURL = NULL; // must release myURL = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, myTranslatePath, kCFURLPOSIXPathStyle, false ); if (myURL && CFURLResourceIsReachable(myURL, NULL)) { CFReadStreamRef readStream = NULL; // must release readStream = CFReadStreamCreateWithFile(kCFAllocatorDefault, myURL); if (readStream) { if (CFReadStreamOpen(readStream)) { /* read in contents of kexttranslation.plist */ sKextTranslationsPlist = CFPropertyListCreateWithStream( kCFAllocatorDefault, readStream, 0, kCFPropertyListMutableContainersAndLeaves, NULL, NULL); CFReadStreamClose(readStream); } SAFE_RELEASE(readStream); } } SAFE_RELEASE(myURL); } /* myTranslatePath */ #endif finish: //SAFE_RELEASE(myTranslatePath); return( myPath ); } /******************************************************************************* * Installer folks tell us the best way to determine if we are doing a system * install is to look for "/private/etc/rc.cdrom". *******************************************************************************/ Boolean doingSystemInstall(void) { CFURLRef myURL = NULL; // must release Boolean result = false; myURL = CFURLCreateWithString(NULL, CFSTR("file://localhost/private/etc/rc.cdrom"), NULL); if (myURL) { result = CFURLResourceIsReachable(myURL, NULL); } SAFE_RELEASE(myURL); return result; } #endif /* ifndef NO_CFUserNotification */