2 * Copyright (c) 2000, 2001, 2003-2005, 2007-2017 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_OS_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
;
60 getSession(mach_port_t server
)
64 if (server
== MACH_PORT_NULL
) {
65 SC_log(LOG_NOTICE
, "Excuse me, why is getSession() being called with an invalid port?");
69 /* look for matching session (note: slot 0 is the "server" port) */
70 for (i
= 1; i
<= lastSession
; i
++) {
71 serverSessionRef thisSession
= sessions
[i
];
73 if (thisSession
== NULL
) {
74 /* found an empty slot, skip it */
78 if (thisSession
->key
== server
) {
79 /* we've seen this server before */
83 if ((thisSession
->store
!= NULL
) &&
84 (((SCDynamicStorePrivateRef
)thisSession
->store
)->notifySignalTask
== server
)) {
85 /* we've seen this task port before */
90 /* no sessions available */
97 tempSession(mach_port_t server
, CFStringRef name
, audit_token_t auditToken
)
99 static dispatch_once_t once
;
100 SCDynamicStorePrivateRef storePrivate
; /* temp session */
101 static serverSession temp_session
;
103 dispatch_once(&once
, ^{
104 temp_session
= *sessions
[0]; /* use "server" session */
105 temp_session
.activity
= NULL
;
106 (void) __SCDynamicStoreOpen(&temp_session
.store
, NULL
);
109 if (temp_session
.key
!= server
) {
110 // if not SCDynamicStore "server" port
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
);
122 temp_session
.callerWriteEntitlement
= kCFNull
; /* UNKNOWN */
125 storePrivate
= (SCDynamicStorePrivateRef
)temp_session
.store
;
126 if (storePrivate
->name
!= NULL
) CFRelease(storePrivate
->name
);
127 storePrivate
->name
= CFRetain(name
);
129 return &temp_session
;
135 addSession(mach_port_t server
, CFStringRef (*copyDescription
)(const void *info
))
137 CFMachPortContext context
= { 0, NULL
, NULL
, NULL
, NULL
};
139 mach_port_t mp
= server
;
141 serverSessionRef newSession
= NULL
;
143 /* save current (SCDynamicStore) runloop */
144 if (sessionRunLoop
== NULL
) {
145 sessionRunLoop
= CFRunLoopGetCurrent();
148 if (nSessions
<= 0) {
149 /* if first session (the "server" port) */
150 n
= 0; /* use slot "0" */
151 lastSession
= 0; /* last used slot */
154 sessions
= malloc(nSessions
* sizeof(serverSessionRef
));
156 // allocate a new session for "the" server
157 newSession
= calloc(1, sizeof(serverSession
));
160 #ifdef HAVE_MACHPORT_GUARDS
161 mach_port_options_t opts
;
162 #endif // HAVE_MACHPORT_GUARDS
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
];
168 if (thisSession
== NULL
) {
169 /* found an empty slot */
171 /* keep track of the first [empty] slot */
175 /* and keep looking for a matching session */
179 if (thisSession
->key
== server
) {
180 /* we've seen this server before */
184 if ((thisSession
->store
!= NULL
) &&
185 (((SCDynamicStorePrivateRef
)thisSession
->store
)->notifySignalTask
== server
)) {
186 /* we've seen this task port before */
191 /* add a new session */
193 /* if no empty slots */
195 if (lastSession
>= nSessions
) {
196 /* expand the session list */
198 sessions
= reallocf(sessions
, (nSessions
* sizeof(serverSessionRef
)));
202 // allocate a session for this client
203 newSession
= calloc(1, sizeof(serverSession
));
205 // create mach port for SCDynamicStore client
210 #ifdef HAVE_MACHPORT_GUARDS
211 bzero(&opts
, sizeof(opts
));
212 opts
.flags
= MPO_CONTEXT_AS_GUARD
;
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
219 if (kr
!= KERN_SUCCESS
) {
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
)) {
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",
232 if (err
!= NULL
) free(err
);
239 // create server port
240 context
.info
= newSession
;
241 context
.copyDescription
= copyDescription
;
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.
248 newSession
->serverPort
= _SC_CFMachPortCreateWithPort("SCDynamicStore/session",
254 // insert send right that will be moved to the client
255 kr
= mach_port_insert_right(mach_task_self(),
258 MACH_MSG_TYPE_MAKE_SEND
);
259 if (kr
!= KERN_SUCCESS
) {
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
265 SC_log(LOG_NOTICE
, "mach_port_insert_right() failed: %s", mach_error_string(kr
));
272 newSession
->activity
= os_activity_create("processing SCDynamicStore notification",
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;
282 sessions
[n
] = newSession
;
290 cleanupSession(mach_port_t server
)
294 for (i
= 1; i
<= lastSession
; i
++) {
295 CFStringRef sessionKey
;
296 serverSessionRef thisSession
= sessions
[i
];
298 if (thisSession
== NULL
) {
299 /* found an empty slot, skip it */
303 if (thisSession
->key
== server
) {
305 * session entry still exists.
308 SC_trace("cleanup : %5d", server
);
311 * Close any open connections including cancelling any outstanding
312 * notification requests and releasing any locks.
314 __MACH_PORT_DEBUG(TRUE
, "*** cleanupSession", server
);
315 (void) __SCDynamicStoreClose(&thisSession
->store
);
316 __MACH_PORT_DEBUG(TRUE
, "*** cleanupSession (after __SCDynamicStoreClose)", server
);
319 * Our send right has already been removed. Remove our receive right.
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
328 * release any entitlement info
330 if ((thisSession
->callerWriteEntitlement
!= NULL
) &&
331 (thisSession
->callerWriteEntitlement
!= kCFNull
)) {
332 CFRelease(thisSession
->callerWriteEntitlement
);
336 * release our per-session activity
338 if (thisSession
->activity
!= NULL
) {
339 os_release(thisSession
->activity
);
343 * We don't need any remaining information in the
344 * sessionData dictionary, remove it.
346 sessionKey
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%d"), server
);
347 CFDictionaryRemoveValue(sessionData
, sessionKey
);
348 CFRelease(sessionKey
);
351 * get rid of the per-session structure.
356 if (i
== lastSession
) {
357 /* we are removing the last session, update last used slot */
358 while (--lastSession
> 0) {
359 if (sessions
[lastSession
] != NULL
) {
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
);
377 listSessions(FILE *f
)
381 SCPrint(TRUE
, f
, CFSTR("Current sessions :\n"));
382 for (i
= 0; i
<= lastSession
; i
++) {
383 serverSessionRef thisSession
= sessions
[i
];
385 if (thisSession
== NULL
) {
389 SCPrint(TRUE
, f
, CFSTR("\t%d : port = 0x%x"), i
, thisSession
->key
);
391 if (thisSession
->store
!= NULL
) {
392 SCDynamicStorePrivateRef storePrivate
= (SCDynamicStorePrivateRef
)thisSession
->store
;
394 if (storePrivate
->notifySignalTask
!= TASK_NULL
) {
395 SCPrint(TRUE
, f
, CFSTR(", task = %d"), storePrivate
->notifySignalTask
);
399 if (sessionData
!= NULL
) {
400 CFDictionaryRef info
;
403 key
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%d"), thisSession
->key
);
404 info
= CFDictionaryGetValue(sessionData
, key
);
409 name
= CFDictionaryGetValue(info
, kSCDName
);
411 SCPrint(TRUE
, f
, CFSTR(", name = %@"), name
);
416 if (thisSession
->serverPort
!= NULL
) {
417 SCPrint(TRUE
, f
, CFSTR("\n\t\t%@"), thisSession
->serverPort
);
420 if (thisSession
->serverRunLoopSource
!= NULL
) {
421 SCPrint(TRUE
, f
, CFSTR("\n\t\t%@"), thisSession
->serverRunLoopSource
);
424 SCPrint(TRUE
, f
, CFSTR("\n"));
427 SCPrint(TRUE
, f
, CFSTR("\n"));
432 #include <Security/Security.h>
433 #include <Security/SecTask.h>
436 sessionName(serverSessionRef session
)
438 CFDictionaryRef info
;
439 CFStringRef name
= NULL
;
440 CFStringRef sessionKey
;
442 sessionKey
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%d"), session
->key
);
443 info
= CFDictionaryGetValue(sessionData
, sessionKey
);
444 CFRelease(sessionKey
);
447 name
= CFDictionaryGetValue(info
, kSCDName
);
450 return (name
!= NULL
) ? name
: CFSTR("???");
454 copyEntitlement(serverSessionRef session
, CFStringRef entitlement
)
457 CFTypeRef value
= NULL
;
459 // Create the security task from the audit token
460 task
= SecTaskCreateWithAuditToken(NULL
, session
->auditToken
);
462 CFErrorRef error
= NULL
;
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
);
470 if (!CFEqual(domain
, kCFErrorDomainMach
) ||
471 ((code
!= kIOReturnInvalid
) && (code
!= kIOReturnNotFound
))) {
472 // if unexpected error
473 SC_log(LOG_NOTICE
, "SecTaskCopyValueForEntitlement(,\"%@\",) failed, error = %@ : %@",
476 sessionName(session
));
483 SC_log(LOG_NOTICE
, "SecTaskCreateWithAuditToken() failed: %@",
484 sessionName(session
));
492 sessionPid(serverSessionRef session
)
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
,
508 #endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
516 hasRootAccess(serverSessionRef session
)
518 #if !TARGET_OS_SIMULATOR
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
,
526 &session
->callerEUID
, // euid
533 #endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
534 session
->callerRootAccess
= (session
->callerEUID
== 0) ? YES
: NO
;
537 return (session
->callerRootAccess
== YES
) ? TRUE
: FALSE
;
539 #else // !TARGET_OS_SIMULATOR
540 #pragma unused(session)
543 * assume that all processes interacting with
544 * the iOS Simulator "configd" are OK.
548 #endif // !TARGET_OS_SIMULATOR
554 hasWriteAccess(serverSessionRef session
, const char *op
, CFStringRef key
)
558 // need to special case writing "Setup:" keys
559 isSetup
= CFStringHasPrefix(key
, kSCDynamicStoreDomainSetup
);
561 if (hasRootAccess(session
)) {
564 // grant write access to eUID==0 processes
566 pid
= sessionPid(session
);
567 if (isSetup
&& (pid
!= getpid())) {
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.
576 SC_log(LOG_NOTICE
, "*** Non-configd process (pid=%d) attempting to %s \"%@\" ***",
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
594 SC_log(LOG_NOTICE
, "*** Non-root process (pid=%d) attempting to modify \"%@\" ***",
598 //return FALSE; // return FALSE when rdar://9811832 has beed fixed
601 if (session
->callerWriteEntitlement
== kCFNull
) {
602 session
->callerWriteEntitlement
= copyEntitlement(session
,
603 kSCWriteEntitlementName
);
606 if (session
->callerWriteEntitlement
== NULL
) {
610 if (isA_CFBoolean(session
->callerWriteEntitlement
) &&
611 CFBooleanGetValue(session
->callerWriteEntitlement
)) {
612 // grant write access to "entitled" processes
616 if (isA_CFDictionary(session
->callerWriteEntitlement
)) {
620 keys
= CFDictionaryGetValue(session
->callerWriteEntitlement
, CFSTR("keys"));
621 if (isA_CFArray(keys
)) {
622 if (CFArrayContainsValue(keys
,
623 CFRangeMake(0, CFArrayGetCount(keys
)),
625 // if key matches one of the entitlement "keys", grant
631 patterns
= CFDictionaryGetValue(session
->callerWriteEntitlement
, CFSTR("patterns"));
632 if (isA_CFArray(patterns
)) {
634 CFIndex n
= CFArrayGetCount(patterns
);
636 for (i
= 0; i
< n
; i
++) {
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
657 hasPathAccess(serverSessionRef session
, const char *path
)
660 char realPath
[PATH_MAX
];
662 if (realpath(path
, realPath
) == NULL
) {
663 SC_log(LOG_INFO
, "realpath() failed: %s", strerror(errno
));
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
,
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
));