#include <SystemConfiguration/SystemConfiguration.h>
#include "configd.h"
#include "configd_server.h"
#include "pattern.h"
#include "session.h"
#include <unistd.h>
#include <bsm/libbsm.h>
#include <os/state_private.h>
#include <sandbox.h>
#if !TARGET_OS_SIMULATOR || (defined(IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED) && (IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED >= 1090))
#define HAVE_MACHPORT_GUARDS
#endif
static serverSessionRef server_session = NULL;
static CFMutableDictionaryRef client_sessions = NULL;
static dispatch_queue_t
sessionQueue(void)
{
static dispatch_once_t once;
static dispatch_queue_t q;
dispatch_once(&once, ^{
client_sessions = CFDictionaryCreateMutable(NULL,
0,
NULL, &kCFTypeDictionaryValueCallBacks);
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, "serverSession", NULL, NULL, __serverSessionDeallocate, NULL, NULL, NULL, __serverSessionCopyDescription };
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("<serverSession %p [%p]> {"), cf, allocator);
CFStringAppendFormat(result, NULL, CFSTR("port = 0x%x (%d)"), session->key, session->key);
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;
dispatch_once(&once, ^{
__serverSessionTypeID = _CFRuntimeRegisterClass(&__serverSessionClass);
});
size = sizeof(serverSession) - sizeof(CFRuntimeBase);
session = (serverSessionRef)_CFRuntimeCreateInstance(allocator,
__serverSessionTypeID,
size,
NULL);
if (session == NULL) {
return NULL;
}
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;
}
kr = mach_port_insert_right(mach_task_self(),
mp,
mp,
MACH_MSG_TYPE_MAKE_SEND);
if (kr != KERN_SUCCESS) {
SC_log(LOG_ERR, "mach_port_insert_right() failed: %s", mach_error_string(kr));
CFRelease(session);
return NULL;
}
server = mp;
}
session->callerEUID = 1;
session->callerRootAccess = UNKNOWN;
session->callerWriteEntitlement = kCFNull;
session->key = server;
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)) {
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 < 500) {
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)
{
__block serverSessionRef session;
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;
}
__private_extern__
serverSessionRef
tempSession(mach_port_t server, CFStringRef name, audit_token_t auditToken)
{
static dispatch_once_t once;
SCDynamicStorePrivateRef storePrivate;
static serverSession temp_session;
dispatch_once(&once, ^{
temp_session = *server_session;
(void) __SCDynamicStoreOpen(&temp_session.store, NULL);
});
if (temp_session.key != server) {
return NULL;
}
temp_session.auditToken = auditToken;
temp_session.callerEUID = 1;
temp_session.callerRootAccess = UNKNOWN;
if ((temp_session.callerWriteEntitlement != NULL) &&
(temp_session.callerWriteEntitlement != kCFNull)) {
CFRelease(temp_session.callerWriteEntitlement);
}
temp_session.callerWriteEntitlement = kCFNull;
storePrivate = (SCDynamicStorePrivateRef)temp_session.store;
if (storePrivate->name != NULL) CFRelease(storePrivate->name);
storePrivate->name = CFRetain(name);
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) {
dispatch_mach_request_no_senders(session->serverChannel);
}
#if TARGET_OS_SIMULATOR
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
addClient(mach_port_t server, audit_token_t audit_token)
{
__block serverSessionRef newSession = NULL;
dispatch_sync(sessionQueue(), ^{
Boolean ok;
ok = CFDictionaryContainsKey(client_sessions,
(const void *)(uintptr_t)server);
if (ok) {
return;
}
newSession = __serverSessionCreate(NULL, MACH_PORT_NULL);
if (newSession != NULL) {
CFDictionarySetValue(client_sessions,
(const void *)(uintptr_t)newSession->key,
newSession);
newSession->auditToken = audit_token;
CFRelease(newSession); }
});
if (newSession != NULL) {
addSession(newSession, FALSE);
}
return newSession;
}
__private_extern__
serverSessionRef
addServer(mach_port_t server)
{
server_session = __serverSessionCreate(NULL, server);
addSession(server_session, TRUE);
add_state_handler();
return server_session;
}
__private_extern__
void
cleanupSession(serverSessionRef session)
{
mach_port_t server = session->key;
SC_trace("cleanup : %5d", server);
__MACH_PORT_DEBUG(TRUE, "*** cleanupSession", server);
(void) __SCDynamicStoreClose(&session->store);
__MACH_PORT_DEBUG(TRUE, "*** cleanupSession (after __SCDynamicStoreClose)", server);
#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
if ((session->callerWriteEntitlement != NULL) &&
(session->callerWriteEntitlement != kCFNull)) {
CFRelease(session->callerWriteEntitlement);
}
dispatch_sync(sessionQueue(), ^{
CFDictionaryRemoveValue(client_sessions,
(const void *)(uintptr_t)server);
});
return;
}
__private_extern__
void
closeSession(serverSessionRef session)
{
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
listSessions(FILE *f)
{
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;
}
#include <Security/Security.h>
#include <Security/SecTask.h>
static CFTypeRef
copyEntitlement(serverSessionRef session, CFStringRef entitlement)
{
SecTaskRef task;
CFTypeRef value = NULL;
task = SecTaskCreateWithAuditToken(NULL, session->auditToken);
if (task != NULL) {
CFErrorRef error = NULL;
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))) {
SC_log(LOG_NOTICE, "SecTaskCopyValueForEntitlement(,\"%@\",) failed, error = %@ : %@",
entitlement,
error,
session->name);
}
CFRelease(error);
}
CFRelease(task);
} else {
SC_log(LOG_NOTICE, "SecTaskCreateWithAuditToken() failed: %@",
session->name);
}
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, NULL, NULL, NULL, NULL, &caller_pid, NULL, NULL); #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, &session->callerEUID, NULL, NULL, NULL, NULL, NULL, NULL); #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_OS_SIMULATOR
#pragma unused(session)
return TRUE;
#endif // !TARGET_OS_SIMULATOR
}
__private_extern__
Boolean
hasWriteAccess(serverSessionRef session, const char *op, CFStringRef key)
{
Boolean isSetup;
isSetup = CFStringHasPrefix(key, kSCDynamicStoreDomainSetup);
if (hasRootAccess(session)) {
pid_t pid;
pid = sessionPid(session);
if (isSetup && (pid != getpid())) {
SC_log(LOG_NOTICE, "*** Non-configd process (pid=%d) attempting to %s \"%@\" ***",
pid,
op,
key);
}
return TRUE;
}
if (isSetup) {
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)) {
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)) {
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)) {
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) {
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, NULL, NULL, NULL, NULL, &pid, NULL, NULL); #endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
if (sandbox_check(pid, "file-write-data", SANDBOX_FILTER_PATH | SANDBOX_CHECK_NO_REPORT, realPath) > 0) { SC_log(LOG_INFO, "sandbox access denied: %s", strerror(errno));
return FALSE;
}
return TRUE;
}