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