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