1 /*
2  * File contexts backend for labeling system
3  *
4  * Author : Eamon Walsh <ewalsh@tycho.nsa.gov>
5  * Author : Stephen Smalley <sds@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 */
get_stem_from_file_name(const char * const buf)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. */
find_stem_from_file(struct saved_data * data,const char ** buf)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  */
nodups_specs(struct saved_data * data,const char * path)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 
load_mmap(struct selabel_handle * rec,const char * path,struct stat * sb,bool isbinary)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 
process_file(const char * path,const char * suffix,struct selabel_handle * rec,const char * prefix)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 
init(struct selabel_handle * rec,const struct selinux_opt * opts,unsigned n)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  */
closef(struct selabel_handle * rec)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 
lookup_common(struct selabel_handle * rec,const char * key,int type,bool partial)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 
lookup(struct selabel_handle * rec,const char * key,int type)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 
partial_match(struct selabel_handle * rec,const char * key)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 
lookup_best_match(struct selabel_handle * rec,const char * key,const char ** aliases,int type)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 
incomp(struct spec * spec1,struct spec * spec2,const char * reason,int i,int j)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 
cmp(struct selabel_handle * h1,struct selabel_handle * h2)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 
stats(struct selabel_handle * rec)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 
selabel_file_init(struct selabel_handle * rec,const struct selinux_opt * opts,unsigned nopts)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