label_file.c revision a4af847dc6f52688a25bb0323ff3b84b13dded67
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 <regex.h>
20#include "callbacks.h"
21#include "label_internal.h"
22
23/*
24 * Internals, mostly moved over from matchpathcon.c
25 */
26
27/* A file security context specification. */
28typedef struct spec {
29	struct selabel_lookup_rec lr;	/* holds contexts for lookup result */
30	char *regex_str;	/* regular expession string for diagnostics */
31	char *type_str;		/* type string for diagnostic messages */
32	regex_t regex;		/* compiled regular expression */
33	char regcomp;           /* regex_str has been compiled to regex */
34	mode_t mode;		/* mode format value */
35	int matches;		/* number of matching pathnames */
36	int hasMetaChars;	/* regular expression has meta-chars */
37	int stem_id;		/* indicates which stem-compression item */
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 = 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 = 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					COMPAT_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					COMPAT_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	int 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
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			return;
214		case '\\':	/* skip the next character */
215			c++;
216			break;
217		default:
218			break;
219
220		}
221		c++;
222	}
223	return;
224}
225
226static int compile_regex(struct saved_data *data, spec_t *spec, char **errbuf)
227{
228	char *reg_buf, *anchored_regex, *cp;
229	stem_t *stem_arr = data->stem_arr;
230	size_t len;
231	int regerr;
232
233	if (spec->regcomp)
234		return 0; /* already done */
235
236	data->ncomp++; /* how many compiled regexes required */
237
238	/* Skip the fixed stem. */
239	reg_buf = spec->regex_str;
240	if (spec->stem_id >= 0)
241		reg_buf += stem_arr[spec->stem_id].len;
242
243	/* Anchor the regular expression. */
244	len = strlen(reg_buf);
245	cp = anchored_regex = malloc(len + 3);
246	if (!anchored_regex)
247		return -1;
248	/* Create ^...$ regexp.  */
249	*cp++ = '^';
250	cp = mempcpy(cp, reg_buf, len);
251	*cp++ = '$';
252	*cp = '\0';
253
254	/* Compile the regular expression. */
255	regerr = regcomp(&spec->regex, anchored_regex,
256			 REG_EXTENDED | REG_NOSUB);
257	if (regerr != 0) {
258		size_t errsz = 0;
259		errsz = regerror(regerr, &spec->regex, NULL, 0);
260		if (errsz && errbuf)
261			*errbuf = malloc(errsz);
262		if (errbuf && *errbuf)
263			(void)regerror(regerr, &spec->regex,
264				       *errbuf, errsz);
265
266		free(anchored_regex);
267		return -1;
268	}
269	free(anchored_regex);
270
271	/* Done. */
272	spec->regcomp = 1;
273
274	return 0;
275}
276
277
278static int process_line(struct selabel_handle *rec,
279			const char *path, const char *prefix,
280			char *line_buf, int pass, unsigned lineno)
281{
282	int items, len;
283	char *buf_p, *regex, *type, *context;
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, "%as %as %as", &regex, &type, &context);
298	if (items < 2) {
299		COMPAT_LOG(SELINUX_WARNING,
300			    "%s:  line %d is missing fields, skipping\n", path,
301			    lineno);
302		if (items == 1)
303			free(regex);
304		return 0;
305	} else if (items == 2) {
306		/* The type field is optional. */
307		free(context);
308		context = type;
309		type = 0;
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		free(regex);
316		free(type);
317		free(context);
318		return 0;
319	}
320
321	if (pass == 1) {
322		/* On the second pass, process and store the specification in spec. */
323		char *errbuf = NULL;
324		spec_arr[nspec].stem_id = find_stem_from_spec(data, regex);
325		spec_arr[nspec].regex_str = regex;
326		if (rec->validating && compile_regex(data, &spec_arr[nspec], &errbuf)) {
327			COMPAT_LOG(SELINUX_WARNING,
328				   "%s:  line %d has invalid regex %s:  %s\n",
329				   path, lineno, regex,
330				   (errbuf ? errbuf : "out of memory"));
331		}
332
333		/* Convert the type string to a mode format */
334		spec_arr[nspec].type_str = type;
335		spec_arr[nspec].mode = 0;
336		if (!type)
337			goto skip_type;
338		len = strlen(type);
339		if (type[0] != '-' || len != 2) {
340			COMPAT_LOG(SELINUX_WARNING,
341				    "%s:  line %d has invalid file type %s\n",
342				    path, lineno, type);
343			return 0;
344		}
345		switch (type[1]) {
346		case 'b':
347			spec_arr[nspec].mode = S_IFBLK;
348			break;
349		case 'c':
350			spec_arr[nspec].mode = S_IFCHR;
351			break;
352		case 'd':
353			spec_arr[nspec].mode = S_IFDIR;
354			break;
355		case 'p':
356			spec_arr[nspec].mode = S_IFIFO;
357			break;
358		case 'l':
359			spec_arr[nspec].mode = S_IFLNK;
360			break;
361		case 's':
362			spec_arr[nspec].mode = S_IFSOCK;
363			break;
364		case '-':
365			spec_arr[nspec].mode = S_IFREG;
366			break;
367		default:
368			COMPAT_LOG(SELINUX_WARNING,
369				    "%s:  line %d has invalid file type %s\n",
370				    path, lineno, type);
371			return 0;
372		}
373
374	skip_type:
375		spec_arr[nspec].lr.ctx_raw = context;
376
377		/* Determine if specification has
378		 * any meta characters in the RE */
379		spec_hasMetaChars(&spec_arr[nspec]);
380
381		if (strcmp(context, "<<none>>") && rec->validating)
382			compat_validate(rec, &spec_arr[nspec].lr, path, lineno);
383	}
384
385	data->nspec = ++nspec;
386	if (pass == 0) {
387		free(regex);
388		if (type)
389			free(type);
390		free(context);
391	}
392	return 0;
393}
394
395static int init(struct selabel_handle *rec, struct selinux_opt *opts,
396		unsigned n)
397{
398	struct saved_data *data = (struct saved_data *)rec->data;
399	const char *path = NULL;
400	const char *prefix = NULL;
401	FILE *fp;
402	FILE *localfp = NULL;
403	FILE *homedirfp = NULL;
404	char local_path[PATH_MAX + 1];
405	char homedir_path[PATH_MAX + 1];
406	char *line_buf = NULL;
407	size_t line_len = 0;
408	unsigned int lineno, pass, i, j, maxnspec;
409	spec_t *spec_copy = NULL;
410	int status = -1, baseonly = 0;
411	struct stat sb;
412
413	/* Process arguments */
414	while (n--)
415		switch(opts[n].type) {
416		case SELABEL_OPT_PATH:
417			path = opts[n].value;
418			break;
419		case SELABEL_OPT_SUBSET:
420			prefix = opts[n].value;
421			break;
422		case SELABEL_OPT_BASEONLY:
423			baseonly = !!opts[n].value;
424			break;
425		}
426
427	/* Open the specification file. */
428	if (!path)
429		path = selinux_file_context_path();
430	if ((fp = fopen(path, "r")) == NULL)
431		return -1;
432	__fsetlocking(fp, FSETLOCKING_BYCALLER);
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		if (homedirfp != NULL)
446			__fsetlocking(homedirfp, FSETLOCKING_BYCALLER);
447
448		snprintf(local_path, sizeof(local_path), "%s.local", path);
449		localfp = fopen(local_path, "r");
450		if (localfp != NULL)
451			__fsetlocking(localfp, FSETLOCKING_BYCALLER);
452	}
453
454	/*
455	 * Perform two passes over the specification file.
456	 * The first pass counts the number of specifications and
457	 * performs simple validation of the input.  At the end
458	 * of the first pass, the spec array is allocated.
459	 * The second pass performs detailed validation of the input
460	 * and fills in the spec array.
461	 */
462	maxnspec = UINT_MAX / sizeof(spec_t);
463	for (pass = 0; pass < 2; pass++) {
464		lineno = 0;
465		data->nspec = 0;
466		data->ncomp = 0;
467		while (getline(&line_buf, &line_len, fp) > 0
468		       && data->nspec < maxnspec) {
469			if (process_line(rec, path, prefix, line_buf,
470					 pass, ++lineno) != 0)
471				goto finish;
472		}
473		if (pass == 1) {
474			status = nodups_specs(data, path);
475			if (status)
476				goto finish;
477		}
478		lineno = 0;
479		if (homedirfp)
480			while (getline(&line_buf, &line_len, homedirfp) > 0
481			       && data->nspec < maxnspec) {
482				if (process_line
483				    (rec, homedir_path, prefix,
484				     line_buf, pass, ++lineno) != 0)
485					goto finish;
486			}
487
488		lineno = 0;
489		if (localfp)
490			while (getline(&line_buf, &line_len, localfp) > 0
491			       && data->nspec < maxnspec) {
492				if (process_line
493				    (rec, local_path, prefix, line_buf,
494				     pass, ++lineno) != 0)
495					goto finish;
496			}
497
498		if (pass == 0) {
499			if (data->nspec == 0) {
500				status = 0;
501				goto finish;
502			}
503			if (NULL == (data->spec_arr =
504				     malloc(sizeof(spec_t) * data->nspec)))
505				goto finish;
506			memset(data->spec_arr, 0, sizeof(spec_t)*data->nspec);
507			maxnspec = data->nspec;
508			rewind(fp);
509			if (homedirfp)
510				rewind(homedirfp);
511			if (localfp)
512				rewind(localfp);
513		}
514	}
515	free(line_buf);
516
517	/* Move exact pathname specifications to the end. */
518	spec_copy = malloc(sizeof(spec_t) * data->nspec);
519	if (!spec_copy)
520		goto finish;
521	j = 0;
522	for (i = 0; i < data->nspec; i++)
523		if (data->spec_arr[i].hasMetaChars)
524			memcpy(&spec_copy[j++],
525			       &data->spec_arr[i], sizeof(spec_t));
526	for (i = 0; i < data->nspec; i++)
527		if (!data->spec_arr[i].hasMetaChars)
528			memcpy(&spec_copy[j++],
529			       &data->spec_arr[i], sizeof(spec_t));
530	free(data->spec_arr);
531	data->spec_arr = spec_copy;
532
533	status = 0;
534finish:
535	fclose(fp);
536	if (data->spec_arr != spec_copy)
537		free(data->spec_arr);
538	if (homedirfp)
539		fclose(homedirfp);
540	if (localfp)
541		fclose(localfp);
542	return status;
543}
544
545/*
546 * Backend interface routines
547 */
548static void close(struct selabel_handle *rec)
549{
550	struct saved_data *data = (struct saved_data *)rec->data;
551	struct spec *spec;
552	struct stem *stem;
553	unsigned int i;
554
555	for (i = 0; i < data->nspec; i++) {
556		spec = &data->spec_arr[i];
557		free(spec->regex_str);
558		free(spec->type_str);
559		free(spec->lr.ctx_raw);
560		free(spec->lr.ctx_trans);
561		regfree(&spec->regex);
562	}
563
564	for (i = 0; i < (unsigned int)data->num_stems; i++) {
565		stem = &data->stem_arr[i];
566		free(stem->buf);
567	}
568
569	if (data->spec_arr)
570		free(data->spec_arr);
571	if (data->stem_arr)
572		free(data->stem_arr);
573
574	free(data);
575}
576
577static struct selabel_lookup_rec *lookup(struct selabel_handle *rec,
578					 const char *key, int type)
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;
583	mode_t mode = (mode_t)type;
584	const char *buf = key;
585
586	if (!data->nspec) {
587		errno = ENOENT;
588		return NULL;
589	}
590
591	file_stem = find_stem_from_file(data, &buf);
592	mode &= S_IFMT;
593
594	/*
595	 * Check for matching specifications in reverse order, so that
596	 * the last matching specification is used.
597	 */
598	for (i = data->nspec - 1; i >= 0; i--) {
599		/* if the spec in question matches no stem or has the same
600		 * stem as the file AND if the spec in question has no mode
601		 * specified or if the mode matches the file mode then we do
602		 * a regex check        */
603		if ((spec_arr[i].stem_id == -1
604		     || spec_arr[i].stem_id == file_stem)
605		    && (!mode || !spec_arr[i].mode
606			|| mode == spec_arr[i].mode)) {
607			if (compile_regex(data, &spec_arr[i], NULL) < 0)
608				return NULL;
609			if (spec_arr[i].stem_id == -1)
610				rc = regexec(&spec_arr[i].regex, key, 0, 0, 0);
611			else
612				rc = regexec(&spec_arr[i].regex, buf, 0, 0, 0);
613
614			if (rc == 0) {
615				spec_arr[i].matches++;
616				break;
617			}
618			if (rc == REG_NOMATCH)
619				continue;
620			/* else it's an error */
621			return NULL;
622		}
623	}
624
625	if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 0) {
626		/* No matching specification. */
627		errno = ENOENT;
628		return NULL;
629	}
630
631	return &spec_arr[i].lr;
632}
633
634static void stats(struct selabel_handle *rec)
635{
636	struct saved_data *data = (struct saved_data *)rec->data;
637	unsigned int i, nspec = data->nspec;
638	spec_t *spec_arr = data->spec_arr;
639
640	for (i = 0; i < nspec; i++) {
641		if (spec_arr[i].matches == 0) {
642			if (spec_arr[i].type_str) {
643				COMPAT_LOG(SELINUX_WARNING,
644				    "Warning!  No matches for (%s, %s, %s)\n",
645				    spec_arr[i].regex_str,
646				    spec_arr[i].type_str,
647				    spec_arr[i].lr.ctx_raw);
648			} else {
649				COMPAT_LOG(SELINUX_WARNING,
650				    "Warning!  No matches for (%s, %s)\n",
651				    spec_arr[i].regex_str,
652				    spec_arr[i].lr.ctx_raw);
653			}
654		}
655	}
656}
657
658int selabel_file_init(struct selabel_handle *rec, struct selinux_opt *opts,
659		      unsigned nopts)
660{
661	struct saved_data *data;
662
663	data = (struct saved_data *)malloc(sizeof(*data));
664	if (!data)
665		return -1;
666	memset(data, 0, sizeof(*data));
667
668	rec->data = data;
669	rec->func_close = &close;
670	rec->func_stats = &stats;
671	rec->func_lookup = &lookup;
672
673	return init(rec, opts, nopts);
674}
675