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