• 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 		if (strcmp(context, "<<none>>") && rec->validating) {
379 			if (selabel_validate(rec, &spec_arr[nspec].lr) < 0) {
380 				selinux_log(SELINUX_WARNING,
381 					    "%s:  line %d has invalid context %s\n",
382 					    path, lineno, spec_arr[nspec].lr.ctx_raw);
383 			}
384 		}
385 
386 		/* Determine if specification has
387 		 * any meta characters in the RE */
388 		spec_hasMetaChars(&spec_arr[nspec]);
389 	}
390 
391 	data->nspec = ++nspec;
392 	return 0;
393 }
394 
init(struct selabel_handle * rec,const struct selinux_opt * opts,unsigned n)395 static int init(struct selabel_handle *rec, const 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[BUFSIZ];
407 	unsigned int lineno, pass, i, j, maxnspec;
408 	spec_t *spec_copy = NULL;
409 	int status = -1, baseonly = 0;
410 	struct stat sb;
411 
412 	/* Process arguments */
413 	while (n--)
414 		switch(opts[n].type) {
415 		case SELABEL_OPT_PATH:
416 			path = opts[n].value;
417 			break;
418 		case SELABEL_OPT_SUBSET:
419 			prefix = opts[n].value;
420 			break;
421 		case SELABEL_OPT_BASEONLY:
422 			baseonly = !!opts[n].value;
423 			break;
424 		}
425 
426 	/* Open the specification file. */
427 	if ((fp = fopen(path, "r")) == NULL)
428 		return -1;
429 
430 	if (fstat(fileno(fp), &sb) < 0)
431 		return -1;
432 	if (!S_ISREG(sb.st_mode)) {
433 		errno = EINVAL;
434 		return -1;
435 	}
436 
437 	if (!baseonly) {
438 		snprintf(homedir_path, sizeof(homedir_path), "%s.homedirs",
439 			 path);
440 		homedirfp = fopen(homedir_path, "r");
441 
442 		snprintf(local_path, sizeof(local_path), "%s.local", path);
443 		localfp = fopen(local_path, "r");
444 	}
445 
446 	/*
447 	 * Perform two passes over the specification file.
448 	 * The first pass counts the number of specifications and
449 	 * performs simple validation of the input.  At the end
450 	 * of the first pass, the spec array is allocated.
451 	 * The second pass performs detailed validation of the input
452 	 * and fills in the spec array.
453 	 */
454 	maxnspec = UINT_MAX / sizeof(spec_t);
455 	for (pass = 0; pass < 2; pass++) {
456 		lineno = 0;
457 		data->nspec = 0;
458 		data->ncomp = 0;
459 		while (fgets(line_buf, sizeof line_buf - 1, fp)
460 		       && data->nspec < maxnspec) {
461 			if (process_line(rec, path, prefix, line_buf,
462 					 pass, ++lineno) != 0)
463 				goto finish;
464 		}
465 		if (pass == 1) {
466 			status = nodups_specs(data, path);
467 			if (status)
468 				goto finish;
469 		}
470 		lineno = 0;
471 		if (homedirfp)
472 			while (fgets(line_buf, sizeof line_buf - 1, homedirfp)
473 			       && data->nspec < maxnspec) {
474 				if (process_line
475 				    (rec, homedir_path, prefix,
476 				     line_buf, pass, ++lineno) != 0)
477 					goto finish;
478 			}
479 
480 		lineno = 0;
481 		if (localfp)
482 			while (fgets(line_buf, sizeof line_buf - 1, localfp)
483 			       && data->nspec < maxnspec) {
484 				if (process_line
485 				    (rec, local_path, prefix, line_buf,
486 				     pass, ++lineno) != 0)
487 					goto finish;
488 			}
489 
490 		if (pass == 0) {
491 			if (data->nspec == 0) {
492 				status = 0;
493 				goto finish;
494 			}
495 			if (NULL == (data->spec_arr =
496 				     malloc(sizeof(spec_t) * data->nspec)))
497 				goto finish;
498 			memset(data->spec_arr, 0, sizeof(spec_t)*data->nspec);
499 			maxnspec = data->nspec;
500 			rewind(fp);
501 			if (homedirfp)
502 				rewind(homedirfp);
503 			if (localfp)
504 				rewind(localfp);
505 		}
506 	}
507 
508 	/* Move exact pathname specifications to the end. */
509 	spec_copy = malloc(sizeof(spec_t) * data->nspec);
510 	if (!spec_copy)
511 		goto finish;
512 	j = 0;
513 	for (i = 0; i < data->nspec; i++)
514 		if (data->spec_arr[i].hasMetaChars)
515 			memcpy(&spec_copy[j++],
516 			       &data->spec_arr[i], sizeof(spec_t));
517 	for (i = 0; i < data->nspec; i++)
518 		if (!data->spec_arr[i].hasMetaChars)
519 			memcpy(&spec_copy[j++],
520 			       &data->spec_arr[i], sizeof(spec_t));
521 	free(data->spec_arr);
522 	data->spec_arr = spec_copy;
523 
524 	status = 0;
525 finish:
526 	fclose(fp);
527 	if (data->spec_arr != spec_copy)
528 		free(data->spec_arr);
529 	if (homedirfp)
530 		fclose(homedirfp);
531 	if (localfp)
532 		fclose(localfp);
533 	return status;
534 }
535 
536 /*
537  * Backend interface routines
538  */
closef(struct selabel_handle * rec)539 static void closef(struct selabel_handle *rec)
540 {
541 	struct saved_data *data = (struct saved_data *)rec->data;
542 	struct spec *spec;
543 	struct stem *stem;
544 	unsigned int i;
545 
546 	for (i = 0; i < data->nspec; i++) {
547 		spec = &data->spec_arr[i];
548 		free(spec->regex_str);
549 		free(spec->type_str);
550 		free(spec->lr.ctx_raw);
551 		free(spec->lr.ctx_trans);
552 		if (spec->regcomp)
553 			regfree(&spec->regex);
554 	}
555 
556 	for (i = 0; i < (unsigned int)data->num_stems; i++) {
557 		stem = &data->stem_arr[i];
558 		free(stem->buf);
559 	}
560 
561 	if (data->spec_arr)
562 		free(data->spec_arr);
563 	if (data->stem_arr)
564 		free(data->stem_arr);
565 
566 	free(data);
567 }
568 
lookup(struct selabel_handle * rec,const char * key,int type)569 static struct selabel_lookup_rec *lookup(struct selabel_handle *rec,
570 					 const char *key, int type)
571 {
572 	struct saved_data *data = (struct saved_data *)rec->data;
573 	spec_t *spec_arr = data->spec_arr;
574 	int i, rc, file_stem;
575 	mode_t mode = (mode_t)type;
576 	const char *buf;
577 	struct selabel_lookup_rec *ret = NULL;
578 	char *clean_key = NULL;
579 	const char *prev_slash, *next_slash;
580 	unsigned int sofar = 0;
581 
582 	if (!data->nspec) {
583 		errno = ENOENT;
584 		goto finish;
585 	}
586 
587 	/* Remove duplicate slashes */
588 	if ((next_slash = strstr(key, "//"))) {
589 		clean_key = malloc(strlen(key) + 1);
590 		if (!clean_key)
591 			goto finish;
592 		prev_slash = key;
593 		while (next_slash) {
594 			memcpy(clean_key + sofar, prev_slash, next_slash - prev_slash);
595 			sofar += next_slash - prev_slash;
596 			prev_slash = next_slash + 1;
597 			next_slash = strstr(prev_slash, "//");
598 		}
599 		strcpy(clean_key + sofar, prev_slash);
600 		key = clean_key;
601 	}
602 
603 	buf = key;
604 	file_stem = find_stem_from_file(data, &buf);
605 	mode &= S_IFMT;
606 
607 	/*
608 	 * Check for matching specifications in reverse order, so that
609 	 * the last matching specification is used.
610 	 */
611 	for (i = data->nspec - 1; i >= 0; i--) {
612 		/* if the spec in question matches no stem or has the same
613 		 * stem as the file AND if the spec in question has no mode
614 		 * specified or if the mode matches the file mode then we do
615 		 * a regex check        */
616 		if ((spec_arr[i].stem_id == -1
617 		     || spec_arr[i].stem_id == file_stem)
618 		    && (!mode || !spec_arr[i].mode
619 			|| mode == spec_arr[i].mode)) {
620 			if (compile_regex(data, &spec_arr[i], NULL) < 0)
621 				goto finish;
622 			if (spec_arr[i].stem_id == -1)
623 				rc = regexec(&spec_arr[i].regex, key, 0, 0, 0);
624 			else
625 				rc = regexec(&spec_arr[i].regex, buf, 0, 0, 0);
626 
627 			if (rc == 0) {
628 				spec_arr[i].matches++;
629 				break;
630 			}
631 			if (rc == REG_NOMATCH)
632 				continue;
633 			/* else it's an error */
634 			goto finish;
635 		}
636 	}
637 
638 	if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 0) {
639 		/* No matching specification. */
640 		errno = ENOENT;
641 		goto finish;
642 	}
643 
644 	ret = &spec_arr[i].lr;
645 
646 finish:
647 	free(clean_key);
648 	return ret;
649 }
650 
stats(struct selabel_handle * rec)651 static void stats(struct selabel_handle *rec)
652 {
653 	struct saved_data *data = (struct saved_data *)rec->data;
654 	unsigned int i, nspec = data->nspec;
655 	spec_t *spec_arr = data->spec_arr;
656 
657 	for (i = 0; i < nspec; i++) {
658 		if (spec_arr[i].matches == 0) {
659 			if (spec_arr[i].type_str) {
660 				selinux_log(SELINUX_WARNING,
661 				    "Warning!  No matches for (%s, %s, %s)\n",
662 				    spec_arr[i].regex_str,
663 				    spec_arr[i].type_str,
664 				    spec_arr[i].lr.ctx_raw);
665 			} else {
666 				selinux_log(SELINUX_WARNING,
667 				    "Warning!  No matches for (%s, %s)\n",
668 				    spec_arr[i].regex_str,
669 				    spec_arr[i].lr.ctx_raw);
670 			}
671 		}
672 	}
673 }
674 
selabel_file_init(struct selabel_handle * rec,const struct selinux_opt * opts,unsigned nopts)675 int selabel_file_init(struct selabel_handle *rec, const struct selinux_opt *opts,
676 		      unsigned nopts)
677 {
678 	struct saved_data *data;
679 
680 	data = (struct saved_data *)malloc(sizeof(*data));
681 	if (!data)
682 		return -1;
683 	memset(data, 0, sizeof(*data));
684 
685 	rec->data = data;
686 	rec->func_close = &closef;
687 	rec->func_stats = &stats;
688 	rec->func_lookup = &lookup;
689 
690 	return init(rec, opts, nopts);
691 }
692