X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/c38e3ce98599a410a47dc10253faa4d5830f13b2..427c49bcad63d042b29ada2ac27e3dfc4845c779:/authd/process.c?ds=sidebyside diff --git a/authd/process.c b/authd/process.c new file mode 100644 index 00000000..9f1b2163 --- /dev/null +++ b/authd/process.c @@ -0,0 +1,484 @@ +/* Copyright (c) 2012 Apple Inc. All rights reserved. */ + +#include "process.h" +#include "server.h" +#include "session.h" +#include "debugging.h" +#include "authd_private.h" +#include "authtoken.h" +#include "authutilities.h" +#include "ccaudit.h" + +#include +#include + +struct _process_s { + __AUTH_BASE_STRUCT_HEADER__; + + audit_info_s auditInfo; + + session_t session; + + CFMutableBagRef authTokens; + dispatch_queue_t dispatch_queue; + + CFMutableSetRef connections; + + SecCodeRef codeRef; + char code_url[PATH_MAX+1]; + char * code_identifier; + CFDataRef code_requirement_data; + SecRequirementRef code_requirement; + CFDictionaryRef code_entitlements; + + mach_port_t bootstrap; + + bool appleSigned; +}; + +static void +_unregister_auth_tokens(const void *value, void *context) +{ + auth_token_t auth = (auth_token_t)value; + process_t proc = (process_t)context; + + CFIndex count = auth_token_remove_process(auth, proc); + if ((count == 0) && auth_token_check_state(auth, auth_token_state_registered)) { + server_unregister_auth_token(auth); + } +} + +static void +_destroy_zombie_tokens(process_t proc) +{ + LOGD("process[%i] destroy zombies, %ld auth tokens", process_get_pid(proc), CFBagGetCount(proc->authTokens)); + _cf_bag_iterate(proc->authTokens, ^bool(CFTypeRef value) { + auth_token_t auth = (auth_token_t)value; + LOGD("process[%i] %p, creator=%i, zombie=%i, process_cout=%ld", process_get_pid(proc), auth, auth_token_is_creator(auth, proc), auth_token_check_state(auth, auth_token_state_zombie), auth_token_get_process_count(auth)); + if (auth_token_is_creator(auth, proc) && auth_token_check_state(auth, auth_token_state_zombie) && (auth_token_get_process_count(auth) == 1)) { + CFBagRemoveValue(proc->authTokens, auth); + } + return true; + }); +} + +static void +_process_finalize(CFTypeRef value) +{ + process_t proc = (process_t)value; + + LOGV("process[%i]: deallocated %p", proc->auditInfo.pid, proc); + + dispatch_barrier_sync(proc->dispatch_queue, ^{ + CFBagApplyFunction(proc->authTokens, _unregister_auth_tokens, proc); + }); + + session_remove_process(proc->session, proc); + + dispatch_release(proc->dispatch_queue); + CFReleaseSafe(proc->authTokens); + CFReleaseSafe(proc->connections); + CFReleaseSafe(proc->session); + CFReleaseSafe(proc->codeRef); + CFReleaseSafe(proc->code_requirement); + CFReleaseSafe(proc->code_requirement_data); + CFReleaseSafe(proc->code_entitlements); + free_safe(proc->code_identifier); + if (proc->bootstrap != MACH_PORT_NULL) { + mach_port_deallocate(mach_task_self(), proc->bootstrap); + } +} + +AUTH_TYPE_INSTANCE(process, + .init = NULL, + .copy = NULL, + .finalize = _process_finalize, + .equal = NULL, + .hash = NULL, + .copyFormattingDesc = NULL, + .copyDebugDesc = NULL + ); + +static CFTypeID process_get_type_id() { + static CFTypeID type_id = _kCFRuntimeNotATypeID; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + type_id = _CFRuntimeRegisterClass(&_auth_type_process); + }); + + return type_id; +} + +process_t +process_create(const audit_info_s * auditInfo, session_t session) +{ + OSStatus status = errSecSuccess; + process_t proc = NULL; + CFDictionaryRef code_info = NULL; + CFURLRef code_url = NULL; + + require(session != NULL, done); + require(auditInfo != NULL, done); + + proc = (process_t)_CFRuntimeCreateInstance(kCFAllocatorDefault, process_get_type_id(), AUTH_CLASS_SIZE(process), NULL); + require(proc != NULL, done); + + proc->auditInfo = *auditInfo; + + proc->session = (session_t)CFRetain(session); + + proc->connections = CFSetCreateMutable(kCFAllocatorDefault, 0, NULL); + + proc->authTokens = CFBagCreateMutable(kCFAllocatorDefault, 0, &kCFTypeBagCallBacks); + check(proc->authTokens != NULL); + + proc->dispatch_queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); + check(proc->dispatch_queue != NULL); + + CFMutableDictionaryRef codeDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFNumberRef codePid = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &proc->auditInfo.pid); + CFDictionarySetValue(codeDict, kSecGuestAttributePid, codePid); + status = SecCodeCopyGuestWithAttributes(NULL, codeDict, kSecCSDefaultFlags, &proc->codeRef); + CFReleaseSafe(codeDict); + CFReleaseSafe(codePid); + + if (status) { + LOGE("process[%i]: failed to create code ref %i", proc->auditInfo.pid, status); + CFReleaseNull(proc); + goto done; + } + + status = SecCodeCopySigningInformation(proc->codeRef, kSecCSRequirementInformation, &code_info); + require_noerr_action(status, done, LOGV("process[%i]: SecCodeCopySigningInformation failed with %i", proc->auditInfo.pid, status)); + + CFTypeRef value = NULL; + if (CFDictionaryGetValueIfPresent(code_info, kSecCodeInfoDesignatedRequirement, (const void**)&value)) { + if (CFGetTypeID(value) == SecRequirementGetTypeID()) { + SecRequirementCopyData((SecRequirementRef)value, kSecCSDefaultFlags, &proc->code_requirement_data); + if (proc->code_requirement_data) { + SecRequirementCreateWithData(proc->code_requirement_data, kSecCSDefaultFlags, &proc->code_requirement); + } + } + value = NULL; + } + + if (SecCodeCopyPath(proc->codeRef, kSecCSDefaultFlags, &code_url) == errSecSuccess) { + CFURLGetFileSystemRepresentation(code_url, true, (UInt8*)proc->code_url, sizeof(proc->code_url)); + } + + if (CFDictionaryGetValueIfPresent(code_info, kSecCodeInfoIdentifier, &value)) { + if (CFGetTypeID(value) == CFStringGetTypeID()) { + proc->code_identifier = _copy_cf_string(value, NULL); + } + value = NULL; + } + + if (CFDictionaryGetValueIfPresent(code_info, kSecCodeInfoEntitlementsDict, &value)) { + if (CFGetTypeID(value) == CFDictionaryGetTypeID()) { + proc->code_entitlements = CFDictionaryCreateCopy(kCFAllocatorDefault, value); + } + value = NULL; + } + + // This is the clownfish supported way to check for a Mac App Store or B&I signed build + CFStringRef requirementString = CFSTR("(anchor apple) or (anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9])"); + SecRequirementRef secRequirementRef = NULL; + status = SecRequirementCreateWithString(requirementString, kSecCSDefaultFlags, &secRequirementRef); + if (status == errSecSuccess) { + proc->appleSigned = process_verify_requirment(proc, secRequirementRef); + } + CFRelease(secRequirementRef); + + LOGV("process[%i]: created (sid=%i) %s %p", proc->auditInfo.pid, proc->auditInfo.asid, proc->code_url, proc); + +done: + CFReleaseSafe(code_info); + CFReleaseSafe(code_url); + return proc; +} + +const void * +process_get_key(process_t proc) +{ + return &proc->auditInfo; +} + +uid_t +process_get_uid(process_t proc) +{ + return proc ? proc->auditInfo.euid : (uid_t)-2; +} + +pid_t +process_get_pid(process_t proc) +{ + return proc ? proc->auditInfo.pid : -1; +} + +int32_t process_get_generation(process_t proc) +{ + return proc->auditInfo.tid; +} + +session_id_t +process_get_session_id(process_t proc) +{ + return proc ? proc->auditInfo.asid : -1; +} + +session_t +process_get_session(process_t proc) +{ + return proc->session; +} + +const audit_info_s * +process_get_audit_info(process_t proc) +{ + return &proc->auditInfo; +} + +SecCodeRef +process_get_code(process_t proc) +{ + return proc->codeRef; +} + +const char * +process_get_code_url(process_t proc) +{ + return proc->code_url; +} + +void +process_add_auth_token(process_t proc, auth_token_t auth) +{ + dispatch_sync(proc->dispatch_queue, ^{ + CFBagAddValue(proc->authTokens, auth); + if (CFBagGetCountOfValue(proc->authTokens, auth) == 1) { + auth_token_add_process(auth, proc); + } + }); +} + +void +process_remove_auth_token(process_t proc, auth_token_t auth, AuthorizationFlags flags) +{ + dispatch_sync(proc->dispatch_queue, ^{ + bool destroy = false; + bool creator = auth_token_is_creator(auth, proc); + CFIndex count = auth_token_get_process_count(auth); + + // if we are the last ones associated with this auth token or the caller passed in the kAuthorizationFlagDestroyRights + // then we break the link between the process and auth token. If another process holds a reference + // then kAuthorizationFlagDestroyRights will only break the link and not destroy the auth token + // + if ((count == 1) || + (flags & kAuthorizationFlagDestroyRights)) + { + destroy = true; + goto done; + } + + // If we created this token and someone else is holding a reference to it + // don't destroy the link until they have freed the authorization ref + // instead set the zombie state on the auth_token + if (creator) { + if (CFBagGetCountOfValue(proc->authTokens, auth) == 1) { + auth_token_set_state(auth, auth_token_state_zombie); + } else { + destroy = true; + } + } else { + destroy = true; + } + + done: + if (destroy) { + CFBagRemoveValue(proc->authTokens, auth); + if (!CFBagContainsValue(proc->authTokens, auth)) { + auth_token_remove_process(auth, proc); + + if ((count == 1) && auth_token_check_state(auth, auth_token_state_registered)) { + server_unregister_auth_token(auth); + } + } + } + + // destroy all eligible zombies + _destroy_zombie_tokens(proc); + }); +} + +auth_token_t +process_find_copy_auth_token(process_t proc, const AuthorizationBlob * blob) +{ + __block CFTypeRef auth = NULL; + dispatch_sync(proc->dispatch_queue, ^{ + _cf_bag_iterate(proc->authTokens, ^bool(CFTypeRef value) { + auth_token_t iter = (auth_token_t)value; + if (memcmp(blob, auth_token_get_blob(iter), sizeof(AuthorizationBlob)) == 0) { + auth = iter; + CFRetain(auth); + return false; + } + return true; + }); + }); + return (auth_token_t)auth; +} + +CFIndex +process_get_auth_token_count(process_t proc) +{ + __block CFIndex count = 0; + dispatch_sync(proc->dispatch_queue, ^{ + count = CFBagGetCount(proc->authTokens); + }); + return count; +} + +CFIndex +process_add_connection(process_t proc, connection_t conn) +{ + __block CFIndex count = 0; + dispatch_sync(proc->dispatch_queue, ^{ + CFSetAddValue(proc->connections, conn); + count = CFSetGetCount(proc->connections); + }); + return count; +} + +CFIndex +process_remove_connection(process_t proc, connection_t conn) +{ + __block CFIndex count = 0; + dispatch_sync(proc->dispatch_queue, ^{ + CFSetRemoveValue(proc->connections, conn); + count = CFSetGetCount(proc->connections); + }); + return count; +} + +CFIndex +process_get_connection_count(process_t proc) +{ + __block CFIndex count = 0; + dispatch_sync(proc->dispatch_queue, ^{ + count = CFSetGetCount(proc->connections); + }); + return count; +} + +CFTypeRef +process_copy_entitlement_value(process_t proc, const char * entitlement) +{ + CFTypeRef value = NULL; + require(entitlement != NULL, done); + + CFStringRef key = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, entitlement, kCFStringEncodingUTF8, kCFAllocatorNull); + if (proc->code_entitlements && key && (CFDictionaryGetValueIfPresent(proc->code_entitlements, key, &value))) { + CFRetainSafe(value); + } + CFReleaseSafe(key); + +done: + return value; +} + +bool +process_has_entitlement(process_t proc, const char * entitlement) +{ + bool entitled = false; + require(entitlement != NULL, done); + + CFStringRef key = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, entitlement, kCFStringEncodingUTF8, kCFAllocatorNull); + CFTypeRef value = NULL; + if (proc->code_entitlements && key && (CFDictionaryGetValueIfPresent(proc->code_entitlements, key, &value))) { + if (CFGetTypeID(value) == CFBooleanGetTypeID()) { + entitled = CFBooleanGetValue(value); + } + } + CFReleaseSafe(key); + +done: + return entitled; +} + +bool +process_has_entitlement_for_right(process_t proc, const char * right) +{ + bool entitled = false; + require(right != NULL, done); + + CFTypeRef rights = NULL; + if (proc->code_entitlements && CFDictionaryGetValueIfPresent(proc->code_entitlements, CFSTR("com.apple.private.AuthorizationServices"), &rights)) { + if (CFGetTypeID(rights) == CFArrayGetTypeID()) { + CFStringRef key = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, right, kCFStringEncodingUTF8, kCFAllocatorNull); + require(key != NULL, done); + + CFIndex count = CFArrayGetCount(rights); + for (CFIndex i = 0; i < count; i++) { + if (CFEqual(CFArrayGetValueAtIndex(rights, i), key)) { + entitled = true; + break; + } + } + CFReleaseSafe(key); + } + } + +done: + return entitled; +} + +const char * +process_get_identifier(process_t proc) +{ + return proc->code_identifier; +} + +CFDataRef +process_get_requirement_data(process_t proc) +{ + return proc->code_requirement_data; +} + +SecRequirementRef +process_get_requirement(process_t proc) +{ + return proc->code_requirement; +} + +bool process_verify_requirment(process_t proc, SecRequirementRef requirment) +{ + OSStatus status = SecCodeCheckValidity(proc->codeRef, kSecCSDefaultFlags, requirment); + if (status != errSecSuccess) { + LOGV("process[%i]: code requirement check failed (%d)", proc->auditInfo.pid, status); + } + return (status == errSecSuccess); +} + +// Returns true if the process was signed by B&I or the Mac App Store +bool process_apple_signed(process_t proc) { + return proc->appleSigned; +} + +mach_port_t process_get_bootstrap(process_t proc) +{ + return proc->bootstrap; +} + +bool process_set_bootstrap(process_t proc, mach_port_t bootstrap) +{ + if (bootstrap != MACH_PORT_NULL) { + if (proc->bootstrap != MACH_PORT_NULL) { + mach_port_deallocate(mach_task_self(), proc->bootstrap); + } + proc->bootstrap = bootstrap; + return true; + } + return false; +} +