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