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