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