1/* 2 * Copyright (c) 2011-2014 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#include <xpc/private.h> 25#include <syslog.h> 26#include <sys/param.h> 27#include <sandbox.h> 28#include <dlfcn.h> 29#include <sysexits.h> 30#include <Security/Security.h> 31#include <stdlib.h> 32#include <libgen.h> 33#include <string.h> 34#include <CommonCrypto/CommonDigest.h> 35#include <CoreFoundation/CoreFoundation.h> 36#include <sys/types.h> 37#include <pwd.h> 38 39struct connection_info { 40 xpc_connection_t peer; 41 int processed; 42 int done; 43}; 44 45typedef typeof(SecKeychainCopyDomainSearchList) SecKeychainCopyDomainSearchListType; 46SecKeychainCopyDomainSearchListType *SecKeychainCopyDomainSearchListFunctionPointer = NULL; 47typedef typeof(SecKeychainGetPath) SecKeychainGetPathType; 48SecKeychainGetPathType *SecKeychainGetPathFunctionPointer = NULL; 49 50// prior to 8723022 we have to do our own idle timeout work 51#ifndef XPC_HANDLES_IDLE_TIMEOUT 52int current_connections = 0; 53// Number of seconds to sit with no clients 54#define IDLE_WAIT_TIME 30 55#endif 56 57xpc_object_t keychain_prefs_path = NULL; 58xpc_object_t home = NULL; 59 60extern xpc_object_t 61xpc_create_reply_with_format(xpc_object_t original, const char * format, ...); 62 63static 64xpc_object_t create_keychain_search_list_for(xpc_connection_t peer, SecPreferencesDomain domain) 65{ 66 CFArrayRef keychains = NULL; 67 pid_t peer_pid = xpc_connection_get_pid(peer); 68 OSStatus status = SecKeychainCopyDomainSearchListFunctionPointer(domain, &keychains); 69 if (errSecSuccess != status) { 70 syslog(LOG_ERR, "Unable to get keychain search list (domain=%d) on behalf of %d, status=0x%lx", domain, peer_pid, (unsigned long)status); 71 return NULL; 72 } 73 74 xpc_object_t paths = xpc_array_create(NULL, 0); 75 CFIndex n_keychains = CFArrayGetCount(keychains); 76 CFIndex i; 77 for(i = 0; i < n_keychains; i++) { 78 char path[MAXPATHLEN]; 79 80 SecKeychainRef keychain = (SecKeychainRef)CFArrayGetValueAtIndex(keychains, i); 81 UInt32 length = MAXPATHLEN; 82 OSStatus status = SecKeychainGetPathFunctionPointer(keychain, &length, path); 83 if (errSecSuccess != status) { 84 syslog(LOG_ERR, "Unable to get path for keychain#%ld of %ld on behalf of %d, status=0x%lx", i, n_keychains, peer_pid, (unsigned long)status); 85 continue; 86 } 87 xpc_object_t path_as_xpc_string = xpc_string_create(path); 88 xpc_array_append_value(paths, path_as_xpc_string); 89 xpc_release(path_as_xpc_string); 90 } 91 CFRelease(keychains); 92 return paths; 93} 94 95static 96bool keychain_domain_needs_writes(const char *domain_name) 97{ 98 return (0 == strcmp("kSecPreferencesDomainUser", domain_name) || 0 == strcmp("kSecPreferencesDomainDynamic", domain_name)); 99} 100 101static 102void _set_keychain_search_lists_for_domain(xpc_connection_t peer, xpc_object_t all_domains, char *domain_name, SecPreferencesDomain domain_enum) 103{ 104 xpc_object_t keychains_for_domain = create_keychain_search_list_for(peer, domain_enum); 105 if (keychains_for_domain) { 106 xpc_dictionary_set_value(all_domains, domain_name, keychains_for_domain); 107 xpc_release(keychains_for_domain); 108 } else { 109 syslog(LOG_ERR, "Can't discover keychain paths for domain %s on behalf of %d", domain_name, xpc_connection_get_pid(peer)); 110 } 111} 112 113#define SET_KEYCHAIN_SEARCH_LISTS_FOR_DOMAIN(peer, all_domains, domain) _set_keychain_search_lists_for_domain(peer, all_domains, #domain, domain); 114 115static 116xpc_object_t create_keychain_search_lists(xpc_connection_t peer) 117{ 118 xpc_object_t all_domains = xpc_dictionary_create(NULL, NULL, 0); 119 120 SET_KEYCHAIN_SEARCH_LISTS_FOR_DOMAIN(peer, all_domains, kSecPreferencesDomainUser); 121 SET_KEYCHAIN_SEARCH_LISTS_FOR_DOMAIN(peer, all_domains, kSecPreferencesDomainSystem); 122 SET_KEYCHAIN_SEARCH_LISTS_FOR_DOMAIN(peer, all_domains, kSecPreferencesDomainCommon); 123 SET_KEYCHAIN_SEARCH_LISTS_FOR_DOMAIN(peer, all_domains, kSecPreferencesDomainDynamic); 124 125 return all_domains; 126} 127 128 129static 130xpc_object_t create_keychain_and_lock_paths(xpc_connection_t peer, xpc_object_t keychain_path_dict) 131{ 132 pid_t peer_pid = xpc_connection_get_pid(peer); 133 char *assembly_queue_label = NULL; 134 asprintf(&assembly_queue_label, "assembly-for-%d", peer_pid); 135 if (!assembly_queue_label) { 136 syslog(LOG_ERR, "Unable to create assembly queue label for %d", peer_pid); 137 return NULL; 138 } 139 dispatch_queue_t assembly_queue = dispatch_queue_create(assembly_queue_label, 0); 140 free(assembly_queue_label); 141 if (!assembly_queue) { 142 syslog(LOG_ERR, "Unable to create assembly queue for %d", peer_pid); 143 return NULL; 144 } 145 xpc_object_t return_paths_dict = xpc_dictionary_create(NULL, NULL, 0); 146 147 xpc_dictionary_apply(keychain_path_dict, ^(const char *keychain_domain, xpc_object_t keychain_path_array) { 148 xpc_object_t return_paths_array = xpc_array_create(NULL, 0); 149 dispatch_apply(xpc_array_get_count(keychain_path_array), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t i) { 150 xpc_object_t path_as_xpc_string = xpc_array_get_value(keychain_path_array, i); 151 dispatch_sync(assembly_queue, ^{ 152 xpc_array_append_value(return_paths_array, path_as_xpc_string); 153 }); 154 155 if (!keychain_domain_needs_writes(keychain_domain)) { 156 // lock files are only to prevent write-write errors, readers don't hold locks, so they don't need the lock files 157 return; 158 } 159 160 // figure out the base and dir 161 const char* path = xpc_array_get_string(keychain_path_array, i); 162 char* dir; 163 char* base; 164 165 char buffer[PATH_MAX]; 166 strcpy(buffer, path); 167 168 if (path != NULL) { 169 ptrdiff_t i = strlen(buffer) - 1; 170 while (i >= 0 && buffer[i] != '/') { 171 i -= 1; 172 } 173 174 if (i >= 0) { 175 // NULL terminate the dir 176 buffer[i] = 0; 177 dir = buffer; 178 base = buffer + i + 1; 179 } else { 180 dir = NULL; 181 base = buffer; 182 } 183 } 184 185 if (!(path && dir && base)) { 186 syslog(LOG_ERR, "Can't get dir or base (likely out of memory) for %s", xpc_array_get_string(keychain_path_array, i)); 187 return; 188 } 189 190 // "network style" lock files 191 path_as_xpc_string = xpc_string_create_with_format("%s/lck~%s", dir, base); 192 dispatch_sync(assembly_queue, ^{ 193 xpc_array_append_value(return_paths_array, path_as_xpc_string); 194 }); 195 xpc_release(path_as_xpc_string); 196 197 CC_SHA1_CTX sha1Context; 198 CC_SHA1_Init(&sha1Context); 199 CC_SHA1_Update(&sha1Context, base, (CC_LONG)strlen(base)); 200 201 unsigned char sha1_result_bytes[CC_SHA1_DIGEST_LENGTH]; 202 203 CC_SHA1_Final(sha1_result_bytes, &sha1Context); 204 205 path_as_xpc_string = xpc_string_create_with_format("%s/.fl%02X%02X%02X%02X", dir, sha1_result_bytes[0], sha1_result_bytes[1], sha1_result_bytes[2], sha1_result_bytes[3]); 206 dispatch_sync(assembly_queue, ^{ 207 xpc_array_append_value(return_paths_array, path_as_xpc_string); 208 }); 209 }); 210 xpc_dictionary_set_value(return_paths_dict, keychain_domain, return_paths_array); 211 xpc_release(return_paths_array); 212 return (bool)true; 213 }); 214 215 dispatch_release(assembly_queue); 216 return return_paths_dict; 217} 218 219static 220xpc_object_t create_one_sandbox_extension(xpc_object_t path, uint64_t extension_flags) 221{ 222 char *sandbox_extension = NULL; 223 int status = sandbox_issue_fs_extension(xpc_string_get_string_ptr(path), extension_flags, &sandbox_extension); 224 if (0 == status && sandbox_extension) { 225 xpc_object_t sandbox_extension_as_xpc_string = xpc_string_create(sandbox_extension); 226 free(sandbox_extension); 227 return sandbox_extension_as_xpc_string; 228 } else { 229 syslog(LOG_ERR, "Can't get sandbox fs extension for %s, status=%d errno=%m ext=%s", xpc_string_get_string_ptr(path), status, sandbox_extension); 230 } 231 return NULL; 232} 233 234static 235xpc_object_t create_all_sandbox_extensions(xpc_object_t path_dict) 236{ 237 xpc_object_t extensions = xpc_array_create(NULL, 0); 238 239 xpc_object_t sandbox_extension = create_one_sandbox_extension(keychain_prefs_path, FS_EXT_FOR_PATH|FS_EXT_READ); 240 if (sandbox_extension) { 241 xpc_array_append_value(extensions, sandbox_extension); 242 xpc_release(sandbox_extension); 243 } 244 245 xpc_dictionary_apply(path_dict, ^(const char *keychain_domain, xpc_object_t path_array) { 246 uint64_t extension_flags = FS_EXT_FOR_PATH|FS_EXT_READ; 247 if (keychain_domain_needs_writes(keychain_domain)) { 248 extension_flags = FS_EXT_FOR_PATH|FS_EXT_READ|FS_EXT_WRITE; 249 } 250 xpc_array_apply(path_array, ^(size_t index, xpc_object_t path) { 251 xpc_object_t sandbox_extension = create_one_sandbox_extension(path, extension_flags); 252 if (sandbox_extension) { 253 xpc_array_append_value(extensions, sandbox_extension); 254 xpc_release(sandbox_extension); 255 } 256 return (bool)true; 257 }); 258 return (bool)true; 259 }); 260 261 return extensions; 262} 263 264static 265void handle_request_event(struct connection_info *info, xpc_object_t event) 266{ 267 xpc_connection_t peer = xpc_dictionary_get_connection(event); 268 xpc_type_t xtype = xpc_get_type(event); 269 if (info->done) { 270 syslog(LOG_ERR, "event %p while done", event); 271 return; 272 } 273 if (xtype == XPC_TYPE_ERROR) { 274 if (event == XPC_ERROR_TERMINATION_IMMINENT) { 275 // launchd would like us to die, but we have open transactions. When we finish with them xpc_service_main 276 // will exit for us, so there is nothing for us to do here. 277 return; 278 } 279 280 if (!info->done) { 281 info->done = true; 282 xpc_release(info->peer); 283#ifndef XPC_HANDLES_IDLE_TIMEOUT 284 if (0 == __sync_add_and_fetch(¤t_connections, -1)) { 285 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * IDLE_WAIT_TIME), dispatch_get_main_queue(), ^(void) { 286 if (0 == current_connections) { 287 exit(0); 288 } 289 }); 290 } 291#endif 292 } 293 if (peer == NULL && XPC_ERROR_CONNECTION_INVALID == event && 0 != info->processed) { 294 // this is a normal shutdown on a connection that has processed at least 295 // one request. Nothing intresting to log. 296 return; 297 } 298 syslog(LOG_ERR, "listener event error (connection %p): %s", peer, xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION)); 299 } else if (xtype == XPC_TYPE_DICTIONARY) { 300 const char *operation = xpc_dictionary_get_string(event, "op"); 301 if (operation && !strcmp(operation, "GrantKeychainPaths")) { 302 xpc_object_t keychain_paths = create_keychain_search_lists(peer); 303 xpc_object_t all_paths = create_keychain_and_lock_paths(peer, keychain_paths); 304 xpc_object_t sandbox_extensions = NULL; 305 if (all_paths) { 306 sandbox_extensions = create_all_sandbox_extensions(all_paths); 307 } 308 309 xpc_object_t reply = xpc_create_reply_with_format(event, "{keychain-paths: %value, all-paths: %value, extensions: %value, keychain-home: %value}", 310 keychain_paths, all_paths, sandbox_extensions, home); 311 xpc_connection_send_message(peer, reply); 312 xpc_release(reply); 313 if (keychain_paths) { 314 xpc_release(keychain_paths); 315 } 316 if (all_paths) { 317 xpc_release(all_paths); 318 } 319 if (sandbox_extensions) { 320 xpc_release(sandbox_extensions); 321 } 322 if (INT32_MAX != info->processed) { 323 info->processed++; 324 } 325 } else { 326 syslog(LOG_ERR, "Unknown op=%s request from pid %d", operation, xpc_connection_get_pid(peer)); 327 } 328 } else { 329 syslog(LOG_ERR, "Unhandled request event=%p type=%p", event, xtype); 330 } 331} 332 333static 334void finalize_connection(void *not_used) 335{ 336#ifdef XPC_HANDLES_IDLE_TIMEOUT 337 xpc_transaction_end(); 338#endif 339} 340 341static 342void handle_connection_event(const xpc_connection_t peer) 343{ 344#ifndef XPC_HANDLES_IDLE_TIMEOUT 345 __sync_add_and_fetch(¤t_connections, 1); 346#endif 347 __block struct connection_info info; 348 info.peer = peer; 349 info.processed = 0; 350 info.done = false; 351 352 xpc_connection_set_event_handler(peer, ^(xpc_object_t event) { 353 handle_request_event(&info, event); 354 }); 355 356 // unlike dispatch objects xpc objects don't need a context set in order to run a finalizer. (we use our finalizer to 357 // end the transaction we are about to begin...this keeps xpc from idle exiting us while we have a live connection) 358 xpc_connection_set_finalizer_f(peer, finalize_connection); 359#ifdef XPC_HANDLES_IDLE_TIMEOUT 360 xpc_transaction_begin(); 361#endif 362 363 // enable the peer connection to receive messages 364 xpc_connection_resume(peer); 365 xpc_retain(peer); 366} 367 368 369static const char* g_path_to_plist = "/Library/Preferences/com.apple.security.plist"; 370 371 372 373int main(int argc, const char *argv[]) 374{ 375 char *wait4debugger = getenv("WAIT4DEBUGGER"); 376 if (wait4debugger && !strcasecmp("YES", wait4debugger)) { 377 syslog(LOG_ERR, "Waiting for debugger"); 378 kill(getpid(), SIGSTOP); 379 } 380 381 // get the home directory 382 const char* home_dir = getenv("HOME"); 383 384 if (home_dir == NULL || strlen(home_dir) == 0) { 385 struct passwd* pwd = getpwuid(getuid()); 386 home_dir = pwd->pw_dir; // look it up in directory services, sort of... 387 } 388 389 size_t home_dir_length = strlen(home_dir); 390 size_t path_to_plist_length = strlen(g_path_to_plist); 391 392 size_t total_length = home_dir_length + path_to_plist_length + 1; // compensate for terminating zero 393 if (total_length > PATH_MAX) { 394 // someone is spoofing us, just exit 395 return -1; 396 } 397 398 // make storage for the real path 399 char buffer[total_length]; 400 strlcpy(buffer, home_dir, total_length); 401 strlcat(buffer, g_path_to_plist, total_length); 402 keychain_prefs_path = xpc_string_create(buffer); 403 home = xpc_string_create(home_dir); 404 405 void *security_framework = dlopen("/System/Library/Frameworks/Security.framework/Security", RTLD_LAZY); 406 if (security_framework) { 407 SecKeychainCopyDomainSearchListFunctionPointer = dlsym(security_framework, "SecKeychainCopyDomainSearchList"); 408 if (!SecKeychainCopyDomainSearchListFunctionPointer) { 409 syslog(LOG_ERR, "Can't lookup SecKeychainCopyDomainSearchList in %p: %s", security_framework, dlerror()); 410 return EX_OSERR; 411 } 412 SecKeychainGetPathFunctionPointer = dlsym(security_framework, "SecKeychainGetPath"); 413 if (!SecKeychainGetPathFunctionPointer) { 414 syslog(LOG_ERR, "Can't lookup SecKeychainGetPath in %p: %s", security_framework, dlerror()); 415 return EX_OSERR; 416 } 417 } 418 419 xpc_main(handle_connection_event); 420 421 return EX_OSERR; 422} 423