• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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. */
26 typedef 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 */
39 typedef struct stem {
40 	char *buf;
41 	int len;
42 } stem_t;
43 
44 /* Our stored configuration */
45 struct 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 */
get_stem_from_spec(const char * const buf)64 static 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 */
get_stem_from_file_name(const char * const buf)80 static 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). */
find_stem_from_spec(struct saved_data * data,const char * buf)92 static 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. */
find_stem_from_file(struct saved_data * data,const char ** buf)127 static 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  */
nodups_specs(struct saved_data * data,const char * path)147 static 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. */
spec_hasMetaChars(struct spec * spec)184 static 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 
compile_regex(struct saved_data * data,spec_t * spec,char ** errbuf)224 static 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 
process_line(struct selabel_handle * rec,const char * path,const char * prefix,char * line_buf,int pass,unsigned lineno)277 static 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 
init(struct selabel_handle * rec,const struct selinux_opt * opts,unsigned n)387 static 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;
517 finish:
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  */
closef(struct selabel_handle * rec)531 static 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 
lookup(struct selabel_handle * rec,const char * key,int type)561 static 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 
638 finish:
639 	free(clean_key);
640 	return ret;
641 }
642 
stats(struct selabel_handle * rec)643 static 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 
selabel_file_init(struct selabel_handle * rec,const struct selinux_opt * opts,unsigned nopts)667 int 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