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