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