1/* 2 * Copyright (c) 2000, 2001, 2003-2005, 2007-2013 Apple Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 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 11 * file. 12 * 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. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24/* 25 * Modification History 26 * 27 * June 1, 2001 Allan Nathanson <ajn@apple.com> 28 * - public API conversion 29 * 30 * March 24, 2000 Allan Nathanson <ajn@apple.com> 31 * - initial revision 32 */ 33 34#include <SystemConfiguration/SystemConfiguration.h> 35#include "configd.h" 36#include "configd_server.h" 37#include "pattern.h" 38#include "session.h" 39 40#include <unistd.h> 41#include <bsm/libbsm.h> 42#include <sandbox.h> 43 44#if !TARGET_IPHONE_SIMULATOR || (defined(IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED) && (IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED >= 1090)) 45#define HAVE_MACHPORT_GUARDS 46#endif 47 48 49/* information maintained for each active session */ 50static serverSessionRef *sessions = NULL; 51static int nSessions = 0; /* # of allocated sessions */ 52static int lastSession = -1; /* # of last used session */ 53 54/* CFMachPortInvalidation runloop */ 55static CFRunLoopRef sessionRunLoop = NULL; 56 57/* temp session */ 58static serverSessionRef temp_session = NULL; 59 60 61__private_extern__ 62serverSessionRef 63getSession(mach_port_t server) 64{ 65 int i; 66 67 if (server == MACH_PORT_NULL) { 68 SCLog(TRUE, LOG_ERR, CFSTR("Excuse me, why is getSession() being called with an invalid port?")); 69 return NULL; 70 } 71 72 /* look for matching session (note: slot 0 is the "server" port) */ 73 for (i = 1; i <= lastSession; i++) { 74 serverSessionRef thisSession = sessions[i]; 75 76 if (thisSession == NULL) { 77 /* found an empty slot, skip it */ 78 continue; 79 } 80 81 if (thisSession->key == server) { 82 /* we've seen this server before */ 83 return thisSession; 84 } 85 86 if ((thisSession->store != NULL) && 87 (((SCDynamicStorePrivateRef)thisSession->store)->notifySignalTask == server)) { 88 /* we've seen this task port before */ 89 return thisSession; 90 } 91 } 92 93 /* no sessions available */ 94 return NULL; 95} 96 97 98__private_extern__ 99serverSessionRef 100tempSession(mach_port_t server, CFStringRef name, audit_token_t auditToken) 101{ 102 static dispatch_once_t once; 103 SCDynamicStorePrivateRef storePrivate; 104 105 if (sessions[0]->key != server) { 106 // if not SCDynamicStore "server" port 107 return NULL; 108 } 109 110 dispatch_once(&once, ^{ 111 temp_session = sessions[0]; /* use "server" session */ 112 (void) __SCDynamicStoreOpen(&temp_session->store, NULL); 113 }); 114 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 TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080/*FIXME*/) 120 if ((temp_session->callerWriteEntitlement != NULL) && 121 (temp_session->callerWriteEntitlement != kCFNull)) { 122 CFRelease(temp_session->callerWriteEntitlement); 123 } 124 temp_session->callerWriteEntitlement = kCFNull; /* UNKNOWN */ 125#endif // TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080/*FIXME*/) 126 127 /* save name */ 128 storePrivate = (SCDynamicStorePrivateRef)temp_session->store; 129 if (storePrivate->name != NULL) CFRelease(storePrivate->name); 130 storePrivate->name = CFRetain(name); 131 132 return temp_session; 133} 134 135 136__private_extern__ 137serverSessionRef 138addSession(mach_port_t server, CFStringRef (*copyDescription)(const void *info)) 139{ 140 CFMachPortContext context = { 0, NULL, NULL, NULL, NULL }; 141 kern_return_t kr; 142 mach_port_t mp = server; 143 int n = -1; 144 serverSessionRef newSession = NULL; 145 146 /* save current (SCDynamicStore) runloop */ 147 if (sessionRunLoop == NULL) { 148 sessionRunLoop = CFRunLoopGetCurrent(); 149 } 150 151 if (nSessions <= 0) { 152 /* if first session (the "server" port) */ 153 n = 0; /* use slot "0" */ 154 lastSession = 0; /* last used slot */ 155 156 nSessions = 64; 157 sessions = malloc(nSessions * sizeof(serverSessionRef)); 158 159 // allocate a new session for "the" server 160 newSession = calloc(1, sizeof(serverSession)); 161 } else { 162 int i; 163#ifdef HAVE_MACHPORT_GUARDS 164 mach_port_options_t opts; 165#endif // HAVE_MACHPORT_GUARDS 166 167 /* check to see if we already have an open session (note: slot 0 is the "server" port) */ 168 for (i = 1; i <= lastSession; i++) { 169 serverSessionRef thisSession = sessions[i]; 170 171 if (thisSession == NULL) { 172 /* found an empty slot */ 173 if (n < 0) { 174 /* keep track of the first [empty] slot */ 175 n = i; 176 } 177 178 /* and keep looking for a matching session */ 179 continue; 180 } 181 182 if (thisSession->key == server) { 183 /* we've seen this server before */ 184 return NULL; 185 } 186 187 if ((thisSession->store != NULL) && 188 (((SCDynamicStorePrivateRef)thisSession->store)->notifySignalTask == server)) { 189 /* we've seen this task port before */ 190 return NULL; 191 } 192 } 193 194 /* add a new session */ 195 if (n < 0) { 196 /* if no empty slots */ 197 n = ++lastSession; 198 if (lastSession >= nSessions) { 199 /* expand the session list */ 200 nSessions *= 2; 201 sessions = reallocf(sessions, (nSessions * sizeof(serverSessionRef))); 202 } 203 } 204 205 // allocate a session for this client 206 newSession = calloc(1, sizeof(serverSession)); 207 208 // create mach port for SCDynamicStore client 209 mp = MACH_PORT_NULL; 210 211 retry_allocate : 212 213#ifdef HAVE_MACHPORT_GUARDS 214 bzero(&opts, sizeof(opts)); 215 opts.flags = MPO_CONTEXT_AS_GUARD; 216 217 kr = mach_port_construct(mach_task_self(), &opts, newSession, &mp); 218#else // HAVE_MACHPORT_GUARDS 219 kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &mp); 220#endif // HAVE_MACHPORT_GUARDS 221 222 if (kr != KERN_SUCCESS) { 223 char *err = NULL; 224 225 SCLog(TRUE, LOG_ERR, CFSTR("addSession: could not allocate mach port: %s"), mach_error_string(kr)); 226 if ((kr == KERN_NO_SPACE) || (kr == KERN_RESOURCE_SHORTAGE)) { 227 sleep(1); 228 goto retry_allocate; 229 } 230 231 (void) asprintf(&err, "addSession: could not allocate mach port: %s", mach_error_string(kr)); 232 _SC_crash(err != NULL ? err : "addSession: could not allocate mach port", 233 NULL, 234 NULL); 235 if (err != NULL) free(err); 236 237 free(newSession); 238 return NULL; 239 } 240 } 241 242 // create server port 243 context.info = newSession; 244 context.copyDescription = copyDescription; 245 246 // 247 // Note: we create the CFMachPort *before* we insert a send 248 // right present to ensure that CF does not establish 249 // its dead name notification. 250 // 251 newSession->serverPort = _SC_CFMachPortCreateWithPort("SCDynamicStore/session", 252 mp, 253 configdCallback, 254 &context); 255 256 if (n > 0) { 257 // insert send right that will be moved to the client 258 kr = mach_port_insert_right(mach_task_self(), 259 mp, 260 mp, 261 MACH_MSG_TYPE_MAKE_SEND); 262 if (kr != KERN_SUCCESS) { 263 /* 264 * We can't insert a send right into our own port! This should 265 * only happen if someone stomped on OUR port (so let's leave 266 * the port alone). 267 */ 268 SCLog(TRUE, LOG_ERR, CFSTR("addSession mach_port_insert_right(): %s"), mach_error_string(kr)); 269 270 free(newSession); 271 return NULL; 272 } 273 } 274 275 sessions[n] = newSession; 276 sessions[n]->key = mp; 277// sessions[n]->serverRunLoopSource = NULL; 278// sessions[n]->store = NULL; 279 sessions[n]->callerEUID = 1; /* not "root" */ 280 sessions[n]->callerRootAccess = UNKNOWN; 281#if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080/*FIXME*/) 282 sessions[n]->callerWriteEntitlement = kCFNull; /* UNKNOWN */ 283#endif // TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080/*FIXME*/) 284 285 return newSession; 286} 287 288 289__private_extern__ 290void 291cleanupSession(mach_port_t server) 292{ 293 int i; 294 295 for (i = 1; i <= lastSession; i++) { 296 CFStringRef sessionKey; 297 serverSessionRef thisSession = sessions[i]; 298 299 if (thisSession == NULL) { 300 /* found an empty slot, skip it */ 301 continue; 302 } 303 304 if (thisSession->key == server) { 305 /* 306 * session entry still exists. 307 */ 308 309 if (_configd_trace) { 310 SCTrace(TRUE, _configd_trace, CFSTR("cleanup : %5d\n"), server); 311 } 312 313 /* 314 * Close any open connections including cancelling any outstanding 315 * notification requests and releasing any locks. 316 */ 317 __MACH_PORT_DEBUG(TRUE, "*** cleanupSession", server); 318 (void) __SCDynamicStoreClose(&thisSession->store); 319 __MACH_PORT_DEBUG(TRUE, "*** cleanupSession (after __SCDynamicStoreClose)", server); 320 321 /* 322 * Our send right has already been removed. Remove our receive right. 323 */ 324#ifdef HAVE_MACHPORT_GUARDS 325 (void) mach_port_destruct(mach_task_self(), server, 0, thisSession); 326#else // HAVE_MACHPORT_GUARDS 327 (void) mach_port_mod_refs(mach_task_self(), server, MACH_PORT_RIGHT_RECEIVE, -1); 328#endif // HAVE_MACHPORT_GUARDS 329 330#if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080/*FIXME*/) 331 /* 332 * release any entitlement info 333 */ 334 if ((thisSession->callerWriteEntitlement != NULL) && 335 (thisSession->callerWriteEntitlement != kCFNull)) { 336 CFRelease(thisSession->callerWriteEntitlement); 337 } 338#endif // TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080/*FIXME*/) 339 340 /* 341 * We don't need any remaining information in the 342 * sessionData dictionary, remove it. 343 */ 344 sessionKey = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), server); 345 CFDictionaryRemoveValue(sessionData, sessionKey); 346 CFRelease(sessionKey); 347 348 /* 349 * get rid of the per-session structure. 350 */ 351 free(thisSession); 352 sessions[i] = NULL; 353 354 if (i == lastSession) { 355 /* we are removing the last session, update last used slot */ 356 while (--lastSession > 0) { 357 if (sessions[lastSession] != NULL) { 358 break; 359 } 360 } 361 } 362 363 return; 364 } 365 } 366 367 SCLog(TRUE, LOG_ERR, CFSTR("MACH_NOTIFY_NO_SENDERS w/no session, port = %d"), server); 368 __MACH_PORT_DEBUG(TRUE, "*** cleanupSession w/no session", server); 369 return; 370} 371 372 373__private_extern__ 374void 375listSessions(FILE *f) 376{ 377 int i; 378 379 SCPrint(TRUE, f, CFSTR("Current sessions :\n")); 380 for (i = 0; i <= lastSession; i++) { 381 serverSessionRef thisSession = sessions[i]; 382 383 if (thisSession == NULL) { 384 continue; 385 } 386 387 SCPrint(TRUE, f, CFSTR("\t%d : port = 0x%x"), i, thisSession->key); 388 389 if (thisSession->store != NULL) { 390 SCDynamicStorePrivateRef storePrivate = (SCDynamicStorePrivateRef)thisSession->store; 391 392 if (storePrivate->notifySignalTask != TASK_NULL) { 393 SCPrint(TRUE, f, CFSTR(", task = %d"), storePrivate->notifySignalTask); 394 } 395 } 396 397 if (sessionData != NULL) { 398 CFDictionaryRef info; 399 CFStringRef key; 400 401 key = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), thisSession->key); 402 info = CFDictionaryGetValue(sessionData, key); 403 CFRelease(key); 404 if (info != NULL) { 405 CFStringRef name; 406 407 name = CFDictionaryGetValue(info, kSCDName); 408 if (name != NULL) { 409 SCPrint(TRUE, f, CFSTR(", name = %@"), name); 410 } 411 } 412 } 413 414 if (thisSession->serverPort != NULL) { 415 SCPrint(TRUE, f, CFSTR("\n\t\t%@"), thisSession->serverPort); 416 } 417 418 if (thisSession->serverRunLoopSource != NULL) { 419 SCPrint(TRUE, f, CFSTR("\n\t\t%@"), thisSession->serverRunLoopSource); 420 } 421 422 SCPrint(TRUE, f, CFSTR("\n")); 423 } 424 425 SCPrint(TRUE, f, CFSTR("\n")); 426 return; 427} 428 429 430#if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080/*FIXME*/) 431 432#include <Security/Security.h> 433#include <Security/SecTask.h> 434 435static CFStringRef 436sessionName(serverSessionRef session) 437{ 438 CFDictionaryRef info; 439 CFStringRef name = NULL; 440 CFStringRef sessionKey; 441 442 sessionKey = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), session->key); 443 info = CFDictionaryGetValue(sessionData, sessionKey); 444 CFRelease(sessionKey); 445 446 if (info != NULL) { 447 name = CFDictionaryGetValue(info, kSCDName); 448 } 449 450 return (name != NULL) ? name : CFSTR("???"); 451} 452 453static CFTypeRef 454copyEntitlement(serverSessionRef session, CFStringRef entitlement) 455{ 456 SecTaskRef task; 457 CFTypeRef value = NULL; 458 459 // Create the security task from the audit token 460 task = SecTaskCreateWithAuditToken(NULL, session->auditToken); 461 if (task != NULL) { 462 CFErrorRef error = NULL; 463 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); 469 470 if (!CFEqual(domain, kCFErrorDomainMach) || 471 ((code != kIOReturnInvalid) && (code != kIOReturnNotFound))) { 472 // if unexpected error 473 SCLog(TRUE, LOG_ERR, 474 CFSTR("SecTaskCopyValueForEntitlement(,\"%@\",) failed, error = %@ : %@"), 475 entitlement, 476 error, 477 sessionName(session)); 478 } 479 CFRelease(error); 480 } 481 482 CFRelease(task); 483 } else { 484 SCLog(TRUE, LOG_ERR, 485 CFSTR("SecTaskCreateWithAuditToken() failed: %@"), 486 sessionName(session)); 487 } 488 489 return value; 490} 491 492#endif // TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080/*FIXME*/) 493 494 495static pid_t 496sessionPid(serverSessionRef session) 497{ 498 pid_t caller_pid; 499 500#if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE 501 caller_pid = audit_token_to_pid(session->auditToken); 502#else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE 503 audit_token_to_au32(session->auditToken, 504 NULL, // auidp 505 NULL, // euid 506 NULL, // egid 507 NULL, // ruid 508 NULL, // rgid 509 &caller_pid, // pid 510 NULL, // asid 511 NULL); // tid 512#endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE 513 514 return caller_pid; 515} 516 517 518__private_extern__ 519Boolean 520hasRootAccess(serverSessionRef session) 521{ 522#if !TARGET_IPHONE_SIMULATOR 523 524 if (session->callerRootAccess == UNKNOWN) { 525#if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE 526 session->callerEUID = audit_token_to_euid(session->auditToken); 527#else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE 528 audit_token_to_au32(session->auditToken, 529 NULL, // auidp 530 &session->callerEUID, // euid 531 NULL, // egid 532 NULL, // ruid 533 NULL, // rgid 534 NULL, // pid 535 NULL, // asid 536 NULL); // tid 537#endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE 538 session->callerRootAccess = (session->callerEUID == 0) ? YES : NO; 539 } 540 541 return (session->callerRootAccess == YES) ? TRUE : FALSE; 542 543#else // !TARGET_IPHONE_SIMULATOR 544 545 /* 546 * assume that all processes interacting with 547 * the iOS Simulator "configd" are OK. 548 */ 549 return TRUE; 550 551#endif // !TARGET_IPHONE_SIMULATOR 552} 553 554 555__private_extern__ 556Boolean 557hasWriteAccess(serverSessionRef session, CFStringRef key) 558{ 559 Boolean isSetup; 560 561 // need to special case writing "Setup:" keys 562 isSetup = CFStringHasPrefix(key, kSCDynamicStoreDomainSetup); 563 564 if (hasRootAccess(session)) { 565 pid_t pid; 566 567 // grant write access to eUID==0 processes 568 569 pid = sessionPid(session); 570 if (isSetup && (pid != getpid())) { 571 /* 572 * WAIT!!! 573 * 574 * This is NOT configd (or a plugin) trying to 575 * write to an SCDynamicStore "Setup:" key. In 576 * general, this is unwise and we should at the 577 * very least complain. 578 */ 579 SCLog(TRUE, LOG_ERR, 580 CFSTR("*** Non-configd process (pid=%d) attempting to modify \"%@\" ***"), 581 pid, 582 key); 583 } 584 585 return TRUE; 586 } 587 588 if (isSetup) { 589 /* 590 * STOP!!! 591 * 592 * This is a non-root process trying to write to 593 * an SCDynamicStore "Setup:" key. This is not 594 * something we should ever allow (regardless of 595 * any entitlements). 596 */ 597 SCLog(TRUE, LOG_ERR, 598 CFSTR("*** Non-root process (pid=%d) attempting to modify \"%@\" ***"), 599 sessionPid(session), 600 key); 601 602 //return FALSE; // return FALSE when rdar://9811832 has beed fixed 603 } 604 605#if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080/*FIXME*/) 606 if (session->callerWriteEntitlement == kCFNull) { 607 session->callerWriteEntitlement = copyEntitlement(session, 608 kSCWriteEntitlementName); 609 } 610 611 if (session->callerWriteEntitlement == NULL) { 612 return FALSE; 613 } 614 615 if (isA_CFBoolean(session->callerWriteEntitlement) && 616 CFBooleanGetValue(session->callerWriteEntitlement)) { 617 // grant write access to "entitled" processes 618 return TRUE; 619 } 620 621 if (isA_CFDictionary(session->callerWriteEntitlement)) { 622 CFArrayRef keys; 623 CFArrayRef patterns; 624 625 keys = CFDictionaryGetValue(session->callerWriteEntitlement, CFSTR("keys")); 626 if (isA_CFArray(keys)) { 627 if (CFArrayContainsValue(keys, 628 CFRangeMake(0, CFArrayGetCount(keys)), 629 key)) { 630 // if key matches one of the entitlement "keys", grant 631 // write access 632 return TRUE; 633 } 634 } 635 636 patterns = CFDictionaryGetValue(session->callerWriteEntitlement, CFSTR("patterns")); 637 if (isA_CFArray(patterns)) { 638 CFIndex i; 639 CFIndex n = CFArrayGetCount(patterns); 640 641 for (i = 0; i < n; i++) { 642 CFStringRef pattern; 643 644 pattern = CFArrayGetValueAtIndex(patterns, i); 645 if (isA_CFString(pattern)) { 646 if (patternKeyMatches(pattern, key)) { 647 // if key matches one of the entitlement 648 // "patterns", grant write access 649 return TRUE; 650 } 651 } 652 } 653 } 654 } 655#endif // TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080/*FIXME*/) 656 657 return FALSE; 658} 659 660 661__private_extern__ 662Boolean 663hasPathAccess(serverSessionRef session, const char *path) 664{ 665 pid_t pid; 666 char realPath[PATH_MAX]; 667 668 if (realpath(path, realPath) == NULL) { 669 SCLog(TRUE, LOG_DEBUG, CFSTR("hasPathAccess realpath() failed: %s"), strerror(errno)); 670 return FALSE; 671 } 672 673#if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE 674 pid = audit_token_to_pid(session->auditToken); 675#else // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE 676 audit_token_to_au32(session->auditToken, 677 NULL, // auidp 678 NULL, // euid 679 NULL, // egid 680 NULL, // ruid 681 NULL, // rgid 682 &pid, // pid 683 NULL, // asid 684 NULL); // tid 685#endif // (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) && !TARGET_OS_IPHONE 686 if (sandbox_check(pid, // pid 687 "file-write-data", // operation 688 SANDBOX_FILTER_PATH | SANDBOX_CHECK_NO_REPORT, // sandbox_filter_type 689 realPath) > 0) { // ... 690 SCLog(TRUE, LOG_DEBUG, CFSTR("hasPathAccess sandbox access denied: %s"), strerror(errno)); 691 return FALSE; 692 } 693 694 return TRUE; 695} 696