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