X-Git-Url: https://git.saurik.com/apple/configd.git/blobdiff_plain/dbf6a266c384fc8b55e00a396eebe5cb62e21547..HEAD:/configd.tproj/session.c diff --git a/configd.tproj/session.c b/configd.tproj/session.c index 94e4213..2e88cc3 100644 --- a/configd.tproj/session.c +++ b/configd.tproj/session.c @@ -1,15 +1,15 @@ /* - * Copyright (c) 2000-2003 Apple Computer, Inc. All rights reserved. + * Copyright (c) 2000, 2001, 2003-2005, 2007-2020 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ - * + * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. - * + * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, @@ -17,7 +17,7 @@ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. - * + * * @APPLE_LICENSE_HEADER_END@ */ @@ -31,196 +31,846 @@ * - initial revision */ +#include #include "configd.h" #include "configd_server.h" +#include "pattern.h" #include "session.h" -/* information maintained for each active session */ -static serverSessionRef *sessions = NULL; -static int nSessions = 0; +#include +#include +#include +#include + +#if !TARGET_OS_SIMULATOR || (defined(IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED) && (IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED >= 1090)) +#define HAVE_MACHPORT_GUARDS +#endif + + +/* information maintained for the main listener */ +static serverSessionRef server_session = NULL; + +/* + * information maintained for each active session + * Note: sync w/sessionQueue() + */ +static CFMutableDictionaryRef client_sessions = NULL; +static CFIndex client_sessions_advise = 250; // when snapshot handler should detail sessions + + +static dispatch_queue_t +sessionQueue(void) +{ + static dispatch_once_t once; + static dispatch_queue_t q; + + dispatch_once(&once, ^{ + // allocate mapping between [client] session mach port and session info + client_sessions = CFDictionaryCreateMutable(NULL, + 0, + NULL, // use the actual mach_port_t as the key + &kCFTypeDictionaryValueCallBacks); + + // and a queue to synchronize access to the mapping + q = dispatch_queue_create("SCDynamicStore/sessions", NULL); + }); + + return q; +} + + +#pragma mark - +#pragma mark __serverSession object + +static CFStringRef __serverSessionCopyDescription (CFTypeRef cf); +static void __serverSessionDeallocate (CFTypeRef cf); + +static const CFRuntimeClass __serverSessionClass = { + 0, // version + "serverSession", // className + NULL, // init + NULL, // copy + __serverSessionDeallocate, // dealloc + NULL, // equal + NULL, // hash + NULL, // copyFormattingDesc + __serverSessionCopyDescription // copyDebugDesc +}; + +static CFTypeID __serverSessionTypeID = _kCFRuntimeNotATypeID; + + +static CFStringRef +__serverSessionCopyDescription(CFTypeRef cf) +{ + CFAllocatorRef allocator = CFGetAllocator(cf); + CFMutableStringRef result; + serverSessionRef session = (serverSessionRef)cf; + + result = CFStringCreateMutable(allocator, 0); + CFStringAppendFormat(result, NULL, CFSTR(" {"), cf, allocator); + + // add client port + CFStringAppendFormat(result, NULL, CFSTR("port = 0x%x (%d)"), session->key, session->key); + + // add session info + if (session->name != NULL) { + CFStringAppendFormat(result, NULL, CFSTR(", name = %@"), session->name); + } + + CFStringAppendFormat(result, NULL, CFSTR("}")); + return result; +} + + +static void +__serverSessionDeallocate(CFTypeRef cf) +{ +#pragma unused(cf) + serverSessionRef session = (serverSessionRef)cf; + + if (session->changedKeys != NULL) CFRelease(session->changedKeys); + if (session->name != NULL) CFRelease(session->name); + if (session->sessionKeys != NULL) CFRelease(session->sessionKeys); + + return; +} + + +static serverSessionRef +__serverSessionCreate(CFAllocatorRef allocator, mach_port_t server) +{ + static dispatch_once_t once; + serverSessionRef session; + uint32_t size; + + // initialize runtime + dispatch_once(&once, ^{ + __serverSessionTypeID = _CFRuntimeRegisterClass(&__serverSessionClass); + }); + + // allocate session + size = sizeof(serverSession) - sizeof(CFRuntimeBase); + session = (serverSessionRef)_CFRuntimeCreateInstance(allocator, + __serverSessionTypeID, + size, + NULL); + if (session == NULL) { + return NULL; + } + + // if needed, allocate a mach port for SCDynamicStore client + if (server == MACH_PORT_NULL) { + kern_return_t kr; + mach_port_t mp = MACH_PORT_NULL; +#ifdef HAVE_MACHPORT_GUARDS + mach_port_options_t opts; +#endif // HAVE_MACHPORT_GUARDS + + retry_allocate : + +#ifdef HAVE_MACHPORT_GUARDS + memset(&opts, 0, sizeof(opts)); + opts.flags = MPO_CONTEXT_AS_GUARD; + + kr = mach_port_construct(mach_task_self(), &opts, (mach_port_context_t)session, &mp); +#else // HAVE_MACHPORT_GUARDS + kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &mp); +#endif // HAVE_MACHPORT_GUARDS + + if (kr != KERN_SUCCESS) { + char *err = NULL; + + SC_log(LOG_NOTICE, "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, "Could not allocate mach port: %s", mach_error_string(kr)); + _SC_crash(err != NULL ? err : "Could not allocate new session (mach) port", + NULL, + NULL); + if (err != NULL) free(err); + CFRelease(session); + return NULL; + } + + // 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) { + /* + * 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). + */ + SC_log(LOG_ERR, "mach_port_insert_right() failed: %s", mach_error_string(kr)); + CFRelease(session); + return NULL; + } + + server = mp; + } + + session->callerEUID = 1; /* not "root" */ + session->callerRootAccess = UNKNOWN; + session->callerWriteEntitlement = kCFNull; /* UNKNOWN */ + session->key = server; +// session->store = NULL; + + return session; +} + + +#pragma mark - +#pragma mark SCDynamicStore state handler + + +static void +addSessionReference(const void *key, const void *value, void *context) +{ +#pragma unused(key) + CFMutableDictionaryRef dict = (CFMutableDictionaryRef)context; + serverSessionRef session = (serverSessionRef)value; + + if (session->name != NULL) { + int cnt; + CFNumberRef num; + + if (!CFDictionaryGetValueIfPresent(dict, + session->name, + (const void **)&num) || + !CFNumberGetValue(num, kCFNumberIntType, &cnt)) { + // if first session + cnt = 0; + } + cnt++; + num = CFNumberCreate(NULL, kCFNumberIntType, &cnt); + CFDictionarySetValue(dict, session->name, num); + CFRelease(num); + } + + return; +} + + +static void +add_state_handler() +{ + os_state_block_t state_block; + + state_block = ^os_state_data_t(os_state_hints_t hints) { +#pragma unused(hints) + CFDataRef data = NULL; + CFIndex n; + Boolean ok; + os_state_data_t state_data; + size_t state_data_size; + CFIndex state_len; + + n = CFDictionaryGetCount(client_sessions); + if (n < client_sessions_advise) { + CFStringRef str; + + str = CFStringCreateWithFormat(NULL, NULL, CFSTR("n = %ld"), n); + ok = _SCSerialize(str, &data, NULL, NULL); + CFRelease(str); + } else { + CFMutableDictionaryRef dict; + + dict = CFDictionaryCreateMutable(NULL, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFDictionaryApplyFunction(client_sessions, addSessionReference, dict); + ok = _SCSerialize(dict, &data, NULL, NULL); + CFRelease(dict); + } + + state_len = (ok && (data != NULL)) ? CFDataGetLength(data) : 0; + state_data_size = OS_STATE_DATA_SIZE_NEEDED(state_len); + if (state_data_size > MAX_STATEDUMP_SIZE) { + SC_log(LOG_ERR, "SCDynamicStore/sessions : state data too large (%zd > %zd)", + state_data_size, + (size_t)MAX_STATEDUMP_SIZE); + if (data != NULL) CFRelease(data); + return NULL; + } + + state_data = calloc(1, state_data_size); + if (state_data == NULL) { + SC_log(LOG_ERR, "SCDynamicStore/sessions: could not allocate state data"); + if (data != NULL) CFRelease(data); + return NULL; + } + + state_data->osd_type = OS_STATE_DATA_SERIALIZED_NSCF_OBJECT; + state_data->osd_data_size = (uint32_t)state_len; + strlcpy(state_data->osd_title, "SCDynamicStore/sessions", sizeof(state_data->osd_title)); + if (state_len > 0) { + memcpy(state_data->osd_data, CFDataGetBytePtr(data), state_len); + } + if (data != NULL) CFRelease(data); + + return state_data; + }; + + (void) os_state_add_handler(sessionQueue(), state_block); + return; +} + + +#pragma mark - +#pragma mark SCDynamicStore session management __private_extern__ serverSessionRef getSession(mach_port_t server) { - int i; + __block serverSessionRef session; - if (server == MACH_PORT_NULL) { - SCLog(TRUE, LOG_NOTICE, CFSTR("Excuse me, why is getSession() being called with an invalid port?")); + assert(server != MACH_PORT_NULL); + dispatch_sync(sessionQueue(), ^{ + session = (serverSessionRef)CFDictionaryGetValue(client_sessions, + (const void *)(uintptr_t)server); + }); + + return session; +} + + +__private_extern__ +serverSessionRef +getSessionNum(CFNumberRef serverNum) +{ + union { + mach_port_t mp; + uint64_t val; + } server; + serverSessionRef session; + + (void) CFNumberGetValue(serverNum, kCFNumberSInt64Type, &server.val); + session = getSession(server.mp); + + return session; +} + + +__private_extern__ +serverSessionRef +getSessionStr(CFStringRef serverKey) +{ + mach_port_t server; + serverSessionRef session; + char str[16]; + + (void) _SC_cfstring_to_cstring(serverKey, str, sizeof(str), kCFStringEncodingASCII); + server = atoi(str); + session = getSession(server); + + return session; +} + + +#if __has_feature(ptrauth_intrinsics) +extern const struct { char c; } objc_absolute_packed_isa_class_mask; +#endif + +static void +memcpy_objc_object(void* dst, const void* restrict src, size_t size) +{ + // first, we copy the object + memcpy(dst, src, size); + + // then, if needed, fix the isa pointer + #if __has_feature(ptrauth_intrinsics) + uintptr_t flags; + uintptr_t isa_mask; + void ** opaqueObject; + uintptr_t raw_isa; + uintptr_t real_isa; + + opaqueObject = (void**)src; + isa_mask = (uintptr_t)&objc_absolute_packed_isa_class_mask; + flags = (uintptr_t)(*opaqueObject) & ~isa_mask; + real_isa = (uintptr_t)(*opaqueObject) & isa_mask; + + #if __has_feature(ptrauth_objc_isa) + raw_isa = (uintptr_t)ptrauth_auth_data((void *)real_isa, + ptrauth_key_process_independent_data, + ptrauth_blend_discriminator(opaqueObject, 0x6AE1)); + #else // __has_feature(ptrauth_objc_isa) + raw_isa = (uintptr_t)ptrauth_strip((void*)real_isa, ptrauth_key_process_independent_data); + #endif // __has_feature(ptrauth_objc_isa) + ((CFRuntimeBase*)dst)->_cfisa = raw_isa; + ((uint64_t*)dst)[0] |= flags; + #endif // __has_feature(ptrauth_intrinsics) +} + + +__private_extern__ +serverSessionRef +tempSession(mach_port_t server, CFStringRef name, audit_token_t auditToken) +{ + static dispatch_once_t once; + SCDynamicStorePrivateRef storePrivate; /* temp session */ + static serverSession temp_session; + + dispatch_once(&once, ^{ + memcpy_objc_object(&temp_session, /* use "server" session clone */ + server_session, + sizeof(temp_session)); + (void) __SCDynamicStoreOpen(&temp_session.store, NULL); + }); + + if (temp_session.key != server) { + // if not SCDynamicStore "server" port return NULL; } - for (i = 0; i < nSessions; 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)) { - return thisSession; - } + /* 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); - /* no sessions available */ - return NULL; + return &temp_session; +} + + +__private_extern__ +void +addSession(serverSessionRef session, Boolean isMain) +{ + session->serverChannel = dispatch_mach_create_f("configd/SCDynamicStore", + server_queue(), + (void *)session, + server_mach_channel_handler); + if (!isMain) { + // if not main SCDynamicStore port, watch for exit + dispatch_mach_notify_no_senders(session->serverChannel, FALSE); + } +#if TARGET_OS_SIMULATOR + // simulators don't support MiG QoS propagation yet + dispatch_set_qos_class_fallback(session->serverChannel, QOS_CLASS_USER_INITIATED); +#else + dispatch_set_qos_class_fallback(session->serverChannel, QOS_CLASS_BACKGROUND); +#endif + dispatch_mach_connect(session->serverChannel, session->key, MACH_PORT_NULL, NULL); + return; } __private_extern__ serverSessionRef -addSession(CFMachPortRef server) +addClient(mach_port_t server, audit_token_t audit_token) { - int i; - int n = -1; - if (nSessions <= 0) { - /* new session (actually, the first) found */ - sessions = malloc(sizeof(serverSessionRef)); - n = 0; - nSessions = 1; - } else { - for (i = 0; i < nSessions; i++) { - if (sessions[i] == NULL) { - /* found an empty slot, use it */ - n = i; - } + __block serverSessionRef newSession = NULL; + + dispatch_sync(sessionQueue(), ^{ + Boolean ok; + + // check to see if we already have an open session + ok = CFDictionaryContainsKey(client_sessions, + (const void *)(uintptr_t)server); + if (ok) { + // if we've already added a session for this port + return; } - /* new session identified */ - if (n < 0) { - /* no empty slots, add one to the list */ - n = nSessions++; - sessions = reallocf(sessions, ((nSessions) * sizeof(serverSessionRef))); + + // allocate a new session for "the" server + newSession = __serverSessionCreate(NULL, MACH_PORT_NULL); + if (newSession != NULL) { + // and add a port --> session mapping + CFDictionarySetValue(client_sessions, + (const void *)(uintptr_t)newSession->key, + newSession); + + // save the audit_token in case we need to check the callers credentials + newSession->auditToken = audit_token; + + CFRelease(newSession); // reference held by dictionary } + }); + + if (newSession != NULL) { + addSession(newSession, FALSE); } - // 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" */ + return newSession; +} + - return sessions[n]; +__private_extern__ +serverSessionRef +addServer(mach_port_t server) +{ + // allocate a session for "the" server + server_session = __serverSessionCreate(NULL, server); + addSession(server_session, TRUE); + + // add a state dump handler + add_state_handler(); + + return server_session; } __private_extern__ void -removeSession(mach_port_t server) +cleanupSession(serverSessionRef session) { - int i; - serverSessionRef thisSession; - CFStringRef sessionKey; + mach_port_t server = session->key; + + SC_trace("cleanup : %5d", server); + + /* + * Close any open connections including cancelling any outstanding + * notification requests and releasing any locks. + */ + __MACH_PORT_DEBUG(TRUE, "*** cleanupSession", server); + (void) __SCDynamicStoreClose(&session->store); + __MACH_PORT_DEBUG(TRUE, "*** cleanupSession (after __SCDynamicStoreClose)", server); + + /* + * Our send right has already been removed. Remove our receive right. + */ +#ifdef HAVE_MACHPORT_GUARDS + (void) mach_port_destruct(mach_task_self(), server, 0, (mach_port_context_t)session); +#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 ((session->callerWriteEntitlement != NULL) && + (session->callerWriteEntitlement != kCFNull)) { + CFRelease(session->callerWriteEntitlement); + } - for (i = 0; i < nSessions; i++) { - thisSession = sessions[i]; + /* + * get rid of the per-session structure. + */ + dispatch_sync(sessionQueue(), ^{ + CFDictionaryRemoveValue(client_sessions, + (const void *)(uintptr_t)server); + }); - 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); + return; +} - /* - * Lastly, get rid of the per-session structure. - */ - free(thisSession); - sessions[i] = NULL; - return; - } +__private_extern__ +void +closeSession(serverSessionRef session) +{ + /* + * cancel and release the mach channel + */ + if (session->serverChannel != NULL) { + dispatch_mach_cancel(session->serverChannel); + dispatch_release(session->serverChannel); + session->serverChannel = NULL; } return; } +typedef struct ReportSessionInfo { + FILE *f; + int n; +} ReportSessionInfo, *ReportSessionInfoRef; + +static void +printOne(const void *key, const void *value, void *context) +{ +#pragma unused(key) + ReportSessionInfoRef reportInfo = (ReportSessionInfoRef)context; + serverSessionRef session = (serverSessionRef)value; + + SCPrint(TRUE, reportInfo->f, CFSTR(" %d : port = 0x%x"), ++reportInfo->n, session->key); + SCPrint(TRUE, reportInfo->f, CFSTR(", name = %@"), session->name); + if (session->changedKeys != NULL) { + SCPrint(TRUE, reportInfo->f, CFSTR("\n changedKeys = %@"), session->changedKeys); + } + if (session->sessionKeys != NULL) { + SCPrint(TRUE, reportInfo->f, CFSTR("\n sessionKeys = %@"), session->sessionKeys); + } + SCPrint(TRUE, reportInfo->f, CFSTR("\n")); + return; +} + + __private_extern__ void -cleanupSession(mach_port_t server) +listSessions(FILE *f) { - int i; + dispatch_sync(sessionQueue(), ^{ + ReportSessionInfo reportInfo = { .f = f, .n = 0 }; + + SCPrint(TRUE, f, CFSTR("Current sessions :\n")); + CFDictionaryApplyFunction(client_sessions, + printOne, + (void *)&reportInfo); + SCPrint(TRUE, f, CFSTR("\n")); + }); + return; +} - for (i = 0; i < nSessions; i++) { - serverSessionRef thisSession = sessions[i]; - if ((thisSession != NULL) && (thisSession->key == server)) { - /* - * session entry still exists. - */ +#include +#include - if (_configd_trace) { - SCTrace(TRUE, _configd_trace, CFSTR("cleanup : %5d\n"), server); +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 + SC_log(LOG_NOTICE, "SecTaskCopyValueForEntitlement(,\"%@\",) failed, error = %@ : %@", + entitlement, + error, + session->name); } + CFRelease(error); + } - /* - * Ensure that any changes made while we held the "lock" - * are discarded. - */ - 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(); - } + CFRelease(task); + } else { + SC_log(LOG_NOTICE, "SecTaskCreateWithAuditToken() failed: %@", + session->name); + } - /* - * Close any open connections including cancelling any outstanding - * notification requests and releasing any locks. - */ - (void) __SCDynamicStoreClose(&thisSession->store, TRUE); + return value; +} - /* - * Lastly, remove the session entry. - */ - removeSession(server); - return; - } +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_OS_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; + + return (session->callerRootAccess == YES) ? TRUE : FALSE; + +#else // !TARGET_OS_SIMULATOR +#pragma unused(session) + + /* + * assume that all processes interacting with + * the iOS Simulator "configd" are OK. + */ + return TRUE; + +#endif // !TARGET_OS_SIMULATOR } __private_extern__ -void -listSessions() +Boolean +hasWriteAccess(serverSessionRef session, const char *op, CFStringRef key) { - int i; + Boolean isSetup; + + // need to special case writing "Setup:" keys + isSetup = CFStringHasPrefix(key, kSCDynamicStoreDomainSetup); - fprintf(stderr, "Current sessions:"); - for (i = 0; i < nSessions; i++) { - serverSessionRef thisSession = sessions[i]; + if (hasRootAccess(session)) { + pid_t pid; - if (thisSession == NULL) { - continue; + // 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. + */ + SC_log(LOG_NOTICE, "*** Non-configd process (pid=%d) attempting to %s \"%@\" ***", + pid, + op, + key); } - fprintf(stderr, " %d", thisSession->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). + */ + SC_log(LOG_NOTICE, "*** Non-root process (pid=%d) attempting to modify \"%@\" ***", + sessionPid(session), + key); + + return FALSE; + } + + 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 (thisSession->store) { - task_t task = ((SCDynamicStorePrivateRef)thisSession->store)->notifySignalTask; + 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; + } + } - if (task != TASK_NULL) { - fprintf(stderr, "/%d", task); + 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; + } + } } } } - fprintf(stderr, "\n"); + + return FALSE; } + +__private_extern__ +Boolean +hasPathAccess(serverSessionRef session, const char *path) +{ + pid_t pid; + char realPath[PATH_MAX]; + + if (realpath(path, realPath) == NULL) { + SC_log(LOG_INFO, "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) { // ... + SC_log(LOG_INFO, "sandbox access denied: %s", strerror(errno)); + return FALSE; + } + + return TRUE; +}