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 */
33static 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. */
45static 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 */
65static 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
100static 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
413err:
414	free(stem_map);
415
416	return rc;
417}
418
419static 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
503out:
504	free(line_buf);
505	if (fp)
506		fclose(fp);
507	return rc;
508}
509
510static void closef(struct selabel_handle *rec);
511
512static 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
583finish:
584	if (status)
585		closef(rec);
586
587	return status;
588}
589
590/*
591 * Backend interface routines
592 */
593static 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
637static 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
728finish:
729	free(clean_key);
730	return ret;
731}
732
733static 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
744static bool partial_match(struct selabel_handle *rec, const char *key)
745{
746	return lookup_common(rec, key, 0, true) ? true : false;
747}
748
749static 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
801out:
802	free(specs);
803	return lr;
804}
805
806static 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
816static 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
897static 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
921int 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