2 * Copyright (c) 2000, 2001, 2003-2005, 2007-2020 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
;
58 static CFIndex client_sessions_advise
= 250; // when snapshot handler should detail sessions
61 static dispatch_queue_t
64 static dispatch_once_t once
;
65 static dispatch_queue_t q
;
67 dispatch_once(&once
, ^{
68 // allocate mapping between [client] session mach port and session info
69 client_sessions
= CFDictionaryCreateMutable(NULL
,
71 NULL
, // use the actual mach_port_t as the key
72 &kCFTypeDictionaryValueCallBacks
);
74 // and a queue to synchronize access to the mapping
75 q
= dispatch_queue_create("SCDynamicStore/sessions", NULL
);
83 #pragma mark __serverSession object
85 static CFStringRef
__serverSessionCopyDescription (CFTypeRef cf
);
86 static void __serverSessionDeallocate (CFTypeRef cf
);
88 static const CFRuntimeClass __serverSessionClass
= {
90 "serverSession", // className
93 __serverSessionDeallocate
, // dealloc
96 NULL
, // copyFormattingDesc
97 __serverSessionCopyDescription
// copyDebugDesc
100 static CFTypeID __serverSessionTypeID
= _kCFRuntimeNotATypeID
;
104 __serverSessionCopyDescription(CFTypeRef cf
)
106 CFAllocatorRef allocator
= CFGetAllocator(cf
);
107 CFMutableStringRef result
;
108 serverSessionRef session
= (serverSessionRef
)cf
;
110 result
= CFStringCreateMutable(allocator
, 0);
111 CFStringAppendFormat(result
, NULL
, CFSTR("<serverSession %p [%p]> {"), cf
, allocator
);
114 CFStringAppendFormat(result
, NULL
, CFSTR("port = 0x%x (%d)"), session
->key
, session
->key
);
117 if (session
->name
!= NULL
) {
118 CFStringAppendFormat(result
, NULL
, CFSTR(", name = %@"), session
->name
);
121 CFStringAppendFormat(result
, NULL
, CFSTR("}"));
127 __serverSessionDeallocate(CFTypeRef cf
)
130 serverSessionRef session
= (serverSessionRef
)cf
;
132 if (session
->changedKeys
!= NULL
) CFRelease(session
->changedKeys
);
133 if (session
->name
!= NULL
) CFRelease(session
->name
);
134 if (session
->sessionKeys
!= NULL
) CFRelease(session
->sessionKeys
);
140 static serverSessionRef
141 __serverSessionCreate(CFAllocatorRef allocator
, mach_port_t server
)
143 static dispatch_once_t once
;
144 serverSessionRef session
;
147 // initialize runtime
148 dispatch_once(&once
, ^{
149 __serverSessionTypeID
= _CFRuntimeRegisterClass(&__serverSessionClass
);
153 size
= sizeof(serverSession
) - sizeof(CFRuntimeBase
);
154 session
= (serverSessionRef
)_CFRuntimeCreateInstance(allocator
,
155 __serverSessionTypeID
,
158 if (session
== NULL
) {
162 // if needed, allocate a mach port for SCDynamicStore client
163 if (server
== MACH_PORT_NULL
) {
165 mach_port_t mp
= MACH_PORT_NULL
;
166 #ifdef HAVE_MACHPORT_GUARDS
167 mach_port_options_t opts
;
168 #endif // HAVE_MACHPORT_GUARDS
172 #ifdef HAVE_MACHPORT_GUARDS
173 memset(&opts
, 0, sizeof(opts
));
174 opts
.flags
= MPO_CONTEXT_AS_GUARD
;
176 kr
= mach_port_construct(mach_task_self(), &opts
, (mach_port_context_t
)session
, &mp
);
177 #else // HAVE_MACHPORT_GUARDS
178 kr
= mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE
, &mp
);
179 #endif // HAVE_MACHPORT_GUARDS
181 if (kr
!= KERN_SUCCESS
) {
184 SC_log(LOG_NOTICE
, "could not allocate mach port: %s", mach_error_string(kr
));
185 if ((kr
== KERN_NO_SPACE
) || (kr
== KERN_RESOURCE_SHORTAGE
)) {
190 (void) asprintf(&err
, "Could not allocate mach port: %s", mach_error_string(kr
));
191 _SC_crash(err
!= NULL
? err
: "Could not allocate new session (mach) port",
194 if (err
!= NULL
) free(err
);
199 // insert send right that will be moved to the client
200 kr
= mach_port_insert_right(mach_task_self(),
203 MACH_MSG_TYPE_MAKE_SEND
);
204 if (kr
!= KERN_SUCCESS
) {
206 * We can't insert a send right into our own port! This should
207 * only happen if someone stomped on OUR port (so let's leave
210 SC_log(LOG_ERR
, "mach_port_insert_right() failed: %s", mach_error_string(kr
));
218 session
->callerEUID
= 1; /* not "root" */
219 session
->callerRootAccess
= UNKNOWN
;
220 session
->callerWriteEntitlement
= kCFNull
; /* UNKNOWN */
221 session
->key
= server
;
222 // session->store = NULL;
229 #pragma mark SCDynamicStore state handler
233 addSessionReference(const void *key
, const void *value
, void *context
)
236 CFMutableDictionaryRef dict
= (CFMutableDictionaryRef
)context
;
237 serverSessionRef session
= (serverSessionRef
)value
;
239 if (session
->name
!= NULL
) {
243 if (!CFDictionaryGetValueIfPresent(dict
,
245 (const void **)&num
) ||
246 !CFNumberGetValue(num
, kCFNumberIntType
, &cnt
)) {
251 num
= CFNumberCreate(NULL
, kCFNumberIntType
, &cnt
);
252 CFDictionarySetValue(dict
, session
->name
, num
);
263 os_state_block_t state_block
;
265 state_block
= ^os_state_data_t(os_state_hints_t hints
) {
266 #pragma unused(hints)
267 CFDataRef data
= NULL
;
270 os_state_data_t state_data
;
271 size_t state_data_size
;
274 n
= CFDictionaryGetCount(client_sessions
);
275 if (n
< client_sessions_advise
) {
278 str
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("n = %ld"), n
);
279 ok
= _SCSerialize(str
, &data
, NULL
, NULL
);
282 CFMutableDictionaryRef dict
;
284 dict
= CFDictionaryCreateMutable(NULL
,
286 &kCFTypeDictionaryKeyCallBacks
,
287 &kCFTypeDictionaryValueCallBacks
);
288 CFDictionaryApplyFunction(client_sessions
, addSessionReference
, dict
);
289 ok
= _SCSerialize(dict
, &data
, NULL
, NULL
);
293 state_len
= (ok
&& (data
!= NULL
)) ? CFDataGetLength(data
) : 0;
294 state_data_size
= OS_STATE_DATA_SIZE_NEEDED(state_len
);
295 if (state_data_size
> MAX_STATEDUMP_SIZE
) {
296 SC_log(LOG_ERR
, "SCDynamicStore/sessions : state data too large (%zd > %zd)",
298 (size_t)MAX_STATEDUMP_SIZE
);
299 if (data
!= NULL
) CFRelease(data
);
303 state_data
= calloc(1, state_data_size
);
304 if (state_data
== NULL
) {
305 SC_log(LOG_ERR
, "SCDynamicStore/sessions: could not allocate state data");
306 if (data
!= NULL
) CFRelease(data
);
310 state_data
->osd_type
= OS_STATE_DATA_SERIALIZED_NSCF_OBJECT
;
311 state_data
->osd_data_size
= (uint32_t)state_len
;
312 strlcpy(state_data
->osd_title
, "SCDynamicStore/sessions", sizeof(state_data
->osd_title
));
314 memcpy(state_data
->osd_data
, CFDataGetBytePtr(data
), state_len
);
316 if (data
!= NULL
) CFRelease(data
);
321 (void) os_state_add_handler(sessionQueue(), state_block
);
327 #pragma mark SCDynamicStore session management
332 getSession(mach_port_t server
)
334 __block serverSessionRef session
;
336 assert(server
!= MACH_PORT_NULL
);
337 dispatch_sync(sessionQueue(), ^{
338 session
= (serverSessionRef
)CFDictionaryGetValue(client_sessions
,
339 (const void *)(uintptr_t)server
);
348 getSessionNum(CFNumberRef serverNum
)
354 serverSessionRef session
;
356 (void) CFNumberGetValue(serverNum
, kCFNumberSInt64Type
, &server
.val
);
357 session
= getSession(server
.mp
);
365 getSessionStr(CFStringRef serverKey
)
368 serverSessionRef session
;
371 (void) _SC_cfstring_to_cstring(serverKey
, str
, sizeof(str
), kCFStringEncodingASCII
);
373 session
= getSession(server
);
379 #if __has_feature(ptrauth_intrinsics)
380 extern const struct { char c
; } objc_absolute_packed_isa_class_mask
;
384 memcpy_objc_object(void* dst
, const void* restrict src
, size_t size
)
386 // first, we copy the object
387 memcpy(dst
, src
, size
);
389 // then, if needed, fix the isa pointer
390 #if __has_feature(ptrauth_intrinsics)
393 void ** opaqueObject
;
397 opaqueObject
= (void**)src
;
398 isa_mask
= (uintptr_t)&objc_absolute_packed_isa_class_mask
;
399 flags
= (uintptr_t)(*opaqueObject
) & ~isa_mask
;
400 real_isa
= (uintptr_t)(*opaqueObject
) & isa_mask
;
402 #if __has_feature(ptrauth_objc_isa)
403 raw_isa
= (uintptr_t)ptrauth_auth_data((void *)real_isa
,
404 ptrauth_key_process_independent_data
,
405 ptrauth_blend_discriminator(opaqueObject
, 0x6AE1));
406 #else // __has_feature(ptrauth_objc_isa)
407 raw_isa
= (uintptr_t)ptrauth_strip((void*)real_isa
, ptrauth_key_process_independent_data
);
408 #endif // __has_feature(ptrauth_objc_isa)
409 ((CFRuntimeBase
*)dst
)->_cfisa
= raw_isa
;
410 ((uint64_t*)dst
)[0] |= flags
;
411 #endif // __has_feature(ptrauth_intrinsics)
417 tempSession(mach_port_t server
, CFStringRef name
, audit_token_t auditToken
)
419 static dispatch_once_t once
;
420 SCDynamicStorePrivateRef storePrivate
; /* temp session */
421 static serverSession temp_session
;
423 dispatch_once(&once
, ^{
424 memcpy_objc_object(&temp_session
, /* use "server" session clone */
426 sizeof(temp_session
));
427 (void) __SCDynamicStoreOpen(&temp_session
.store
, NULL
);
430 if (temp_session
.key
!= server
) {
431 // if not SCDynamicStore "server" port
435 /* save audit token, caller entitlements */
436 temp_session
.auditToken
= auditToken
;
437 temp_session
.callerEUID
= 1; /* not "root" */
438 temp_session
.callerRootAccess
= UNKNOWN
;
439 if ((temp_session
.callerWriteEntitlement
!= NULL
) &&
440 (temp_session
.callerWriteEntitlement
!= kCFNull
)) {
441 CFRelease(temp_session
.callerWriteEntitlement
);
443 temp_session
.callerWriteEntitlement
= kCFNull
; /* UNKNOWN */
446 storePrivate
= (SCDynamicStorePrivateRef
)temp_session
.store
;
447 if (storePrivate
->name
!= NULL
) CFRelease(storePrivate
->name
);
448 storePrivate
->name
= CFRetain(name
);
450 return &temp_session
;
456 addSession(serverSessionRef session
, Boolean isMain
)
458 session
->serverChannel
= dispatch_mach_create_f("configd/SCDynamicStore",
461 server_mach_channel_handler
);
463 // if not main SCDynamicStore port, watch for exit
464 dispatch_mach_notify_no_senders(session
->serverChannel
, FALSE
);
466 #if TARGET_OS_SIMULATOR
467 // simulators don't support MiG QoS propagation yet
468 dispatch_set_qos_class_fallback(session
->serverChannel
, QOS_CLASS_USER_INITIATED
);
470 dispatch_set_qos_class_fallback(session
->serverChannel
, QOS_CLASS_BACKGROUND
);
472 dispatch_mach_connect(session
->serverChannel
, session
->key
, MACH_PORT_NULL
, NULL
);
479 addClient(mach_port_t server
, audit_token_t audit_token
)
482 __block serverSessionRef newSession
= NULL
;
484 dispatch_sync(sessionQueue(), ^{
487 // check to see if we already have an open session
488 ok
= CFDictionaryContainsKey(client_sessions
,
489 (const void *)(uintptr_t)server
);
491 // if we've already added a session for this port
495 // allocate a new session for "the" server
496 newSession
= __serverSessionCreate(NULL
, MACH_PORT_NULL
);
497 if (newSession
!= NULL
) {
498 // and add a port --> session mapping
499 CFDictionarySetValue(client_sessions
,
500 (const void *)(uintptr_t)newSession
->key
,
503 // save the audit_token in case we need to check the callers credentials
504 newSession
->auditToken
= audit_token
;
506 CFRelease(newSession
); // reference held by dictionary
510 if (newSession
!= NULL
) {
511 addSession(newSession
, FALSE
);
520 addServer(mach_port_t server
)
522 // allocate a session for "the" server
523 server_session
= __serverSessionCreate(NULL
, server
);
524 addSession(server_session
, TRUE
);
526 // add a state dump handler
529 return server_session
;
535 cleanupSession(serverSessionRef session
)
537 mach_port_t server
= session
->key
;
539 SC_trace("cleanup : %5d", server
);
542 * Close any open connections including cancelling any outstanding
543 * notification requests and releasing any locks.
545 __MACH_PORT_DEBUG(TRUE
, "*** cleanupSession", server
);
546 (void) __SCDynamicStoreClose(&session
->store
);
547 __MACH_PORT_DEBUG(TRUE
, "*** cleanupSession (after __SCDynamicStoreClose)", server
);
550 * Our send right has already been removed. Remove our receive right.
552 #ifdef HAVE_MACHPORT_GUARDS
553 (void) mach_port_destruct(mach_task_self(), server
, 0, (mach_port_context_t
)session
);
554 #else // HAVE_MACHPORT_GUARDS
555 (void) mach_port_mod_refs(mach_task_self(), server
, MACH_PORT_RIGHT_RECEIVE
, -1);
556 #endif // HAVE_MACHPORT_GUARDS
559 * release any entitlement info
561 if ((session
->callerWriteEntitlement
!= NULL
) &&
562 (session
->callerWriteEntitlement
!= kCFNull
)) {
563 CFRelease(session
->callerWriteEntitlement
);
567 * get rid of the per-session structure.
569 dispatch_sync(sessionQueue(), ^{
570 CFDictionaryRemoveValue(client_sessions
,
571 (const void *)(uintptr_t)server
);
580 closeSession(serverSessionRef session
)
583 * cancel and release the mach channel
585 if (session
->serverChannel
!= NULL
) {
586 dispatch_mach_cancel(session
->serverChannel
);
587 dispatch_release(session
->serverChannel
);
588 session
->serverChannel
= NULL
;
595 typedef struct ReportSessionInfo
{
598 } ReportSessionInfo
, *ReportSessionInfoRef
;
601 printOne(const void *key
, const void *value
, void *context
)
604 ReportSessionInfoRef reportInfo
= (ReportSessionInfoRef
)context
;
605 serverSessionRef session
= (serverSessionRef
)value
;
607 SCPrint(TRUE
, reportInfo
->f
, CFSTR(" %d : port = 0x%x"), ++reportInfo
->n
, session
->key
);
608 SCPrint(TRUE
, reportInfo
->f
, CFSTR(", name = %@"), session
->name
);
609 if (session
->changedKeys
!= NULL
) {
610 SCPrint(TRUE
, reportInfo
->f
, CFSTR("\n changedKeys = %@"), session
->changedKeys
);
612 if (session
->sessionKeys
!= NULL
) {
613 SCPrint(TRUE
, reportInfo
->f
, CFSTR("\n sessionKeys = %@"), session
->sessionKeys
);
615 SCPrint(TRUE
, reportInfo
->f
, CFSTR("\n"));
622 listSessions(FILE *f
)
624 dispatch_sync(sessionQueue(), ^{
625 ReportSessionInfo reportInfo
= { .f
= f
, .n
= 0 };
627 SCPrint(TRUE
, f
, CFSTR("Current sessions :\n"));
628 CFDictionaryApplyFunction(client_sessions
,
630 (void *)&reportInfo
);
631 SCPrint(TRUE
, f
, CFSTR("\n"));
637 #include <Security/Security.h>
638 #include <Security/SecTask.h>
641 copyEntitlement(serverSessionRef session
, CFStringRef entitlement
)
644 CFTypeRef value
= NULL
;
646 // Create the security task from the audit token
647 task
= SecTaskCreateWithAuditToken(NULL
, session
->auditToken
);
649 CFErrorRef error
= NULL
;
651 // Get the value for the entitlement
652 value
= SecTaskCopyValueForEntitlement(task
, entitlement
, &error
);
653 if ((value
== NULL
) && (error
!= NULL
)) {
654 CFIndex code
= CFErrorGetCode(error
);
655 CFStringRef domain
= CFErrorGetDomain(error
);
657 if (!CFEqual(domain
, kCFErrorDomainMach
) ||
658 ((code
!= kIOReturnInvalid
) && (code
!= kIOReturnNotFound
))) {
659 // if unexpected error
660 SC_log(LOG_NOTICE
, "SecTaskCopyValueForEntitlement(,\"%@\",) failed, error = %@ : %@",
670 SC_log(LOG_NOTICE
, "SecTaskCreateWithAuditToken() failed: %@",
679 sessionPid(serverSessionRef session
)
683 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
684 caller_pid
= audit_token_to_pid(session
->auditToken
);
685 #else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
686 audit_token_to_au32(session
->auditToken
,
695 #endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
703 hasRootAccess(serverSessionRef session
)
705 #if !TARGET_OS_SIMULATOR
707 if (session
->callerRootAccess
== UNKNOWN
) {
708 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
709 session
->callerEUID
= audit_token_to_euid(session
->auditToken
);
710 #else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
711 audit_token_to_au32(session
->auditToken
,
713 &session
->callerEUID
, // euid
720 #endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
721 session
->callerRootAccess
= (session
->callerEUID
== 0) ? YES
: NO
;
724 return (session
->callerRootAccess
== YES
) ? TRUE
: FALSE
;
726 #else // !TARGET_OS_SIMULATOR
727 #pragma unused(session)
730 * assume that all processes interacting with
731 * the iOS Simulator "configd" are OK.
735 #endif // !TARGET_OS_SIMULATOR
741 hasWriteAccess(serverSessionRef session
, const char *op
, CFStringRef key
)
745 // need to special case writing "Setup:" keys
746 isSetup
= CFStringHasPrefix(key
, kSCDynamicStoreDomainSetup
);
748 if (hasRootAccess(session
)) {
751 // grant write access to eUID==0 processes
753 pid
= sessionPid(session
);
754 if (isSetup
&& (pid
!= getpid())) {
758 * This is NOT configd (or a plugin) trying to
759 * write to an SCDynamicStore "Setup:" key. In
760 * general, this is unwise and we should at the
761 * very least complain.
763 SC_log(LOG_NOTICE
, "*** Non-configd process (pid=%d) attempting to %s \"%@\" ***",
776 * This is a non-root process trying to write to
777 * an SCDynamicStore "Setup:" key. This is not
778 * something we should ever allow (regardless of
781 SC_log(LOG_NOTICE
, "*** Non-root process (pid=%d) attempting to modify \"%@\" ***",
788 if (session
->callerWriteEntitlement
== kCFNull
) {
789 session
->callerWriteEntitlement
= copyEntitlement(session
,
790 kSCWriteEntitlementName
);
793 if (session
->callerWriteEntitlement
== NULL
) {
797 if (isA_CFBoolean(session
->callerWriteEntitlement
) &&
798 CFBooleanGetValue(session
->callerWriteEntitlement
)) {
799 // grant write access to "entitled" processes
803 if (isA_CFDictionary(session
->callerWriteEntitlement
)) {
807 keys
= CFDictionaryGetValue(session
->callerWriteEntitlement
, CFSTR("keys"));
808 if (isA_CFArray(keys
)) {
809 if (CFArrayContainsValue(keys
,
810 CFRangeMake(0, CFArrayGetCount(keys
)),
812 // if key matches one of the entitlement "keys", grant
818 patterns
= CFDictionaryGetValue(session
->callerWriteEntitlement
, CFSTR("patterns"));
819 if (isA_CFArray(patterns
)) {
821 CFIndex n
= CFArrayGetCount(patterns
);
823 for (i
= 0; i
< n
; i
++) {
826 pattern
= CFArrayGetValueAtIndex(patterns
, i
);
827 if (isA_CFString(pattern
)) {
828 if (patternKeyMatches(pattern
, key
)) {
829 // if key matches one of the entitlement
830 // "patterns", grant write access
844 hasPathAccess(serverSessionRef session
, const char *path
)
847 char realPath
[PATH_MAX
];
849 if (realpath(path
, realPath
) == NULL
) {
850 SC_log(LOG_INFO
, "realpath() failed: %s", strerror(errno
));
854 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
855 pid
= audit_token_to_pid(session
->auditToken
);
856 #else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
857 audit_token_to_au32(session
->auditToken
,
866 #endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE
867 if (sandbox_check(pid
, // pid
868 "file-write-data", // operation
869 SANDBOX_FILTER_PATH
| SANDBOX_CHECK_NO_REPORT
, // sandbox_filter_type
870 realPath
) > 0) { // ...
871 SC_log(LOG_INFO
, "sandbox access denied: %s", strerror(errno
));