• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <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 */
get_stem_from_file_name(const char * const buf)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. */
find_stem_from_file(struct saved_data * data,const char ** buf)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  */
nodups_specs(struct saved_data * data,const char * path)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 
process_text_file(FILE * fp,const char * prefix,struct selabel_handle * rec,const char * path)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 
load_mmap(FILE * fp,size_t len,struct selabel_handle * rec,const char * path)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 
rolling_append(char * current,const char * suffix,size_t max)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 
fcontext_is_binary(FILE * fp)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 
open_file(const char * path,const char * suffix,char * save_path,size_t len,struct stat * sb,bool open_oldest)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, "re");
524 }
525 
process_file(const char * path,const char * suffix,struct selabel_handle * rec,const char * prefix,struct selabel_digest * digest)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 
init(struct selabel_handle * rec,const struct selinux_opt * opts,unsigned n)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  */
closef(struct selabel_handle * rec)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 
lookup_common(struct selabel_handle * rec,const char * key,int type,bool partial)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 
lookup(struct selabel_handle * rec,const char * key,int type)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 
partial_match(struct selabel_handle * rec,const char * key)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 
lookup_best_match(struct selabel_handle * rec,const char * key,const char ** aliases,int type)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 
incomp(struct spec * spec1,struct spec * spec2,const char * reason,int i,int j)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 
cmp(struct selabel_handle * h1,struct selabel_handle * h2)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 
stats(struct selabel_handle * rec)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 
selabel_file_init(struct selabel_handle * rec,const struct selinux_opt * opts,unsigned nopts)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