2 * Copyright (c) 2000, 2001, 2003-2005, 2007-2019 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>
42 #include <os/state_private.h>
45 #if !TARGET_OS_SIMULATOR || (defined(IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED) && (IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED >= 1090))
46 #define HAVE_MACHPORT_GUARDS
50 /* information maintained for the main listener */
51 static serverSessionRef server_session
= NULL
;
54 * information maintained for each active session
55 * Note: sync w/sessionQueue()
57 static CFMutableDictionaryRef client_sessions
= NULL
;
60 static dispatch_queue_t
63 static dispatch_once_t once
;
64 static dispatch_queue_t q
;
66 dispatch_once(&once
, ^{
67 // allocate mapping between [client] session mach port and session info
68 client_sessions
= CFDictionaryCreateMutable(NULL
,
70 NULL
, // use the actual mach_port_t as the key
71 &kCFTypeDictionaryValueCallBacks
);
73 // and a queue to synchronize access to the mapping
74 q
= dispatch_queue_create("SCDynamicStore/sessions", NULL
);
82 #pragma mark __serverSession object
84 static CFStringRef
__serverSessionCopyDescription (CFTypeRef cf
);
85 static void __serverSessionDeallocate (CFTypeRef cf
);
87 static const CFRuntimeClass __serverSessionClass
= {
89 "serverSession", // className
92 __serverSessionDeallocate
, // dealloc
95 NULL
, // copyFormattingDesc
96 __serverSessionCopyDescription
// copyDebugDesc
99 static CFTypeID __serverSessionTypeID
= _kCFRuntimeNotATypeID
;
103 __serverSessionCopyDescription(CFTypeRef cf
)
105 CFAllocatorRef allocator
= CFGetAllocator(cf
);
106 CFMutableStringRef result
;
107 serverSessionRef session
= (serverSessionRef
)cf
;
109 result
= CFStringCreateMutable(allocator
, 0);
110 CFStringAppendFormat(result
, NULL
, CFSTR("<serverSession %p [%p]> {"), cf
, allocator
);
113 CFStringAppendFormat(result
, NULL
, CFSTR("port = 0x%x (%d)"), session
->key
, session
->key
);
116 if (session
->name
!= NULL
) {
117 CFStringAppendFormat(result
, NULL
, CFSTR(", name = %@"), session
->name
);
120 CFStringAppendFormat(result
, NULL
, CFSTR("}"));
126 __serverSessionDeallocate(CFTypeRef cf
)
129 serverSessionRef session
= (serverSessionRef
)cf
;
131 if (session
->changedKeys
!= NULL
) CFRelease(session
->changedKeys
);
132 if (session
->name
!= NULL
) CFRelease(session
->name
);
133 if (session
->sessionKeys
!= NULL
) CFRelease(session
->sessionKeys
);
139 static serverSessionRef
140 __serverSessionCreate(CFAllocatorRef allocator
, mach_port_t server
)
142 static dispatch_once_t once
;
143 serverSessionRef session
;
146 // initialize runtime
147 dispatch_once(&once
, ^{
148 __serverSessionTypeID
= _CFRuntimeRegisterClass(&__serverSessionClass
);
152 size
= sizeof(serverSession
) - sizeof(CFRuntimeBase
);
153 session
= (serverSessionRef
)_CFRuntimeCreateInstance(allocator
,
154 __serverSessionTypeID
,
157 if (session
== NULL
) {
161 // if needed, allocate a mach port for SCDynamicStore client
162 if (server
== MACH_PORT_NULL
) {
164 mach_port_t mp
= MACH_PORT_NULL
;
165 #ifdef HAVE_MACHPORT_GUARDS
166 mach_port_options_t opts
;
167 #endif // HAVE_MACHPORT_GUARDS
171 #ifdef HAVE_MACHPORT_GUARDS
172 memset(&opts
, 0, sizeof(opts
));
173 opts
.flags
= MPO_CONTEXT_AS_GUARD
;
175 kr
= mach_port_construct(mach_task_self(), &opts
, (mach_port_context_t
)session
, &mp
);
176 #else // HAVE_MACHPORT_GUARDS
177 kr
= mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE
, &mp
);
178 #endif // HAVE_MACHPORT_GUARDS
180 if (kr
!= KERN_SUCCESS
) {
183 SC_log(LOG_NOTICE
, "could not allocate mach port: %s", mach_error_string(kr
));
184 if ((kr
== KERN_NO_SPACE
) || (kr
== KERN_RESOURCE_SHORTAGE
)) {
189 (void) asprintf(&err
, "Could not allocate mach port: %s", mach_error_string(kr
));
190 _SC_crash(err
!= NULL
? err
: "Could not allocate new session (mach) port",
193 if (err
!= NULL
) free(err
);
198 // insert send right that will be moved to the client
199 kr
= mach_port_insert_right(mach_task_self(),
202 MACH_MSG_TYPE_MAKE_SEND
);
203 if (kr
!= KERN_SUCCESS
) {
205 * We can't insert a send right into our own port! This should
206 * only happen if someone stomped on OUR port (so let's leave
209 SC_log(LOG_ERR
, "mach_port_insert_right() failed: %s", mach_error_string(kr
));
217 session
->callerEUID
= 1; /* not "root" */
218 session
->callerRootAccess
= UNKNOWN
;
219 session
->callerWriteEntitlement
= kCFNull
; /* UNKNOWN */
220 session
->key
= server
;
221 // session->store = NULL;
228 #pragma mark SCDynamicStore state handler
232 addSessionReference(const void *key
, const void *value
, void *context
)
235 CFMutableDictionaryRef dict
= (CFMutableDictionaryRef
)context
;
236 serverSessionRef session
= (serverSessionRef
)value
;
238 if (session
->name
!= NULL
) {
242 if (!CFDictionaryGetValueIfPresent(dict
,
244 (const void **)&num
) ||
245 !CFNumberGetValue(num
, kCFNumberIntType
, &cnt
)) {
250 num
= CFNumberCreate(NULL
, kCFNumberIntType
, &cnt
);
251 CFDictionarySetValue(dict
, session
->name
, num
);
262 os_state_block_t state_block
;
264 state_block
= ^os_state_data_t(os_state_hints_t hints
) {
265 #pragma unused(hints)
266 CFDataRef data
= NULL
;
269 os_state_data_t state_data
;
270 size_t state_data_size
;
273 n
= CFDictionaryGetCount(client_sessions
);
277 str
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("n = %ld"), n
);
278 ok
= _SCSerialize(str
, &data
, NULL
, NULL
);
281 CFMutableDictionaryRef dict
;
283 dict
= CFDictionaryCreateMutable(NULL
,
285 &kCFTypeDictionaryKeyCallBacks
,
286 &kCFTypeDictionaryValueCallBacks
);
287 CFDictionaryApplyFunction(client_sessions
, addSessionReference
, dict
);
288 ok
= _SCSerialize(dict
, &data
, NULL
, NULL
);
292 state_len
= (ok
&& (data
!= NULL
)) ? CFDataGetLength(data
) : 0;
293 state_data_size
= OS_STATE_DATA_SIZE_NEEDED(state_len
);
294 if (state_data_size
> MAX_STATEDUMP_SIZE
) {
295 SC_log(LOG_ERR
, "SCDynamicStore/sessions : state data too large (%zd > %zd)",
297 (size_t)MAX_STATEDUMP_SIZE
);
298 if (data
!= NULL
) CFRelease(data
);
302 state_data
= calloc(1, state_data_size
);
303 if (state_data
== NULL
) {
304 SC_log(LOG_ERR
, "SCDynamicStore/sessions: could not allocate state data");
305 if (data
!= NULL
) CFRelease(data
);
309 state_data
->osd_type
= OS_STATE_DATA_SERIALIZED_NSCF_OBJECT
;
310 state_data
->osd_data_size
= (uint32_t)state_len
;
311 strlcpy(state_data
->osd_title
, "SCDynamicStore/sessions", sizeof(state_data
->osd_title
));
313 memcpy(state_data
->osd_data
, CFDataGetBytePtr(data
), state_len
);
315 if (data
!= NULL
) CFRelease(data
);
320 (void) os_state_add_handler(sessionQueue(), state_block
);
326 #pragma mark SCDynamicStore session management
331 getSession(mach_port_t server
)
333 __block serverSessionRef session
;
335 assert(server
!= MACH_PORT_NULL
);
336 dispatch_sync(sessionQueue(), ^{
337 session
= (serverSessionRef
)CFDictionaryGetValue(client_sessions
,
338 (const void *)(uintptr_t)server
);
347 getSessionNum(CFNumberRef serverNum
)
353 serverSessionRef session
;
355 (void) CFNumberGetValue(serverNum
, kCFNumberSInt64Type
, &server
.val
);
356 session
= getSession(server
.mp
);
364 getSessionStr(CFStringRef serverKey
)
367 serverSessionRef session
;
370 (void) _SC_cfstring_to_cstring(serverKey
, str
, sizeof(str
), kCFStringEncodingASCII
);
372 session
= getSession(server
);
380 tempSession(mach_port_t server
, CFStringRef name
, audit_token_t auditToken
)
382 static dispatch_once_t once
;
383 SCDynamicStorePrivateRef storePrivate
; /* temp session */
384 static serverSession temp_session
;
386 dispatch_once(&once
, ^{
387 temp_session
= *server_session
; /* use "server" session clone */
388 (void) __SCDynamicStoreOpen(&temp_session
.store
, NULL
);
391 if (temp_session
.key
!= server
) {
392 // if not SCDynamicStore "server" port
396 /* save audit token, caller entitlements */
397 temp_session
.auditToken
= auditToken
;
398 temp_session
.callerEUID
= 1; /* not "root" */
399 temp_session
.callerRootAccess
= UNKNOWN
;
400 if ((temp_session
.callerWriteEntitlement
!= NULL
) &&
401 (temp_session
.callerWriteEntitlement
!= kCFNull
)) {
402 CFRelease(temp_session
.callerWriteEntitlement
);
404 temp_session
.callerWriteEntitlement
= kCFNull
; /* UNKNOWN */
407 storePrivate
= (SCDynamicStorePrivateRef
)temp_session
.store
;
408 if (storePrivate
->name
!= NULL
) CFRelease(storePrivate
->name
);
409 storePrivate
->name
= CFRetain(name
);
411 return &temp_session
;
417 addSession(serverSessionRef session
, Boolean isMain
)
419 session
->serverChannel
= dispatch_mach_create_f("configd/SCDynamicStore",
422 server_mach_channel_handler
);
424 // if not main SCDynamicStore port, watch for exit
425 dispatch_mach_request_no_senders(session
->serverChannel
);
427 #if TARGET_OS_SIMULATOR
428 // simulators don't support MiG QoS propagation yet
429 dispatch_set_qos_class_fallback(session
->serverChannel
, QOS_CLASS_USER_INITIATED
);
431 dispatch_set_qos_class_fallback(session
->serverChannel
, QOS_CLASS_BACKGROUND
);
433 dispatch_mach_connect(session
->serverChannel
, session
->key
, MACH_PORT_NULL
, NULL
);
440 addClient(mach_port_t server
, audit_token_t audit_token
)
443 __block serverSessionRef newSession
= NULL
;
445 dispatch_sync(sessionQueue(), ^{
448 // check to see if we already have an open session
449 ok
= CFDictionaryContainsKey(client_sessions
,
450 (const void *)(uintptr_t)server
);
452 // if we've already added a session for this port
456 // allocate a new session for "the" server
457 newSession
= __serverSessionCreate(NULL
, MACH_PORT_NULL
);
458 if (newSession
!= NULL
) {
459 // and add a port --> session mapping
460 CFDictionarySetValue(client_sessions
,
461 (const void *)(uintptr_t)newSession
->key
,
464 // save the audit_token in case we need to check the callers credentials
465 newSession
->auditToken
= audit_token
;
467 CFRelease(newSession
); // reference held by dictionary
471 if (newSession
!= NULL
) {
472 addSession(newSession
, FALSE
);
481 addServer(mach_port_t server
)
483 // allocate a session for "the" server
484 server_session
= __serverSessionCreate(NULL
, server
);
485 addSession(server_session
, TRUE
);
487 // add a state dump handler
490 return server_session
;
496 cleanupSession(serverSessionRef session
)
498 mach_port_t server
= session
->key
;
500 SC_trace("cleanup : %5d", server
);
503 * Close any open connections including cancelling any outstanding
504 * notification requests and releasing any locks.
506 __MACH_PORT_DEBUG(TRUE
, "*** cleanupSession", server
);
507 (void) __SCDynamicStoreClose(&session
->store
);
508 __MACH_PORT_DEBUG(TRUE
, "*** cleanupSession (after __SCDynamicStoreClose)", server
);
511 * Our send right has already been removed. Remove our receive right.
513 #ifdef HAVE_MACHPORT_GUARDS
514 (void) mach_port_destruct(mach_task_self(), server
, 0, (mach_port_context_t
)session
);
515 #else // HAVE_MACHPORT_GUARDS
516 (void) mach_port_mod_refs(mach_task_self(), server
, MACH_PORT_RIGHT_RECEIVE
, -1);
517 #endif // HAVE_MACHPORT_GUARDS
520 * release any entitlement info
522 if ((session
->callerWriteEntitlement
!= NULL
) &&
523 (session
->callerWriteEntitlement
!= kCFNull
)) {
524 CFRelease(session
->callerWriteEntitlement
);
528 * get rid of the per-session structure.
530 dispatch_sync(sessionQueue(), ^{
531 CFDictionaryRemoveValue(client_sessions
,
532 (const void *)(uintptr_t)server
);
541 closeSession(serverSessionRef session
)
544 * cancel and release the mach channel
546 if (session
->serverChannel
!= NULL
) {
547 dispatch_mach_cancel(session
->serverChannel
);
548 dispatch_release(session
->serverChannel
);
549 session
->serverChannel
= NULL
;
556 typedef struct ReportSessionInfo
{
559 } ReportSessionInfo
, *ReportSessionInfoRef
;
562 printOne(const void *key
, const void *value
, void *context
)
565 ReportSessionInfoRef reportInfo
= (ReportSessionInfoRef
)context
;
566 serverSessionRef session
= (serverSessionRef
)value
;
568 SCPrint(TRUE
, reportInfo
->f
, CFSTR(" %d : port = 0x%x"), ++reportInfo
->n
, session
->key
);
569 SCPrint(TRUE
, reportInfo
->f
, CFSTR(", name = %@"), session
->name
);
570 if (session
->changedKeys
!= NULL
) {
571 SCPrint(TRUE
, reportInfo
->f
, CFSTR("\n changedKeys = %@"), session
->changedKeys
);
573 if (session
->sessionKeys
!= NULL
) {
574 SCPrint(TRUE
, reportInfo
->f
, CFSTR("\n sessionKeys = %@"), session
->sessionKeys
);
576 SCPrint(TRUE
, reportInfo
->f
, CFSTR("\n"));
583 listSessions(FILE *f
)
585 dispatch_sync(sessionQueue(), ^{
586 ReportSessionInfo reportInfo
= { .f
= f
, .n
= 0 };
588 SCPrint(TRUE
, f
, CFSTR("Current sessions :\n"));
589 CFDictionaryApplyFunction(client_sessions
,
591 (void *)&reportInfo
);
592 SCPrint(TRUE
, f
, CFSTR("\n"));
598 #include <Security/Security.h>
599 #include <Security/SecTask.h>
602 copyEntitlement(serverSessionRef session
, CFStringRef entitlement
)
605 CFTypeRef value
= NULL
;
607 // Create the security task from the audit token
608 task
= SecTaskCreateWithAuditToken(NULL
, session
->auditToken
);
610 CFErrorRef error
= NULL
;
612 // Get the value for the entitlement
613 value
= SecTaskCopyValueForEntitlement(task
, entitlement
, &error
);
614 if ((value
== NULL
) && (error
!= NULL
)) {
615 CFIndex code
= CFErrorGetCode(error
);
616 CFStringRef domain
= CFErrorGetDomain(error
);
618 if (!CFEqual(domain
, kCFErrorDomainMach
) ||
619 ((code
!= kIOReturnInvalid
) && (code
!= kIOReturnNotFound
))) {
620 // if unexpected error
621 SC_log(LOG_NOTICE
, "SecTaskCopyValueForEntitlement(,\"%@\",) failed, error = %@ : %@",
631 SC_log(LOG_NOTICE
, "SecTaskCreateWithAuditToken() failed: %@",
640 sessionPid(serverSessionRef session
)
644 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
645 caller_pid
= audit_token_to_pid(session
->auditToken
);
646 #else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
647 audit_token_to_au32(session
->auditToken
,
656 #endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
664 hasRootAccess(serverSessionRef session
)
666 #if !TARGET_OS_SIMULATOR
668 if (session
->callerRootAccess
== UNKNOWN
) {
669 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
670 session
->callerEUID
= audit_token_to_euid(session
->auditToken
);
671 #else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
672 audit_token_to_au32(session
->auditToken
,
674 &session
->callerEUID
, // euid
681 #endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
682 session
->callerRootAccess
= (session
->callerEUID
== 0) ? YES
: NO
;
685 return (session
->callerRootAccess
== YES
) ? TRUE
: FALSE
;
687 #else // !TARGET_OS_SIMULATOR
688 #pragma unused(session)
691 * assume that all processes interacting with
692 * the iOS Simulator "configd" are OK.
696 #endif // !TARGET_OS_SIMULATOR
702 hasWriteAccess(serverSessionRef session
, const char *op
, CFStringRef key
)
706 // need to special case writing "Setup:" keys
707 isSetup
= CFStringHasPrefix(key
, kSCDynamicStoreDomainSetup
);
709 if (hasRootAccess(session
)) {
712 // grant write access to eUID==0 processes
714 pid
= sessionPid(session
);
715 if (isSetup
&& (pid
!= getpid())) {
719 * This is NOT configd (or a plugin) trying to
720 * write to an SCDynamicStore "Setup:" key. In
721 * general, this is unwise and we should at the
722 * very least complain.
724 SC_log(LOG_NOTICE
, "*** Non-configd process (pid=%d) attempting to %s \"%@\" ***",
737 * This is a non-root process trying to write to
738 * an SCDynamicStore "Setup:" key. This is not
739 * something we should ever allow (regardless of
742 SC_log(LOG_NOTICE
, "*** Non-root process (pid=%d) attempting to modify \"%@\" ***",
749 if (session
->callerWriteEntitlement
== kCFNull
) {
750 session
->callerWriteEntitlement
= copyEntitlement(session
,
751 kSCWriteEntitlementName
);
754 if (session
->callerWriteEntitlement
== NULL
) {
758 if (isA_CFBoolean(session
->callerWriteEntitlement
) &&
759 CFBooleanGetValue(session
->callerWriteEntitlement
)) {
760 // grant write access to "entitled" processes
764 if (isA_CFDictionary(session
->callerWriteEntitlement
)) {
768 keys
= CFDictionaryGetValue(session
->callerWriteEntitlement
, CFSTR("keys"));
769 if (isA_CFArray(keys
)) {
770 if (CFArrayContainsValue(keys
,
771 CFRangeMake(0, CFArrayGetCount(keys
)),
773 // if key matches one of the entitlement "keys", grant
779 patterns
= CFDictionaryGetValue(session
->callerWriteEntitlement
, CFSTR("patterns"));
780 if (isA_CFArray(patterns
)) {
782 CFIndex n
= CFArrayGetCount(patterns
);
784 for (i
= 0; i
< n
; i
++) {
787 pattern
= CFArrayGetValueAtIndex(patterns
, i
);
788 if (isA_CFString(pattern
)) {
789 if (patternKeyMatches(pattern
, key
)) {
790 // if key matches one of the entitlement
791 // "patterns", grant write access
805 hasPathAccess(serverSessionRef session
, const char *path
)
808 char realPath
[PATH_MAX
];
810 if (realpath(path
, realPath
) == NULL
) {
811 SC_log(LOG_INFO
, "realpath() failed: %s", strerror(errno
));
815 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
816 pid
= audit_token_to_pid(session
->auditToken
);
817 #else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
818 audit_token_to_au32(session
->auditToken
,
827 #endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
828 if (sandbox_check(pid
, // pid
829 "file-write-data", // operation
830 SANDBOX_FILTER_PATH
| SANDBOX_CHECK_NO_REPORT
, // sandbox_filter_type
831 realPath
) > 0) { // ...
832 SC_log(LOG_INFO
, "sandbox access denied: %s", strerror(errno
));