2 * Copyright (c) 2000, 2001, 2003-2005, 2007-2015 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
25 * Modification History
27 * June 1, 2001 Allan Nathanson <ajn@apple.com>
28 * - public API conversion
30 * March 24, 2000 Allan Nathanson <ajn@apple.com>
34 #include <SystemConfiguration/SystemConfiguration.h>
36 #include "configd_server.h"
41 #include <bsm/libbsm.h>
44 #if !TARGET_IPHONE_SIMULATOR || (defined(IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED) && (IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED >= 1090))
45 #define HAVE_MACHPORT_GUARDS
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 */
54 /* CFMachPortInvalidation runloop */
55 static CFRunLoopRef sessionRunLoop
= NULL
;
58 static serverSessionRef temp_session
= NULL
;
63 getSession(mach_port_t server
)
67 if (server
== MACH_PORT_NULL
) {
68 SC_log(LOG_NOTICE
, "Excuse me, why is getSession() being called with an invalid port?");
72 /* look for matching session (note: slot 0 is the "server" port) */
73 for (i
= 1; i
<= lastSession
; i
++) {
74 serverSessionRef thisSession
= sessions
[i
];
76 if (thisSession
== NULL
) {
77 /* found an empty slot, skip it */
81 if (thisSession
->key
== server
) {
82 /* we've seen this server before */
86 if ((thisSession
->store
!= NULL
) &&
87 (((SCDynamicStorePrivateRef
)thisSession
->store
)->notifySignalTask
== server
)) {
88 /* we've seen this task port before */
93 /* no sessions available */
100 tempSession(mach_port_t server
, CFStringRef name
, audit_token_t auditToken
)
102 static dispatch_once_t once
;
103 SCDynamicStorePrivateRef storePrivate
;
105 if (sessions
[0]->key
!= server
) {
106 // if not SCDynamicStore "server" port
110 dispatch_once(&once
, ^{
111 temp_session
= sessions
[0]; /* use "server" session */
112 (void) __SCDynamicStoreOpen(&temp_session
->store
, NULL
);
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
);
123 temp_session
->callerWriteEntitlement
= kCFNull
; /* UNKNOWN */
126 storePrivate
= (SCDynamicStorePrivateRef
)temp_session
->store
;
127 if (storePrivate
->name
!= NULL
) CFRelease(storePrivate
->name
);
128 storePrivate
->name
= CFRetain(name
);
136 addSession(mach_port_t server
, CFStringRef (*copyDescription
)(const void *info
))
138 CFMachPortContext context
= { 0, NULL
, NULL
, NULL
, NULL
};
140 mach_port_t mp
= server
;
142 serverSessionRef newSession
= NULL
;
144 /* save current (SCDynamicStore) runloop */
145 if (sessionRunLoop
== NULL
) {
146 sessionRunLoop
= CFRunLoopGetCurrent();
149 if (nSessions
<= 0) {
150 /* if first session (the "server" port) */
151 n
= 0; /* use slot "0" */
152 lastSession
= 0; /* last used slot */
155 sessions
= malloc(nSessions
* sizeof(serverSessionRef
));
157 // allocate a new session for "the" server
158 newSession
= calloc(1, sizeof(serverSession
));
161 #ifdef HAVE_MACHPORT_GUARDS
162 mach_port_options_t opts
;
163 #endif // HAVE_MACHPORT_GUARDS
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
];
169 if (thisSession
== NULL
) {
170 /* found an empty slot */
172 /* keep track of the first [empty] slot */
176 /* and keep looking for a matching session */
180 if (thisSession
->key
== server
) {
181 /* we've seen this server before */
185 if ((thisSession
->store
!= NULL
) &&
186 (((SCDynamicStorePrivateRef
)thisSession
->store
)->notifySignalTask
== server
)) {
187 /* we've seen this task port before */
192 /* add a new session */
194 /* if no empty slots */
196 if (lastSession
>= nSessions
) {
197 /* expand the session list */
199 sessions
= reallocf(sessions
, (nSessions
* sizeof(serverSessionRef
)));
203 // allocate a session for this client
204 newSession
= calloc(1, sizeof(serverSession
));
206 // create mach port for SCDynamicStore client
211 #ifdef HAVE_MACHPORT_GUARDS
212 bzero(&opts
, sizeof(opts
));
213 opts
.flags
= MPO_CONTEXT_AS_GUARD
;
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
220 if (kr
!= KERN_SUCCESS
) {
223 SC_log(LOG_NOTICE
, "could not allocate mach port: %s", mach_error_string(kr
));
224 if ((kr
== KERN_NO_SPACE
) || (kr
== KERN_RESOURCE_SHORTAGE
)) {
229 (void) asprintf(&err
, "Could not allocate mach port: %s", mach_error_string(kr
));
230 _SC_crash(err
!= NULL
? err
: "Could not allocate new session (mach) port",
233 if (err
!= NULL
) free(err
);
240 // create server port
241 context
.info
= newSession
;
242 context
.copyDescription
= copyDescription
;
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.
249 newSession
->serverPort
= _SC_CFMachPortCreateWithPort("SCDynamicStore/session",
255 // insert send right that will be moved to the client
256 kr
= mach_port_insert_right(mach_task_self(),
259 MACH_MSG_TYPE_MAKE_SEND
);
260 if (kr
!= KERN_SUCCESS
) {
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
266 SC_log(LOG_NOTICE
, "mach_port_insert_right() failed: %s", mach_error_string(kr
));
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 */
287 cleanupSession(mach_port_t server
)
291 for (i
= 1; i
<= lastSession
; i
++) {
292 CFStringRef sessionKey
;
293 serverSessionRef thisSession
= sessions
[i
];
295 if (thisSession
== NULL
) {
296 /* found an empty slot, skip it */
300 if (thisSession
->key
== server
) {
302 * session entry still exists.
305 SC_trace(_configd_trace
, "cleanup : %5d\n", server
);
308 * Close any open connections including cancelling any outstanding
309 * notification requests and releasing any locks.
311 __MACH_PORT_DEBUG(TRUE
, "*** cleanupSession", server
);
312 (void) __SCDynamicStoreClose(&thisSession
->store
);
313 __MACH_PORT_DEBUG(TRUE
, "*** cleanupSession (after __SCDynamicStoreClose)", server
);
316 * Our send right has already been removed. Remove our receive right.
318 #ifdef HAVE_MACHPORT_GUARDS
319 (void) mach_port_destruct(mach_task_self(), server
, 0, thisSession
);
320 #else // HAVE_MACHPORT_GUARDS
321 (void) mach_port_mod_refs(mach_task_self(), server
, MACH_PORT_RIGHT_RECEIVE
, -1);
322 #endif // HAVE_MACHPORT_GUARDS
325 * release any entitlement info
327 if ((thisSession
->callerWriteEntitlement
!= NULL
) &&
328 (thisSession
->callerWriteEntitlement
!= kCFNull
)) {
329 CFRelease(thisSession
->callerWriteEntitlement
);
333 * We don't need any remaining information in the
334 * sessionData dictionary, remove it.
336 sessionKey
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%d"), server
);
337 CFDictionaryRemoveValue(sessionData
, sessionKey
);
338 CFRelease(sessionKey
);
341 * get rid of the per-session structure.
346 if (i
== lastSession
) {
347 /* we are removing the last session, update last used slot */
348 while (--lastSession
> 0) {
349 if (sessions
[lastSession
] != NULL
) {
359 SC_log(LOG_NOTICE
, "MACH_NOTIFY_NO_SENDERS w/no session, port = %d", server
);
360 __MACH_PORT_DEBUG(TRUE
, "*** cleanupSession w/no session", server
);
367 listSessions(FILE *f
)
371 SCPrint(TRUE
, f
, CFSTR("Current sessions :\n"));
372 for (i
= 0; i
<= lastSession
; i
++) {
373 serverSessionRef thisSession
= sessions
[i
];
375 if (thisSession
== NULL
) {
379 SCPrint(TRUE
, f
, CFSTR("\t%d : port = 0x%x"), i
, thisSession
->key
);
381 if (thisSession
->store
!= NULL
) {
382 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)thisSession
->store
;
384 if (storePrivate
->notifySignalTask
!= TASK_NULL
) {
385 SCPrint(TRUE
, f
, CFSTR(", task = %d"), storePrivate
->notifySignalTask
);
389 if (sessionData
!= NULL
) {
390 CFDictionaryRef info
;
393 key
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%d"), thisSession
->key
);
394 info
= CFDictionaryGetValue(sessionData
, key
);
399 name
= CFDictionaryGetValue(info
, kSCDName
);
401 SCPrint(TRUE
, f
, CFSTR(", name = %@"), name
);
406 if (thisSession
->serverPort
!= NULL
) {
407 SCPrint(TRUE
, f
, CFSTR("\n\t\t%@"), thisSession
->serverPort
);
410 if (thisSession
->serverRunLoopSource
!= NULL
) {
411 SCPrint(TRUE
, f
, CFSTR("\n\t\t%@"), thisSession
->serverRunLoopSource
);
414 SCPrint(TRUE
, f
, CFSTR("\n"));
417 SCPrint(TRUE
, f
, CFSTR("\n"));
422 #include <Security/Security.h>
423 #include <Security/SecTask.h>
426 sessionName(serverSessionRef session
)
428 CFDictionaryRef info
;
429 CFStringRef name
= NULL
;
430 CFStringRef sessionKey
;
432 sessionKey
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%d"), session
->key
);
433 info
= CFDictionaryGetValue(sessionData
, sessionKey
);
434 CFRelease(sessionKey
);
437 name
= CFDictionaryGetValue(info
, kSCDName
);
440 return (name
!= NULL
) ? name
: CFSTR("???");
444 copyEntitlement(serverSessionRef session
, CFStringRef entitlement
)
447 CFTypeRef value
= NULL
;
449 // Create the security task from the audit token
450 task
= SecTaskCreateWithAuditToken(NULL
, session
->auditToken
);
452 CFErrorRef error
= NULL
;
454 // Get the value for the entitlement
455 value
= SecTaskCopyValueForEntitlement(task
, entitlement
, &error
);
456 if ((value
== NULL
) && (error
!= NULL
)) {
457 CFIndex code
= CFErrorGetCode(error
);
458 CFStringRef domain
= CFErrorGetDomain(error
);
460 if (!CFEqual(domain
, kCFErrorDomainMach
) ||
461 ((code
!= kIOReturnInvalid
) && (code
!= kIOReturnNotFound
))) {
462 // if unexpected error
463 SC_log(LOG_NOTICE
, "SecTaskCopyValueForEntitlement(,\"%@\",) failed, error = %@ : %@",
466 sessionName(session
));
473 SC_log(LOG_NOTICE
, "SecTaskCreateWithAuditToken() failed: %@",
474 sessionName(session
));
482 sessionPid(serverSessionRef session
)
486 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
487 caller_pid
= audit_token_to_pid(session
->auditToken
);
488 #else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
489 audit_token_to_au32(session
->auditToken
,
498 #endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
506 hasRootAccess(serverSessionRef session
)
508 #if !TARGET_IPHONE_SIMULATOR
510 if (session
->callerRootAccess
== UNKNOWN
) {
511 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
512 session
->callerEUID
= audit_token_to_euid(session
->auditToken
);
513 #else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
514 audit_token_to_au32(session
->auditToken
,
516 &session
->callerEUID
, // euid
523 #endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
524 session
->callerRootAccess
= (session
->callerEUID
== 0) ? YES
: NO
;
527 return (session
->callerRootAccess
== YES
) ? TRUE
: FALSE
;
529 #else // !TARGET_IPHONE_SIMULATOR
532 * assume that all processes interacting with
533 * the iOS Simulator "configd" are OK.
537 #endif // !TARGET_IPHONE_SIMULATOR
543 hasWriteAccess(serverSessionRef session
, CFStringRef key
)
547 // need to special case writing "Setup:" keys
548 isSetup
= CFStringHasPrefix(key
, kSCDynamicStoreDomainSetup
);
550 if (hasRootAccess(session
)) {
553 // grant write access to eUID==0 processes
555 pid
= sessionPid(session
);
556 if (isSetup
&& (pid
!= getpid())) {
560 * This is NOT configd (or a plugin) trying to
561 * write to an SCDynamicStore "Setup:" key. In
562 * general, this is unwise and we should at the
563 * very least complain.
565 SC_log(LOG_NOTICE
, "*** Non-configd process (pid=%d) attempting to modify \"%@\" ***",
577 * This is a non-root process trying to write to
578 * an SCDynamicStore "Setup:" key. This is not
579 * something we should ever allow (regardless of
582 SC_log(LOG_NOTICE
, "*** Non-root process (pid=%d) attempting to modify \"%@\" ***",
586 //return FALSE; // return FALSE when rdar://9811832 has beed fixed
589 if (session
->callerWriteEntitlement
== kCFNull
) {
590 session
->callerWriteEntitlement
= copyEntitlement(session
,
591 kSCWriteEntitlementName
);
594 if (session
->callerWriteEntitlement
== NULL
) {
598 if (isA_CFBoolean(session
->callerWriteEntitlement
) &&
599 CFBooleanGetValue(session
->callerWriteEntitlement
)) {
600 // grant write access to "entitled" processes
604 if (isA_CFDictionary(session
->callerWriteEntitlement
)) {
608 keys
= CFDictionaryGetValue(session
->callerWriteEntitlement
, CFSTR("keys"));
609 if (isA_CFArray(keys
)) {
610 if (CFArrayContainsValue(keys
,
611 CFRangeMake(0, CFArrayGetCount(keys
)),
613 // if key matches one of the entitlement "keys", grant
619 patterns
= CFDictionaryGetValue(session
->callerWriteEntitlement
, CFSTR("patterns"));
620 if (isA_CFArray(patterns
)) {
622 CFIndex n
= CFArrayGetCount(patterns
);
624 for (i
= 0; i
< n
; i
++) {
627 pattern
= CFArrayGetValueAtIndex(patterns
, i
);
628 if (isA_CFString(pattern
)) {
629 if (patternKeyMatches(pattern
, key
)) {
630 // if key matches one of the entitlement
631 // "patterns", grant write access
645 hasPathAccess(serverSessionRef session
, const char *path
)
648 char realPath
[PATH_MAX
];
650 if (realpath(path
, realPath
) == NULL
) {
651 SC_log(LOG_INFO
, "realpath() failed: %s", strerror(errno
));
655 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
656 pid
= audit_token_to_pid(session
->auditToken
);
657 #else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
658 audit_token_to_au32(session
->auditToken
,
667 #endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
668 if (sandbox_check(pid
, // pid
669 "file-write-data", // operation
670 SANDBOX_FILTER_PATH
| SANDBOX_CHECK_NO_REPORT
, // sandbox_filter_type
671 realPath
) > 0) { // ...
672 SC_log(LOG_INFO
, "sandbox access denied: %s", strerror(errno
));