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 <pcre.h>
     18 #include <unistd.h>
     19 #include <sys/mman.h>
     20 #include <sys/types.h>
     21 #include <sys/stat.h>
     22 
     23 #include "callbacks.h"
     24 #include "label_internal.h"
     25 #include "label_file.h"
     26 
     27 /*
     28  * Internals, mostly moved over from matchpathcon.c
     29  */
     30 
     31 /* return the length of the text that is the stem of a file name */
     32 static int get_stem_from_file_name(const char *const buf)
     33 {
     34 	const char *tmp = strchr(buf + 1, '/');
     35 
     36 	if (!tmp)
     37 		return 0;
     38 	return tmp - buf;
     39 }
     40 
     41 /* find the stem of a file name, returns the index into stem_arr (or -1 if
     42  * there is no match - IE for a file in the root directory or a regex that is
     43  * too complex for us).  Makes buf point to the text AFTER the stem. */
     44 static int find_stem_from_file(struct saved_data *data, const char **buf)
     45 {
     46 	int i;
     47 	int stem_len = get_stem_from_file_name(*buf);
     48 
     49 	if (!stem_len)
     50 		return -1;
     51 	for (i = 0; i < data->num_stems; i++) {
     52 		if (stem_len == data->stem_arr[i].len
     53 		    && !strncmp(*buf, data->stem_arr[i].buf, stem_len)) {
     54 			*buf += stem_len;
     55 			return i;
     56 		}
     57 	}
     58 	return -1;
     59 }
     60 
     61 /*
     62  * Warn about duplicate specifications.
     63  */
     64 static int nodups_specs(struct saved_data *data, const char *path)
     65 {
     66 	int rc = 0;
     67 	unsigned int ii, jj;
     68 	struct spec *curr_spec, *spec_arr = data->spec_arr;
     69 
     70 	for (ii = 0; ii < data->nspec; ii++) {
     71 		curr_spec = &spec_arr[ii];
     72 		for (jj = ii + 1; jj < data->nspec; jj++) {
     73 			if ((!strcmp(spec_arr[jj].regex_str,
     74 				curr_spec->regex_str))
     75 			    && (!spec_arr[jj].mode || !curr_spec->mode
     76 				|| spec_arr[jj].mode == curr_spec->mode)) {
     77 				rc = -1;
     78 				errno = EINVAL;
     79 				if (strcmp(spec_arr[jj].lr.ctx_raw,
     80 					    curr_spec->lr.ctx_raw)) {
     81 					selinux_log
     82 						(SELINUX_ERROR,
     83 						 "%s: Multiple different specifications for %s  (%s and %s).\n",
     84 						 path, curr_spec->regex_str,
     85 						 spec_arr[jj].lr.ctx_raw,
     86 						 curr_spec->lr.ctx_raw);
     87 				} else {
     88 					selinux_log
     89 						(SELINUX_ERROR,
     90 						 "%s: Multiple same specifications for %s.\n",
     91 						 path, curr_spec->regex_str);
     92 				}
     93 			}
     94 		}
     95 	}
     96 	return rc;
     97 }
     98 
     99 static int load_mmap(struct selabel_handle *rec, const char *path,
    100 		     struct stat *sb, bool isbinary)
    101 {
    102 	struct saved_data *data = (struct saved_data *)rec->data;
    103 	char mmap_path[PATH_MAX + 1];
    104 	int mmapfd;
    105 	int rc;
    106 	struct stat mmap_stat;
    107 	char *addr, *str_buf;
    108 	size_t len;
    109 	int *stem_map;
    110 	struct mmap_area *mmap_area;
    111 	uint32_t i, magic, version;
    112 	uint32_t entry_len, stem_map_len, regex_array_len;
    113 
    114 	if (isbinary) {
    115 		len = strlen(path);
    116 		if (len >= sizeof(mmap_path))
    117 			return -1;
    118 		strcpy(mmap_path, path);
    119 	} else {
    120 		rc = snprintf(mmap_path, sizeof(mmap_path), "%s.bin", path);
    121 		if (rc >= (int)sizeof(mmap_path))
    122 			return -1;
    123 	}
    124 
    125 	mmapfd = open(mmap_path, O_RDONLY | O_CLOEXEC);
    126 	if (mmapfd < 0)
    127 		return -1;
    128 
    129 	rc = fstat(mmapfd, &mmap_stat);
    130 	if (rc < 0) {
    131 		close(mmapfd);
    132 		return -1;
    133 	}
    134 
    135 	/* if mmap is old, ignore it */
    136 	if (mmap_stat.st_mtime < sb->st_mtime) {
    137 		close(mmapfd);
    138 		return -1;
    139 	}
    140 
    141 	/* ok, read it in... */
    142 	len = mmap_stat.st_size;
    143 	len += (sysconf(_SC_PAGE_SIZE) - 1);
    144 	len &= ~(sysconf(_SC_PAGE_SIZE) - 1);
    145 
    146 	mmap_area = malloc(sizeof(*mmap_area));
    147 	if (!mmap_area) {
    148 		close(mmapfd);
    149 		return -1;
    150 	}
    151 
    152 	addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, mmapfd, 0);
    153 	close(mmapfd);
    154 	if (addr == MAP_FAILED) {
    155 		free(mmap_area);
    156 		perror("mmap");
    157 		return -1;
    158 	}
    159 
    160 	/* save where we mmap'd the file to cleanup on close() */
    161 	mmap_area->addr = mmap_area->next_addr = addr;
    162 	mmap_area->len = mmap_area->next_len = len;
    163 	mmap_area->next = data->mmap_areas;
    164 	data->mmap_areas = mmap_area;
    165 
    166 	/* check if this looks like an fcontext file */
    167 	rc = next_entry(&magic, mmap_area, sizeof(uint32_t));
    168 	if (rc < 0 || magic != SELINUX_MAGIC_COMPILED_FCONTEXT)
    169 		return -1;
    170 
    171 	/* check if this version is higher than we understand */
    172 	rc = next_entry(&version, mmap_area, sizeof(uint32_t));
    173 	if (rc < 0 || version > SELINUX_COMPILED_FCONTEXT_MAX_VERS)
    174 		return -1;
    175 
    176 	if (version >= SELINUX_COMPILED_FCONTEXT_PCRE_VERS) {
    177 		len = strlen(pcre_version());
    178 
    179 		rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t));
    180 		if (rc < 0)
    181 			return -1;
    182 
    183 		/* Check version lengths */
    184 		if (len != entry_len)
    185 			return -1;
    186 
    187 		/* Check if pcre version mismatch */
    188 		str_buf = malloc(entry_len + 1);
    189 		if (!str_buf)
    190 			return -1;
    191 
    192 		rc = next_entry(str_buf, mmap_area, entry_len);
    193 		if (rc < 0) {
    194 			free(str_buf);
    195 			return -1;
    196 		}
    197 
    198 		str_buf[entry_len] = '\0';
    199 		if ((strcmp(str_buf, pcre_version()) != 0)) {
    200 			free(str_buf);
    201 			return -1;
    202 		}
    203 		free(str_buf);
    204 	}
    205 
    206 	/* allocate the stems_data array */
    207 	rc = next_entry(&stem_map_len, mmap_area, sizeof(uint32_t));
    208 	if (rc < 0 || !stem_map_len)
    209 		return -1;
    210 
    211 	/*
    212 	 * map indexed by the stem # in the mmap file and contains the stem
    213 	 * number in the data stem_arr
    214 	 */
    215 	stem_map = calloc(stem_map_len, sizeof(*stem_map));
    216 	if (!stem_map)
    217 		return -1;
    218 
    219 	for (i = 0; i < stem_map_len; i++) {
    220 		char *buf;
    221 		uint32_t stem_len;
    222 		int newid;
    223 
    224 		/* the length does not inlude the nul */
    225 		rc = next_entry(&stem_len, mmap_area, sizeof(uint32_t));
    226 		if (rc < 0 || !stem_len) {
    227 			rc = -1;
    228 			goto err;
    229 		}
    230 
    231 		/* Check for stem_len wrap around. */
    232 		if (stem_len < UINT32_MAX) {
    233 			buf = (char *)mmap_area->next_addr;
    234 			/* Check if over-run before null check. */
    235 			rc = next_entry(NULL, mmap_area, (stem_len + 1));
    236 			if (rc < 0)
    237 				goto err;
    238 
    239 			if (buf[stem_len] != '\0') {
    240 				rc = -1;
    241 				goto err;
    242 			}
    243 		} else {
    244 			rc = -1;
    245 			goto err;
    246 		}
    247 
    248 		/* store the mapping between old and new */
    249 		newid = find_stem(data, buf, stem_len);
    250 		if (newid < 0) {
    251 			newid = store_stem(data, buf, stem_len);
    252 			if (newid < 0) {
    253 				rc = newid;
    254 				goto err;
    255 			}
    256 			data->stem_arr[newid].from_mmap = 1;
    257 		}
    258 		stem_map[i] = newid;
    259 	}
    260 
    261 	/* allocate the regex array */
    262 	rc = next_entry(&regex_array_len, mmap_area, sizeof(uint32_t));
    263 	if (rc < 0 || !regex_array_len) {
    264 		rc = -1;
    265 		goto err;
    266 	}
    267 
    268 	for (i = 0; i < regex_array_len; i++) {
    269 		struct spec *spec;
    270 		int32_t stem_id, meta_chars;
    271 		uint32_t mode = 0, prefix_len = 0;
    272 
    273 		rc = grow_specs(data);
    274 		if (rc < 0)
    275 			goto err;
    276 
    277 		spec = &data->spec_arr[data->nspec];
    278 		spec->from_mmap = 1;
    279 		spec->regcomp = 1;
    280 
    281 		/* Process context */
    282 		rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t));
    283 		if (rc < 0 || !entry_len) {
    284 			rc = -1;
    285 			goto err;
    286 		}
    287 
    288 		str_buf = malloc(entry_len);
    289 		if (!str_buf) {
    290 			rc = -1;
    291 			goto err;
    292 		}
    293 		rc = next_entry(str_buf, mmap_area, entry_len);
    294 		if (rc < 0)
    295 			goto err;
    296 
    297 		if (str_buf[entry_len - 1] != '\0') {
    298 			free(str_buf);
    299 			rc = -1;
    300 			goto err;
    301 		}
    302 		spec->lr.ctx_raw = str_buf;
    303 
    304 		if (strcmp(spec->lr.ctx_raw, "<<none>>") && rec->validating) {
    305 			if (selabel_validate(rec, &spec->lr) < 0) {
    306 				selinux_log(SELINUX_ERROR,
    307 					    "%s: context %s is invalid\n", mmap_path, spec->lr.ctx_raw);
    308 				goto err;
    309 			}
    310 		}
    311 
    312 		/* Process regex string */
    313 		rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t));
    314 		if (rc < 0 || !entry_len) {
    315 			rc = -1;
    316 			goto err;
    317 		}
    318 
    319 		spec->regex_str = (char *)mmap_area->next_addr;
    320 		rc = next_entry(NULL, mmap_area, entry_len);
    321 		if (rc < 0)
    322 			goto err;
    323 
    324 		if (spec->regex_str[entry_len - 1] != '\0') {
    325 			rc = -1;
    326 			goto err;
    327 		}
    328 
    329 		/* Process mode */
    330 		if (version >= SELINUX_COMPILED_FCONTEXT_MODE)
    331 			rc = next_entry(&mode, mmap_area, sizeof(uint32_t));
    332 		else
    333 			rc = next_entry(&mode, mmap_area, sizeof(mode_t));
    334 		if (rc < 0)
    335 			goto err;
    336 
    337 		spec->mode = mode;
    338 
    339 		/* map the stem id from the mmap file to the data->stem_arr */
    340 		rc = next_entry(&stem_id, mmap_area, sizeof(int32_t));
    341 		if (rc < 0)
    342 			goto err;
    343 
    344 		if (stem_id < 0 || stem_id >= (int32_t)stem_map_len)
    345 			spec->stem_id = -1;
    346 		 else
    347 			spec->stem_id = stem_map[stem_id];
    348 
    349 		/* retrieve the hasMetaChars bit */
    350 		rc = next_entry(&meta_chars, mmap_area, sizeof(uint32_t));
    351 		if (rc < 0)
    352 			goto err;
    353 
    354 		spec->hasMetaChars = meta_chars;
    355 		/* and prefix length for use by selabel_lookup_best_match */
    356                if (version >= SELINUX_COMPILED_FCONTEXT_PREFIX_LEN) {
    357                        rc = next_entry(&prefix_len, mmap_area,
    358                                            sizeof(uint32_t));
    359                        if (rc < 0)
    360                                goto err;
    361 
    362                        spec->prefix_len = prefix_len;
    363                }
    364 
    365 		/* Process regex and study_data entries */
    366 		rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t));
    367 		if (rc < 0 || !entry_len) {
    368 			rc = -1;
    369 			goto err;
    370 		}
    371 		spec->regex = (pcre *)mmap_area->next_addr;
    372 		rc = next_entry(NULL, mmap_area, entry_len);
    373 		if (rc < 0)
    374 			goto err;
    375 
    376 		/* Check that regex lengths match. pcre_fullinfo()
    377 		 * also validates its magic number. */
    378 		rc = pcre_fullinfo(spec->regex, NULL, PCRE_INFO_SIZE, &len);
    379 		if (rc < 0 || len != entry_len) {
    380 			rc = -1;
    381 			goto err;
    382 		}
    383 
    384 		rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t));
    385 		if (rc < 0 || !entry_len) {
    386 			rc = -1;
    387 			goto err;
    388 		}
    389 		spec->lsd.study_data = (void *)mmap_area->next_addr;
    390 		spec->lsd.flags |= PCRE_EXTRA_STUDY_DATA;
    391 		rc = next_entry(NULL, mmap_area, entry_len);
    392 		if (rc < 0)
    393 			goto err;
    394 
    395 		/* Check that study data lengths match. */
    396 		rc = pcre_fullinfo(spec->regex, &spec->lsd,
    397 				    PCRE_INFO_STUDYSIZE, &len);
    398 		if (rc < 0 || len != entry_len) {
    399 			rc = -1;
    400 			goto err;
    401 		}
    402 
    403 		data->nspec++;
    404 	}
    405 	/* win */
    406 	rc = 0;
    407 err:
    408 	free(stem_map);
    409 
    410 	return rc;
    411 }
    412 
    413 static int process_file(const char *path, const char *suffix,
    414 			  struct selabel_handle *rec, const char *prefix)
    415 {
    416 	FILE *fp;
    417 	struct stat sb;
    418 	unsigned int lineno;
    419 	size_t line_len = 0;
    420 	char *line_buf = NULL;
    421 	int rc;
    422 	char stack_path[PATH_MAX + 1];
    423 	bool isbinary = false;
    424 	uint32_t magic;
    425 
    426 	/* append the path suffix if we have one */
    427 	if (suffix) {
    428 		rc = snprintf(stack_path, sizeof(stack_path),
    429 					    "%s.%s", path, suffix);
    430 		if (rc >= (int)sizeof(stack_path)) {
    431 			errno = ENAMETOOLONG;
    432 			return -1;
    433 		}
    434 		path = stack_path;
    435 	}
    436 
    437 	/* Open the specification file. */
    438 	fp = fopen(path, "r");
    439 	if (fp) {
    440 		if (fstat(fileno(fp), &sb) < 0)
    441 			return -1;
    442 		if (!S_ISREG(sb.st_mode)) {
    443 			errno = EINVAL;
    444 			return -1;
    445 		}
    446 
    447 		if (fread(&magic, sizeof magic, 1, fp) != 1) {
    448 			errno = EINVAL;
    449 			fclose(fp);
    450 			return -1;
    451 		}
    452 
    453 		if (magic == SELINUX_MAGIC_COMPILED_FCONTEXT) {
    454 			/* file_contexts.bin format */
    455 			fclose(fp);
    456 			fp = NULL;
    457 			isbinary = true;
    458 		} else {
    459 			rewind(fp);
    460 		}
    461 	} else {
    462 		/*
    463 		 * Text file does not exist, so clear the timestamp
    464 		 * so that we will always pass the timestamp comparison
    465 		 * with the bin file in load_mmap().
    466 		 */
    467 		sb.st_mtime = 0;
    468 	}
    469 
    470 	rc = load_mmap(rec, path, &sb, isbinary);
    471 	if (rc == 0)
    472 		goto out;
    473 
    474 	if (!fp)
    475 		return -1; /* no text or bin file */
    476 
    477 	/*
    478 	 * Then do detailed validation of the input and fill the spec array
    479 	 */
    480 	lineno = 0;
    481 	rc = 0;
    482 	while (getline(&line_buf, &line_len, fp) > 0) {
    483 		rc = process_line(rec, path, prefix, line_buf, ++lineno);
    484 		if (rc)
    485 			goto out;
    486 	}
    487 
    488 out:
    489 	free(line_buf);
    490 	if (fp)
    491 		fclose(fp);
    492 	return rc;
    493 }
    494 
    495 static void closef(struct selabel_handle *rec);
    496 
    497 static int init(struct selabel_handle *rec, const struct selinux_opt *opts,
    498 		unsigned n)
    499 {
    500 	struct saved_data *data = (struct saved_data *)rec->data;
    501 	const char *path = NULL;
    502 	const char *prefix = NULL;
    503 	int status = -1, baseonly = 0;
    504 
    505 	/* Process arguments */
    506 	while (n--)
    507 		switch(opts[n].type) {
    508 		case SELABEL_OPT_PATH:
    509 			path = opts[n].value;
    510 			break;
    511 		case SELABEL_OPT_SUBSET:
    512 			prefix = opts[n].value;
    513 			break;
    514 		case SELABEL_OPT_BASEONLY:
    515 			baseonly = !!opts[n].value;
    516 			break;
    517 		}
    518 
    519 	rec->spec_file = strdup(path);
    520 
    521 	/*
    522 	 * The do detailed validation of the input and fill the spec array
    523 	 */
    524 	status = process_file(path, NULL, rec, prefix);
    525 	if (status)
    526 		goto finish;
    527 
    528 	if (rec->validating) {
    529 		status = nodups_specs(data, path);
    530 		if (status)
    531 			goto finish;
    532 	}
    533 
    534 	if (!baseonly) {
    535 		status = process_file(path, "homedirs", rec, prefix);
    536 		if (status && errno != ENOENT)
    537 			goto finish;
    538 
    539 		status = process_file(path, "local", rec, prefix);
    540 		if (status && errno != ENOENT)
    541 			goto finish;
    542 	}
    543 
    544 	status = sort_specs(data);
    545 
    546 finish:
    547 	if (status)
    548 		closef(rec);
    549 
    550 	return status;
    551 }
    552 
    553 /*
    554  * Backend interface routines
    555  */
    556 static void closef(struct selabel_handle *rec)
    557 {
    558 	struct saved_data *data = (struct saved_data *)rec->data;
    559 	struct mmap_area *area, *last_area;
    560 	struct spec *spec;
    561 	struct stem *stem;
    562 	unsigned int i;
    563 
    564 	for (i = 0; i < data->nspec; i++) {
    565 		spec = &data->spec_arr[i];
    566 		free(spec->lr.ctx_trans);
    567 		free(spec->lr.ctx_raw);
    568 		if (spec->from_mmap)
    569 			continue;
    570 		free(spec->regex_str);
    571 		free(spec->type_str);
    572 		if (spec->regcomp) {
    573 			pcre_free(spec->regex);
    574 			pcre_free_study(spec->sd);
    575 		}
    576 	}
    577 
    578 	for (i = 0; i < (unsigned int)data->num_stems; i++) {
    579 		stem = &data->stem_arr[i];
    580 		if (stem->from_mmap)
    581 			continue;
    582 		free(stem->buf);
    583 	}
    584 
    585 	if (data->spec_arr)
    586 		free(data->spec_arr);
    587 	if (data->stem_arr)
    588 		free(data->stem_arr);
    589 
    590 	area = data->mmap_areas;
    591 	while (area) {
    592 		munmap(area->addr, area->len);
    593 		last_area = area;
    594 		area = area->next;
    595 		free(last_area);
    596 	}
    597 	free(data);
    598 }
    599 
    600 static struct spec *lookup_common(struct selabel_handle *rec,
    601 					     const char *key,
    602 					     int type,
    603 					     bool partial)
    604 {
    605 	struct saved_data *data = (struct saved_data *)rec->data;
    606 	struct spec *spec_arr = data->spec_arr;
    607 	int i, rc, file_stem, pcre_options = 0;
    608 	mode_t mode = (mode_t)type;
    609 	const char *buf;
    610 	struct spec *ret = NULL;
    611 	char *clean_key = NULL;
    612 	const char *prev_slash, *next_slash;
    613 	unsigned int sofar = 0;
    614 
    615 	if (!data->nspec) {
    616 		errno = ENOENT;
    617 		goto finish;
    618 	}
    619 
    620 	/* Remove duplicate slashes */
    621 	if ((next_slash = strstr(key, "//"))) {
    622 		clean_key = (char *) malloc(strlen(key) + 1);
    623 		if (!clean_key)
    624 			goto finish;
    625 		prev_slash = key;
    626 		while (next_slash) {
    627 			memcpy(clean_key + sofar, prev_slash, next_slash - prev_slash);
    628 			sofar += next_slash - prev_slash;
    629 			prev_slash = next_slash + 1;
    630 			next_slash = strstr(prev_slash, "//");
    631 		}
    632 		strcpy(clean_key + sofar, prev_slash);
    633 		key = clean_key;
    634 	}
    635 
    636 	buf = key;
    637 	file_stem = find_stem_from_file(data, &buf);
    638 	mode &= S_IFMT;
    639 
    640 	if (partial)
    641 		pcre_options |= PCRE_PARTIAL_SOFT;
    642 
    643 	/*
    644 	 * Check for matching specifications in reverse order, so that
    645 	 * the last matching specification is used.
    646 	 */
    647 	for (i = data->nspec - 1; i >= 0; i--) {
    648 		struct spec *spec = &spec_arr[i];
    649 		/* if the spec in question matches no stem or has the same
    650 		 * stem as the file AND if the spec in question has no mode
    651 		 * specified or if the mode matches the file mode then we do
    652 		 * a regex check        */
    653 		if ((spec->stem_id == -1 || spec->stem_id == file_stem) &&
    654 		    (!mode || !spec->mode || mode == spec->mode)) {
    655 			if (compile_regex(data, spec, NULL) < 0)
    656 				goto finish;
    657 			if (spec->stem_id == -1)
    658 				rc = pcre_exec(spec->regex,
    659 						    get_pcre_extra(spec),
    660 						    key, strlen(key), 0,
    661 						    pcre_options, NULL, 0);
    662 			else
    663 				rc = pcre_exec(spec->regex,
    664 						    get_pcre_extra(spec),
    665 						    buf, strlen(buf), 0,
    666 						    pcre_options, NULL, 0);
    667 			if (rc == 0) {
    668 				spec->matches++;
    669 				break;
    670 			} else if (partial && rc == PCRE_ERROR_PARTIAL)
    671 				break;
    672 
    673 			if (rc == PCRE_ERROR_NOMATCH)
    674 				continue;
    675 
    676 			errno = ENOENT;
    677 			/* else it's an error */
    678 			goto finish;
    679 		}
    680 	}
    681 
    682 	if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 0) {
    683 		/* No matching specification. */
    684 		errno = ENOENT;
    685 		goto finish;
    686 	}
    687 
    688 	errno = 0;
    689 	ret = &spec_arr[i];
    690 
    691 finish:
    692 	free(clean_key);
    693 	return ret;
    694 }
    695 
    696 static struct selabel_lookup_rec *lookup(struct selabel_handle *rec,
    697 					 const char *key, int type)
    698 {
    699 	struct spec *spec;
    700 
    701 	spec = lookup_common(rec, key, type, false);
    702 	if (spec)
    703 		return &spec->lr;
    704 	return NULL;
    705 }
    706 
    707 static bool partial_match(struct selabel_handle *rec, const char *key)
    708 {
    709 	return lookup_common(rec, key, 0, true) ? true : false;
    710 }
    711 
    712 static struct selabel_lookup_rec *lookup_best_match(struct selabel_handle *rec,
    713 						    const char *key,
    714 						    const char **aliases,
    715 						    int type)
    716 {
    717 	size_t n, i;
    718 	int best = -1;
    719 	struct spec **specs;
    720 	size_t prefix_len = 0;
    721 	struct selabel_lookup_rec *lr = NULL;
    722 
    723 	if (!aliases || !aliases[0])
    724 		return lookup(rec, key, type);
    725 
    726 	for (n = 0; aliases[n]; n++)
    727 		;
    728 
    729 	specs = calloc(n+1, sizeof(struct spec *));
    730 	if (!specs)
    731 		return NULL;
    732 	specs[0] = lookup_common(rec, key, type, false);
    733 	if (specs[0]) {
    734 		if (!specs[0]->hasMetaChars) {
    735 			/* exact match on key */
    736 			lr = &specs[0]->lr;
    737 			goto out;
    738 		}
    739 		best = 0;
    740 		prefix_len = specs[0]->prefix_len;
    741 	}
    742 	for (i = 1; i <= n; i++) {
    743 		specs[i] = lookup_common(rec, aliases[i-1], type, false);
    744 		if (specs[i]) {
    745 			if (!specs[i]->hasMetaChars) {
    746 				/* exact match on alias */
    747 				lr = &specs[i]->lr;
    748 				goto out;
    749 			}
    750 			if (specs[i]->prefix_len > prefix_len) {
    751 				best = i;
    752 				prefix_len = specs[i]->prefix_len;
    753 			}
    754 		}
    755 	}
    756 
    757 	if (best >= 0) {
    758 		/* longest fixed prefix match on key or alias */
    759 		lr = &specs[best]->lr;
    760 	} else {
    761 		errno = ENOENT;
    762 	}
    763 
    764 out:
    765 	free(specs);
    766 	return lr;
    767 }
    768 
    769 static enum selabel_cmp_result incomp(struct spec *spec1, struct spec *spec2, const char *reason, int i, int j)
    770 {
    771 	selinux_log(SELINUX_INFO,
    772 		    "selabel_cmp: mismatched %s on entry %d: (%s, %x, %s) vs entry %d: (%s, %x, %s)\n",
    773 		    reason,
    774 		    i, spec1->regex_str, spec1->mode, spec1->lr.ctx_raw,
    775 		    j, spec2->regex_str, spec2->mode, spec2->lr.ctx_raw);
    776 	return SELABEL_INCOMPARABLE;
    777 }
    778 
    779 static enum selabel_cmp_result cmp(struct selabel_handle *h1,
    780 				   struct selabel_handle *h2)
    781 {
    782 	struct saved_data *data1 = (struct saved_data *)h1->data;
    783 	struct saved_data *data2 = (struct saved_data *)h2->data;
    784 	unsigned int i, nspec1 = data1->nspec, j, nspec2 = data2->nspec;
    785 	struct spec *spec_arr1 = data1->spec_arr, *spec_arr2 = data2->spec_arr;
    786 	struct stem *stem_arr1 = data1->stem_arr, *stem_arr2 = data2->stem_arr;
    787 	bool skipped1 = false, skipped2 = false;
    788 
    789 	i = 0;
    790 	j = 0;
    791 	while (i < nspec1 && j < nspec2) {
    792 		struct spec *spec1 = &spec_arr1[i];
    793 		struct spec *spec2 = &spec_arr2[j];
    794 
    795 		/*
    796 		 * Because sort_specs() moves exact pathnames to the
    797 		 * end, we might need to skip over additional regex
    798 		 * entries that only exist in one of the configurations.
    799 		 */
    800 		if (!spec1->hasMetaChars && spec2->hasMetaChars) {
    801 			j++;
    802 			skipped2 = true;
    803 			continue;
    804 		}
    805 
    806 		if (spec1->hasMetaChars && !spec2->hasMetaChars) {
    807 			i++;
    808 			skipped1 = true;
    809 			continue;
    810 		}
    811 
    812 		if (spec1->regcomp && spec2->regcomp) {
    813 			size_t len1, len2;
    814 			int rc;
    815 
    816 			rc = pcre_fullinfo(spec1->regex, NULL, PCRE_INFO_SIZE, &len1);
    817 			assert(rc == 0);
    818 			rc = pcre_fullinfo(spec2->regex, NULL, PCRE_INFO_SIZE, &len2);
    819 			assert(rc == 0);
    820 			if (len1 != len2 ||
    821 			    memcmp(spec1->regex, spec2->regex, len1))
    822 				return incomp(spec1, spec2, "regex", i, j);
    823 		} else {
    824 			if (strcmp(spec1->regex_str, spec2->regex_str))
    825 				return incomp(spec1, spec2, "regex_str", i, j);
    826 		}
    827 
    828 		if (spec1->mode != spec2->mode)
    829 			return incomp(spec1, spec2, "mode", i, j);
    830 
    831 		if (spec1->stem_id == -1 && spec2->stem_id != -1)
    832 			return incomp(spec1, spec2, "stem_id", i, j);
    833 		if (spec2->stem_id == -1 && spec1->stem_id != -1)
    834 			return incomp(spec1, spec2, "stem_id", i, j);
    835 		if (spec1->stem_id != -1 && spec2->stem_id != -1) {
    836 			struct stem *stem1 = &stem_arr1[spec1->stem_id];
    837 			struct stem *stem2 = &stem_arr2[spec2->stem_id];
    838 			if (stem1->len != stem2->len ||
    839 			    strncmp(stem1->buf, stem2->buf, stem1->len))
    840 				return incomp(spec1, spec2, "stem", i, j);
    841 		}
    842 
    843 		if (strcmp(spec1->lr.ctx_raw, spec2->lr.ctx_raw))
    844 			return incomp(spec1, spec2, "ctx_raw", i, j);
    845 
    846 		i++;
    847 		j++;
    848 	}
    849 
    850 	if ((skipped1 || i < nspec1) && !skipped2)
    851 		return SELABEL_SUPERSET;
    852 	if ((skipped2 || j < nspec2) && !skipped1)
    853 		return SELABEL_SUBSET;
    854 	if (skipped1 && skipped2)
    855 		return SELABEL_INCOMPARABLE;
    856 	return SELABEL_EQUAL;
    857 }
    858 
    859 
    860 static void stats(struct selabel_handle *rec)
    861 {
    862 	struct saved_data *data = (struct saved_data *)rec->data;
    863 	unsigned int i, nspec = data->nspec;
    864 	struct spec *spec_arr = data->spec_arr;
    865 
    866 	for (i = 0; i < nspec; i++) {
    867 		if (spec_arr[i].matches == 0) {
    868 			if (spec_arr[i].type_str) {
    869 				selinux_log(SELINUX_WARNING,
    870 				    "Warning!  No matches for (%s, %s, %s)\n",
    871 				    spec_arr[i].regex_str,
    872 				    spec_arr[i].type_str,
    873 				    spec_arr[i].lr.ctx_raw);
    874 			} else {
    875 				selinux_log(SELINUX_WARNING,
    876 				    "Warning!  No matches for (%s, %s)\n",
    877 				    spec_arr[i].regex_str,
    878 				    spec_arr[i].lr.ctx_raw);
    879 			}
    880 		}
    881 	}
    882 }
    883 
    884 int selabel_file_init(struct selabel_handle *rec, const struct selinux_opt *opts,
    885 		      unsigned nopts)
    886 {
    887 	struct saved_data *data;
    888 
    889 	data = (struct saved_data *)malloc(sizeof(*data));
    890 	if (!data)
    891 		return -1;
    892 	memset(data, 0, sizeof(*data));
    893 
    894 	rec->data = data;
    895 	rec->func_close = &closef;
    896 	rec->func_stats = &stats;
    897 	rec->func_lookup = &lookup;
    898 	rec->func_partial_match = &partial_match;
    899 	rec->func_lookup_best_match = &lookup_best_match;
    900 	rec->func_cmp = &cmp;
    901 
    902 	return init(rec, opts, nopts);
    903 }
    904