2 * Copyright (c) 2000, 2001, 2003-2005, 2007-2013 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>
45 /* information maintained for each active session */
46 static serverSessionRef
*sessions
= NULL
;
47 static int nSessions
= 0; /* # of allocated sessions */
48 static int lastSession
= -1; /* # of last used session */
50 /* CFMachPortInvalidation runloop */
51 static CFRunLoopRef sessionRunLoop
= NULL
;
54 static serverSessionRef temp_session
= NULL
;
59 getSession(mach_port_t server
)
63 if (server
== MACH_PORT_NULL
) {
64 SCLog(TRUE
, LOG_ERR
, CFSTR("Excuse me, why is getSession() being called with an invalid port?"));
68 /* look for matching session (note: slot 0 is the "server" port) */
69 for (i
= 1; i
<= lastSession
; i
++) {
70 serverSessionRef thisSession
= sessions
[i
];
72 if (thisSession
== NULL
) {
73 /* found an empty slot, skip it */
77 if (thisSession
->key
== server
) {
78 /* we've seen this server before */
82 if ((thisSession
->store
!= NULL
) &&
83 (((SCDynamicStorePrivateRef
)thisSession
->store
)->notifySignalTask
== server
)) {
84 /* we've seen this task port before */
89 /* no sessions available */
96 tempSession(mach_port_t server
, CFStringRef name
, audit_token_t auditToken
)
98 static dispatch_once_t once
;
99 SCDynamicStorePrivateRef storePrivate
;
101 if (sessions
[0]->key
!= server
) {
102 // if not SCDynamicStore "server" port
106 dispatch_once(&once
, ^{
107 temp_session
= sessions
[0]; /* use "server" session */
108 (void) __SCDynamicStoreOpen(&temp_session
->store
, NULL
);
111 /* save audit token, caller entitlements */
112 temp_session
->auditToken
= auditToken
;
113 temp_session
->callerEUID
= 1; /* not "root" */
114 temp_session
->callerRootAccess
= UNKNOWN
;
115 #if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080/*FIXME*/)
116 if ((temp_session
->callerWriteEntitlement
!= NULL
) &&
117 (temp_session
->callerWriteEntitlement
!= kCFNull
)) {
118 CFRelease(temp_session
->callerWriteEntitlement
);
120 temp_session
->callerWriteEntitlement
= kCFNull
; /* UNKNOWN */
121 #endif // TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080/*FIXME*/)
124 storePrivate
= (SCDynamicStorePrivateRef
)temp_session
->store
;
125 if (storePrivate
->name
!= NULL
) CFRelease(storePrivate
->name
);
126 storePrivate
->name
= CFRetain(name
);
134 addSession(mach_port_t server
, CFStringRef (*copyDescription
)(const void *info
))
136 CFMachPortContext context
= { 0, NULL
, NULL
, NULL
, NULL
};
137 mach_port_t mp
= server
;
140 /* save current (SCDynamicStore) runloop */
141 if (sessionRunLoop
== NULL
) {
142 sessionRunLoop
= CFRunLoopGetCurrent();
145 if (nSessions
<= 0) {
146 /* if first session (the "server" port) */
147 n
= 0; /* use slot "0" */
148 lastSession
= 0; /* last used slot */
151 sessions
= malloc(nSessions
* sizeof(serverSessionRef
));
155 /* check to see if we already have an open session (note: slot 0 is the "server" port) */
156 for (i
= 1; i
<= lastSession
; i
++) {
157 serverSessionRef thisSession
= sessions
[i
];
159 if (thisSession
== NULL
) {
160 /* found an empty slot */
162 /* keep track of the first [empty] slot */
166 /* and keep looking for a matching session */
170 if (thisSession
->key
== server
) {
171 /* we've seen this server before */
175 if ((thisSession
->store
!= NULL
) &&
176 (((SCDynamicStorePrivateRef
)thisSession
->store
)->notifySignalTask
== server
)) {
177 /* we've seen this task port before */
182 /* add a new session */
184 /* if no empty slots */
186 if (lastSession
>= nSessions
) {
187 /* expand the session list */
189 sessions
= reallocf(sessions
, (nSessions
* sizeof(serverSessionRef
)));
193 // create mach port for SCDynamicStore client
195 (void) mach_port_allocate(mach_task_self(),
196 MACH_PORT_RIGHT_RECEIVE
,
200 // allocate a new session for this server
201 sessions
[n
] = malloc(sizeof(serverSession
));
202 bzero(sessions
[n
], sizeof(serverSession
));
204 // create server port
205 context
.info
= sessions
[n
];
206 context
.copyDescription
= copyDescription
;
209 // Note: we create the CFMachPort *before* we insert a send
210 // right present to ensure that CF does not establish
211 // its dead name notification.
213 sessions
[n
]->serverPort
= _SC_CFMachPortCreateWithPort("SCDynamicStore/session",
219 // insert send right that will be moved to the client
220 (void) mach_port_insert_right(mach_task_self(),
223 MACH_MSG_TYPE_MAKE_SEND
);
226 sessions
[n
]->key
= mp
;
227 // sessions[n]->serverRunLoopSource = NULL;
228 // sessions[n]->store = NULL;
229 sessions
[n
]->callerEUID
= 1; /* not "root" */
230 sessions
[n
]->callerRootAccess
= UNKNOWN
;
231 #if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080/*FIXME*/)
232 sessions
[n
]->callerWriteEntitlement
= kCFNull
; /* UNKNOWN */
233 #endif // TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080/*FIXME*/)
241 cleanupSession(mach_port_t server
)
245 for (i
= 1; i
<= lastSession
; i
++) {
246 CFStringRef sessionKey
;
247 serverSessionRef thisSession
= sessions
[i
];
249 if (thisSession
== NULL
) {
250 /* found an empty slot, skip it */
254 if (thisSession
->key
== server
) {
256 * session entry still exists.
259 if (_configd_trace
) {
260 SCTrace(TRUE
, _configd_trace
, CFSTR("cleanup : %5d\n"), server
);
264 * Close any open connections including cancelling any outstanding
265 * notification requests and releasing any locks.
267 __MACH_PORT_DEBUG(TRUE
, "*** cleanupSession", server
);
268 (void) __SCDynamicStoreClose(&thisSession
->store
);
269 __MACH_PORT_DEBUG(TRUE
, "*** cleanupSession (after __SCDynamicStoreClose)", server
);
272 * Our send right has already been removed. Remove our receive right.
274 (void) mach_port_mod_refs(mach_task_self(), server
, MACH_PORT_RIGHT_RECEIVE
, -1);
276 #if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080/*FIXME*/)
278 * release any entitlement info
280 if ((thisSession
->callerWriteEntitlement
!= NULL
) &&
281 (thisSession
->callerWriteEntitlement
!= kCFNull
)) {
282 CFRelease(thisSession
->callerWriteEntitlement
);
284 #endif // TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080/*FIXME*/)
287 * We don't need any remaining information in the
288 * sessionData dictionary, remove it.
290 sessionKey
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%d"), server
);
291 CFDictionaryRemoveValue(sessionData
, sessionKey
);
292 CFRelease(sessionKey
);
295 * get rid of the per-session structure.
300 if (i
== lastSession
) {
301 /* we are removing the last session, update last used slot */
302 while (--lastSession
> 0) {
303 if (sessions
[lastSession
] != NULL
) {
313 SCLog(TRUE
, LOG_ERR
, CFSTR("MACH_NOTIFY_NO_SENDERS w/no session, port = %d"), server
);
314 __MACH_PORT_DEBUG(TRUE
, "*** cleanupSession w/no session", server
);
321 listSessions(FILE *f
)
325 SCPrint(TRUE
, f
, CFSTR("Current sessions :\n"));
326 for (i
= 0; i
<= lastSession
; i
++) {
327 serverSessionRef thisSession
= sessions
[i
];
329 if (thisSession
== NULL
) {
333 SCPrint(TRUE
, f
, CFSTR("\t%d : port = 0x%x"), i
, thisSession
->key
);
335 if (thisSession
->store
!= NULL
) {
336 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)thisSession
->store
;
338 if (storePrivate
->notifySignalTask
!= TASK_NULL
) {
339 SCPrint(TRUE
, f
, CFSTR(", task = %d"), storePrivate
->notifySignalTask
);
343 if (sessionData
!= NULL
) {
344 CFDictionaryRef info
;
347 key
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%d"), thisSession
->key
);
348 info
= CFDictionaryGetValue(sessionData
, key
);
353 name
= CFDictionaryGetValue(info
, kSCDName
);
355 SCPrint(TRUE
, f
, CFSTR(", name = %@"), name
);
360 if (thisSession
->serverPort
!= NULL
) {
361 SCPrint(TRUE
, f
, CFSTR("\n\t\t%@"), thisSession
->serverPort
);
364 if (thisSession
->serverRunLoopSource
!= NULL
) {
365 SCPrint(TRUE
, f
, CFSTR("\n\t\t%@"), thisSession
->serverRunLoopSource
);
368 SCPrint(TRUE
, f
, CFSTR("\n"));
371 SCPrint(TRUE
, f
, CFSTR("\n"));
376 #if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080/*FIXME*/)
378 #include <Security/Security.h>
379 #include <Security/SecTask.h>
382 sessionName(serverSessionRef session
)
384 CFDictionaryRef info
;
385 CFStringRef name
= NULL
;
386 CFStringRef sessionKey
;
388 sessionKey
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%d"), session
->key
);
389 info
= CFDictionaryGetValue(sessionData
, sessionKey
);
390 CFRelease(sessionKey
);
393 name
= CFDictionaryGetValue(info
, kSCDName
);
396 return (name
!= NULL
) ? name
: CFSTR("???");
400 copyEntitlement(serverSessionRef session
, CFStringRef entitlement
)
403 CFTypeRef value
= NULL
;
405 // Create the security task from the audit token
406 task
= SecTaskCreateWithAuditToken(NULL
, session
->auditToken
);
408 CFErrorRef error
= NULL
;
410 // Get the value for the entitlement
411 value
= SecTaskCopyValueForEntitlement(task
, entitlement
, &error
);
412 if ((value
== NULL
) && (error
!= NULL
)) {
413 CFIndex code
= CFErrorGetCode(error
);
414 CFStringRef domain
= CFErrorGetDomain(error
);
416 if (!CFEqual(domain
, kCFErrorDomainMach
) ||
417 ((code
!= kIOReturnInvalid
) && (code
!= kIOReturnNotFound
))) {
418 // if unexpected error
420 CFSTR("SecTaskCopyValueForEntitlement(,\"%@\",) failed, error = %@ : %@"),
423 sessionName(session
));
431 CFSTR("SecTaskCreateWithAuditToken() failed: %@"),
432 sessionName(session
));
438 #endif // TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080/*FIXME*/)
442 sessionPid(serverSessionRef session
)
446 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
447 caller_pid
= audit_token_to_pid(session
->auditToken
);
448 #else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
449 audit_token_to_au32(session
->auditToken
,
458 #endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
466 hasRootAccess(serverSessionRef session
)
468 #if !TARGET_IPHONE_SIMULATOR
470 if (session
->callerRootAccess
== UNKNOWN
) {
471 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
472 session
->callerEUID
= audit_token_to_euid(session
->auditToken
);
473 #else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
474 audit_token_to_au32(session
->auditToken
,
476 &session
->callerEUID
, // euid
483 #endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
484 session
->callerRootAccess
= (session
->callerEUID
== 0) ? YES
: NO
;
487 return (session
->callerRootAccess
== YES
) ? TRUE
: FALSE
;
489 #else // !TARGET_IPHONE_SIMULATOR
492 * assume that all processes interacting with
493 * the iOS Simulator "configd" are OK.
497 #endif // !TARGET_IPHONE_SIMULATOR
503 hasWriteAccess(serverSessionRef session
, CFStringRef key
)
507 // need to special case writing "Setup:" keys
508 isSetup
= CFStringHasPrefix(key
, kSCDynamicStoreDomainSetup
);
510 if (hasRootAccess(session
)) {
513 // grant write access to eUID==0 processes
515 pid
= sessionPid(session
);
516 if (isSetup
&& (pid
!= getpid())) {
520 * This is NOT configd (or a plugin) trying to
521 * write to an SCDynamicStore "Setup:" key. In
522 * general, this is unwise and we should at the
523 * very least complain.
526 CFSTR("*** Non-configd process (pid=%d) attempting to modify \"%@\" ***"),
538 * This is a non-root process trying to write to
539 * an SCDynamicStore "Setup:" key. This is not
540 * something we should ever allow (regardless of
544 CFSTR("*** Non-root process (pid=%d) attempting to modify \"%@\" ***"),
548 //return FALSE; // return FALSE when rdar://9811832 has beed fixed
551 #if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080/*FIXME*/)
552 if (session
->callerWriteEntitlement
== kCFNull
) {
553 session
->callerWriteEntitlement
= copyEntitlement(session
,
554 kSCWriteEntitlementName
);
557 if (session
->callerWriteEntitlement
== NULL
) {
561 if (isA_CFBoolean(session
->callerWriteEntitlement
) &&
562 CFBooleanGetValue(session
->callerWriteEntitlement
)) {
563 // grant write access to "entitled" processes
567 if (isA_CFDictionary(session
->callerWriteEntitlement
)) {
571 keys
= CFDictionaryGetValue(session
->callerWriteEntitlement
, CFSTR("keys"));
572 if (isA_CFArray(keys
)) {
573 if (CFArrayContainsValue(keys
,
574 CFRangeMake(0, CFArrayGetCount(keys
)),
576 // if key matches one of the entitlement "keys", grant
582 patterns
= CFDictionaryGetValue(session
->callerWriteEntitlement
, CFSTR("patterns"));
583 if (isA_CFArray(patterns
)) {
585 CFIndex n
= CFArrayGetCount(patterns
);
587 for (i
= 0; i
< n
; i
++) {
590 pattern
= CFArrayGetValueAtIndex(patterns
, i
);
591 if (isA_CFString(pattern
)) {
592 if (patternKeyMatches(pattern
, key
)) {
593 // if key matches one of the entitlement
594 // "patterns", grant write access
601 #endif // TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080/*FIXME*/)
609 hasPathAccess(serverSessionRef session
, const char *path
)
612 char realPath
[PATH_MAX
];
614 if (realpath(path
, realPath
) == NULL
) {
615 SCLog(TRUE
, LOG_DEBUG
, CFSTR("hasPathAccess realpath() failed: %s"), strerror(errno
));
619 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
620 pid
= audit_token_to_pid(session
->auditToken
);
621 #else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
622 audit_token_to_au32(session
->auditToken
,
631 #endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
632 if (sandbox_check(pid
, // pid
633 "file-write-data", // operation
634 SANDBOX_FILTER_PATH
| SANDBOX_CHECK_NO_REPORT
, // sandbox_filter_type
635 realPath
) > 0) { // ...
636 SCLog(TRUE
, LOG_DEBUG
, CFSTR("hasPathAccess sandbox access denied: %s"), strerror(errno
));