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