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