• 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,struct selinux_opt * opts,unsigned n)387 static int init(struct selabel_handle *rec, 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 		regfree(&spec->regex);
545 	}
546 
547 	for (i = 0; i < (unsigned int)data->num_stems; i++) {
548 		stem = &data->stem_arr[i];
549 		free(stem->buf);
550 	}
551 
552 	if (data->spec_arr)
553 		free(data->spec_arr);
554 	if (data->stem_arr)
555 		free(data->stem_arr);
556 
557 	free(data);
558 }
559 
lookup(struct selabel_handle * rec,const char * key,int type)560 static struct selabel_lookup_rec *lookup(struct selabel_handle *rec,
561 					 const char *key, int type)
562 {
563 	struct saved_data *data = (struct saved_data *)rec->data;
564 	spec_t *spec_arr = data->spec_arr;
565 	int i, rc, file_stem;
566 	mode_t mode = (mode_t)type;
567 	const char *buf;
568 	struct selabel_lookup_rec *ret = NULL;
569 	char *clean_key = NULL;
570 	const char *prev_slash, *next_slash;
571 	unsigned int sofar = 0;
572 
573 	if (!data->nspec) {
574 		errno = ENOENT;
575 		goto finish;
576 	}
577 
578 	/* Remove duplicate slashes */
579 	if ((next_slash = strstr(key, "//"))) {
580 		clean_key = malloc(strlen(key) + 1);
581 		if (!clean_key)
582 			goto finish;
583 		prev_slash = key;
584 		while (next_slash) {
585 			memcpy(clean_key + sofar, prev_slash, next_slash - prev_slash);
586 			sofar += next_slash - prev_slash;
587 			prev_slash = next_slash + 1;
588 			next_slash = strstr(prev_slash, "//");
589 		}
590 		strcpy(clean_key + sofar, prev_slash);
591 		key = clean_key;
592 	}
593 
594 	buf = key;
595 	file_stem = find_stem_from_file(data, &buf);
596 	mode &= S_IFMT;
597 
598 	/*
599 	 * Check for matching specifications in reverse order, so that
600 	 * the last matching specification is used.
601 	 */
602 	for (i = data->nspec - 1; i >= 0; i--) {
603 		/* if the spec in question matches no stem or has the same
604 		 * stem as the file AND if the spec in question has no mode
605 		 * specified or if the mode matches the file mode then we do
606 		 * a regex check        */
607 		if ((spec_arr[i].stem_id == -1
608 		     || spec_arr[i].stem_id == file_stem)
609 		    && (!mode || !spec_arr[i].mode
610 			|| mode == spec_arr[i].mode)) {
611 			if (compile_regex(data, &spec_arr[i], NULL) < 0)
612 				goto finish;
613 			if (spec_arr[i].stem_id == -1)
614 				rc = regexec(&spec_arr[i].regex, key, 0, 0, 0);
615 			else
616 				rc = regexec(&spec_arr[i].regex, buf, 0, 0, 0);
617 
618 			if (rc == 0) {
619 				spec_arr[i].matches++;
620 				break;
621 			}
622 			if (rc == REG_NOMATCH)
623 				continue;
624 			/* else it's an error */
625 			goto finish;
626 		}
627 	}
628 
629 	if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 0) {
630 		/* No matching specification. */
631 		errno = ENOENT;
632 		goto finish;
633 	}
634 
635 	ret = &spec_arr[i].lr;
636 
637 finish:
638 	free(clean_key);
639 	return ret;
640 }
641 
stats(struct selabel_handle * rec)642 static void stats(struct selabel_handle *rec)
643 {
644 	struct saved_data *data = (struct saved_data *)rec->data;
645 	unsigned int i, nspec = data->nspec;
646 	spec_t *spec_arr = data->spec_arr;
647 
648 	for (i = 0; i < nspec; i++) {
649 		if (spec_arr[i].matches == 0) {
650 			if (spec_arr[i].type_str) {
651 				selinux_log(SELINUX_WARNING,
652 				    "Warning!  No matches for (%s, %s, %s)\n",
653 				    spec_arr[i].regex_str,
654 				    spec_arr[i].type_str,
655 				    spec_arr[i].lr.ctx_raw);
656 			} else {
657 				selinux_log(SELINUX_WARNING,
658 				    "Warning!  No matches for (%s, %s)\n",
659 				    spec_arr[i].regex_str,
660 				    spec_arr[i].lr.ctx_raw);
661 			}
662 		}
663 	}
664 }
665 
selabel_file_init(struct selabel_handle * rec,struct selinux_opt * opts,unsigned nopts)666 int selabel_file_init(struct selabel_handle *rec, struct selinux_opt *opts,
667 		      unsigned nopts)
668 {
669 	struct saved_data *data;
670 
671 	data = (struct saved_data *)malloc(sizeof(*data));
672 	if (!data)
673 		return -1;
674 	memset(data, 0, sizeof(*data));
675 
676 	rec->data = data;
677 	rec->func_close = &closef;
678 	rec->func_stats = &stats;
679 	rec->func_lookup = &lookup;
680 
681 	return init(rec, opts, nopts);
682 }
683