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
63static const mode_t modes[] = {
64    0755,	/* user */
65    0700,	/* temp */
66    0700,	/* cache */
67};
68
69static const char *subdirs[] = {
70    DIRHELPER_TOP_STR,
71    DIRHELPER_TEMP_STR,
72    DIRHELPER_CACHE_STR,
73};
74
75static pthread_once_t userdir_control = PTHREAD_ONCE_INIT;
76static char *userdir = NULL;
77
78// lower case letter (minus vowels), plus numbers and _, making
79// 32 characters.
80static const char encode[] = "0123456789_bcdfghjklmnpqrstvwxyz";
81
82#if TARGET_OS_IPHONE
83
84#define setcrashlogmessage(fmt, ...) /* nothing */
85
86#else /* !TARGET_OS_IPHONE */
87
88static void
89encode_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
139static 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
158char *
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
222char *
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
241static 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}
450