1/*
2 * File contexts backend for labeling system
3 *
4 * Author : Eamon Walsh <ewalsh@tycho.nsa.gov>
5 */
6
7#include <fcntl.h>
8#include <stdarg.h>
9#include <string.h>
10#include <stdio.h>
11#include <ctype.h>
12#include <errno.h>
13#include <limits.h>
14#include <sys/types.h>
15#include <sys/stat.h>
16#include <unistd.h>
17#include "callbacks.h"
18#include "label_internal.h"
19#include <pcre.h>
20
21/*
22 * Internals, mostly moved over from matchpathcon.c
23 */
24
25/* A file security context specification. */
26typedef struct spec {
27	struct selabel_lookup_rec lr;	/* holds contexts for lookup result */
28	char *regex_str;	/* regular expession string for diagnostics */
29	char *type_str;		/* type string for diagnostic messages */
30	pcre *regex;		/* compiled regular expression */
31	pcre_extra *sd;         /* pcre study result */
32	char regcomp;           /* regex_str has been compiled to regex */
33	mode_t mode;		/* mode format value */
34	int matches;		/* number of matching pathnames */
35	int hasMetaChars;	/* regular expression has meta-chars */
36	int stem_id;		/* indicates which stem-compression item */
37	size_t prefix_len;      /* length of fixed path prefix */
38} spec_t;
39
40/* A regular expression stem */
41typedef struct stem {
42	char *buf;
43	int len;
44} stem_t;
45
46/* Our stored configuration */
47struct saved_data {
48	/*
49	 * The array of specifications, initially in the same order as in
50	 * the specification file. Sorting occurs based on hasMetaChars.
51	 */
52	spec_t *spec_arr;
53	unsigned int nspec;
54	unsigned int ncomp;
55
56	/*
57	 * The array of regular expression stems.
58	 */
59	stem_t *stem_arr;
60	int num_stems;
61	int alloc_stems;
62};
63
64/* Return the length of the text that can be considered the stem, returns 0
65 * if there is no identifiable stem */
66static int get_stem_from_spec(const char *const buf)
67{
68	const char *tmp = strchr(buf + 1, '/');
69	const char *ind;
70
71	if (!tmp)
72		return 0;
73
74	for (ind = buf; ind < tmp; ind++) {
75		if (strchr(".^$?*+|[({", (int)*ind))
76			return 0;
77	}
78	return tmp - buf;
79}
80
81/* return the length of the text that is the stem of a file name */
82static int get_stem_from_file_name(const char *const buf)
83{
84	const char *tmp = strchr(buf + 1, '/');
85
86	if (!tmp)
87		return 0;
88	return tmp - buf;
89}
90
91/* find the stem of a file spec, returns the index into stem_arr for a new
92 * or existing stem, (or -1 if there is no possible stem - IE for a file in
93 * the root directory or a regex that is too complex for us). */
94static int find_stem_from_spec(struct saved_data *data, const char *buf)
95{
96	int i, num = data->num_stems;
97	int stem_len = get_stem_from_spec(buf);
98
99	if (!stem_len)
100		return -1;
101	for (i = 0; i < num; i++) {
102		if (stem_len == data->stem_arr[i].len
103		    && !strncmp(buf, data->stem_arr[i].buf, stem_len))
104			return i;
105	}
106	if (data->alloc_stems == num) {
107		stem_t *tmp_arr;
108		data->alloc_stems = data->alloc_stems * 2 + 16;
109		tmp_arr = (stem_t *) realloc(data->stem_arr,
110				  sizeof(stem_t) * data->alloc_stems);
111		if (!tmp_arr)
112			return -1;
113		data->stem_arr = tmp_arr;
114	}
115	data->stem_arr[num].len = stem_len;
116	data->stem_arr[num].buf = (char *) malloc(stem_len + 1);
117	if (!data->stem_arr[num].buf)
118		return -1;
119	memcpy(data->stem_arr[num].buf, buf, stem_len);
120	data->stem_arr[num].buf[stem_len] = '\0';
121	data->num_stems++;
122	buf += stem_len;
123	return num;
124}
125
126/* find the stem of a file name, returns the index into stem_arr (or -1 if
127 * there is no match - IE for a file in the root directory or a regex that is
128 * too complex for us).  Makes buf point to the text AFTER the stem. */
129static int find_stem_from_file(struct saved_data *data, const char **buf)
130{
131	int i;
132	int stem_len = get_stem_from_file_name(*buf);
133
134	if (!stem_len)
135		return -1;
136	for (i = 0; i < data->num_stems; i++) {
137		if (stem_len == data->stem_arr[i].len
138		    && !strncmp(*buf, data->stem_arr[i].buf, stem_len)) {
139			*buf += stem_len;
140			return i;
141		}
142	}
143	return -1;
144}
145
146/*
147 * Warn about duplicate specifications.
148 */
149static int nodups_specs(struct saved_data *data, const char *path)
150{
151	int rc = 0;
152	unsigned int ii, jj;
153	struct spec *curr_spec, *spec_arr = data->spec_arr;
154
155	for (ii = 0; ii < data->nspec; ii++) {
156		curr_spec = &spec_arr[ii];
157		for (jj = ii + 1; jj < data->nspec; jj++) {
158			if ((!strcmp
159			     (spec_arr[jj].regex_str, curr_spec->regex_str))
160			    && (!spec_arr[jj].mode || !curr_spec->mode
161				|| spec_arr[jj].mode == curr_spec->mode)) {
162				rc = -1;
163				errno = EINVAL;
164				if (strcmp
165				    (spec_arr[jj].lr.ctx_raw,
166				     curr_spec->lr.ctx_raw)) {
167					selinux_log
168						(SELINUX_ERROR,
169						 "%s: Multiple different specifications for %s  (%s and %s).\n",
170						 path, curr_spec->regex_str,
171						 spec_arr[jj].lr.ctx_raw,
172						 curr_spec->lr.ctx_raw);
173				} else {
174					selinux_log
175						(SELINUX_ERROR,
176						 "%s: Multiple same specifications for %s.\n",
177						 path, curr_spec->regex_str);
178				}
179			}
180		}
181	}
182	return rc;
183}
184
185/* Determine if the regular expression specification has any meta characters. */
186static void spec_hasMetaChars(struct spec *spec)
187{
188	char *c;
189	size_t len;
190	char *end;
191
192	c = spec->regex_str;
193	len = strlen(spec->regex_str);
194	end = c + len;
195
196	spec->hasMetaChars = 0;
197	spec->prefix_len = len;
198
199	/* Look at each character in the RE specification string for a
200	 * meta character. Return when any meta character reached. */
201	while (c != end) {
202		switch (*c) {
203		case '.':
204		case '^':
205		case '$':
206		case '?':
207		case '*':
208		case '+':
209		case '|':
210		case '[':
211		case '(':
212		case '{':
213			spec->hasMetaChars = 1;
214			spec->prefix_len = c - spec->regex_str;
215			return;
216		case '\\':	/* skip the next character */
217			c++;
218			break;
219		default:
220			break;
221
222		}
223		c++;
224	}
225	return;
226}
227
228static int compile_regex(struct saved_data *data, spec_t *spec, const char **errbuf)
229{
230	const char *tmperrbuf;
231	char *reg_buf, *anchored_regex, *cp;
232	stem_t *stem_arr = data->stem_arr;
233	size_t len;
234	int erroff;
235
236	if (spec->regcomp)
237		return 0; /* already done */
238
239	data->ncomp++; /* how many compiled regexes required */
240
241	/* Skip the fixed stem. */
242	reg_buf = spec->regex_str;
243	if (spec->stem_id >= 0)
244		reg_buf += stem_arr[spec->stem_id].len;
245
246	/* Anchor the regular expression. */
247	len = strlen(reg_buf);
248	cp = anchored_regex = (char *) malloc(len + 3);
249	if (!anchored_regex)
250		return -1;
251	/* Create ^...$ regexp.  */
252	*cp++ = '^';
253	memcpy(cp, reg_buf, len);
254	cp += len;
255	*cp++ = '$';
256	*cp = '\0';
257
258	/* Compile the regular expression. */
259	spec->regex = pcre_compile(anchored_regex, 0, &tmperrbuf, &erroff, NULL);
260	free(anchored_regex);
261	if (!spec->regex) {
262		if (errbuf)
263			*errbuf=tmperrbuf;
264		return -1;
265	}
266
267	spec->sd = pcre_study(spec->regex, 0, &tmperrbuf);
268	if (!spec->sd) {
269		if (errbuf)
270			*errbuf=tmperrbuf;
271		return -1;
272	}
273
274	/* Done. */
275	spec->regcomp = 1;
276
277	return 0;
278}
279
280
281static int process_line(struct selabel_handle *rec,
282			const char *path, const char *prefix,
283			char *line_buf, int pass, unsigned lineno)
284{
285	int items, len;
286	char buf1[BUFSIZ], buf2[BUFSIZ], buf3[BUFSIZ];
287	char *buf_p, *regex = buf1, *type = buf2, *context = buf3;
288	struct saved_data *data = (struct saved_data *)rec->data;
289	spec_t *spec_arr = data->spec_arr;
290	unsigned int nspec = data->nspec;
291
292	len = strlen(line_buf);
293	if (line_buf[len - 1] == '\n')
294		line_buf[len - 1] = 0;
295	buf_p = line_buf;
296	while (isspace(*buf_p))
297		buf_p++;
298	/* Skip comment lines and empty lines. */
299	if (*buf_p == '#' || *buf_p == 0)
300		return 0;
301	items = sscanf(line_buf, "%255s %255s %255s", regex, type, context);
302	if (items < 2) {
303		selinux_log(SELINUX_WARNING,
304			    "%s:  line %d is missing fields, skipping\n", path,
305			    lineno);
306		return 0;
307	} else if (items == 2) {
308		/* The type field is optional. */
309		context = type;
310		type = NULL;
311	}
312
313	len = get_stem_from_spec(regex);
314	if (len && prefix && strncmp(prefix, regex, len)) {
315		/* Stem of regex does not match requested prefix, discard. */
316		return 0;
317	}
318
319	if (pass == 1) {
320		/* On the second pass, process and store the specification in spec. */
321		const char *errbuf = NULL;
322		spec_arr[nspec].stem_id = find_stem_from_spec(data, regex);
323		spec_arr[nspec].regex_str = strdup(regex);
324		if (!spec_arr[nspec].regex_str) {
325			selinux_log(SELINUX_WARNING,
326				   "%s:  out of memory at line %d on regex %s\n",
327				   path, lineno, regex);
328			return -1;
329
330		}
331		if (rec->validating && compile_regex(data, &spec_arr[nspec], &errbuf)) {
332			selinux_log(SELINUX_WARNING,
333				   "%s:  line %d has invalid regex %s:  %s\n",
334				   path, lineno, regex,
335				   (errbuf ? errbuf : "out of memory"));
336		}
337
338		/* Convert the type string to a mode format */
339		spec_arr[nspec].mode = 0;
340		if (!type)
341			goto skip_type;
342		spec_arr[nspec].type_str = strdup(type);
343		len = strlen(type);
344		if (type[0] != '-' || len != 2) {
345			selinux_log(SELINUX_WARNING,
346				    "%s:  line %d has invalid file type %s\n",
347				    path, lineno, type);
348			return 0;
349		}
350		switch (type[1]) {
351		case 'b':
352			spec_arr[nspec].mode = S_IFBLK;
353			break;
354		case 'c':
355			spec_arr[nspec].mode = S_IFCHR;
356			break;
357		case 'd':
358			spec_arr[nspec].mode = S_IFDIR;
359			break;
360		case 'p':
361			spec_arr[nspec].mode = S_IFIFO;
362			break;
363		case 'l':
364			spec_arr[nspec].mode = S_IFLNK;
365			break;
366		case 's':
367			spec_arr[nspec].mode = S_IFSOCK;
368			break;
369		case '-':
370			spec_arr[nspec].mode = S_IFREG;
371			break;
372		default:
373			selinux_log(SELINUX_WARNING,
374				    "%s:  line %d has invalid file type %s\n",
375				    path, lineno, type);
376			return 0;
377		}
378
379	skip_type:
380		spec_arr[nspec].lr.ctx_raw = strdup(context);
381
382		if (strcmp(context, "<<none>>") && rec->validating) {
383			if (selabel_validate(rec, &spec_arr[nspec].lr) < 0) {
384				selinux_log(SELINUX_WARNING,
385					    "%s:  line %d has invalid context %s\n",
386					    path, lineno, spec_arr[nspec].lr.ctx_raw);
387			}
388		}
389
390		/* Determine if specification has
391		 * any meta characters in the RE */
392		spec_hasMetaChars(&spec_arr[nspec]);
393	}
394
395	data->nspec = ++nspec;
396	return 0;
397}
398
399static int init(struct selabel_handle *rec, const struct selinux_opt *opts,
400		unsigned n)
401{
402	struct saved_data *data = (struct saved_data *)rec->data;
403	const char *path = NULL;
404	const char *prefix = NULL;
405	FILE *fp;
406	FILE *localfp = NULL;
407	FILE *homedirfp = NULL;
408	char local_path[PATH_MAX + 1];
409	char homedir_path[PATH_MAX + 1];
410	char line_buf[BUFSIZ];
411	unsigned int lineno, pass, i, j, maxnspec;
412	spec_t *spec_copy = NULL;
413	int status = -1, baseonly = 0;
414	struct stat sb;
415
416	/* Process arguments */
417	while (n--)
418		switch(opts[n].type) {
419		case SELABEL_OPT_PATH:
420			path = opts[n].value;
421			break;
422		case SELABEL_OPT_SUBSET:
423			prefix = opts[n].value;
424			break;
425		case SELABEL_OPT_BASEONLY:
426			baseonly = !!opts[n].value;
427			break;
428		}
429
430	/* Open the specification file. */
431	if ((fp = fopen(path, "r")) == NULL)
432		return -1;
433
434	if (fstat(fileno(fp), &sb) < 0)
435		return -1;
436	if (!S_ISREG(sb.st_mode)) {
437		errno = EINVAL;
438		return -1;
439	}
440
441	if (!baseonly) {
442		snprintf(homedir_path, sizeof(homedir_path), "%s.homedirs",
443			 path);
444		homedirfp = fopen(homedir_path, "r");
445
446		snprintf(local_path, sizeof(local_path), "%s.local", path);
447		localfp = fopen(local_path, "r");
448	}
449
450	/*
451	 * Perform two passes over the specification file.
452	 * The first pass counts the number of specifications and
453	 * performs simple validation of the input.  At the end
454	 * of the first pass, the spec array is allocated.
455	 * The second pass performs detailed validation of the input
456	 * and fills in the spec array.
457	 */
458	maxnspec = UINT_MAX / sizeof(spec_t);
459	for (pass = 0; pass < 2; pass++) {
460		lineno = 0;
461		data->nspec = 0;
462		data->ncomp = 0;
463		while (fgets(line_buf, sizeof line_buf - 1, fp)
464		       && data->nspec < maxnspec) {
465			if (process_line(rec, path, prefix, line_buf,
466					 pass, ++lineno) != 0)
467				goto finish;
468		}
469		if (pass == 1) {
470			status = nodups_specs(data, path);
471			if (status)
472				goto finish;
473		}
474		lineno = 0;
475		if (homedirfp)
476			while (fgets(line_buf, sizeof line_buf - 1, homedirfp)
477			       && data->nspec < maxnspec) {
478				if (process_line
479				    (rec, homedir_path, prefix,
480				     line_buf, pass, ++lineno) != 0)
481					goto finish;
482			}
483
484		lineno = 0;
485		if (localfp)
486			while (fgets(line_buf, sizeof line_buf - 1, localfp)
487			       && data->nspec < maxnspec) {
488				if (process_line
489				    (rec, local_path, prefix, line_buf,
490				     pass, ++lineno) != 0)
491					goto finish;
492			}
493
494		if (pass == 0) {
495			if (data->nspec == 0) {
496				status = 0;
497				goto finish;
498			}
499			if (NULL == (data->spec_arr =
500				     (spec_t *) malloc(sizeof(spec_t) * data->nspec)))
501				goto finish;
502			memset(data->spec_arr, 0, sizeof(spec_t)*data->nspec);
503			maxnspec = data->nspec;
504			rewind(fp);
505			if (homedirfp)
506				rewind(homedirfp);
507			if (localfp)
508				rewind(localfp);
509		}
510	}
511
512	/* Move exact pathname specifications to the end. */
513	spec_copy = (spec_t *) malloc(sizeof(spec_t) * data->nspec);
514	if (!spec_copy)
515		goto finish;
516	j = 0;
517	for (i = 0; i < data->nspec; i++)
518		if (data->spec_arr[i].hasMetaChars)
519			memcpy(&spec_copy[j++],
520			       &data->spec_arr[i], sizeof(spec_t));
521	for (i = 0; i < data->nspec; i++)
522		if (!data->spec_arr[i].hasMetaChars)
523			memcpy(&spec_copy[j++],
524			       &data->spec_arr[i], sizeof(spec_t));
525	free(data->spec_arr);
526	data->spec_arr = spec_copy;
527
528	status = 0;
529finish:
530	fclose(fp);
531	if (data->spec_arr != spec_copy)
532		free(data->spec_arr);
533	if (homedirfp)
534		fclose(homedirfp);
535	if (localfp)
536		fclose(localfp);
537	return status;
538}
539
540/*
541 * Backend interface routines
542 */
543static void closef(struct selabel_handle *rec)
544{
545	struct saved_data *data = (struct saved_data *)rec->data;
546	struct spec *spec;
547	struct stem *stem;
548	unsigned int i;
549
550	for (i = 0; i < data->nspec; i++) {
551		spec = &data->spec_arr[i];
552		free(spec->regex_str);
553		free(spec->type_str);
554		free(spec->lr.ctx_raw);
555		free(spec->lr.ctx_trans);
556		if (spec->regcomp) {
557			pcre_free(spec->regex);
558			pcre_free_study(spec->sd);
559		}
560	}
561
562	for (i = 0; i < (unsigned int)data->num_stems; i++) {
563		stem = &data->stem_arr[i];
564		free(stem->buf);
565	}
566
567	if (data->spec_arr)
568		free(data->spec_arr);
569	if (data->stem_arr)
570		free(data->stem_arr);
571
572	free(data);
573}
574
575static spec_t *lookup_common(struct selabel_handle *rec,
576			     const char *key,
577			     int type,
578			     bool partial)
579{
580	struct saved_data *data = (struct saved_data *)rec->data;
581	spec_t *spec_arr = data->spec_arr;
582	int i, rc, file_stem, pcre_options = 0;
583	mode_t mode = (mode_t)type;
584	const char *buf;
585	spec_t *ret = NULL;
586	char *clean_key = NULL;
587	const char *prev_slash, *next_slash;
588	unsigned int sofar = 0;
589
590	if (!data->nspec) {
591		errno = ENOENT;
592		goto finish;
593	}
594
595	/* Remove duplicate slashes */
596	if ((next_slash = strstr(key, "//"))) {
597		clean_key = (char *) malloc(strlen(key) + 1);
598		if (!clean_key)
599			goto finish;
600		prev_slash = key;
601		while (next_slash) {
602			memcpy(clean_key + sofar, prev_slash, next_slash - prev_slash);
603			sofar += next_slash - prev_slash;
604			prev_slash = next_slash + 1;
605			next_slash = strstr(prev_slash, "//");
606		}
607		strcpy(clean_key + sofar, prev_slash);
608		key = clean_key;
609	}
610
611	buf = key;
612	file_stem = find_stem_from_file(data, &buf);
613	mode &= S_IFMT;
614
615	if (partial)
616		pcre_options |= PCRE_PARTIAL_SOFT;
617
618	/*
619	 * Check for matching specifications in reverse order, so that
620	 * the last matching specification is used.
621	 */
622	for (i = data->nspec - 1; i >= 0; i--) {
623		/* if the spec in question matches no stem or has the same
624		 * stem as the file AND if the spec in question has no mode
625		 * specified or if the mode matches the file mode then we do
626		 * a regex check        */
627		if ((spec_arr[i].stem_id == -1
628		     || spec_arr[i].stem_id == file_stem)
629		    && (!mode || !spec_arr[i].mode
630			|| mode == spec_arr[i].mode)) {
631			if (compile_regex(data, &spec_arr[i], NULL) < 0)
632				goto finish;
633			if (spec_arr[i].stem_id == -1)
634				rc = pcre_exec(spec_arr[i].regex, spec_arr[i].sd, key, strlen(key), 0, pcre_options, NULL, 0);
635			else
636				rc = pcre_exec(spec_arr[i].regex, spec_arr[i].sd, buf, strlen(buf), 0, pcre_options, NULL, 0);
637
638			if (rc == 0) {
639				spec_arr[i].matches++;
640				break;
641			} else if (partial && rc == PCRE_ERROR_PARTIAL)
642				break;
643
644			if (rc == PCRE_ERROR_NOMATCH)
645				continue;
646			/* else it's an error */
647			goto finish;
648		}
649	}
650
651	if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 0) {
652		/* No matching specification. */
653		errno = ENOENT;
654		goto finish;
655	}
656
657	ret = &spec_arr[i];
658
659finish:
660	free(clean_key);
661	return ret;
662}
663
664static struct selabel_lookup_rec *lookup(struct selabel_handle *rec,
665					 const char *key, int type)
666{
667	spec_t *spec;
668	spec = lookup_common(rec, key, type, false);
669	if (spec)
670		return &spec->lr;
671	return NULL;
672}
673
674static bool partial_match(struct selabel_handle *rec, const char *key)
675{
676	return lookup_common(rec, key, 0, true) ? true : false;
677}
678
679static struct selabel_lookup_rec *lookup_best_match(struct selabel_handle *rec,
680						    const char *key,
681						    const char **aliases,
682						    int type)
683{
684	size_t n, i;
685	int best = -1;
686	spec_t **specs;
687	size_t prefix_len = 0;
688	struct selabel_lookup_rec *lr = NULL;
689
690	if (!aliases || !aliases[0])
691		return lookup(rec, key, type);
692
693	for (n = 0; aliases[n]; n++)
694		;
695
696	specs = calloc(n+1, sizeof(spec_t *));
697	if (!specs)
698		return NULL;
699	specs[0] = lookup_common(rec, key, type, false);
700	if (specs[0]) {
701		if (!specs[0]->hasMetaChars) {
702			/* exact match on key */
703			lr = &specs[0]->lr;
704			goto out;
705		}
706		best = 0;
707		prefix_len = specs[0]->prefix_len;
708	}
709	for (i = 1; i <= n; i++) {
710		specs[i] = lookup_common(rec, aliases[i-1], type, false);
711		if (specs[i]) {
712			if (!specs[i]->hasMetaChars) {
713				/* exact match on alias */
714				lr = &specs[i]->lr;
715				goto out;
716			}
717			if (specs[i]->prefix_len > prefix_len) {
718				best = i;
719				prefix_len = specs[i]->prefix_len;
720			}
721		}
722	}
723
724	if (best >= 0) {
725		/* longest fixed prefix match on key or alias */
726		lr = &specs[best]->lr;
727	}
728
729out:
730	free(specs);
731	return lr;
732}
733
734static void stats(struct selabel_handle *rec)
735{
736	struct saved_data *data = (struct saved_data *)rec->data;
737	unsigned int i, nspec = data->nspec;
738	spec_t *spec_arr = data->spec_arr;
739
740	for (i = 0; i < nspec; i++) {
741		if (spec_arr[i].matches == 0) {
742			if (spec_arr[i].type_str) {
743				selinux_log(SELINUX_WARNING,
744				    "Warning!  No matches for (%s, %s, %s)\n",
745				    spec_arr[i].regex_str,
746				    spec_arr[i].type_str,
747				    spec_arr[i].lr.ctx_raw);
748			} else {
749				selinux_log(SELINUX_WARNING,
750				    "Warning!  No matches for (%s, %s)\n",
751				    spec_arr[i].regex_str,
752				    spec_arr[i].lr.ctx_raw);
753			}
754		}
755	}
756}
757
758int selabel_file_init(struct selabel_handle *rec, const struct selinux_opt *opts,
759		      unsigned nopts)
760{
761	struct saved_data *data;
762
763	data = (struct saved_data *)malloc(sizeof(*data));
764	if (!data)
765		return -1;
766	memset(data, 0, sizeof(*data));
767
768	rec->data = data;
769	rec->func_close = &closef;
770	rec->func_stats = &stats;
771	rec->func_lookup = &lookup;
772	rec->func_partial_match = &partial_match;
773	rec->func_lookup_best_match = &lookup_best_match;
774
775	return init(rec, opts, nopts);
776}
777