1 #include <sys/stat.h> 2 #include <string.h> 3 #include <errno.h> 4 #include <stdio.h> 5 #include "selinux_internal.h" 6 #include "label_internal.h" 7 #include "callbacks.h" 8 #include <limits.h> 9 10 static __thread struct selabel_handle *hnd; 11 12 /* 13 * An array for mapping integers to contexts 14 */ 15 static __thread char **con_array; 16 static __thread int con_array_size; 17 static __thread int con_array_used; 18 19 static pthread_once_t once = PTHREAD_ONCE_INIT; 20 static pthread_key_t destructor_key; 21 static int destructor_key_initialized = 0; 22 23 static int add_array_elt(char *con) 24 { 25 if (con_array_size) { 26 while (con_array_used >= con_array_size) { 27 con_array_size *= 2; 28 con_array = (char **)realloc(con_array, sizeof(char*) * 29 con_array_size); 30 if (!con_array) { 31 con_array_size = con_array_used = 0; 32 return -1; 33 } 34 } 35 } else { 36 con_array_size = 1000; 37 con_array = (char **)malloc(sizeof(char*) * con_array_size); 38 if (!con_array) { 39 con_array_size = con_array_used = 0; 40 return -1; 41 } 42 } 43 44 con_array[con_array_used] = strdup(con); 45 if (!con_array[con_array_used]) 46 return -1; 47 return con_array_used++; 48 } 49 50 static void free_array_elts(void) 51 { 52 con_array_size = con_array_used = 0; 53 free(con_array); 54 con_array = NULL; 55 } 56 57 static void 58 #ifdef __GNUC__ 59 __attribute__ ((format(printf, 1, 2))) 60 #endif 61 default_printf(const char *fmt, ...) 62 { 63 va_list ap; 64 va_start(ap, fmt); 65 vfprintf(stderr, fmt, ap); 66 va_end(ap); 67 } 68 69 void 70 #ifdef __GNUC__ 71 __attribute__ ((format(printf, 1, 2))) 72 #endif 73 (*myprintf) (const char *fmt,...) = &default_printf; 74 int myprintf_compat = 0; 75 76 void set_matchpathcon_printf(void (*f) (const char *fmt, ...)) 77 { 78 myprintf = f ? f : &default_printf; 79 myprintf_compat = 1; 80 } 81 82 static int (*myinvalidcon) (const char *p, unsigned l, char *c) = NULL; 83 84 void set_matchpathcon_invalidcon(int (*f) (const char *p, unsigned l, char *c)) 85 { 86 myinvalidcon = f; 87 } 88 89 static int default_canoncon(const char *path, unsigned lineno, char **context) 90 { 91 char *tmpcon; 92 if (security_canonicalize_context_raw(*context, &tmpcon) < 0) { 93 if (errno == ENOENT) 94 return 0; 95 if (lineno) 96 myprintf("%s: line %u has invalid context %s\n", path, 97 lineno, *context); 98 else 99 myprintf("%s: invalid context %s\n", path, *context); 100 return 1; 101 } 102 free(*context); 103 *context = tmpcon; 104 return 0; 105 } 106 107 static int (*mycanoncon) (const char *p, unsigned l, char **c) = 108 NULL; 109 110 void set_matchpathcon_canoncon(int (*f) (const char *p, unsigned l, char **c)) 111 { 112 if (f) 113 mycanoncon = f; 114 else 115 mycanoncon = &default_canoncon; 116 } 117 118 static __thread struct selinux_opt options[SELABEL_NOPT]; 119 static __thread int notrans; 120 121 void set_matchpathcon_flags(unsigned int flags) 122 { 123 int i; 124 memset(options, 0, sizeof(options)); 125 i = SELABEL_OPT_BASEONLY; 126 options[i].type = i; 127 options[i].value = (flags & MATCHPATHCON_BASEONLY) ? (char*)1 : NULL; 128 i = SELABEL_OPT_VALIDATE; 129 options[i].type = i; 130 options[i].value = (flags & MATCHPATHCON_VALIDATE) ? (char*)1 : NULL; 131 notrans = flags & MATCHPATHCON_NOTRANS; 132 } 133 134 /* 135 * An association between an inode and a 136 * specification. 137 */ 138 typedef struct file_spec { 139 ino_t ino; /* inode number */ 140 int specind; /* index of specification in spec */ 141 char *file; /* full pathname for diagnostic messages about conflicts */ 142 struct file_spec *next; /* next association in hash bucket chain */ 143 } file_spec_t; 144 145 /* 146 * The hash table of associations, hashed by inode number. 147 * Chaining is used for collisions, with elements ordered 148 * by inode number in each bucket. Each hash bucket has a dummy 149 * header. 150 */ 151 #define HASH_BITS 16 152 #define HASH_BUCKETS (1 << HASH_BITS) 153 #define HASH_MASK (HASH_BUCKETS-1) 154 static file_spec_t *fl_head; 155 156 /* 157 * Try to add an association between an inode and 158 * a specification. If there is already an association 159 * for the inode and it conflicts with this specification, 160 * then use the specification that occurs later in the 161 * specification array. 162 */ 163 int matchpathcon_filespec_add(ino_t ino, int specind, const char *file) 164 { 165 file_spec_t *prevfl, *fl; 166 int h, ret; 167 struct stat sb; 168 169 if (!fl_head) { 170 fl_head = malloc(sizeof(file_spec_t) * HASH_BUCKETS); 171 if (!fl_head) 172 goto oom; 173 memset(fl_head, 0, sizeof(file_spec_t) * HASH_BUCKETS); 174 } 175 176 h = (ino + (ino >> HASH_BITS)) & HASH_MASK; 177 for (prevfl = &fl_head[h], fl = fl_head[h].next; fl; 178 prevfl = fl, fl = fl->next) { 179 if (ino == fl->ino) { 180 ret = lstat(fl->file, &sb); 181 if (ret < 0 || sb.st_ino != ino) { 182 fl->specind = specind; 183 free(fl->file); 184 fl->file = malloc(strlen(file) + 1); 185 if (!fl->file) 186 goto oom; 187 strcpy(fl->file, file); 188 return fl->specind; 189 190 } 191 192 if (!strcmp(con_array[fl->specind], 193 con_array[specind])) 194 return fl->specind; 195 196 myprintf 197 ("%s: conflicting specifications for %s and %s, using %s.\n", 198 __FUNCTION__, file, fl->file, 199 con_array[fl->specind]); 200 free(fl->file); 201 fl->file = malloc(strlen(file) + 1); 202 if (!fl->file) 203 goto oom; 204 strcpy(fl->file, file); 205 return fl->specind; 206 } 207 208 if (ino > fl->ino) 209 break; 210 } 211 212 fl = malloc(sizeof(file_spec_t)); 213 if (!fl) 214 goto oom; 215 fl->ino = ino; 216 fl->specind = specind; 217 fl->file = malloc(strlen(file) + 1); 218 if (!fl->file) 219 goto oom_freefl; 220 strcpy(fl->file, file); 221 fl->next = prevfl->next; 222 prevfl->next = fl; 223 return fl->specind; 224 oom_freefl: 225 free(fl); 226 oom: 227 myprintf("%s: insufficient memory for file label entry for %s\n", 228 __FUNCTION__, file); 229 return -1; 230 } 231 232 /* 233 * Evaluate the association hash table distribution. 234 */ 235 void matchpathcon_filespec_eval(void) 236 { 237 file_spec_t *fl; 238 int h, used, nel, len, longest; 239 240 if (!fl_head) 241 return; 242 243 used = 0; 244 longest = 0; 245 nel = 0; 246 for (h = 0; h < HASH_BUCKETS; h++) { 247 len = 0; 248 for (fl = fl_head[h].next; fl; fl = fl->next) { 249 len++; 250 } 251 if (len) 252 used++; 253 if (len > longest) 254 longest = len; 255 nel += len; 256 } 257 258 myprintf 259 ("%s: hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n", 260 __FUNCTION__, nel, used, HASH_BUCKETS, longest); 261 } 262 263 /* 264 * Destroy the association hash table. 265 */ 266 void matchpathcon_filespec_destroy(void) 267 { 268 file_spec_t *fl, *tmp; 269 int h; 270 271 free_array_elts(); 272 273 if (!fl_head) 274 return; 275 276 for (h = 0; h < HASH_BUCKETS; h++) { 277 fl = fl_head[h].next; 278 while (fl) { 279 tmp = fl; 280 fl = fl->next; 281 free(tmp->file); 282 free(tmp); 283 } 284 fl_head[h].next = NULL; 285 } 286 free(fl_head); 287 fl_head = NULL; 288 } 289 290 static void matchpathcon_thread_destructor(void __attribute__((unused)) *ptr) 291 { 292 matchpathcon_fini(); 293 } 294 295 void __attribute__((destructor)) matchpathcon_lib_destructor(void); 296 297 void hidden __attribute__((destructor)) matchpathcon_lib_destructor(void) 298 { 299 if (destructor_key_initialized) 300 __selinux_key_delete(destructor_key); 301 } 302 303 static void matchpathcon_init_once(void) 304 { 305 if (__selinux_key_create(&destructor_key, matchpathcon_thread_destructor) == 0) 306 destructor_key_initialized = 1; 307 } 308 309 int matchpathcon_init_prefix(const char *path, const char *subset) 310 { 311 if (!mycanoncon) 312 mycanoncon = default_canoncon; 313 314 __selinux_once(once, matchpathcon_init_once); 315 __selinux_setspecific(destructor_key, (void *)1); 316 317 options[SELABEL_OPT_SUBSET].type = SELABEL_OPT_SUBSET; 318 options[SELABEL_OPT_SUBSET].value = subset; 319 options[SELABEL_OPT_PATH].type = SELABEL_OPT_PATH; 320 options[SELABEL_OPT_PATH].value = path; 321 322 hnd = selabel_open(SELABEL_CTX_FILE, options, SELABEL_NOPT); 323 return hnd ? 0 : -1; 324 } 325 326 hidden_def(matchpathcon_init_prefix) 327 328 int matchpathcon_init(const char *path) 329 { 330 return matchpathcon_init_prefix(path, NULL); 331 } 332 333 void matchpathcon_fini(void) 334 { 335 free_array_elts(); 336 337 if (hnd) { 338 selabel_close(hnd); 339 hnd = NULL; 340 } 341 } 342 343 /* 344 * We do not want to resolve a symlink to a real path if it is the final 345 * component of the name. Thus we split the pathname on the last "/" and 346 * determine a real path component of the first portion. We then have to 347 * copy the last part back on to get the final real path. Wheww. 348 */ 349 int realpath_not_final(const char *name, char *resolved_path) 350 { 351 char *last_component; 352 char *tmp_path, *p; 353 size_t len = 0; 354 int rc = 0; 355 356 tmp_path = strdup(name); 357 if (!tmp_path) { 358 myprintf("symlink_realpath(%s) strdup() failed: %s\n", 359 name, strerror(errno)); 360 rc = -1; 361 goto out; 362 } 363 364 /* strip leading // */ 365 while (tmp_path[len] && tmp_path[len] == '/' && 366 tmp_path[len+1] && tmp_path[len+1] == '/') { 367 tmp_path++; 368 len++; 369 } 370 last_component = strrchr(tmp_path, '/'); 371 372 if (last_component == tmp_path) { 373 last_component++; 374 p = strcpy(resolved_path, ""); 375 } else if (last_component) { 376 *last_component = '\0'; 377 last_component++; 378 p = realpath(tmp_path, resolved_path); 379 } else { 380 last_component = tmp_path; 381 p = realpath("./", resolved_path); 382 } 383 384 if (!p) { 385 myprintf("symlink_realpath(%s) realpath() failed: %s\n", 386 name, strerror(errno)); 387 rc = -1; 388 goto out; 389 } 390 391 len = strlen(p); 392 if (len + strlen(last_component) + 2 > PATH_MAX) { 393 myprintf("symlink_realpath(%s) failed: Filename too long \n", 394 name); 395 errno=ENAMETOOLONG; 396 rc = -1; 397 goto out; 398 } 399 400 resolved_path += len; 401 strcpy(resolved_path, "/"); 402 resolved_path += 1; 403 strcpy(resolved_path, last_component); 404 out: 405 free(tmp_path); 406 return rc; 407 } 408 409 int matchpathcon(const char *path, mode_t mode, char ** con) 410 { 411 char stackpath[PATH_MAX + 1]; 412 char *p = NULL; 413 if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0)) 414 return -1; 415 416 if (S_ISLNK(mode)) { 417 if (!realpath_not_final(path, stackpath)) 418 path = stackpath; 419 } else { 420 p = realpath(path, stackpath); 421 if (p) 422 path = p; 423 } 424 425 return notrans ? 426 selabel_lookup_raw(hnd, con, path, mode) : 427 selabel_lookup(hnd, con, path, mode); 428 } 429 430 int matchpathcon_index(const char *name, mode_t mode, char ** con) 431 { 432 int i = matchpathcon(name, mode, con); 433 434 if (i < 0) 435 return -1; 436 437 return add_array_elt(*con); 438 } 439 440 void matchpathcon_checkmatches(char *str __attribute__((unused))) 441 { 442 selabel_stats(hnd); 443 } 444 445 /* Compare two contexts to see if their differences are "significant", 446 * or whether the only difference is in the user. */ 447 int selinux_file_context_cmp(const char * a, 448 const char * b) 449 { 450 char *rest_a, *rest_b; /* Rest of the context after the user */ 451 if (!a && !b) 452 return 0; 453 if (!a) 454 return -1; 455 if (!b) 456 return 1; 457 rest_a = strchr((char *)a, ':'); 458 rest_b = strchr((char *)b, ':'); 459 if (!rest_a && !rest_b) 460 return 0; 461 if (!rest_a) 462 return -1; 463 if (!rest_b) 464 return 1; 465 return strcmp(rest_a, rest_b); 466 } 467 468 int selinux_file_context_verify(const char *path, mode_t mode) 469 { 470 char * con = NULL; 471 char * fcontext = NULL; 472 int rc = 0; 473 474 rc = lgetfilecon_raw(path, &con); 475 if (rc == -1) { 476 if (errno != ENOTSUP) 477 return -1; 478 else 479 return 0; 480 } 481 482 if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0)) 483 return -1; 484 485 if (selabel_lookup_raw(hnd, &fcontext, path, mode) != 0) { 486 if (errno != ENOENT) 487 rc = -1; 488 else 489 rc = 0; 490 } else { 491 /* 492 * Need to set errno to 0 as it can be set to ENOENT if the 493 * file_contexts.subs file does not exist (see selabel_open in 494 * label.c), thus causing confusion if errno is checked on return. 495 */ 496 errno = 0; 497 rc = (selinux_file_context_cmp(fcontext, con) == 0); 498 } 499 500 freecon(con); 501 freecon(fcontext); 502 return rc; 503 } 504 505 int selinux_lsetfilecon_default(const char *path) 506 { 507 struct stat st; 508 int rc = -1; 509 char * scontext = NULL; 510 if (lstat(path, &st) != 0) 511 return rc; 512 513 if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0)) 514 return -1; 515 516 /* If there's an error determining the context, or it has none, 517 return to allow default context */ 518 if (selabel_lookup_raw(hnd, &scontext, path, st.st_mode)) { 519 if (errno == ENOENT) 520 rc = 0; 521 } else { 522 rc = lsetfilecon_raw(path, scontext); 523 freecon(scontext); 524 } 525 return rc; 526 } 527 528 int compat_validate(struct selabel_handle *rec, 529 struct selabel_lookup_rec *contexts, 530 const char *path, unsigned lineno) 531 { 532 int rc; 533 char **ctx = &contexts->ctx_raw; 534 535 if (myinvalidcon) 536 rc = myinvalidcon(path, lineno, *ctx); 537 else if (mycanoncon) 538 rc = mycanoncon(path, lineno, ctx); 539 else { 540 rc = selabel_validate(rec, contexts); 541 if (rc < 0) { 542 if (lineno) { 543 COMPAT_LOG(SELINUX_WARNING, 544 "%s: line %u has invalid context %s\n", 545 path, lineno, *ctx); 546 } else { 547 COMPAT_LOG(SELINUX_WARNING, 548 "%s: has invalid context %s\n", path, *ctx); 549 } 550 } 551 } 552 553 return rc ? -1 : 0; 554 } 555