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