/* * 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" // 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 CFUserNotificationRef sCurrentNotification = NULL; // must release CFRunLoopSourceRef sCurrentNotificationRunLoopSource = NULL; // must release CFMutableArrayRef sPendedNonsecureKextPaths = NULL; // must release CFMutableDictionaryRef sNotifiedNonsecureKextPaths = NULL; // must release static void _sessionDidChange( SCDynamicStoreRef store, CFArrayRef changedKeys, void * info); void _checkNotificationQueue(void * info); void _notificationDismissed( CFUserNotificationRef userNotification, CFOptionFlags responseFlags); /******************************************************************************* *******************************************************************************/ 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 (!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(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 the current user notification. */ if (sCurrentNotification) { CFUserNotificationCancel(sCurrentNotification); CFRelease(sCurrentNotification); sCurrentNotification = NULL; } if (sCurrentNotificationRunLoopSource) { CFRunLoopRemoveSource(CFRunLoopGetCurrent(), sCurrentNotificationRunLoopSource, kCFRunLoopDefaultMode); CFRelease(sCurrentNotificationRunLoopSource); sCurrentNotificationRunLoopSource = 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); return; } /******************************************************************************* *******************************************************************************/ void _checkNotificationQueue(void * info __unused) { CFStringRef kextPath = NULL; // do not release CFMutableArrayRef alertMessageArray = NULL; // must release OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogGeneralFlag, "Checking user notification queue."); if (sConsoleUser == (uid_t)-1 || sCurrentNotificationRunLoopSource) { goto finish; } if (CFArrayGetCount(sPendedNonsecureKextPaths)) { kextPath = (CFStringRef)CFArrayGetValueAtIndex( sPendedNonsecureKextPaths, 0); alertMessageArray = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (!kextPath || !alertMessageArray) { goto finish; } /* This is the localized format string for the alert message. */ CFArrayAppendValue(alertMessageArray, CFSTR("The system extension \"")); CFArrayAppendValue(alertMessageArray, kextPath); CFArrayAppendValue(alertMessageArray, CFSTR("\" was installed improperly and cannot be used. " "Please try reinstalling it, or contact the product's vendor " "for an update.")); kextd_raise_notification(CFSTR("System extension cannot be used"), alertMessageArray); } finish: SAFE_RELEASE(alertMessageArray); if (kextPath) { CFArrayRemoveValueAtIndex(sPendedNonsecureKextPaths, 0); } 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; } /******************************************************************************* *******************************************************************************/ void kextd_raise_notification( CFStringRef alertHeader, CFArrayRef alertMessageArray) { CFMutableDictionaryRef alertDict = NULL; // must release CFURLRef iokitFrameworkBundleURL = NULL; // must release SInt32 userNotificationError = 0; OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogGeneralFlag, "Raising user notification."); if (sConsoleUser == (uid_t)-1) { OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogGeneralFlag, "No logged in user."); goto finish; } alertDict = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!alertDict) { goto finish; } iokitFrameworkBundleURL = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, CFSTR("/System/Library/Frameworks/IOKit.framework"), kCFURLPOSIXPathStyle, true); if (!iokitFrameworkBundleURL) { goto finish; } CFDictionarySetValue(alertDict, kCFUserNotificationLocalizationURLKey, iokitFrameworkBundleURL); CFDictionarySetValue(alertDict, kCFUserNotificationAlertHeaderKey, alertHeader); CFDictionarySetValue(alertDict, kCFUserNotificationDefaultButtonTitleKey, CFSTR("OK")); CFDictionarySetValue(alertDict, kCFUserNotificationAlertMessageKey, alertMessageArray); sCurrentNotification = CFUserNotificationCreate(kCFAllocatorDefault, 0 /* time interval */, kCFUserNotificationCautionAlertLevel, &userNotificationError, alertDict); if (!sCurrentNotification) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Can't create user notification - %d", (int)userNotificationError); goto finish; } sCurrentNotificationRunLoopSource = CFUserNotificationCreateRunLoopSource( kCFAllocatorDefault, sCurrentNotification, &_notificationDismissed, /* order */ 5 /* xxx - cheesy! */); if (!sCurrentNotificationRunLoopSource) { CFRelease(sCurrentNotification); sCurrentNotification = NULL; } CFRunLoopAddSource(CFRunLoopGetCurrent(), sCurrentNotificationRunLoopSource, kCFRunLoopDefaultMode); finish: SAFE_RELEASE(alertDict); SAFE_RELEASE(iokitFrameworkBundleURL); return; } /******************************************************************************* *******************************************************************************/ void _notificationDismissed( CFUserNotificationRef userNotification, CFOptionFlags responseFlags) { if (sCurrentNotification) { CFRelease(sCurrentNotification); sCurrentNotification = NULL; } if (sCurrentNotificationRunLoopSource) { CFRunLoopRemoveSource(CFRunLoopGetCurrent(), sCurrentNotificationRunLoopSource, kCFRunLoopDefaultMode); CFRelease(sCurrentNotificationRunLoopSource); sCurrentNotificationRunLoopSource = NULL; } CFRunLoopSourceSignal(sNotificationQueueRunLoopSource); CFRunLoopWakeUp(CFRunLoopGetCurrent()); return; } #endif /* ifndef NO_CFUserNotification */