]>
Commit | Line | Data |
---|---|---|
427c49bc A |
1 | /* Copyright (c) 2012 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 | ||
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 | ||
36 | bool appleSigned; | |
37 | }; | |
38 | ||
39 | static 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 | ||
51 | static 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 | ||
65 | static 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 | ||
92 | AUTH_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 | ||
102 | static 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 | ||
113 | process_t | |
114 | process_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 | CFRelease(secRequirementRef); | |
192 | ||
193 | LOGV("process[%i]: created (sid=%i) %s %p", proc->auditInfo.pid, proc->auditInfo.asid, proc->code_url, proc); | |
194 | ||
195 | done: | |
196 | CFReleaseSafe(code_info); | |
197 | CFReleaseSafe(code_url); | |
198 | return proc; | |
199 | } | |
200 | ||
201 | const void * | |
202 | process_get_key(process_t proc) | |
203 | { | |
204 | return &proc->auditInfo; | |
205 | } | |
206 | ||
207 | uid_t | |
208 | process_get_uid(process_t proc) | |
209 | { | |
210 | return proc ? proc->auditInfo.euid : (uid_t)-2; | |
211 | } | |
212 | ||
213 | pid_t | |
214 | process_get_pid(process_t proc) | |
215 | { | |
216 | return proc ? proc->auditInfo.pid : -1; | |
217 | } | |
218 | ||
219 | int32_t process_get_generation(process_t proc) | |
220 | { | |
221 | return proc->auditInfo.tid; | |
222 | } | |
223 | ||
224 | session_id_t | |
225 | process_get_session_id(process_t proc) | |
226 | { | |
227 | return proc ? proc->auditInfo.asid : -1; | |
228 | } | |
229 | ||
230 | session_t | |
231 | process_get_session(process_t proc) | |
232 | { | |
233 | return proc->session; | |
234 | } | |
235 | ||
236 | const audit_info_s * | |
237 | process_get_audit_info(process_t proc) | |
238 | { | |
239 | return &proc->auditInfo; | |
240 | } | |
241 | ||
242 | SecCodeRef | |
243 | process_get_code(process_t proc) | |
244 | { | |
245 | return proc->codeRef; | |
246 | } | |
247 | ||
248 | const char * | |
249 | process_get_code_url(process_t proc) | |
250 | { | |
251 | return proc->code_url; | |
252 | } | |
253 | ||
254 | void | |
255 | process_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 | ||
265 | void | |
266 | process_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 | ||
314 | auth_token_t | |
315 | process_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 | ||
332 | CFIndex | |
333 | process_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 | ||
342 | CFIndex | |
343 | process_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 | ||
353 | CFIndex | |
354 | process_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 | ||
364 | CFIndex | |
365 | process_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 | ||
374 | CFTypeRef | |
375 | process_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 | ||
386 | done: | |
387 | return value; | |
388 | } | |
389 | ||
390 | bool | |
391 | process_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 | ||
405 | done: | |
406 | return entitled; | |
407 | } | |
408 | ||
409 | bool | |
410 | process_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 | ||
432 | done: | |
433 | return entitled; | |
434 | } | |
435 | ||
436 | const char * | |
437 | process_get_identifier(process_t proc) | |
438 | { | |
439 | return proc->code_identifier; | |
440 | } | |
441 | ||
442 | CFDataRef | |
443 | process_get_requirement_data(process_t proc) | |
444 | { | |
445 | return proc->code_requirement_data; | |
446 | } | |
447 | ||
448 | SecRequirementRef | |
449 | process_get_requirement(process_t proc) | |
450 | { | |
451 | return proc->code_requirement; | |
452 | } | |
453 | ||
454 | bool 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 | |
464 | bool process_apple_signed(process_t proc) { | |
465 | return proc->appleSigned; | |
466 | } | |
467 | ||
468 | mach_port_t process_get_bootstrap(process_t proc) | |
469 | { | |
470 | return proc->bootstrap; | |
471 | } | |
472 | ||
473 | bool 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 |