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