1/* Copyright (c) 2012-2013 Apple Inc. All Rights Reserved. */
2
3#include "process.h"
4#include "server.h"
5#include "session.h"
6#include "debugging.h"
7#include "authd_private.h"
8#include "authtoken.h"
9#include "authutilities.h"
10#include "ccaudit.h"
11
12#include <Security/SecCode.h>
13#include <Security/SecRequirement.h>
14
15struct _process_s {
16    __AUTH_BASE_STRUCT_HEADER__;
17
18    audit_info_s auditInfo;
19
20    session_t session;
21
22    CFMutableBagRef authTokens;
23    dispatch_queue_t dispatch_queue;
24
25    CFMutableSetRef connections;
26
27    SecCodeRef codeRef;
28    char code_url[PATH_MAX+1];
29    char * code_identifier;
30    CFDataRef code_requirement_data;
31    SecRequirementRef code_requirement;
32    CFDictionaryRef code_entitlements;
33
34    mach_port_t bootstrap;
35
36    bool appleSigned;
37};
38
39static void
40_unregister_auth_tokens(const void *value, void *context)
41{
42    auth_token_t auth = (auth_token_t)value;
43    process_t proc = (process_t)context;
44
45    CFIndex count = auth_token_remove_process(auth, proc);
46    if ((count == 0)  && auth_token_check_state(auth, auth_token_state_registered)) {
47        server_unregister_auth_token(auth);
48    }
49}
50
51static void
52_destroy_zombie_tokens(process_t proc)
53{
54    LOGD("process[%i] destroy zombies, %ld auth tokens", process_get_pid(proc), CFBagGetCount(proc->authTokens));
55    _cf_bag_iterate(proc->authTokens, ^bool(CFTypeRef value) {
56        auth_token_t auth = (auth_token_t)value;
57        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));
58        if (auth_token_is_creator(auth, proc) && auth_token_check_state(auth, auth_token_state_zombie) && (auth_token_get_process_count(auth) == 1)) {
59            CFBagRemoveValue(proc->authTokens, auth);
60        }
61        return true;
62    });
63}
64
65static void
66_process_finalize(CFTypeRef value)
67{
68    process_t proc = (process_t)value;
69
70    LOGV("process[%i]: deallocated %p", proc->auditInfo.pid, proc);
71
72    dispatch_barrier_sync(proc->dispatch_queue, ^{
73        CFBagApplyFunction(proc->authTokens, _unregister_auth_tokens, proc);
74    });
75
76    session_remove_process(proc->session, proc);
77
78    dispatch_release(proc->dispatch_queue);
79    CFReleaseSafe(proc->authTokens);
80    CFReleaseSafe(proc->connections);
81    CFReleaseSafe(proc->session);
82    CFReleaseSafe(proc->codeRef);
83    CFReleaseSafe(proc->code_requirement);
84    CFReleaseSafe(proc->code_requirement_data);
85    CFReleaseSafe(proc->code_entitlements);
86    free_safe(proc->code_identifier);
87    if (proc->bootstrap != MACH_PORT_NULL) {
88        mach_port_deallocate(mach_task_self(), proc->bootstrap);
89    }
90}
91
92AUTH_TYPE_INSTANCE(process,
93                   .init = NULL,
94                   .copy = NULL,
95                   .finalize = _process_finalize,
96                   .equal = NULL,
97                   .hash = NULL,
98                   .copyFormattingDesc = NULL,
99                   .copyDebugDesc = NULL
100                   );
101
102static CFTypeID process_get_type_id() {
103    static CFTypeID type_id = _kCFRuntimeNotATypeID;
104    static dispatch_once_t onceToken;
105
106    dispatch_once(&onceToken, ^{
107        type_id = _CFRuntimeRegisterClass(&_auth_type_process);
108    });
109
110    return type_id;
111}
112
113process_t
114process_create(const audit_info_s * auditInfo, session_t session)
115{
116    OSStatus status = errSecSuccess;
117    process_t proc = NULL;
118    CFDictionaryRef code_info = NULL;
119    CFURLRef code_url = NULL;
120
121    require(session != NULL, done);
122    require(auditInfo != NULL, done);
123
124    proc = (process_t)_CFRuntimeCreateInstance(kCFAllocatorDefault, process_get_type_id(), AUTH_CLASS_SIZE(process), NULL);
125    require(proc != NULL, done);
126
127    proc->auditInfo = *auditInfo;
128
129    proc->session = (session_t)CFRetain(session);
130
131    proc->connections = CFSetCreateMutable(kCFAllocatorDefault, 0, NULL);
132
133    proc->authTokens = CFBagCreateMutable(kCFAllocatorDefault, 0, &kCFTypeBagCallBacks);
134    check(proc->authTokens != NULL);
135
136    proc->dispatch_queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
137    check(proc->dispatch_queue != NULL);
138
139    CFMutableDictionaryRef codeDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
140    CFNumberRef codePid = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &proc->auditInfo.pid);
141    CFDictionarySetValue(codeDict, kSecGuestAttributePid, codePid);
142    status = SecCodeCopyGuestWithAttributes(NULL, codeDict, kSecCSDefaultFlags, &proc->codeRef);
143    CFReleaseSafe(codeDict);
144    CFReleaseSafe(codePid);
145
146    if (status) {
147        LOGE("process[%i]: failed to create code ref %i", proc->auditInfo.pid, status);
148        CFReleaseNull(proc);
149        goto done;
150    }
151
152    status = SecCodeCopySigningInformation(proc->codeRef, kSecCSRequirementInformation, &code_info);
153    require_noerr_action(status, done, LOGV("process[%i]: SecCodeCopySigningInformation failed with %i", proc->auditInfo.pid, status));
154
155    CFTypeRef value = NULL;
156    if (CFDictionaryGetValueIfPresent(code_info, kSecCodeInfoDesignatedRequirement, (const void**)&value)) {
157        if (CFGetTypeID(value) == SecRequirementGetTypeID()) {
158            SecRequirementCopyData((SecRequirementRef)value, kSecCSDefaultFlags, &proc->code_requirement_data);
159            if (proc->code_requirement_data) {
160                SecRequirementCreateWithData(proc->code_requirement_data, kSecCSDefaultFlags, &proc->code_requirement);
161            }
162        }
163        value = NULL;
164    }
165
166    if (SecCodeCopyPath(proc->codeRef, kSecCSDefaultFlags, &code_url) == errSecSuccess) {
167        CFURLGetFileSystemRepresentation(code_url, true, (UInt8*)proc->code_url, sizeof(proc->code_url));
168    }
169
170    if (CFDictionaryGetValueIfPresent(code_info, kSecCodeInfoIdentifier, &value)) {
171        if (CFGetTypeID(value) == CFStringGetTypeID()) {
172            proc->code_identifier = _copy_cf_string(value, NULL);
173        }
174        value = NULL;
175    }
176
177    if (CFDictionaryGetValueIfPresent(code_info, kSecCodeInfoEntitlementsDict, &value)) {
178        if (CFGetTypeID(value) == CFDictionaryGetTypeID()) {
179            proc->code_entitlements = CFDictionaryCreateCopy(kCFAllocatorDefault, value);
180        }
181        value = NULL;
182    }
183
184    // This is the clownfish supported way to check for a Mac App Store or B&I signed build
185    CFStringRef requirementString = CFSTR("(anchor apple) or (anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9])");
186    SecRequirementRef  secRequirementRef = NULL;
187    status = SecRequirementCreateWithString(requirementString, kSecCSDefaultFlags, &secRequirementRef);
188    if (status == errSecSuccess) {
189        proc->appleSigned = process_verify_requirment(proc, secRequirementRef);
190    }
191    CFReleaseSafe(secRequirementRef);
192
193    LOGV("process[%i]: created (sid=%i) %s %p", proc->auditInfo.pid, proc->auditInfo.asid, proc->code_url, proc);
194
195done:
196    CFReleaseSafe(code_info);
197    CFReleaseSafe(code_url);
198    return proc;
199}
200
201const void *
202process_get_key(process_t proc)
203{
204    return &proc->auditInfo;
205}
206
207uid_t
208process_get_uid(process_t proc)
209{
210    return proc ? proc->auditInfo.euid : (uid_t)-2;
211}
212
213pid_t
214process_get_pid(process_t proc)
215{
216    return proc ? proc->auditInfo.pid : -1;
217}
218
219int32_t process_get_generation(process_t proc)
220{
221    return proc->auditInfo.tid;
222}
223
224session_id_t
225process_get_session_id(process_t proc)
226{
227    return proc ? proc->auditInfo.asid : -1;
228}
229
230session_t
231process_get_session(process_t proc)
232{
233    return proc->session;
234}
235
236const audit_info_s *
237process_get_audit_info(process_t proc)
238{
239    return &proc->auditInfo;
240}
241
242SecCodeRef
243process_get_code(process_t proc)
244{
245    return proc->codeRef;
246}
247
248const char *
249process_get_code_url(process_t proc)
250{
251    return proc->code_url;
252}
253
254void
255process_add_auth_token(process_t proc, auth_token_t auth)
256{
257    dispatch_sync(proc->dispatch_queue, ^{
258        CFBagAddValue(proc->authTokens, auth);
259        if (CFBagGetCountOfValue(proc->authTokens, auth) == 1) {
260            auth_token_add_process(auth, proc);
261        }
262    });
263}
264
265void
266process_remove_auth_token(process_t proc, auth_token_t auth, AuthorizationFlags flags)
267{
268    dispatch_sync(proc->dispatch_queue, ^{
269        bool destroy = false;
270        bool creator = auth_token_is_creator(auth, proc);
271        CFIndex count = auth_token_get_process_count(auth);
272
273        // if we are the last ones associated with this auth token or the caller passed in the kAuthorizationFlagDestroyRights
274        // then we break the link between the process and auth token.  If another process holds a reference
275        // then kAuthorizationFlagDestroyRights will only break the link and not destroy the auth token
276        // <rdar://problem/14553640>
277        if ((count == 1) ||
278            (flags & kAuthorizationFlagDestroyRights))
279        {
280            destroy = true;
281            goto done;
282        }
283
284        // If we created this token and someone else is holding a reference to it
285        // don't destroy the link until they have freed the authorization ref
286        // instead set the zombie state on the auth_token
287        if (creator) {
288            if (CFBagGetCountOfValue(proc->authTokens, auth) == 1) {
289                auth_token_set_state(auth, auth_token_state_zombie);
290            } else {
291                destroy = true;
292            }
293        } else {
294            destroy = true;
295        }
296
297    done:
298        if (destroy) {
299            CFBagRemoveValue(proc->authTokens, auth);
300            if (!CFBagContainsValue(proc->authTokens, auth)) {
301                auth_token_remove_process(auth, proc);
302
303                if ((count == 1) && auth_token_check_state(auth, auth_token_state_registered)) {
304                    server_unregister_auth_token(auth);
305                }
306            }
307        }
308
309        // destroy all eligible zombies
310        _destroy_zombie_tokens(proc);
311    });
312}
313
314auth_token_t
315process_find_copy_auth_token(process_t proc, const AuthorizationBlob * blob)
316{
317    __block CFTypeRef auth = NULL;
318    dispatch_sync(proc->dispatch_queue, ^{
319        _cf_bag_iterate(proc->authTokens, ^bool(CFTypeRef value) {
320            auth_token_t iter = (auth_token_t)value;
321            if (memcmp(blob, auth_token_get_blob(iter), sizeof(AuthorizationBlob)) == 0) {
322                auth = iter;
323                CFRetain(auth);
324                return false;
325            }
326            return true;
327        });
328    });
329    return (auth_token_t)auth;
330}
331
332CFIndex
333process_get_auth_token_count(process_t proc)
334{
335    __block CFIndex count = 0;
336    dispatch_sync(proc->dispatch_queue, ^{
337        count = CFBagGetCount(proc->authTokens);
338    });
339    return count;
340}
341
342CFIndex
343process_add_connection(process_t proc, connection_t conn)
344{
345    __block CFIndex count = 0;
346    dispatch_sync(proc->dispatch_queue, ^{
347        CFSetAddValue(proc->connections, conn);
348        count = CFSetGetCount(proc->connections);
349    });
350    return count;
351}
352
353CFIndex
354process_remove_connection(process_t proc, connection_t conn)
355{
356    __block CFIndex count = 0;
357    dispatch_sync(proc->dispatch_queue, ^{
358        CFSetRemoveValue(proc->connections, conn);
359        count = CFSetGetCount(proc->connections);
360    });
361    return count;
362}
363
364CFIndex
365process_get_connection_count(process_t proc)
366{
367    __block CFIndex count = 0;
368    dispatch_sync(proc->dispatch_queue, ^{
369        count = CFSetGetCount(proc->connections);
370    });
371    return count;
372}
373
374CFTypeRef
375process_copy_entitlement_value(process_t proc, const char * entitlement)
376{
377    CFTypeRef value = NULL;
378    require(entitlement != NULL, done);
379
380    CFStringRef key = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, entitlement, kCFStringEncodingUTF8, kCFAllocatorNull);
381    if (proc->code_entitlements && key && (CFDictionaryGetValueIfPresent(proc->code_entitlements, key, &value))) {
382        CFRetainSafe(value);
383    }
384    CFReleaseSafe(key);
385
386done:
387    return value;
388}
389
390bool
391process_has_entitlement(process_t proc, const char * entitlement)
392{
393    bool entitled = false;
394    require(entitlement != NULL, done);
395
396    CFStringRef key = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, entitlement, kCFStringEncodingUTF8, kCFAllocatorNull);
397    CFTypeRef value = NULL;
398    if (proc->code_entitlements && key && (CFDictionaryGetValueIfPresent(proc->code_entitlements, key, &value))) {
399        if (CFGetTypeID(value) == CFBooleanGetTypeID()) {
400            entitled = CFBooleanGetValue(value);
401        }
402    }
403    CFReleaseSafe(key);
404
405done:
406    return entitled;
407}
408
409bool
410process_has_entitlement_for_right(process_t proc, const char * right)
411{
412    bool entitled = false;
413    require(right != NULL, done);
414
415    CFTypeRef rights = NULL;
416    if (proc->code_entitlements && CFDictionaryGetValueIfPresent(proc->code_entitlements, CFSTR("com.apple.private.AuthorizationServices"), &rights)) {
417        if (CFGetTypeID(rights) == CFArrayGetTypeID()) {
418            CFStringRef key = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, right, kCFStringEncodingUTF8, kCFAllocatorNull);
419            require(key != NULL, done);
420
421            CFIndex count = CFArrayGetCount(rights);
422            for (CFIndex i = 0; i < count; i++) {
423                if (CFEqual(CFArrayGetValueAtIndex(rights, i), key)) {
424                    entitled = true;
425                    break;
426                }
427            }
428            CFReleaseSafe(key);
429        }
430    }
431
432done:
433    return entitled;
434}
435
436const char *
437process_get_identifier(process_t proc)
438{
439    return proc->code_identifier;
440}
441
442CFDataRef
443process_get_requirement_data(process_t proc)
444{
445    return proc->code_requirement_data;
446}
447
448SecRequirementRef
449process_get_requirement(process_t proc)
450{
451    return proc->code_requirement;
452}
453
454bool process_verify_requirment(process_t proc, SecRequirementRef requirment)
455{
456    OSStatus status = SecCodeCheckValidity(proc->codeRef, kSecCSDefaultFlags, requirment);
457    if (status != errSecSuccess) {
458        LOGV("process[%i]: code requirement check failed (%d)", proc->auditInfo.pid, status);
459    }
460    return (status == errSecSuccess);
461}
462
463// Returns true if the process was signed by B&I or the Mac App Store
464bool process_apple_signed(process_t proc) {
465    return proc->appleSigned;
466}
467
468mach_port_t process_get_bootstrap(process_t proc)
469{
470    return proc->bootstrap;
471}
472
473bool process_set_bootstrap(process_t proc, mach_port_t bootstrap)
474{
475    if (bootstrap != MACH_PORT_NULL) {
476        if (proc->bootstrap != MACH_PORT_NULL) {
477            mach_port_deallocate(mach_task_self(), proc->bootstrap);
478        }
479        proc->bootstrap = bootstrap;
480        return true;
481    }
482    return false;
483}
484
485