X-Git-Url: https://git.saurik.com/apple/configd.git/blobdiff_plain/dbf6a266c384fc8b55e00a396eebe5cb62e21547..b7ffbc6af5a0697ee3d9a3052dedfebc6b8270dd:/configd.tproj/session.c diff --git a/configd.tproj/session.c b/configd.tproj/session.c index 94e4213..60b824e 100644 --- a/configd.tproj/session.c +++ b/configd.tproj/session.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000-2003 Apple Computer, Inc. All rights reserved. + * Copyright (c) 2000, 2001, 2003-2005, 2007-2014 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * @@ -31,13 +31,31 @@ * - initial revision */ +#include #include "configd.h" #include "configd_server.h" +#include "pattern.h" #include "session.h" +#include +#include +#include + +#if !TARGET_IPHONE_SIMULATOR || (defined(IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED) && (IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED >= 1090)) +#define HAVE_MACHPORT_GUARDS +#endif + + /* information maintained for each active session */ -static serverSessionRef *sessions = NULL; -static int nSessions = 0; +static serverSessionRef *sessions = NULL; +static int nSessions = 0; /* # of allocated sessions */ +static int lastSession = -1; /* # of last used session */ + +/* CFMachPortInvalidation runloop */ +static CFRunLoopRef sessionRunLoop = NULL; + +/* temp session */ +static serverSessionRef temp_session = NULL; __private_extern__ @@ -47,20 +65,27 @@ getSession(mach_port_t server) int i; if (server == MACH_PORT_NULL) { - SCLog(TRUE, LOG_NOTICE, CFSTR("Excuse me, why is getSession() being called with an invalid port?")); + SCLog(TRUE, LOG_ERR, CFSTR("Excuse me, why is getSession() being called with an invalid port?")); return NULL; } - for (i = 0; i < nSessions; i++) { + /* look for matching session (note: slot 0 is the "server" port) */ + for (i = 1; i <= lastSession; i++) { serverSessionRef thisSession = sessions[i]; if (thisSession == NULL) { /* found an empty slot, skip it */ continue; - } else if (thisSession->key == server) { - return thisSession; /* we've seen this server before */ - } else if (thisSession->store && - (((SCDynamicStorePrivateRef)thisSession->store)->notifySignalTask == server)) { + } + + if (thisSession->key == server) { + /* we've seen this server before */ + return thisSession; + } + + if ((thisSession->store != NULL) && + (((SCDynamicStorePrivateRef)thisSession->store)->notifySignalTask == server)) { + /* we've seen this task port before */ return thisSession; } } @@ -72,78 +97,188 @@ getSession(mach_port_t server) __private_extern__ serverSessionRef -addSession(CFMachPortRef server) +tempSession(mach_port_t server, CFStringRef name, audit_token_t auditToken) { - int i; - int n = -1; + static dispatch_once_t once; + SCDynamicStorePrivateRef storePrivate; + + if (sessions[0]->key != server) { + // if not SCDynamicStore "server" port + return NULL; + } + + dispatch_once(&once, ^{ + temp_session = sessions[0]; /* use "server" session */ + (void) __SCDynamicStoreOpen(&temp_session->store, NULL); + }); + + /* save audit token, caller entitlements */ + temp_session->auditToken = auditToken; + temp_session->callerEUID = 1; /* not "root" */ + temp_session->callerRootAccess = UNKNOWN; + if ((temp_session->callerWriteEntitlement != NULL) && + (temp_session->callerWriteEntitlement != kCFNull)) { + CFRelease(temp_session->callerWriteEntitlement); + } + temp_session->callerWriteEntitlement = kCFNull; /* UNKNOWN */ + + /* save name */ + storePrivate = (SCDynamicStorePrivateRef)temp_session->store; + if (storePrivate->name != NULL) CFRelease(storePrivate->name); + storePrivate->name = CFRetain(name); + + return temp_session; +} + + +__private_extern__ +serverSessionRef +addSession(mach_port_t server, CFStringRef (*copyDescription)(const void *info)) +{ + CFMachPortContext context = { 0, NULL, NULL, NULL, NULL }; + kern_return_t kr; + mach_port_t mp = server; + int n = -1; + serverSessionRef newSession = NULL; + + /* save current (SCDynamicStore) runloop */ + if (sessionRunLoop == NULL) { + sessionRunLoop = CFRunLoopGetCurrent(); + } if (nSessions <= 0) { - /* new session (actually, the first) found */ - sessions = malloc(sizeof(serverSessionRef)); - n = 0; - nSessions = 1; + /* if first session (the "server" port) */ + n = 0; /* use slot "0" */ + lastSession = 0; /* last used slot */ + + nSessions = 64; + sessions = malloc(nSessions * sizeof(serverSessionRef)); + + // allocate a new session for "the" server + newSession = calloc(1, sizeof(serverSession)); } else { - for (i = 0; i < nSessions; i++) { - if (sessions[i] == NULL) { - /* found an empty slot, use it */ - n = i; + int i; +#ifdef HAVE_MACHPORT_GUARDS + mach_port_options_t opts; +#endif // HAVE_MACHPORT_GUARDS + + /* check to see if we already have an open session (note: slot 0 is the "server" port) */ + for (i = 1; i <= lastSession; i++) { + serverSessionRef thisSession = sessions[i]; + + if (thisSession == NULL) { + /* found an empty slot */ + if (n < 0) { + /* keep track of the first [empty] slot */ + n = i; + } + + /* and keep looking for a matching session */ + continue; + } + + if (thisSession->key == server) { + /* we've seen this server before */ + return NULL; + } + + if ((thisSession->store != NULL) && + (((SCDynamicStorePrivateRef)thisSession->store)->notifySignalTask == server)) { + /* we've seen this task port before */ + return NULL; } } - /* new session identified */ + + /* add a new session */ if (n < 0) { - /* no empty slots, add one to the list */ - n = nSessions++; - sessions = reallocf(sessions, ((nSessions) * sizeof(serverSessionRef))); + /* if no empty slots */ + n = ++lastSession; + if (lastSession >= nSessions) { + /* expand the session list */ + nSessions *= 2; + sessions = reallocf(sessions, (nSessions * sizeof(serverSessionRef))); + } } - } - // allocate a new session for this server - sessions[n] = malloc(sizeof(serverSession)); - sessions[n]->key = CFMachPortGetPort(server); - sessions[n]->serverPort = server; - sessions[n]->serverRunLoopSource = NULL; - sessions[n]->store = NULL; - sessions[n]->callerEUID = 1; /* not "root" */ - sessions[n]->callerEGID = 1; /* not "wheel" */ + // allocate a session for this client + newSession = calloc(1, sizeof(serverSession)); - return sessions[n]; -} + // create mach port for SCDynamicStore client + mp = MACH_PORT_NULL; + retry_allocate : -__private_extern__ -void -removeSession(mach_port_t server) -{ - int i; - serverSessionRef thisSession; - CFStringRef sessionKey; +#ifdef HAVE_MACHPORT_GUARDS + bzero(&opts, sizeof(opts)); + opts.flags = MPO_CONTEXT_AS_GUARD; - for (i = 0; i < nSessions; i++) { - thisSession = sessions[i]; + kr = mach_port_construct(mach_task_self(), &opts, newSession, &mp); +#else // HAVE_MACHPORT_GUARDS + kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &mp); +#endif // HAVE_MACHPORT_GUARDS - if (thisSession == NULL) { - /* found an empty slot, skip it */ - continue; - } else if (thisSession->key == server) { - /* - * We don't need any remaining information in the - * sessionData dictionary, remove it. - */ - sessionKey = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), server); - CFDictionaryRemoveValue(sessionData, sessionKey); - CFRelease(sessionKey); + if (kr != KERN_SUCCESS) { + char *err = NULL; + SCLog(TRUE, LOG_ERR, CFSTR("addSession: could not allocate mach port: %s"), mach_error_string(kr)); + if ((kr == KERN_NO_SPACE) || (kr == KERN_RESOURCE_SHORTAGE)) { + sleep(1); + goto retry_allocate; + } + + (void) asprintf(&err, "addSession: could not allocate mach port: %s", mach_error_string(kr)); + _SC_crash(err != NULL ? err : "addSession: could not allocate mach port", + NULL, + NULL); + if (err != NULL) free(err); + + free(newSession); + return NULL; + } + } + + // create server port + context.info = newSession; + context.copyDescription = copyDescription; + + // + // Note: we create the CFMachPort *before* we insert a send + // right present to ensure that CF does not establish + // its dead name notification. + // + newSession->serverPort = _SC_CFMachPortCreateWithPort("SCDynamicStore/session", + mp, + configdCallback, + &context); + + if (n > 0) { + // insert send right that will be moved to the client + kr = mach_port_insert_right(mach_task_self(), + mp, + mp, + MACH_MSG_TYPE_MAKE_SEND); + if (kr != KERN_SUCCESS) { /* - * Lastly, get rid of the per-session structure. + * We can't insert a send right into our own port! This should + * only happen if someone stomped on OUR port (so let's leave + * the port alone). */ - free(thisSession); - sessions[i] = NULL; + SCLog(TRUE, LOG_ERR, CFSTR("addSession mach_port_insert_right(): %s"), mach_error_string(kr)); - return; + free(newSession); + return NULL; } } - return; + sessions[n] = newSession; + sessions[n]->key = mp; +// sessions[n]->serverRunLoopSource = NULL; +// sessions[n]->store = NULL; + sessions[n]->callerEUID = 1; /* not "root" */ + sessions[n]->callerRootAccess = UNKNOWN; + sessions[n]->callerWriteEntitlement = kCFNull; /* UNKNOWN */ + + return newSession; } @@ -153,10 +288,16 @@ cleanupSession(mach_port_t server) { int i; - for (i = 0; i < nSessions; i++) { + for (i = 1; i <= lastSession; i++) { + CFStringRef sessionKey; serverSessionRef thisSession = sessions[i]; - if ((thisSession != NULL) && (thisSession->key == server)) { + if (thisSession == NULL) { + /* found an empty slot, skip it */ + continue; + } + + if (thisSession->key == server) { /* * session entry still exists. */ @@ -166,61 +307,377 @@ cleanupSession(mach_port_t server) } /* - * Ensure that any changes made while we held the "lock" - * are discarded. + * Close any open connections including cancelling any outstanding + * notification requests and releasing any locks. + */ + __MACH_PORT_DEBUG(TRUE, "*** cleanupSession", server); + (void) __SCDynamicStoreClose(&thisSession->store); + __MACH_PORT_DEBUG(TRUE, "*** cleanupSession (after __SCDynamicStoreClose)", server); + + /* + * Our send right has already been removed. Remove our receive right. */ - if ((storeLocked > 0) && - ((SCDynamicStorePrivateRef)thisSession->store)->locked) { - /* - * swap store and associated data which, after - * being closed, will result in the restoration - * of the original pre-"locked" data. - */ - _swapLockedStoreData(); +#ifdef HAVE_MACHPORT_GUARDS + (void) mach_port_destruct(mach_task_self(), server, 0, thisSession); +#else // HAVE_MACHPORT_GUARDS + (void) mach_port_mod_refs(mach_task_self(), server, MACH_PORT_RIGHT_RECEIVE, -1); +#endif // HAVE_MACHPORT_GUARDS + + /* + * release any entitlement info + */ + if ((thisSession->callerWriteEntitlement != NULL) && + (thisSession->callerWriteEntitlement != kCFNull)) { + CFRelease(thisSession->callerWriteEntitlement); } /* - * Close any open connections including cancelling any outstanding - * notification requests and releasing any locks. + * We don't need any remaining information in the + * sessionData dictionary, remove it. */ - (void) __SCDynamicStoreClose(&thisSession->store, TRUE); + sessionKey = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), server); + CFDictionaryRemoveValue(sessionData, sessionKey); + CFRelease(sessionKey); /* - * Lastly, remove the session entry. + * get rid of the per-session structure. */ - removeSession(server); + free(thisSession); + sessions[i] = NULL; + + if (i == lastSession) { + /* we are removing the last session, update last used slot */ + while (--lastSession > 0) { + if (sessions[lastSession] != NULL) { + break; + } + } + } return; } } + + SCLog(TRUE, LOG_ERR, CFSTR("MACH_NOTIFY_NO_SENDERS w/no session, port = %d"), server); + __MACH_PORT_DEBUG(TRUE, "*** cleanupSession w/no session", server); return; } __private_extern__ void -listSessions() +listSessions(FILE *f) { int i; - fprintf(stderr, "Current sessions:"); - for (i = 0; i < nSessions; i++) { + SCPrint(TRUE, f, CFSTR("Current sessions :\n")); + for (i = 0; i <= lastSession; i++) { serverSessionRef thisSession = sessions[i]; if (thisSession == NULL) { continue; } - fprintf(stderr, " %d", thisSession->key); + SCPrint(TRUE, f, CFSTR("\t%d : port = 0x%x"), i, thisSession->key); - if (thisSession->store) { - task_t task = ((SCDynamicStorePrivateRef)thisSession->store)->notifySignalTask; + if (thisSession->store != NULL) { + SCDynamicStorePrivateRef storePrivate = (SCDynamicStorePrivateRef)thisSession->store; - if (task != TASK_NULL) { - fprintf(stderr, "/%d", task); + if (storePrivate->notifySignalTask != TASK_NULL) { + SCPrint(TRUE, f, CFSTR(", task = %d"), storePrivate->notifySignalTask); } } + + if (sessionData != NULL) { + CFDictionaryRef info; + CFStringRef key; + + key = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), thisSession->key); + info = CFDictionaryGetValue(sessionData, key); + CFRelease(key); + if (info != NULL) { + CFStringRef name; + + name = CFDictionaryGetValue(info, kSCDName); + if (name != NULL) { + SCPrint(TRUE, f, CFSTR(", name = %@"), name); + } + } + } + + if (thisSession->serverPort != NULL) { + SCPrint(TRUE, f, CFSTR("\n\t\t%@"), thisSession->serverPort); + } + + if (thisSession->serverRunLoopSource != NULL) { + SCPrint(TRUE, f, CFSTR("\n\t\t%@"), thisSession->serverRunLoopSource); + } + + SCPrint(TRUE, f, CFSTR("\n")); + } + + SCPrint(TRUE, f, CFSTR("\n")); + return; +} + + +#include +#include + +static CFStringRef +sessionName(serverSessionRef session) +{ + CFDictionaryRef info; + CFStringRef name = NULL; + CFStringRef sessionKey; + + sessionKey = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), session->key); + info = CFDictionaryGetValue(sessionData, sessionKey); + CFRelease(sessionKey); + + if (info != NULL) { + name = CFDictionaryGetValue(info, kSCDName); + } + + return (name != NULL) ? name : CFSTR("???"); +} + +static CFTypeRef +copyEntitlement(serverSessionRef session, CFStringRef entitlement) +{ + SecTaskRef task; + CFTypeRef value = NULL; + + // Create the security task from the audit token + task = SecTaskCreateWithAuditToken(NULL, session->auditToken); + if (task != NULL) { + CFErrorRef error = NULL; + + // Get the value for the entitlement + value = SecTaskCopyValueForEntitlement(task, entitlement, &error); + if ((value == NULL) && (error != NULL)) { + CFIndex code = CFErrorGetCode(error); + CFStringRef domain = CFErrorGetDomain(error); + + if (!CFEqual(domain, kCFErrorDomainMach) || + ((code != kIOReturnInvalid) && (code != kIOReturnNotFound))) { + // if unexpected error + SCLog(TRUE, LOG_ERR, + CFSTR("SecTaskCopyValueForEntitlement(,\"%@\",) failed, error = %@ : %@"), + entitlement, + error, + sessionName(session)); + } + CFRelease(error); + } + + CFRelease(task); + } else { + SCLog(TRUE, LOG_ERR, + CFSTR("SecTaskCreateWithAuditToken() failed: %@"), + sessionName(session)); } - fprintf(stderr, "\n"); + + return value; } + +static pid_t +sessionPid(serverSessionRef session) +{ + pid_t caller_pid; + +#if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE + caller_pid = audit_token_to_pid(session->auditToken); +#else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE + audit_token_to_au32(session->auditToken, + NULL, // auidp + NULL, // euid + NULL, // egid + NULL, // ruid + NULL, // rgid + &caller_pid, // pid + NULL, // asid + NULL); // tid +#endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE + + return caller_pid; +} + + +__private_extern__ +Boolean +hasRootAccess(serverSessionRef session) +{ +#if !TARGET_IPHONE_SIMULATOR + + if (session->callerRootAccess == UNKNOWN) { +#if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE + session->callerEUID = audit_token_to_euid(session->auditToken); +#else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE + audit_token_to_au32(session->auditToken, + NULL, // auidp + &session->callerEUID, // euid + NULL, // egid + NULL, // ruid + NULL, // rgid + NULL, // pid + NULL, // asid + NULL); // tid +#endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE + session->callerRootAccess = (session->callerEUID == 0) ? YES : NO; + } + + return (session->callerRootAccess == YES) ? TRUE : FALSE; + +#else // !TARGET_IPHONE_SIMULATOR + + /* + * assume that all processes interacting with + * the iOS Simulator "configd" are OK. + */ + return TRUE; + +#endif // !TARGET_IPHONE_SIMULATOR +} + + +__private_extern__ +Boolean +hasWriteAccess(serverSessionRef session, CFStringRef key) +{ + Boolean isSetup; + + // need to special case writing "Setup:" keys + isSetup = CFStringHasPrefix(key, kSCDynamicStoreDomainSetup); + + if (hasRootAccess(session)) { + pid_t pid; + + // grant write access to eUID==0 processes + + pid = sessionPid(session); + if (isSetup && (pid != getpid())) { + /* + * WAIT!!! + * + * This is NOT configd (or a plugin) trying to + * write to an SCDynamicStore "Setup:" key. In + * general, this is unwise and we should at the + * very least complain. + */ + SCLog(TRUE, LOG_ERR, + CFSTR("*** Non-configd process (pid=%d) attempting to modify \"%@\" ***"), + pid, + key); + } + + return TRUE; + } + + if (isSetup) { + /* + * STOP!!! + * + * This is a non-root process trying to write to + * an SCDynamicStore "Setup:" key. This is not + * something we should ever allow (regardless of + * any entitlements). + */ + SCLog(TRUE, LOG_ERR, + CFSTR("*** Non-root process (pid=%d) attempting to modify \"%@\" ***"), + sessionPid(session), + key); + + //return FALSE; // return FALSE when rdar://9811832 has beed fixed + } + + if (session->callerWriteEntitlement == kCFNull) { + session->callerWriteEntitlement = copyEntitlement(session, + kSCWriteEntitlementName); + } + + if (session->callerWriteEntitlement == NULL) { + return FALSE; + } + + if (isA_CFBoolean(session->callerWriteEntitlement) && + CFBooleanGetValue(session->callerWriteEntitlement)) { + // grant write access to "entitled" processes + return TRUE; + } + + if (isA_CFDictionary(session->callerWriteEntitlement)) { + CFArrayRef keys; + CFArrayRef patterns; + + keys = CFDictionaryGetValue(session->callerWriteEntitlement, CFSTR("keys")); + if (isA_CFArray(keys)) { + if (CFArrayContainsValue(keys, + CFRangeMake(0, CFArrayGetCount(keys)), + key)) { + // if key matches one of the entitlement "keys", grant + // write access + return TRUE; + } + } + + patterns = CFDictionaryGetValue(session->callerWriteEntitlement, CFSTR("patterns")); + if (isA_CFArray(patterns)) { + CFIndex i; + CFIndex n = CFArrayGetCount(patterns); + + for (i = 0; i < n; i++) { + CFStringRef pattern; + + pattern = CFArrayGetValueAtIndex(patterns, i); + if (isA_CFString(pattern)) { + if (patternKeyMatches(pattern, key)) { + // if key matches one of the entitlement + // "patterns", grant write access + return TRUE; + } + } + } + } + } + + return FALSE; +} + + +__private_extern__ +Boolean +hasPathAccess(serverSessionRef session, const char *path) +{ + pid_t pid; + char realPath[PATH_MAX]; + + if (realpath(path, realPath) == NULL) { + SCLog(TRUE, LOG_DEBUG, CFSTR("hasPathAccess realpath() failed: %s"), strerror(errno)); + return FALSE; + } + +#if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE + pid = audit_token_to_pid(session->auditToken); +#else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE + audit_token_to_au32(session->auditToken, + NULL, // auidp + NULL, // euid + NULL, // egid + NULL, // ruid + NULL, // rgid + &pid, // pid + NULL, // asid + NULL); // tid +#endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE + if (sandbox_check(pid, // pid + "file-write-data", // operation + SANDBOX_FILTER_PATH | SANDBOX_CHECK_NO_REPORT, // sandbox_filter_type + realPath) > 0) { // ... + SCLog(TRUE, LOG_DEBUG, CFSTR("hasPathAccess sandbox access denied: %s"), strerror(errno)); + return FALSE; + } + + return TRUE; +}