2 * Copyright (c) 2006, 2007, 2009-2013 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@
24 #include <mach/mach.h>
25 #include <mach/mach_error.h>
26 #include <servers/bootstrap.h>
27 #include <bootstrap_priv.h>
32 #include <mach-o/dyld_priv.h>
33 #include <membership.h>
36 #include <sys/types.h>
38 #include <sys/param.h>
39 #include <uuid/uuid.h>
41 #include <libkern/OSByteOrder.h>
42 #include <TargetConditionals.h>
44 #include <xpc/private.h>
46 #include <CrashReporterClient.h>
47 #endif /* !TARGET_OS_IPHONE */
49 #include "dirhelper.h"
50 #include "dirhelper_priv.h"
54 #define MUTEX_LOCK(x) pthread_mutex_lock(x)
55 #define MUTEX_UNLOCK(x) pthread_mutex_unlock(x)
57 // Use 5 bits per character, to avoid uppercase and shell magic characters
59 #define ENCODEDSIZE ((8 * UUID_UID_SIZE + ENCODEBITS - 1) / ENCODEBITS)
60 #define MASK(x) ((1 << (x)) - 1)
61 #define UUID_UID_SIZE (sizeof(uuid_t) + sizeof(uid_t))
63 static const mode_t modes
[] = {
69 static const char *subdirs
[] = {
75 static pthread_once_t userdir_control
= PTHREAD_ONCE_INIT
;
76 static char *userdir
= NULL
;
78 // lower case letter (minus vowels), plus numbers and _, making
80 static const char encode
[] = "0123456789_bcdfghjklmnpqrstvwxyz";
84 #define setcrashlogmessage(fmt, ...) /* nothing */
86 #else /* !TARGET_OS_IPHONE */
89 encode_uuid_uid(const uuid_t uuid
, uid_t uid
, char *str
)
91 unsigned char buf
[UUID_UID_SIZE
+ 1];
92 unsigned char *bp
= buf
;
96 memcpy(bp
, uuid
, sizeof(uuid_t
));
97 uid
= OSSwapHostToBigInt32(uid
);
98 memcpy(bp
+ sizeof(uuid_t
), &uid
, sizeof(uid_t
));
99 bp
[UUID_UID_SIZE
] = 0; // this ensures the last encoded byte will have trailing zeros
100 for(i
= 0; i
< ENCODEDSIZE
; i
++) {
101 // 5 bits has 8 states
105 *str
++ = encode
[n
>> 3];
108 n
= ((n
& MASK(3)) << 8) | *bp
++;
109 *str
++ = encode
[n
>> 6];
113 *str
++ = encode
[n
>> 1];
116 n
= ((n
& MASK(1)) << 8) | *bp
++;
117 *str
++ = encode
[n
>> 4];
120 n
= ((n
& MASK(4)) << 8) | *bp
++;
121 *str
++ = encode
[n
>> 7];
125 *str
++ = encode
[n
>> 2];
128 n
= ((n
& MASK(2)) << 8) | *bp
++;
129 *str
++ = encode
[n
>> 5];
132 *str
++ = encode
[n
& MASK(5)];
140 _setcrashlogmessage(const char *fmt
, ...) __attribute__((__format__(__printf__
,1,2)))
147 res
= vasprintf(&mess
, fmt
, ap
);
150 mess
= (char *)fmt
; /* the format string is better than nothing */
151 CRSetCrashLogMessage(mess
);
154 #define setcrashlogmessage(fmt, ...) _setcrashlogmessage("%s: %u: " fmt, __func__, __LINE__, ##__VA_ARGS__)
156 #endif /* !TARGET_OS_IPHONE */
159 __user_local_dirname(uid_t uid
, dirhelper_which_t which
, char *path
, size_t pathlen
)
165 char str
[ENCODEDSIZE
+ 1];
169 if((int)which
< 0 || which
> DIRHELPER_USER_LOCAL_LAST
) {
170 setcrashlogmessage("Out of range: which=%d", (int)which
);
176 /* We only support DIRHELPER_USER_LOCAL_TEMP on embedded.
177 * This interface really doesn't map from OSX to embedded,
178 * and clients of this interface will need to adapt when
179 * porting their applications to embedded.
180 * See: <rdar://problem/7515613>
182 if(which
== DIRHELPER_USER_LOCAL_TEMP
) {
183 tmpdir
= getenv("TMPDIR");
185 setcrashlogmessage("TMPDIR not set");
189 res
= snprintf(path
, pathlen
, "%s", tmpdir
);
191 setcrashlogmessage("Only DIRHELPER_USER_LOCAL_TEMP is supported: which=%d", (int)which
);
196 res
= mbr_uid_to_uuid(uid
, uuid
);
198 setcrashlogmessage("mbr_uid_to_uuid returned %d, uid=%d", res
, (int)uid
);
204 // We partition the namespace so that we don't end up with too
205 // many users in a single directory. With 1024 buckets, we
206 // could scale to 1,000,000 users while keeping the average
207 // number of files in a single directory around 1000
209 encode_uuid_uid(uuid
, uid
, str
);
210 res
= snprintf(path
, pathlen
,
212 VAR_FOLDERS_PATH
, BUCKETLEN
, str
, str
+ BUCKETLEN
, subdirs
[which
]);
215 setcrashlogmessage("snprintf: buffer too small: res=%d >= pathlen=%zu", res
, pathlen
);
217 return NULL
; /* buffer too small */
223 __user_local_mkdir_p(char *path
)
228 next
= path
+ strlen(VAR_FOLDERS_PATH
);
229 while ((next
= strchr(next
, '/')) != NULL
) {
230 *next
= 0; // temporarily truncate
231 res
= mkdir(path
, 0755);
232 if (res
!= 0 && errno
!= EEXIST
) {
233 setcrashlogmessage("mkdir: path=%s mode=0755: %s", path
, strerror(errno
));
236 *next
++ = '/'; // restore the slash and increment
241 static void userdir_allocate(void)
243 userdir
= calloc(PATH_MAX
, sizeof(char));
247 * 9407258: Invalidate the dirhelper cache (userdir) of the child after fork.
248 * There is a rare case when launchd will have userdir set, and child process
249 * will sometimes inherit this cached value.
251 __private_extern__
void _dirhelper_fork_child(void);
252 __private_extern__
void
253 _dirhelper_fork_child(void)
255 if(userdir
) *userdir
= 0;
258 __private_extern__
char *_dirhelper(dirhelper_which_t which
, char *path
, size_t pathlen
);
259 __private_extern__
char *
260 _dirhelper(dirhelper_which_t which
, char *path
, size_t pathlen
)
262 static pthread_mutex_t lock
= PTHREAD_MUTEX_INITIALIZER
;
265 if((int)which
< 0 || which
> DIRHELPER_USER_LOCAL_LAST
) {
266 setcrashlogmessage("Out of range: which=%d", (int)which
);
271 if (pthread_once(&userdir_control
, userdir_allocate
)
273 setcrashlogmessage("Out of memory in userdir_allocate");
282 if(__user_local_dirname(geteuid(), DIRHELPER_USER_LOCAL
, userdir
, PATH_MAX
) == NULL
) {
287 * All dirhelper directories are now at the same level, so
288 * we need to remove the DIRHELPER_TOP_STR suffix to get the
291 userdir
[strlen(userdir
) - (sizeof(DIRHELPER_TOP_STR
) - 1)] = 0;
293 * check if userdir exists, and if not, either do the work
294 * ourself if we are root, or call
295 * __dirhelper_create_user_local to create it (we have to
296 * check again afterwards).
298 if(stat(userdir
, &sb
) < 0) {
301 if(errno
!= ENOENT
) { /* some unknown error */
302 setcrashlogmessage("stat: %s: %s", userdir
, strerror(errno
));
308 * If we are root, lets do what dirhelper does for us.
310 if (geteuid() == 0) {
311 if (__user_local_mkdir_p(userdir
) == NULL
) {
318 #if TARGET_IPHONE_SIMULATOR
319 res
= bootstrap_look_up(bootstrap_port
, DIRHELPER_BOOTSTRAP_NAME
, &mp
);
321 res
= bootstrap_look_up2(bootstrap_port
, DIRHELPER_BOOTSTRAP_NAME
, &mp
, 0, BOOTSTRAP_PRIVILEGED_SERVER
);
324 if(res
!= KERN_SUCCESS
) {
325 setcrashlogmessage("bootstrap_look_up returned %d", res
);
328 mach_port_deallocate(mach_task_self(), mp
);
332 if((res
= __dirhelper_create_user_local(mp
)) != KERN_SUCCESS
) {
333 setcrashlogmessage("__dirhelper_create_user_local returned %d", res
);
337 /* double check that the directory really got created */
338 if(stat(userdir
, &sb
) < 0) {
339 setcrashlogmessage("stat: %s: %s", userdir
, strerror(errno
));
342 mach_port_deallocate(mach_task_self(), mp
);
349 if(pathlen
< strlen(userdir
) + strlen(subdirs
[which
]) + 1) {
350 setcrashlogmessage("buffer too small: pathlen=%zu userdir=%s subdirs[%d]=%s", pathlen
, userdir
, which
, subdirs
[which
]);
352 return NULL
; /* buffer too small */
354 strcpy(path
, userdir
);
355 strcat(path
, subdirs
[which
]);
358 * create the subdir with the appropriate permissions if it doesn't already
359 * exist. On OS X, if we're under App Sandbox, we rely on the subdir having
360 * been already created for us.
362 #if !TARGET_OS_IPHONE
363 if (!_xpc_runtime_is_app_sandboxed())
365 if(mkdir(path
, modes
[which
]) != 0 && errno
!= EEXIST
) {
366 setcrashlogmessage("mkdir: path=%s modes[%d]=0%o: %s", path
, which
, modes
[which
], strerror(errno
));
370 #if !TARGET_OS_IPHONE
371 char *userdir_suffix
= NULL
;
373 if (_xpc_runtime_is_app_sandboxed()) {
375 * if the subdir wasn't made for us, bail since we probably don't have
376 * permission to create it ourselves.
378 if(stat(path
, &sb
) < 0) {
379 setcrashlogmessage("stat: %s: %s", path
, strerror(errno
));
385 * sandboxed applications get per-application directories named
386 * after the container
388 userdir_suffix
= getenv(XPC_ENV_SANDBOX_CONTAINER_ID
);
389 if (!userdir_suffix
) {
390 setcrashlogmessage("XPC_ENV_SANDBOX_CONTAINER_ID not set");
394 } else if (!dyld_process_is_restricted()) {
395 userdir_suffix
= getenv(DIRHELPER_ENV_USER_DIR_SUFFIX
);
398 if (userdir_suffix
) {
400 * do not allow paths that contain path traversal dots.
402 const char *pos
= userdir_suffix
;
403 while ((pos
= strnstr(pos
, "..", strlen(pos
)))) {
404 if ((pos
== userdir_suffix
&& strlen(userdir_suffix
) == 2) || // string is ".." only
405 (pos
== userdir_suffix
&& strlen(userdir_suffix
) > 2 && userdir_suffix
[2] == '/') || // prefixed with "../"
406 ((pos
- userdir_suffix
== strlen(userdir_suffix
) - 2) && pos
[-1] == '/') || // suffixed with "/.."
407 (pos
[-1] == '/' && pos
[2] == '/')) // middle of string with '/../'
409 setcrashlogmessage("illegal path traversal (..) pattern found in DIRHELPER_USER_DIR_SUFFIX");
417 * suffix (usually container ID) doesn't end in a slash, so +2 is for slash and \0
419 if (pathlen
< strlen(path
) + strlen(userdir_suffix
) + 2) {
420 setcrashlogmessage("buffer too small: pathlen=%zu path=%s userdir_suffix=%s", pathlen
, path
, userdir_suffix
);
422 return NULL
; /* buffer too small */
425 strcat(path
, userdir_suffix
);
429 * create suffix subdirectory with the appropriate permissions
430 * if it doesn't already exist.
432 if (mkdir(path
, modes
[which
]) != 0 && errno
!= EEXIST
) {
433 setcrashlogmessage("mkdir: path=%s modes[%d]=0%o: %s", path
, which
, modes
[which
], strerror(errno
));
438 * update TMPDIR if necessary
440 if (which
== DIRHELPER_USER_LOCAL_TEMP
) {
441 char *tmpdir
= getenv("TMPDIR");
442 if (!tmpdir
|| strncmp(tmpdir
, path
, strlen(path
)))
443 setenv("TMPDIR", path
, 1);