]> git.saurik.com Git - apple/libc.git/blob - darwin/_dirhelper.c
bec1e1d10e7a65dfba47a664e95f75b4b80e1e76
[apple/libc.git] / darwin / _dirhelper.c
1 /*
2 * Copyright (c) 2006, 2007, 2009-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 #include <mach/mach.h>
25 #include <mach/mach_error.h>
26 #include <servers/bootstrap.h>
27 #include <bootstrap_priv.h>
28 #include <unistd.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <stdbool.h>
32 #include <mach-o/dyld_priv.h>
33 #include <membership.h>
34 #include <pthread.h>
35 #include <errno.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <sys/param.h>
39 #include <uuid/uuid.h>
40 #include <string.h>
41 #include <libkern/OSByteOrder.h>
42 #include <TargetConditionals.h>
43 #include <xpc/xpc.h>
44 #include <xpc/private.h>
45 #if !TARGET_OS_IPHONE
46 #include <CrashReporterClient.h>
47 #endif /* !TARGET_OS_IPHONE */
48
49 #include "dirhelper.h"
50 #include "dirhelper_priv.h"
51
52 #define BUCKETLEN 2
53
54 #define MUTEX_LOCK(x) pthread_mutex_lock(x)
55 #define MUTEX_UNLOCK(x) pthread_mutex_unlock(x)
56
57 // Use 5 bits per character, to avoid uppercase and shell magic characters
58 #define ENCODEBITS 5
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))
62
63 static const mode_t modes[] = {
64 0755, /* user */
65 0700, /* temp */
66 0700, /* cache */
67 };
68
69 static const char *subdirs[] = {
70 DIRHELPER_TOP_STR,
71 DIRHELPER_TEMP_STR,
72 DIRHELPER_CACHE_STR,
73 };
74
75 static pthread_once_t userdir_control = PTHREAD_ONCE_INIT;
76 static char *userdir = NULL;
77
78 // lower case letter (minus vowels), plus numbers and _, making
79 // 32 characters.
80 static const char encode[] = "0123456789_bcdfghjklmnpqrstvwxyz";
81
82 #if TARGET_OS_IPHONE
83
84 #define setcrashlogmessage(fmt, ...) /* nothing */
85
86 #else /* !TARGET_OS_IPHONE */
87
88 static void
89 encode_uuid_uid(const uuid_t uuid, uid_t uid, char *str)
90 {
91 unsigned char buf[UUID_UID_SIZE + 1];
92 unsigned char *bp = buf;
93 int i;
94 unsigned int n;
95
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
102 switch(i % 8) {
103 case 0:
104 n = *bp++;
105 *str++ = encode[n >> 3];
106 break;
107 case 1:
108 n = ((n & MASK(3)) << 8) | *bp++;
109 *str++ = encode[n >> 6];
110 break;
111 case 2:
112 n &= MASK(6);
113 *str++ = encode[n >> 1];
114 break;
115 case 3:
116 n = ((n & MASK(1)) << 8) | *bp++;
117 *str++ = encode[n >> 4];
118 break;
119 case 4:
120 n = ((n & MASK(4)) << 8) | *bp++;
121 *str++ = encode[n >> 7];
122 break;
123 case 5:
124 n &= MASK(7);
125 *str++ = encode[n >> 2];
126 break;
127 case 6:
128 n = ((n & MASK(2)) << 8) | *bp++;
129 *str++ = encode[n >> 5];
130 break;
131 case 7:
132 *str++ = encode[n & MASK(5)];
133 break;
134 }
135 }
136 *str = 0;
137 }
138
139 static void
140 _setcrashlogmessage(const char *fmt, ...) __attribute__((__format__(__printf__,1,2)))
141 {
142 char *mess = NULL;
143 int res;
144 va_list ap;
145
146 va_start(ap, fmt);
147 res = vasprintf(&mess, fmt, ap);
148 va_end(ap);
149 if (res < 0)
150 mess = (char *)fmt; /* the format string is better than nothing */
151 CRSetCrashLogMessage(mess);
152 }
153
154 #define setcrashlogmessage(fmt, ...) _setcrashlogmessage("%s: %u: " fmt, __func__, __LINE__, ##__VA_ARGS__)
155
156 #endif /* !TARGET_OS_IPHONE */
157
158 char *
159 __user_local_dirname(uid_t uid, dirhelper_which_t which, char *path, size_t pathlen)
160 {
161 #if TARGET_OS_IPHONE
162 char *tmpdir;
163 #else
164 uuid_t uuid;
165 char str[ENCODEDSIZE + 1];
166 #endif
167 int res;
168
169 if((int)which < 0 || which > DIRHELPER_USER_LOCAL_LAST) {
170 setcrashlogmessage("Out of range: which=%d", (int)which);
171 errno = EINVAL;
172 return NULL;
173 }
174
175 #if TARGET_OS_IPHONE
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>
181 */
182 if(which == DIRHELPER_USER_LOCAL_TEMP) {
183 tmpdir = getenv("TMPDIR");
184 if(!tmpdir) {
185 setcrashlogmessage("TMPDIR not set");
186 errno = EINVAL;
187 return NULL;
188 }
189 res = snprintf(path, pathlen, "%s", tmpdir);
190 } else {
191 setcrashlogmessage("Only DIRHELPER_USER_LOCAL_TEMP is supported: which=%d", (int)which);
192 errno = EINVAL;
193 return NULL;
194 }
195 #else
196 res = mbr_uid_to_uuid(uid, uuid);
197 if(res != 0) {
198 setcrashlogmessage("mbr_uid_to_uuid returned %d, uid=%d", res, (int)uid);
199 errno = res;
200 return NULL;
201 }
202
203 //
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
208 //
209 encode_uuid_uid(uuid, uid, str);
210 res = snprintf(path, pathlen,
211 "%s%.*s/%s/%s",
212 VAR_FOLDERS_PATH, BUCKETLEN, str, str + BUCKETLEN, subdirs[which]);
213 #endif
214 if(res >= pathlen) {
215 setcrashlogmessage("snprintf: buffer too small: res=%d >= pathlen=%zu", res, pathlen);
216 errno = EINVAL;
217 return NULL; /* buffer too small */
218 }
219 return path;
220 }
221
222 char *
223 __user_local_mkdir_p(char *path)
224 {
225 char *next;
226 int res;
227
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));
234 return NULL;
235 }
236 *next++ = '/'; // restore the slash and increment
237 }
238 return path;
239 }
240
241 static void userdir_allocate(void)
242 {
243 userdir = calloc(PATH_MAX, sizeof(char));
244 }
245
246 /*
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.
250 */
251 __private_extern__ void _dirhelper_fork_child(void);
252 __private_extern__ void
253 _dirhelper_fork_child(void)
254 {
255 if(userdir) *userdir = 0;
256 }
257
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)
261 {
262 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
263 struct stat sb;
264
265 if((int)which < 0 || which > DIRHELPER_USER_LOCAL_LAST) {
266 setcrashlogmessage("Out of range: which=%d", (int)which);
267 errno = EINVAL;
268 return NULL;
269 }
270
271 if (pthread_once(&userdir_control, userdir_allocate)
272 || !userdir) {
273 setcrashlogmessage("Out of memory in userdir_allocate");
274 errno = ENOMEM;
275 return NULL;
276 }
277
278 if(!*userdir) {
279 MUTEX_LOCK(&lock);
280 if (!*userdir) {
281
282 if(__user_local_dirname(geteuid(), DIRHELPER_USER_LOCAL, userdir, PATH_MAX) == NULL) {
283 MUTEX_UNLOCK(&lock);
284 return NULL;
285 }
286 /*
287 * All dirhelper directories are now at the same level, so
288 * we need to remove the DIRHELPER_TOP_STR suffix to get the
289 * parent directory.
290 */
291 userdir[strlen(userdir) - (sizeof(DIRHELPER_TOP_STR) - 1)] = 0;
292 /*
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).
297 */
298 if(stat(userdir, &sb) < 0) {
299 mach_port_t mp;
300
301 if(errno != ENOENT) { /* some unknown error */
302 setcrashlogmessage("stat: %s: %s", userdir, strerror(errno));
303 *userdir = 0;
304 MUTEX_UNLOCK(&lock);
305 return NULL;
306 }
307 /*
308 * If we are root, lets do what dirhelper does for us.
309 */
310 if (geteuid() == 0) {
311 if (__user_local_mkdir_p(userdir) == NULL) {
312 *userdir = 0;
313 MUTEX_UNLOCK(&lock);
314 return NULL;
315 }
316 } else {
317 kern_return_t res;
318 #if TARGET_IPHONE_SIMULATOR
319 res = bootstrap_look_up(bootstrap_port, DIRHELPER_BOOTSTRAP_NAME, &mp);
320 #else
321 res = bootstrap_look_up2(bootstrap_port, DIRHELPER_BOOTSTRAP_NAME, &mp, 0, BOOTSTRAP_PRIVILEGED_SERVER);
322 #endif
323
324 if(res != KERN_SUCCESS) {
325 setcrashlogmessage("bootstrap_look_up returned %d", res);
326 errno = EPERM;
327 server_error:
328 mach_port_deallocate(mach_task_self(), mp);
329 MUTEX_UNLOCK(&lock);
330 return NULL;
331 }
332 if((res = __dirhelper_create_user_local(mp)) != KERN_SUCCESS) {
333 setcrashlogmessage("__dirhelper_create_user_local returned %d", res);
334 errno = EPERM;
335 goto server_error;
336 }
337 /* double check that the directory really got created */
338 if(stat(userdir, &sb) < 0) {
339 setcrashlogmessage("stat: %s: %s", userdir, strerror(errno));
340 goto server_error;
341 }
342 mach_port_deallocate(mach_task_self(), mp);
343 }
344 }
345 }
346 MUTEX_UNLOCK(&lock);
347 }
348
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]);
351 errno = EINVAL;
352 return NULL; /* buffer too small */
353 }
354 strcpy(path, userdir);
355 strcat(path, subdirs[which]);
356
357 /*
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.
361 */
362 #if !TARGET_OS_IPHONE
363 if (!_xpc_runtime_is_app_sandboxed())
364 #endif
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));
367 return NULL;
368 }
369
370 #if !TARGET_OS_IPHONE
371 char *userdir_suffix = NULL;
372
373 if (_xpc_runtime_is_app_sandboxed()) {
374 /*
375 * if the subdir wasn't made for us, bail since we probably don't have
376 * permission to create it ourselves.
377 */
378 if(stat(path, &sb) < 0) {
379 setcrashlogmessage("stat: %s: %s", path, strerror(errno));
380 errno = EPERM;
381 return NULL;
382 }
383
384 /*
385 * sandboxed applications get per-application directories named
386 * after the container
387 */
388 userdir_suffix = getenv(XPC_ENV_SANDBOX_CONTAINER_ID);
389 if (!userdir_suffix) {
390 setcrashlogmessage("XPC_ENV_SANDBOX_CONTAINER_ID not set");
391 errno = EINVAL;
392 return NULL;
393 }
394 } else if (!dyld_process_is_restricted()) {
395 userdir_suffix = getenv(DIRHELPER_ENV_USER_DIR_SUFFIX);
396 }
397
398 if (userdir_suffix) {
399 /*
400 * do not allow paths that contain path traversal dots.
401 */
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 '/../'
408 {
409 setcrashlogmessage("illegal path traversal (..) pattern found in DIRHELPER_USER_DIR_SUFFIX");
410 errno = EINVAL;
411 return NULL;
412 }
413 pos += 2;
414 }
415
416 /*
417 * suffix (usually container ID) doesn't end in a slash, so +2 is for slash and \0
418 */
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);
421 errno = EINVAL;
422 return NULL; /* buffer too small */
423 }
424
425 strcat(path, userdir_suffix);
426 strcat(path, "/");
427
428 /*
429 * create suffix subdirectory with the appropriate permissions
430 * if it doesn't already exist.
431 */
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));
434 return NULL;
435 }
436
437 /*
438 * update TMPDIR if necessary
439 */
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);
444 }
445 }
446 #endif
447
448 return path;
449 }