Home | History | Annotate | Download | only in src
      1 /*
      2  * File contexts backend for labeling system
      3  *
      4  * Author : Eamon Walsh <ewalsh (at) tycho.nsa.gov>
      5  * Author : Stephen Smalley <sds (at) tycho.nsa.gov>
      6  */
      7 
      8 #include <assert.h>
      9 #include <fcntl.h>
     10 #include <stdarg.h>
     11 #include <string.h>
     12 #include <stdio.h>
     13 #include <ctype.h>
     14 #include <errno.h>
     15 #include <limits.h>
     16 #include <stdint.h>
     17 #include <unistd.h>
     18 #include <sys/mman.h>
     19 #include <sys/types.h>
     20 #include <sys/stat.h>
     21 
     22 #include "callbacks.h"
     23 #include "label_internal.h"
     24 #include "label_file.h"
     25 
     26 /*
     27  * Internals, mostly moved over from matchpathcon.c
     28  */
     29 
     30 /* return the length of the text that is the stem of a file name */
     31 static int get_stem_from_file_name(const char *const buf)
     32 {
     33 	const char *tmp = strchr(buf + 1, '/');
     34 
     35 	if (!tmp)
     36 		return 0;
     37 	return tmp - buf;
     38 }
     39 
     40 /* find the stem of a file name, returns the index into stem_arr (or -1 if
     41  * there is no match - IE for a file in the root directory or a regex that is
     42  * too complex for us).  Makes buf point to the text AFTER the stem. */
     43 static int find_stem_from_file(struct saved_data *data, const char **buf)
     44 {
     45 	int i;
     46 	int stem_len = get_stem_from_file_name(*buf);
     47 
     48 	if (!stem_len)
     49 		return -1;
     50 	for (i = 0; i < data->num_stems; i++) {
     51 		if (stem_len == data->stem_arr[i].len
     52 		    && !strncmp(*buf, data->stem_arr[i].buf, stem_len)) {
     53 			*buf += stem_len;
     54 			return i;
     55 		}
     56 	}
     57 	return -1;
     58 }
     59 
     60 /*
     61  * Warn about duplicate specifications.
     62  */
     63 static int nodups_specs(struct saved_data *data, const char *path)
     64 {
     65 	int rc = 0;
     66 	unsigned int ii, jj;
     67 	struct spec *curr_spec, *spec_arr = data->spec_arr;
     68 
     69 	for (ii = 0; ii < data->nspec; ii++) {
     70 		curr_spec = &spec_arr[ii];
     71 		for (jj = ii + 1; jj < data->nspec; jj++) {
     72 			if ((!strcmp(spec_arr[jj].regex_str,
     73 				curr_spec->regex_str))
     74 			    && (!spec_arr[jj].mode || !curr_spec->mode
     75 				|| spec_arr[jj].mode == curr_spec->mode)) {
     76 				rc = -1;
     77 				errno = EINVAL;
     78 				if (strcmp(spec_arr[jj].lr.ctx_raw,
     79 					    curr_spec->lr.ctx_raw)) {
     80 					COMPAT_LOG
     81 						(SELINUX_ERROR,
     82 						 "%s: Multiple different specifications for %s  (%s and %s).\n",
     83 						 path, curr_spec->regex_str,
     84 						 spec_arr[jj].lr.ctx_raw,
     85 						 curr_spec->lr.ctx_raw);
     86 				} else {
     87 					COMPAT_LOG
     88 						(SELINUX_ERROR,
     89 						 "%s: Multiple same specifications for %s.\n",
     90 						 path, curr_spec->regex_str);
     91 				}
     92 			}
     93 		}
     94 	}
     95 	return rc;
     96 }
     97 
     98 static int process_text_file(FILE *fp, const char *prefix,
     99 			     struct selabel_handle *rec, const char *path)
    100 {
    101 	int rc;
    102 	size_t line_len;
    103 	unsigned int lineno = 0;
    104 	char *line_buf = NULL;
    105 
    106 	while (getline(&line_buf, &line_len, fp) > 0) {
    107 		rc = process_line(rec, path, prefix, line_buf, ++lineno);
    108 		if (rc)
    109 			goto out;
    110 	}
    111 	rc = 0;
    112 out:
    113 	free(line_buf);
    114 	return rc;
    115 }
    116 
    117 static int load_mmap(FILE *fp, size_t len, struct selabel_handle *rec,
    118 		     const char *path)
    119 {
    120 	struct saved_data *data = (struct saved_data *)rec->data;
    121 	int rc;
    122 	char *addr, *str_buf;
    123 	int *stem_map;
    124 	struct mmap_area *mmap_area;
    125 	uint32_t i, magic, version;
    126 	uint32_t entry_len, stem_map_len, regex_array_len;
    127 	const char *reg_version;
    128 	const char *reg_arch;
    129 	char reg_arch_matches = 0;
    130 
    131 	mmap_area = malloc(sizeof(*mmap_area));
    132 	if (!mmap_area) {
    133 		return -1;
    134 	}
    135 
    136 	addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
    137 	if (addr == MAP_FAILED) {
    138 		free(mmap_area);
    139 		perror("mmap");
    140 		return -1;
    141 	}
    142 
    143 	/* save where we mmap'd the file to cleanup on close() */
    144 	mmap_area->addr = mmap_area->next_addr = addr;
    145 	mmap_area->len = mmap_area->next_len = len;
    146 	mmap_area->next = data->mmap_areas;
    147 	data->mmap_areas = mmap_area;
    148 
    149 	/* check if this looks like an fcontext file */
    150 	rc = next_entry(&magic, mmap_area, sizeof(uint32_t));
    151 	if (rc < 0 || magic != SELINUX_MAGIC_COMPILED_FCONTEXT)
    152 		return -1;
    153 
    154 	/* check if this version is higher than we understand */
    155 	rc = next_entry(&version, mmap_area, sizeof(uint32_t));
    156 	if (rc < 0 || version > SELINUX_COMPILED_FCONTEXT_MAX_VERS)
    157 		return -1;
    158 
    159 	reg_version = regex_version();
    160 	if (!reg_version)
    161 		return -1;
    162 
    163 	reg_arch = regex_arch_string();
    164 	if (!reg_arch)
    165 		return -1;
    166 
    167 	if (version >= SELINUX_COMPILED_FCONTEXT_PCRE_VERS) {
    168 
    169 		len = strlen(reg_version);
    170 
    171 		rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t));
    172 		if (rc < 0)
    173 			return -1;
    174 
    175 		/* Check version lengths */
    176 		if (len != entry_len)
    177 			return -1;
    178 
    179 		/* Check if regex version mismatch */
    180 		str_buf = malloc(entry_len + 1);
    181 		if (!str_buf)
    182 			return -1;
    183 
    184 		rc = next_entry(str_buf, mmap_area, entry_len);
    185 		if (rc < 0) {
    186 			free(str_buf);
    187 			return -1;
    188 		}
    189 
    190 		str_buf[entry_len] = '\0';
    191 		if ((strcmp(str_buf, reg_version) != 0)) {
    192 			free(str_buf);
    193 			return -1;
    194 		}
    195 		free(str_buf);
    196 
    197 		if (version >= SELINUX_COMPILED_FCONTEXT_REGEX_ARCH) {
    198 			len = strlen(reg_arch);
    199 
    200 			rc = next_entry(&entry_len, mmap_area,
    201 					sizeof(uint32_t));
    202 			if (rc < 0)
    203 				return -1;
    204 
    205 			/* Check arch string lengths */
    206 			if (len != entry_len) {
    207 				/*
    208 				 * Skip the entry and conclude that we have
    209 				 * a mismatch, which is not fatal.
    210 				 */
    211 				next_entry(NULL, mmap_area, entry_len);
    212 				goto end_arch_check;
    213 			}
    214 
    215 			/* Check if arch string mismatch */
    216 			str_buf = malloc(entry_len + 1);
    217 			if (!str_buf)
    218 				return -1;
    219 
    220 			rc = next_entry(str_buf, mmap_area, entry_len);
    221 			if (rc < 0) {
    222 				free(str_buf);
    223 				return -1;
    224 			}
    225 
    226 			str_buf[entry_len] = '\0';
    227 			reg_arch_matches = strcmp(str_buf, reg_arch) == 0;
    228 			free(str_buf);
    229 		}
    230 	}
    231 end_arch_check:
    232 
    233 	/* allocate the stems_data array */
    234 	rc = next_entry(&stem_map_len, mmap_area, sizeof(uint32_t));
    235 	if (rc < 0 || !stem_map_len)
    236 		return -1;
    237 
    238 	/*
    239 	 * map indexed by the stem # in the mmap file and contains the stem
    240 	 * number in the data stem_arr
    241 	 */
    242 	stem_map = calloc(stem_map_len, sizeof(*stem_map));
    243 	if (!stem_map)
    244 		return -1;
    245 
    246 	for (i = 0; i < stem_map_len; i++) {
    247 		char *buf;
    248 		uint32_t stem_len;
    249 		int newid;
    250 
    251 		/* the length does not inlude the nul */
    252 		rc = next_entry(&stem_len, mmap_area, sizeof(uint32_t));
    253 		if (rc < 0 || !stem_len) {
    254 			rc = -1;
    255 			goto out;
    256 		}
    257 
    258 		/* Check for stem_len wrap around. */
    259 		if (stem_len < UINT32_MAX) {
    260 			buf = (char *)mmap_area->next_addr;
    261 			/* Check if over-run before null check. */
    262 			rc = next_entry(NULL, mmap_area, (stem_len + 1));
    263 			if (rc < 0)
    264 				goto out;
    265 
    266 			if (buf[stem_len] != '\0') {
    267 				rc = -1;
    268 				goto out;
    269 			}
    270 		} else {
    271 			rc = -1;
    272 			goto out;
    273 		}
    274 
    275 		/* store the mapping between old and new */
    276 		newid = find_stem(data, buf, stem_len);
    277 		if (newid < 0) {
    278 			newid = store_stem(data, buf, stem_len);
    279 			if (newid < 0) {
    280 				rc = newid;
    281 				goto out;
    282 			}
    283 			data->stem_arr[newid].from_mmap = 1;
    284 		}
    285 		stem_map[i] = newid;
    286 	}
    287 
    288 	/* allocate the regex array */
    289 	rc = next_entry(&regex_array_len, mmap_area, sizeof(uint32_t));
    290 	if (rc < 0 || !regex_array_len) {
    291 		rc = -1;
    292 		goto out;
    293 	}
    294 
    295 	for (i = 0; i < regex_array_len; i++) {
    296 		struct spec *spec;
    297 		int32_t stem_id, meta_chars;
    298 		uint32_t mode = 0, prefix_len = 0;
    299 
    300 		rc = grow_specs(data);
    301 		if (rc < 0)
    302 			goto out;
    303 
    304 		spec = &data->spec_arr[data->nspec];
    305 		spec->from_mmap = 1;
    306 
    307 		/* Process context */
    308 		rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t));
    309 		if (rc < 0 || !entry_len) {
    310 			rc = -1;
    311 			goto out;
    312 		}
    313 
    314 		str_buf = malloc(entry_len);
    315 		if (!str_buf) {
    316 			rc = -1;
    317 			goto out;
    318 		}
    319 		rc = next_entry(str_buf, mmap_area, entry_len);
    320 		if (rc < 0)
    321 			goto out;
    322 
    323 		if (str_buf[entry_len - 1] != '\0') {
    324 			free(str_buf);
    325 			rc = -1;
    326 			goto out;
    327 		}
    328 		spec->lr.ctx_raw = str_buf;
    329 
    330 		if (strcmp(spec->lr.ctx_raw, "<<none>>") && rec->validating) {
    331 			if (selabel_validate(rec, &spec->lr) < 0) {
    332 				selinux_log(SELINUX_ERROR,
    333 					    "%s: context %s is invalid\n",
    334 					    path, spec->lr.ctx_raw);
    335 				goto out;
    336 			}
    337 		}
    338 
    339 		/* Process regex string */
    340 		rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t));
    341 		if (rc < 0 || !entry_len) {
    342 			rc = -1;
    343 			goto out;
    344 		}
    345 
    346 		spec->regex_str = (char *)mmap_area->next_addr;
    347 		rc = next_entry(NULL, mmap_area, entry_len);
    348 		if (rc < 0)
    349 			goto out;
    350 
    351 		if (spec->regex_str[entry_len - 1] != '\0') {
    352 			rc = -1;
    353 			goto out;
    354 		}
    355 
    356 		/* Process mode */
    357 		if (version >= SELINUX_COMPILED_FCONTEXT_MODE)
    358 			rc = next_entry(&mode, mmap_area, sizeof(uint32_t));
    359 		else
    360 			rc = next_entry(&mode, mmap_area, sizeof(mode_t));
    361 		if (rc < 0)
    362 			goto out;
    363 
    364 		spec->mode = mode;
    365 
    366 		/* map the stem id from the mmap file to the data->stem_arr */
    367 		rc = next_entry(&stem_id, mmap_area, sizeof(int32_t));
    368 		if (rc < 0)
    369 			goto out;
    370 
    371 		if (stem_id < 0 || stem_id >= (int32_t)stem_map_len)
    372 			spec->stem_id = -1;
    373 		 else
    374 			spec->stem_id = stem_map[stem_id];
    375 
    376 		/* retrieve the hasMetaChars bit */
    377 		rc = next_entry(&meta_chars, mmap_area, sizeof(uint32_t));
    378 		if (rc < 0)
    379 			goto out;
    380 
    381 		spec->hasMetaChars = meta_chars;
    382 		/* and prefix length for use by selabel_lookup_best_match */
    383 		if (version >= SELINUX_COMPILED_FCONTEXT_PREFIX_LEN) {
    384 			rc = next_entry(&prefix_len, mmap_area,
    385 					    sizeof(uint32_t));
    386 			if (rc < 0)
    387 				goto out;
    388 
    389 			spec->prefix_len = prefix_len;
    390 		}
    391 
    392 		rc = regex_load_mmap(mmap_area, &spec->regex, reg_arch_matches);
    393 		if (rc < 0)
    394 			goto out;
    395 
    396 		data->nspec++;
    397 	}
    398 
    399 	rc = 0;
    400 out:
    401 	free(stem_map);
    402 
    403 	return rc;
    404 }
    405 
    406 struct file_details {
    407 	const char *suffix;
    408 	struct stat sb;
    409 };
    410 
    411 static char *rolling_append(char *current, const char *suffix, size_t max)
    412 {
    413 	size_t size;
    414 	size_t suffix_size;
    415 	size_t current_size;
    416 
    417 	if (!suffix)
    418 		return current;
    419 
    420 	current_size = strlen(current);
    421 	suffix_size = strlen(suffix);
    422 
    423 	size = current_size + suffix_size;
    424 	if (size < current_size || size < suffix_size)
    425 		return NULL;
    426 
    427 	/* ensure space for the '.' and the '\0' characters. */
    428 	if (size >= (SIZE_MAX - 2))
    429 		return NULL;
    430 
    431 	size += 2;
    432 
    433 	if (size > max)
    434 		return NULL;
    435 
    436 	/* Append any given suffix */
    437 	char *to = current + current_size;
    438 	*to++ = '.';
    439 	strcpy(to, suffix);
    440 
    441 	return current;
    442 }
    443 
    444 static bool fcontext_is_binary(FILE *fp)
    445 {
    446 	uint32_t magic;
    447 
    448 	size_t len = fread(&magic, sizeof(magic), 1, fp);
    449 	rewind(fp);
    450 
    451 	return (len && (magic == SELINUX_MAGIC_COMPILED_FCONTEXT));
    452 }
    453 
    454 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
    455 
    456 static FILE *open_file(const char *path, const char *suffix,
    457 	       char *save_path, size_t len, struct stat *sb, bool open_oldest)
    458 {
    459 	unsigned int i;
    460 	int rc;
    461 	char stack_path[len];
    462 	struct file_details *found = NULL;
    463 
    464 	/*
    465 	 * Rolling append of suffix. Try to open with path.suffix then the
    466 	 * next as path.suffix.suffix and so forth.
    467 	 */
    468 	struct file_details fdetails[2] = {
    469 			{ .suffix = suffix },
    470 			{ .suffix = "bin" }
    471 	};
    472 
    473 	rc = snprintf(stack_path, sizeof(stack_path), "%s", path);
    474 	if (rc >= (int) sizeof(stack_path)) {
    475 		errno = ENAMETOOLONG;
    476 		return NULL;
    477 	}
    478 
    479 	for (i = 0; i < ARRAY_SIZE(fdetails); i++) {
    480 
    481 		/* This handles the case if suffix is null */
    482 		path = rolling_append(stack_path, fdetails[i].suffix,
    483 				      sizeof(stack_path));
    484 		if (!path)
    485 			return NULL;
    486 
    487 		rc = stat(path, &fdetails[i].sb);
    488 		if (rc)
    489 			continue;
    490 
    491 		/* first file thing found, just take it */
    492 		if (!found) {
    493 			strcpy(save_path, path);
    494 			found = &fdetails[i];
    495 			continue;
    496 		}
    497 
    498 		/*
    499 		 * Keep picking the newest file found. Where "newest"
    500 		 * includes equality. This provides a precedence on
    501 		 * secondary suffixes even when the timestamp is the
    502 		 * same. Ie choose file_contexts.bin over file_contexts
    503 		 * even if the time stamp is the same. Invert this logic
    504 		 * on open_oldest set to true. The idea is that if the
    505 		 * newest file failed to process, we can attempt to
    506 		 * process the oldest. The logic here is subtle and depends
    507 		 * on the array ordering in fdetails for the case when time
    508 		 * stamps are the same.
    509 		 */
    510 		if (open_oldest ^
    511 			(fdetails[i].sb.st_mtime >= found->sb.st_mtime)) {
    512 			found = &fdetails[i];
    513 			strcpy(save_path, path);
    514 		}
    515 	}
    516 
    517 	if (!found) {
    518 		errno = ENOENT;
    519 		return NULL;
    520 	}
    521 
    522 	memcpy(sb, &found->sb, sizeof(*sb));
    523 	return fopen(save_path, "r");
    524 }
    525 
    526 static int process_file(const char *path, const char *suffix,
    527 			  struct selabel_handle *rec,
    528 			  const char *prefix, struct selabel_digest *digest)
    529 {
    530 	int rc;
    531 	unsigned int i;
    532 	struct stat sb;
    533 	FILE *fp = NULL;
    534 	char found_path[PATH_MAX];
    535 
    536 	/*
    537 	 * On the first pass open the newest modified file. If it fails to
    538 	 * process, then the second pass shall open the oldest file. If both
    539 	 * passes fail, then it's a fatal error.
    540 	 */
    541 	for (i = 0; i < 2; i++) {
    542 		fp = open_file(path, suffix, found_path, sizeof(found_path),
    543 			&sb, i > 0);
    544 		if (fp == NULL)
    545 			return -1;
    546 
    547 		rc = fcontext_is_binary(fp) ?
    548 				load_mmap(fp, sb.st_size, rec, found_path) :
    549 				process_text_file(fp, prefix, rec, found_path);
    550 		if (!rc)
    551 			rc = digest_add_specfile(digest, fp, NULL, sb.st_size,
    552 				found_path);
    553 
    554 		fclose(fp);
    555 
    556 		if (!rc)
    557 			return 0;
    558 	}
    559 	return -1;
    560 }
    561 
    562 static void closef(struct selabel_handle *rec);
    563 
    564 static int init(struct selabel_handle *rec, const struct selinux_opt *opts,
    565 		unsigned n)
    566 {
    567 	struct saved_data *data = (struct saved_data *)rec->data;
    568 	size_t num_paths = 0;
    569 	char **path = NULL;
    570 	const char *prefix = NULL;
    571 	int status = -1;
    572 	size_t i;
    573 	bool baseonly = false;
    574 	bool path_provided;
    575 
    576 	/* Process arguments */
    577 	i = n;
    578 	while (i--)
    579 		switch(opts[i].type) {
    580 		case SELABEL_OPT_PATH:
    581 			num_paths++;
    582 			break;
    583 		case SELABEL_OPT_SUBSET:
    584 			prefix = opts[i].value;
    585 			break;
    586 		case SELABEL_OPT_BASEONLY:
    587 			baseonly = !!opts[i].value;
    588 			break;
    589 		}
    590 
    591 	if (!num_paths) {
    592 		num_paths = 1;
    593 		path_provided = false;
    594 	} else {
    595 		path_provided = true;
    596 	}
    597 
    598 	path = calloc(num_paths, sizeof(*path));
    599 	if (path == NULL) {
    600 		goto finish;
    601 	}
    602 	rec->spec_files = path;
    603 	rec->spec_files_len = num_paths;
    604 
    605 	if (path_provided) {
    606 		for (i = 0; i < n; i++) {
    607 			switch(opts[i].type) {
    608 			case SELABEL_OPT_PATH:
    609 				*path = strdup(opts[i].value);
    610 				if (*path == NULL)
    611 					goto finish;
    612 				path++;
    613 				break;
    614 			default:
    615 				break;
    616 			}
    617 		}
    618 	}
    619 #if !defined(BUILD_HOST) && !defined(ANDROID)
    620 	char subs_file[PATH_MAX + 1];
    621 	/* Process local and distribution substitution files */
    622 	if (!path_provided) {
    623 		rec->dist_subs =
    624 		    selabel_subs_init(selinux_file_context_subs_dist_path(),
    625 		    rec->dist_subs, rec->digest);
    626 		rec->subs = selabel_subs_init(selinux_file_context_subs_path(),
    627 		    rec->subs, rec->digest);
    628 		rec->spec_files[0] = strdup(selinux_file_context_path());
    629 		if (rec->spec_files[0] == NULL)
    630 			goto finish;
    631 	} else {
    632 		for (i = 0; i < num_paths; i++) {
    633 			snprintf(subs_file, sizeof(subs_file), "%s.subs_dist", rec->spec_files[i]);
    634 			rec->dist_subs = selabel_subs_init(subs_file, rec->dist_subs, rec->digest);
    635 			snprintf(subs_file, sizeof(subs_file), "%s.subs", rec->spec_files[i]);
    636 			rec->subs = selabel_subs_init(subs_file, rec->subs, rec->digest);
    637 		}
    638 	}
    639 #else
    640 	if (!path_provided) {
    641 		selinux_log(SELINUX_ERROR, "No path given to file labeling backend\n");
    642 		goto finish;
    643 	}
    644 #endif
    645 
    646 	/*
    647 	 * Do detailed validation of the input and fill the spec array
    648 	 */
    649 	for (i = 0; i < num_paths; i++) {
    650 		status = process_file(rec->spec_files[i], NULL, rec, prefix, rec->digest);
    651 		if (status)
    652 			goto finish;
    653 
    654 		if (rec->validating) {
    655 			status = nodups_specs(data, rec->spec_files[i]);
    656 			if (status)
    657 				goto finish;
    658 		}
    659 	}
    660 
    661 	if (!baseonly) {
    662 		status = process_file(rec->spec_files[0], "homedirs", rec, prefix,
    663 							    rec->digest);
    664 		if (status && errno != ENOENT)
    665 			goto finish;
    666 
    667 		status = process_file(rec->spec_files[0], "local", rec, prefix,
    668 							    rec->digest);
    669 		if (status && errno != ENOENT)
    670 			goto finish;
    671 	}
    672 
    673 	digest_gen_hash(rec->digest);
    674 
    675 	status = sort_specs(data);
    676 
    677 finish:
    678 	if (status)
    679 		closef(rec);
    680 
    681 	return status;
    682 }
    683 
    684 /*
    685  * Backend interface routines
    686  */
    687 static void closef(struct selabel_handle *rec)
    688 {
    689 	struct saved_data *data = (struct saved_data *)rec->data;
    690 	struct mmap_area *area, *last_area;
    691 	struct spec *spec;
    692 	struct stem *stem;
    693 	unsigned int i;
    694 
    695 	if (!data)
    696 		return;
    697 
    698 	/* make sure successive ->func_close() calls are harmless */
    699 	rec->data = NULL;
    700 
    701 	for (i = 0; i < data->nspec; i++) {
    702 		spec = &data->spec_arr[i];
    703 		free(spec->lr.ctx_trans);
    704 		free(spec->lr.ctx_raw);
    705 		regex_data_free(spec->regex);
    706 		if (spec->from_mmap)
    707 			continue;
    708 		free(spec->regex_str);
    709 		free(spec->type_str);
    710 	}
    711 
    712 	for (i = 0; i < (unsigned int)data->num_stems; i++) {
    713 		stem = &data->stem_arr[i];
    714 		if (stem->from_mmap)
    715 			continue;
    716 		free(stem->buf);
    717 	}
    718 
    719 	if (data->spec_arr)
    720 		free(data->spec_arr);
    721 	if (data->stem_arr)
    722 		free(data->stem_arr);
    723 
    724 	area = data->mmap_areas;
    725 	while (area) {
    726 		munmap(area->addr, area->len);
    727 		last_area = area;
    728 		area = area->next;
    729 		free(last_area);
    730 	}
    731 	free(data);
    732 }
    733 
    734 static struct spec *lookup_common(struct selabel_handle *rec,
    735 					     const char *key,
    736 					     int type,
    737 					     bool partial)
    738 {
    739 	struct saved_data *data = (struct saved_data *)rec->data;
    740 	struct spec *spec_arr = data->spec_arr;
    741 	int i, rc, file_stem;
    742 	mode_t mode = (mode_t)type;
    743 	const char *buf;
    744 	struct spec *ret = NULL;
    745 	char *clean_key = NULL;
    746 	const char *prev_slash, *next_slash;
    747 	unsigned int sofar = 0;
    748 
    749 	if (!data->nspec) {
    750 		errno = ENOENT;
    751 		goto finish;
    752 	}
    753 
    754 	/* Remove duplicate slashes */
    755 	if ((next_slash = strstr(key, "//"))) {
    756 		clean_key = (char *) malloc(strlen(key) + 1);
    757 		if (!clean_key)
    758 			goto finish;
    759 		prev_slash = key;
    760 		while (next_slash) {
    761 			memcpy(clean_key + sofar, prev_slash, next_slash - prev_slash);
    762 			sofar += next_slash - prev_slash;
    763 			prev_slash = next_slash + 1;
    764 			next_slash = strstr(prev_slash, "//");
    765 		}
    766 		strcpy(clean_key + sofar, prev_slash);
    767 		key = clean_key;
    768 	}
    769 
    770 	buf = key;
    771 	file_stem = find_stem_from_file(data, &buf);
    772 	mode &= S_IFMT;
    773 
    774 	/*
    775 	 * Check for matching specifications in reverse order, so that
    776 	 * the last matching specification is used.
    777 	 */
    778 	for (i = data->nspec - 1; i >= 0; i--) {
    779 		struct spec *spec = &spec_arr[i];
    780 		/* if the spec in question matches no stem or has the same
    781 		 * stem as the file AND if the spec in question has no mode
    782 		 * specified or if the mode matches the file mode then we do
    783 		 * a regex check        */
    784 		if ((spec->stem_id == -1 || spec->stem_id == file_stem) &&
    785 		    (!mode || !spec->mode || mode == spec->mode)) {
    786 			if (compile_regex(data, spec, NULL) < 0)
    787 				goto finish;
    788 			if (spec->stem_id == -1)
    789 				rc = regex_match(spec->regex, key, partial);
    790 			else
    791 				rc = regex_match(spec->regex, buf, partial);
    792 			if (rc == REGEX_MATCH) {
    793 				spec->matches++;
    794 				break;
    795 			} else if (partial && rc == REGEX_MATCH_PARTIAL)
    796 				break;
    797 
    798 			if (rc == REGEX_NO_MATCH)
    799 				continue;
    800 
    801 			errno = ENOENT;
    802 			/* else it's an error */
    803 			goto finish;
    804 		}
    805 	}
    806 
    807 	if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 0) {
    808 		/* No matching specification. */
    809 		errno = ENOENT;
    810 		goto finish;
    811 	}
    812 
    813 	errno = 0;
    814 	ret = &spec_arr[i];
    815 
    816 finish:
    817 	free(clean_key);
    818 	return ret;
    819 }
    820 
    821 static struct selabel_lookup_rec *lookup(struct selabel_handle *rec,
    822 					 const char *key, int type)
    823 {
    824 	struct spec *spec;
    825 
    826 	spec = lookup_common(rec, key, type, false);
    827 	if (spec)
    828 		return &spec->lr;
    829 	return NULL;
    830 }
    831 
    832 static bool partial_match(struct selabel_handle *rec, const char *key)
    833 {
    834 	return lookup_common(rec, key, 0, true) ? true : false;
    835 }
    836 
    837 static struct selabel_lookup_rec *lookup_best_match(struct selabel_handle *rec,
    838 						    const char *key,
    839 						    const char **aliases,
    840 						    int type)
    841 {
    842 	size_t n, i;
    843 	int best = -1;
    844 	struct spec **specs;
    845 	size_t prefix_len = 0;
    846 	struct selabel_lookup_rec *lr = NULL;
    847 
    848 	if (!aliases || !aliases[0])
    849 		return lookup(rec, key, type);
    850 
    851 	for (n = 0; aliases[n]; n++)
    852 		;
    853 
    854 	specs = calloc(n+1, sizeof(struct spec *));
    855 	if (!specs)
    856 		return NULL;
    857 	specs[0] = lookup_common(rec, key, type, false);
    858 	if (specs[0]) {
    859 		if (!specs[0]->hasMetaChars) {
    860 			/* exact match on key */
    861 			lr = &specs[0]->lr;
    862 			goto out;
    863 		}
    864 		best = 0;
    865 		prefix_len = specs[0]->prefix_len;
    866 	}
    867 	for (i = 1; i <= n; i++) {
    868 		specs[i] = lookup_common(rec, aliases[i-1], type, false);
    869 		if (specs[i]) {
    870 			if (!specs[i]->hasMetaChars) {
    871 				/* exact match on alias */
    872 				lr = &specs[i]->lr;
    873 				goto out;
    874 			}
    875 			if (specs[i]->prefix_len > prefix_len) {
    876 				best = i;
    877 				prefix_len = specs[i]->prefix_len;
    878 			}
    879 		}
    880 	}
    881 
    882 	if (best >= 0) {
    883 		/* longest fixed prefix match on key or alias */
    884 		lr = &specs[best]->lr;
    885 	} else {
    886 		errno = ENOENT;
    887 	}
    888 
    889 out:
    890 	free(specs);
    891 	return lr;
    892 }
    893 
    894 static enum selabel_cmp_result incomp(struct spec *spec1, struct spec *spec2, const char *reason, int i, int j)
    895 {
    896 	selinux_log(SELINUX_INFO,
    897 		    "selabel_cmp: mismatched %s on entry %d: (%s, %x, %s) vs entry %d: (%s, %x, %s)\n",
    898 		    reason,
    899 		    i, spec1->regex_str, spec1->mode, spec1->lr.ctx_raw,
    900 		    j, spec2->regex_str, spec2->mode, spec2->lr.ctx_raw);
    901 	return SELABEL_INCOMPARABLE;
    902 }
    903 
    904 static enum selabel_cmp_result cmp(struct selabel_handle *h1,
    905 				   struct selabel_handle *h2)
    906 {
    907 	struct saved_data *data1 = (struct saved_data *)h1->data;
    908 	struct saved_data *data2 = (struct saved_data *)h2->data;
    909 	unsigned int i, nspec1 = data1->nspec, j, nspec2 = data2->nspec;
    910 	struct spec *spec_arr1 = data1->spec_arr, *spec_arr2 = data2->spec_arr;
    911 	struct stem *stem_arr1 = data1->stem_arr, *stem_arr2 = data2->stem_arr;
    912 	bool skipped1 = false, skipped2 = false;
    913 
    914 	i = 0;
    915 	j = 0;
    916 	while (i < nspec1 && j < nspec2) {
    917 		struct spec *spec1 = &spec_arr1[i];
    918 		struct spec *spec2 = &spec_arr2[j];
    919 
    920 		/*
    921 		 * Because sort_specs() moves exact pathnames to the
    922 		 * end, we might need to skip over additional regex
    923 		 * entries that only exist in one of the configurations.
    924 		 */
    925 		if (!spec1->hasMetaChars && spec2->hasMetaChars) {
    926 			j++;
    927 			skipped2 = true;
    928 			continue;
    929 		}
    930 
    931 		if (spec1->hasMetaChars && !spec2->hasMetaChars) {
    932 			i++;
    933 			skipped1 = true;
    934 			continue;
    935 		}
    936 
    937 		if (spec1->regex && spec2->regex) {
    938 			if (regex_cmp(spec1->regex, spec2->regex) == SELABEL_INCOMPARABLE){
    939 				return incomp(spec1, spec2, "regex", i, j);
    940 			}
    941 		} else {
    942 			if (strcmp(spec1->regex_str, spec2->regex_str))
    943 				return incomp(spec1, spec2, "regex_str", i, j);
    944 		}
    945 
    946 		if (spec1->mode != spec2->mode)
    947 			return incomp(spec1, spec2, "mode", i, j);
    948 
    949 		if (spec1->stem_id == -1 && spec2->stem_id != -1)
    950 			return incomp(spec1, spec2, "stem_id", i, j);
    951 		if (spec2->stem_id == -1 && spec1->stem_id != -1)
    952 			return incomp(spec1, spec2, "stem_id", i, j);
    953 		if (spec1->stem_id != -1 && spec2->stem_id != -1) {
    954 			struct stem *stem1 = &stem_arr1[spec1->stem_id];
    955 			struct stem *stem2 = &stem_arr2[spec2->stem_id];
    956 			if (stem1->len != stem2->len ||
    957 			    strncmp(stem1->buf, stem2->buf, stem1->len))
    958 				return incomp(spec1, spec2, "stem", i, j);
    959 		}
    960 
    961 		if (strcmp(spec1->lr.ctx_raw, spec2->lr.ctx_raw))
    962 			return incomp(spec1, spec2, "ctx_raw", i, j);
    963 
    964 		i++;
    965 		j++;
    966 	}
    967 
    968 	if ((skipped1 || i < nspec1) && !skipped2)
    969 		return SELABEL_SUPERSET;
    970 	if ((skipped2 || j < nspec2) && !skipped1)
    971 		return SELABEL_SUBSET;
    972 	if (skipped1 && skipped2)
    973 		return SELABEL_INCOMPARABLE;
    974 	return SELABEL_EQUAL;
    975 }
    976 
    977 
    978 static void stats(struct selabel_handle *rec)
    979 {
    980 	struct saved_data *data = (struct saved_data *)rec->data;
    981 	unsigned int i, nspec = data->nspec;
    982 	struct spec *spec_arr = data->spec_arr;
    983 
    984 	for (i = 0; i < nspec; i++) {
    985 		if (spec_arr[i].matches == 0) {
    986 			if (spec_arr[i].type_str) {
    987 				COMPAT_LOG(SELINUX_WARNING,
    988 				    "Warning!  No matches for (%s, %s, %s)\n",
    989 				    spec_arr[i].regex_str,
    990 				    spec_arr[i].type_str,
    991 				    spec_arr[i].lr.ctx_raw);
    992 			} else {
    993 				COMPAT_LOG(SELINUX_WARNING,
    994 				    "Warning!  No matches for (%s, %s)\n",
    995 				    spec_arr[i].regex_str,
    996 				    spec_arr[i].lr.ctx_raw);
    997 			}
    998 		}
    999 	}
   1000 }
   1001 
   1002 int selabel_file_init(struct selabel_handle *rec,
   1003 				    const struct selinux_opt *opts,
   1004 				    unsigned nopts)
   1005 {
   1006 	struct saved_data *data;
   1007 
   1008 	data = (struct saved_data *)malloc(sizeof(*data));
   1009 	if (!data)
   1010 		return -1;
   1011 	memset(data, 0, sizeof(*data));
   1012 
   1013 	rec->data = data;
   1014 	rec->func_close = &closef;
   1015 	rec->func_stats = &stats;
   1016 	rec->func_lookup = &lookup;
   1017 	rec->func_partial_match = &partial_match;
   1018 	rec->func_lookup_best_match = &lookup_best_match;
   1019 	rec->func_cmp = &cmp;
   1020 
   1021 	return init(rec, opts, nopts);
   1022 }
   1023