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