]> git.saurik.com Git - apple/configd.git/blob - configd.tproj/session.c
configd-699.1.5.tar.gz
[apple/configd.git] / configd.tproj / session.c
1 /*
2 * Copyright (c) 2000, 2001, 2003-2005, 2007-2014 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 /*
25 * Modification History
26 *
27 * June 1, 2001 Allan Nathanson <ajn@apple.com>
28 * - public API conversion
29 *
30 * March 24, 2000 Allan Nathanson <ajn@apple.com>
31 * - initial revision
32 */
33
34 #include <SystemConfiguration/SystemConfiguration.h>
35 #include "configd.h"
36 #include "configd_server.h"
37 #include "pattern.h"
38 #include "session.h"
39
40 #include <unistd.h>
41 #include <bsm/libbsm.h>
42 #include <sandbox.h>
43
44 #if !TARGET_IPHONE_SIMULATOR || (defined(IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED) && (IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED >= 1090))
45 #define HAVE_MACHPORT_GUARDS
46 #endif
47
48
49 /* information maintained for each active session */
50 static serverSessionRef *sessions = NULL;
51 static int nSessions = 0; /* # of allocated sessions */
52 static int lastSession = -1; /* # of last used session */
53
54 /* CFMachPortInvalidation runloop */
55 static CFRunLoopRef sessionRunLoop = NULL;
56
57 /* temp session */
58 static serverSessionRef temp_session = NULL;
59
60
61 __private_extern__
62 serverSessionRef
63 getSession(mach_port_t server)
64 {
65 int i;
66
67 if (server == MACH_PORT_NULL) {
68 SCLog(TRUE, LOG_ERR, CFSTR("Excuse me, why is getSession() being called with an invalid port?"));
69 return NULL;
70 }
71
72 /* look for matching session (note: slot 0 is the "server" port) */
73 for (i = 1; i <= lastSession; i++) {
74 serverSessionRef thisSession = sessions[i];
75
76 if (thisSession == NULL) {
77 /* found an empty slot, skip it */
78 continue;
79 }
80
81 if (thisSession->key == server) {
82 /* we've seen this server before */
83 return thisSession;
84 }
85
86 if ((thisSession->store != NULL) &&
87 (((SCDynamicStorePrivateRef)thisSession->store)->notifySignalTask == server)) {
88 /* we've seen this task port before */
89 return thisSession;
90 }
91 }
92
93 /* no sessions available */
94 return NULL;
95 }
96
97
98 __private_extern__
99 serverSessionRef
100 tempSession(mach_port_t server, CFStringRef name, audit_token_t auditToken)
101 {
102 static dispatch_once_t once;
103 SCDynamicStorePrivateRef storePrivate;
104
105 if (sessions[0]->key != server) {
106 // if not SCDynamicStore "server" port
107 return NULL;
108 }
109
110 dispatch_once(&once, ^{
111 temp_session = sessions[0]; /* use "server" session */
112 (void) __SCDynamicStoreOpen(&temp_session->store, NULL);
113 });
114
115 /* save audit token, caller entitlements */
116 temp_session->auditToken = auditToken;
117 temp_session->callerEUID = 1; /* not "root" */
118 temp_session->callerRootAccess = UNKNOWN;
119 if ((temp_session->callerWriteEntitlement != NULL) &&
120 (temp_session->callerWriteEntitlement != kCFNull)) {
121 CFRelease(temp_session->callerWriteEntitlement);
122 }
123 temp_session->callerWriteEntitlement = kCFNull; /* UNKNOWN */
124
125 /* save name */
126 storePrivate = (SCDynamicStorePrivateRef)temp_session->store;
127 if (storePrivate->name != NULL) CFRelease(storePrivate->name);
128 storePrivate->name = CFRetain(name);
129
130 return temp_session;
131 }
132
133
134 __private_extern__
135 serverSessionRef
136 addSession(mach_port_t server, CFStringRef (*copyDescription)(const void *info))
137 {
138 CFMachPortContext context = { 0, NULL, NULL, NULL, NULL };
139 kern_return_t kr;
140 mach_port_t mp = server;
141 int n = -1;
142 serverSessionRef newSession = NULL;
143
144 /* save current (SCDynamicStore) runloop */
145 if (sessionRunLoop == NULL) {
146 sessionRunLoop = CFRunLoopGetCurrent();
147 }
148
149 if (nSessions <= 0) {
150 /* if first session (the "server" port) */
151 n = 0; /* use slot "0" */
152 lastSession = 0; /* last used slot */
153
154 nSessions = 64;
155 sessions = malloc(nSessions * sizeof(serverSessionRef));
156
157 // allocate a new session for "the" server
158 newSession = calloc(1, sizeof(serverSession));
159 } else {
160 int i;
161 #ifdef HAVE_MACHPORT_GUARDS
162 mach_port_options_t opts;
163 #endif // HAVE_MACHPORT_GUARDS
164
165 /* check to see if we already have an open session (note: slot 0 is the "server" port) */
166 for (i = 1; i <= lastSession; i++) {
167 serverSessionRef thisSession = sessions[i];
168
169 if (thisSession == NULL) {
170 /* found an empty slot */
171 if (n < 0) {
172 /* keep track of the first [empty] slot */
173 n = i;
174 }
175
176 /* and keep looking for a matching session */
177 continue;
178 }
179
180 if (thisSession->key == server) {
181 /* we've seen this server before */
182 return NULL;
183 }
184
185 if ((thisSession->store != NULL) &&
186 (((SCDynamicStorePrivateRef)thisSession->store)->notifySignalTask == server)) {
187 /* we've seen this task port before */
188 return NULL;
189 }
190 }
191
192 /* add a new session */
193 if (n < 0) {
194 /* if no empty slots */
195 n = ++lastSession;
196 if (lastSession >= nSessions) {
197 /* expand the session list */
198 nSessions *= 2;
199 sessions = reallocf(sessions, (nSessions * sizeof(serverSessionRef)));
200 }
201 }
202
203 // allocate a session for this client
204 newSession = calloc(1, sizeof(serverSession));
205
206 // create mach port for SCDynamicStore client
207 mp = MACH_PORT_NULL;
208
209 retry_allocate :
210
211 #ifdef HAVE_MACHPORT_GUARDS
212 bzero(&opts, sizeof(opts));
213 opts.flags = MPO_CONTEXT_AS_GUARD;
214
215 kr = mach_port_construct(mach_task_self(), &opts, newSession, &mp);
216 #else // HAVE_MACHPORT_GUARDS
217 kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &mp);
218 #endif // HAVE_MACHPORT_GUARDS
219
220 if (kr != KERN_SUCCESS) {
221 char *err = NULL;
222
223 SCLog(TRUE, LOG_ERR, CFSTR("addSession: could not allocate mach port: %s"), mach_error_string(kr));
224 if ((kr == KERN_NO_SPACE) || (kr == KERN_RESOURCE_SHORTAGE)) {
225 sleep(1);
226 goto retry_allocate;
227 }
228
229 (void) asprintf(&err, "addSession: could not allocate mach port: %s", mach_error_string(kr));
230 _SC_crash(err != NULL ? err : "addSession: could not allocate mach port",
231 NULL,
232 NULL);
233 if (err != NULL) free(err);
234
235 free(newSession);
236 return NULL;
237 }
238 }
239
240 // create server port
241 context.info = newSession;
242 context.copyDescription = copyDescription;
243
244 //
245 // Note: we create the CFMachPort *before* we insert a send
246 // right present to ensure that CF does not establish
247 // its dead name notification.
248 //
249 newSession->serverPort = _SC_CFMachPortCreateWithPort("SCDynamicStore/session",
250 mp,
251 configdCallback,
252 &context);
253
254 if (n > 0) {
255 // insert send right that will be moved to the client
256 kr = mach_port_insert_right(mach_task_self(),
257 mp,
258 mp,
259 MACH_MSG_TYPE_MAKE_SEND);
260 if (kr != KERN_SUCCESS) {
261 /*
262 * We can't insert a send right into our own port! This should
263 * only happen if someone stomped on OUR port (so let's leave
264 * the port alone).
265 */
266 SCLog(TRUE, LOG_ERR, CFSTR("addSession mach_port_insert_right(): %s"), mach_error_string(kr));
267
268 free(newSession);
269 return NULL;
270 }
271 }
272
273 sessions[n] = newSession;
274 sessions[n]->key = mp;
275 // sessions[n]->serverRunLoopSource = NULL;
276 // sessions[n]->store = NULL;
277 sessions[n]->callerEUID = 1; /* not "root" */
278 sessions[n]->callerRootAccess = UNKNOWN;
279 sessions[n]->callerWriteEntitlement = kCFNull; /* UNKNOWN */
280
281 return newSession;
282 }
283
284
285 __private_extern__
286 void
287 cleanupSession(mach_port_t server)
288 {
289 int i;
290
291 for (i = 1; i <= lastSession; i++) {
292 CFStringRef sessionKey;
293 serverSessionRef thisSession = sessions[i];
294
295 if (thisSession == NULL) {
296 /* found an empty slot, skip it */
297 continue;
298 }
299
300 if (thisSession->key == server) {
301 /*
302 * session entry still exists.
303 */
304
305 if (_configd_trace) {
306 SCTrace(TRUE, _configd_trace, CFSTR("cleanup : %5d\n"), server);
307 }
308
309 /*
310 * Close any open connections including cancelling any outstanding
311 * notification requests and releasing any locks.
312 */
313 __MACH_PORT_DEBUG(TRUE, "*** cleanupSession", server);
314 (void) __SCDynamicStoreClose(&thisSession->store);
315 __MACH_PORT_DEBUG(TRUE, "*** cleanupSession (after __SCDynamicStoreClose)", server);
316
317 /*
318 * Our send right has already been removed. Remove our receive right.
319 */
320 #ifdef HAVE_MACHPORT_GUARDS
321 (void) mach_port_destruct(mach_task_self(), server, 0, thisSession);
322 #else // HAVE_MACHPORT_GUARDS
323 (void) mach_port_mod_refs(mach_task_self(), server, MACH_PORT_RIGHT_RECEIVE, -1);
324 #endif // HAVE_MACHPORT_GUARDS
325
326 /*
327 * release any entitlement info
328 */
329 if ((thisSession->callerWriteEntitlement != NULL) &&
330 (thisSession->callerWriteEntitlement != kCFNull)) {
331 CFRelease(thisSession->callerWriteEntitlement);
332 }
333
334 /*
335 * We don't need any remaining information in the
336 * sessionData dictionary, remove it.
337 */
338 sessionKey = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), server);
339 CFDictionaryRemoveValue(sessionData, sessionKey);
340 CFRelease(sessionKey);
341
342 /*
343 * get rid of the per-session structure.
344 */
345 free(thisSession);
346 sessions[i] = NULL;
347
348 if (i == lastSession) {
349 /* we are removing the last session, update last used slot */
350 while (--lastSession > 0) {
351 if (sessions[lastSession] != NULL) {
352 break;
353 }
354 }
355 }
356
357 return;
358 }
359 }
360
361 SCLog(TRUE, LOG_ERR, CFSTR("MACH_NOTIFY_NO_SENDERS w/no session, port = %d"), server);
362 __MACH_PORT_DEBUG(TRUE, "*** cleanupSession w/no session", server);
363 return;
364 }
365
366
367 __private_extern__
368 void
369 listSessions(FILE *f)
370 {
371 int i;
372
373 SCPrint(TRUE, f, CFSTR("Current sessions :\n"));
374 for (i = 0; i <= lastSession; i++) {
375 serverSessionRef thisSession = sessions[i];
376
377 if (thisSession == NULL) {
378 continue;
379 }
380
381 SCPrint(TRUE, f, CFSTR("\t%d : port = 0x%x"), i, thisSession->key);
382
383 if (thisSession->store != NULL) {
384 SCDynamicStorePrivateRef storePrivate = (SCDynamicStorePrivateRef)thisSession->store;
385
386 if (storePrivate->notifySignalTask != TASK_NULL) {
387 SCPrint(TRUE, f, CFSTR(", task = %d"), storePrivate->notifySignalTask);
388 }
389 }
390
391 if (sessionData != NULL) {
392 CFDictionaryRef info;
393 CFStringRef key;
394
395 key = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), thisSession->key);
396 info = CFDictionaryGetValue(sessionData, key);
397 CFRelease(key);
398 if (info != NULL) {
399 CFStringRef name;
400
401 name = CFDictionaryGetValue(info, kSCDName);
402 if (name != NULL) {
403 SCPrint(TRUE, f, CFSTR(", name = %@"), name);
404 }
405 }
406 }
407
408 if (thisSession->serverPort != NULL) {
409 SCPrint(TRUE, f, CFSTR("\n\t\t%@"), thisSession->serverPort);
410 }
411
412 if (thisSession->serverRunLoopSource != NULL) {
413 SCPrint(TRUE, f, CFSTR("\n\t\t%@"), thisSession->serverRunLoopSource);
414 }
415
416 SCPrint(TRUE, f, CFSTR("\n"));
417 }
418
419 SCPrint(TRUE, f, CFSTR("\n"));
420 return;
421 }
422
423
424 #include <Security/Security.h>
425 #include <Security/SecTask.h>
426
427 static CFStringRef
428 sessionName(serverSessionRef session)
429 {
430 CFDictionaryRef info;
431 CFStringRef name = NULL;
432 CFStringRef sessionKey;
433
434 sessionKey = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), session->key);
435 info = CFDictionaryGetValue(sessionData, sessionKey);
436 CFRelease(sessionKey);
437
438 if (info != NULL) {
439 name = CFDictionaryGetValue(info, kSCDName);
440 }
441
442 return (name != NULL) ? name : CFSTR("???");
443 }
444
445 static CFTypeRef
446 copyEntitlement(serverSessionRef session, CFStringRef entitlement)
447 {
448 SecTaskRef task;
449 CFTypeRef value = NULL;
450
451 // Create the security task from the audit token
452 task = SecTaskCreateWithAuditToken(NULL, session->auditToken);
453 if (task != NULL) {
454 CFErrorRef error = NULL;
455
456 // Get the value for the entitlement
457 value = SecTaskCopyValueForEntitlement(task, entitlement, &error);
458 if ((value == NULL) && (error != NULL)) {
459 CFIndex code = CFErrorGetCode(error);
460 CFStringRef domain = CFErrorGetDomain(error);
461
462 if (!CFEqual(domain, kCFErrorDomainMach) ||
463 ((code != kIOReturnInvalid) && (code != kIOReturnNotFound))) {
464 // if unexpected error
465 SCLog(TRUE, LOG_ERR,
466 CFSTR("SecTaskCopyValueForEntitlement(,\"%@\",) failed, error = %@ : %@"),
467 entitlement,
468 error,
469 sessionName(session));
470 }
471 CFRelease(error);
472 }
473
474 CFRelease(task);
475 } else {
476 SCLog(TRUE, LOG_ERR,
477 CFSTR("SecTaskCreateWithAuditToken() failed: %@"),
478 sessionName(session));
479 }
480
481 return value;
482 }
483
484
485 static pid_t
486 sessionPid(serverSessionRef session)
487 {
488 pid_t caller_pid;
489
490 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
491 caller_pid = audit_token_to_pid(session->auditToken);
492 #else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
493 audit_token_to_au32(session->auditToken,
494 NULL, // auidp
495 NULL, // euid
496 NULL, // egid
497 NULL, // ruid
498 NULL, // rgid
499 &caller_pid, // pid
500 NULL, // asid
501 NULL); // tid
502 #endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
503
504 return caller_pid;
505 }
506
507
508 __private_extern__
509 Boolean
510 hasRootAccess(serverSessionRef session)
511 {
512 #if !TARGET_IPHONE_SIMULATOR
513
514 if (session->callerRootAccess == UNKNOWN) {
515 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
516 session->callerEUID = audit_token_to_euid(session->auditToken);
517 #else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
518 audit_token_to_au32(session->auditToken,
519 NULL, // auidp
520 &session->callerEUID, // euid
521 NULL, // egid
522 NULL, // ruid
523 NULL, // rgid
524 NULL, // pid
525 NULL, // asid
526 NULL); // tid
527 #endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
528 session->callerRootAccess = (session->callerEUID == 0) ? YES : NO;
529 }
530
531 return (session->callerRootAccess == YES) ? TRUE : FALSE;
532
533 #else // !TARGET_IPHONE_SIMULATOR
534
535 /*
536 * assume that all processes interacting with
537 * the iOS Simulator "configd" are OK.
538 */
539 return TRUE;
540
541 #endif // !TARGET_IPHONE_SIMULATOR
542 }
543
544
545 __private_extern__
546 Boolean
547 hasWriteAccess(serverSessionRef session, CFStringRef key)
548 {
549 Boolean isSetup;
550
551 // need to special case writing "Setup:" keys
552 isSetup = CFStringHasPrefix(key, kSCDynamicStoreDomainSetup);
553
554 if (hasRootAccess(session)) {
555 pid_t pid;
556
557 // grant write access to eUID==0 processes
558
559 pid = sessionPid(session);
560 if (isSetup && (pid != getpid())) {
561 /*
562 * WAIT!!!
563 *
564 * This is NOT configd (or a plugin) trying to
565 * write to an SCDynamicStore "Setup:" key. In
566 * general, this is unwise and we should at the
567 * very least complain.
568 */
569 SCLog(TRUE, LOG_ERR,
570 CFSTR("*** Non-configd process (pid=%d) attempting to modify \"%@\" ***"),
571 pid,
572 key);
573 }
574
575 return TRUE;
576 }
577
578 if (isSetup) {
579 /*
580 * STOP!!!
581 *
582 * This is a non-root process trying to write to
583 * an SCDynamicStore "Setup:" key. This is not
584 * something we should ever allow (regardless of
585 * any entitlements).
586 */
587 SCLog(TRUE, LOG_ERR,
588 CFSTR("*** Non-root process (pid=%d) attempting to modify \"%@\" ***"),
589 sessionPid(session),
590 key);
591
592 //return FALSE; // return FALSE when rdar://9811832 has beed fixed
593 }
594
595 if (session->callerWriteEntitlement == kCFNull) {
596 session->callerWriteEntitlement = copyEntitlement(session,
597 kSCWriteEntitlementName);
598 }
599
600 if (session->callerWriteEntitlement == NULL) {
601 return FALSE;
602 }
603
604 if (isA_CFBoolean(session->callerWriteEntitlement) &&
605 CFBooleanGetValue(session->callerWriteEntitlement)) {
606 // grant write access to "entitled" processes
607 return TRUE;
608 }
609
610 if (isA_CFDictionary(session->callerWriteEntitlement)) {
611 CFArrayRef keys;
612 CFArrayRef patterns;
613
614 keys = CFDictionaryGetValue(session->callerWriteEntitlement, CFSTR("keys"));
615 if (isA_CFArray(keys)) {
616 if (CFArrayContainsValue(keys,
617 CFRangeMake(0, CFArrayGetCount(keys)),
618 key)) {
619 // if key matches one of the entitlement "keys", grant
620 // write access
621 return TRUE;
622 }
623 }
624
625 patterns = CFDictionaryGetValue(session->callerWriteEntitlement, CFSTR("patterns"));
626 if (isA_CFArray(patterns)) {
627 CFIndex i;
628 CFIndex n = CFArrayGetCount(patterns);
629
630 for (i = 0; i < n; i++) {
631 CFStringRef pattern;
632
633 pattern = CFArrayGetValueAtIndex(patterns, i);
634 if (isA_CFString(pattern)) {
635 if (patternKeyMatches(pattern, key)) {
636 // if key matches one of the entitlement
637 // "patterns", grant write access
638 return TRUE;
639 }
640 }
641 }
642 }
643 }
644
645 return FALSE;
646 }
647
648
649 __private_extern__
650 Boolean
651 hasPathAccess(serverSessionRef session, const char *path)
652 {
653 pid_t pid;
654 char realPath[PATH_MAX];
655
656 if (realpath(path, realPath) == NULL) {
657 SCLog(TRUE, LOG_DEBUG, CFSTR("hasPathAccess realpath() failed: %s"), strerror(errno));
658 return FALSE;
659 }
660
661 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
662 pid = audit_token_to_pid(session->auditToken);
663 #else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
664 audit_token_to_au32(session->auditToken,
665 NULL, // auidp
666 NULL, // euid
667 NULL, // egid
668 NULL, // ruid
669 NULL, // rgid
670 &pid, // pid
671 NULL, // asid
672 NULL); // tid
673 #endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
674 if (sandbox_check(pid, // pid
675 "file-write-data", // operation
676 SANDBOX_FILTER_PATH | SANDBOX_CHECK_NO_REPORT, // sandbox_filter_type
677 realPath) > 0) { // ...
678 SCLog(TRUE, LOG_DEBUG, CFSTR("hasPathAccess sandbox access denied: %s"), strerror(errno));
679 return FALSE;
680 }
681
682 return TRUE;
683 }