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