blob: 937e509022bdc066e13d469cbf30949462a14945 [file] [log] [blame]
Joshua Brindle13cd4c82008-08-19 15:30:36 -04001/*
2 * File contexts backend for labeling system
3 *
4 * Author : Eamon Walsh <ewalsh@tycho.nsa.gov>
5 * Author : Stephen Smalley <sds@tycho.nsa.gov>
6 *
7 * This library derived in part from setfiles and the setfiles.pl script
8 * developed by Secure Computing Corporation.
9 */
10
11#include <fcntl.h>
12#include <stdarg.h>
13#include <string.h>
14#include <stdio.h>
15#include <stdio_ext.h>
16#include <ctype.h>
17#include <errno.h>
18#include <limits.h>
19#include <regex.h>
Stephen Smalley070505f2010-02-16 09:29:31 -050020#include <sys/types.h>
21#include <sys/stat.h>
22#include <unistd.h>
Joshua Brindle13cd4c82008-08-19 15:30:36 -040023#include "callbacks.h"
24#include "label_internal.h"
25
26/*
27 * Internals, mostly moved over from matchpathcon.c
28 */
29
30/* A file security context specification. */
31typedef struct spec {
32 struct selabel_lookup_rec lr; /* holds contexts for lookup result */
33 char *regex_str; /* regular expession string for diagnostics */
34 char *type_str; /* type string for diagnostic messages */
35 regex_t regex; /* compiled regular expression */
36 char regcomp; /* regex_str has been compiled to regex */
37 mode_t mode; /* mode format value */
38 int matches; /* number of matching pathnames */
39 int hasMetaChars; /* regular expression has meta-chars */
40 int stem_id; /* indicates which stem-compression item */
41} spec_t;
42
43/* A regular expression stem */
44typedef struct stem {
45 char *buf;
46 int len;
47} stem_t;
48
49/* Our stored configuration */
50struct saved_data {
51 /*
52 * The array of specifications, initially in the same order as in
53 * the specification file. Sorting occurs based on hasMetaChars.
54 */
55 spec_t *spec_arr;
56 unsigned int nspec;
57 unsigned int ncomp;
58
59 /*
60 * The array of regular expression stems.
61 */
62 stem_t *stem_arr;
63 int num_stems;
64 int alloc_stems;
65};
66
67/* Return the length of the text that can be considered the stem, returns 0
68 * if there is no identifiable stem */
69static int get_stem_from_spec(const char *const buf)
70{
71 const char *tmp = strchr(buf + 1, '/');
72 const char *ind;
73
74 if (!tmp)
75 return 0;
76
77 for (ind = buf; ind < tmp; ind++) {
78 if (strchr(".^$?*+|[({", (int)*ind))
79 return 0;
80 }
81 return tmp - buf;
82}
83
84/* return the length of the text that is the stem of a file name */
85static int get_stem_from_file_name(const char *const buf)
86{
87 const char *tmp = strchr(buf + 1, '/');
88
89 if (!tmp)
90 return 0;
91 return tmp - buf;
92}
93
94/* find the stem of a file spec, returns the index into stem_arr for a new
95 * or existing stem, (or -1 if there is no possible stem - IE for a file in
96 * the root directory or a regex that is too complex for us). */
97static int find_stem_from_spec(struct saved_data *data, const char *buf)
98{
99 int i, num = data->num_stems;
100 int stem_len = get_stem_from_spec(buf);
101
102 if (!stem_len)
103 return -1;
104 for (i = 0; i < num; i++) {
105 if (stem_len == data->stem_arr[i].len
106 && !strncmp(buf, data->stem_arr[i].buf, stem_len))
107 return i;
108 }
109 if (data->alloc_stems == num) {
110 stem_t *tmp_arr;
111 data->alloc_stems = data->alloc_stems * 2 + 16;
112 tmp_arr = realloc(data->stem_arr,
113 sizeof(stem_t) * data->alloc_stems);
114 if (!tmp_arr)
115 return -1;
116 data->stem_arr = tmp_arr;
117 }
118 data->stem_arr[num].len = stem_len;
119 data->stem_arr[num].buf = malloc(stem_len + 1);
120 if (!data->stem_arr[num].buf)
121 return -1;
122 memcpy(data->stem_arr[num].buf, buf, stem_len);
123 data->stem_arr[num].buf[stem_len] = '\0';
124 data->num_stems++;
125 buf += stem_len;
126 return num;
127}
128
129/* find the stem of a file name, returns the index into stem_arr (or -1 if
130 * there is no match - IE for a file in the root directory or a regex that is
131 * too complex for us). Makes buf point to the text AFTER the stem. */
132static int find_stem_from_file(struct saved_data *data, const char **buf)
133{
134 int i;
135 int stem_len = get_stem_from_file_name(*buf);
136
137 if (!stem_len)
138 return -1;
139 for (i = 0; i < data->num_stems; i++) {
140 if (stem_len == data->stem_arr[i].len
141 && !strncmp(*buf, data->stem_arr[i].buf, stem_len)) {
142 *buf += stem_len;
143 return i;
144 }
145 }
146 return -1;
147}
148
149/*
150 * Warn about duplicate specifications.
151 */
152static int nodups_specs(struct saved_data *data, const char *path)
153{
154 int rc = 0;
155 unsigned int ii, jj;
156 struct spec *curr_spec, *spec_arr = data->spec_arr;
157
158 for (ii = 0; ii < data->nspec; ii++) {
159 curr_spec = &spec_arr[ii];
160 for (jj = ii + 1; jj < data->nspec; jj++) {
161 if ((!strcmp
162 (spec_arr[jj].regex_str, curr_spec->regex_str))
163 && (!spec_arr[jj].mode || !curr_spec->mode
164 || spec_arr[jj].mode == curr_spec->mode)) {
165 rc = -1;
166 errno = EINVAL;
167 if (strcmp
168 (spec_arr[jj].lr.ctx_raw,
169 curr_spec->lr.ctx_raw)) {
170 COMPAT_LOG
171 (SELINUX_ERROR,
172 "%s: Multiple different specifications for %s (%s and %s).\n",
173 path, curr_spec->regex_str,
174 spec_arr[jj].lr.ctx_raw,
175 curr_spec->lr.ctx_raw);
176 } else {
177 COMPAT_LOG
178 (SELINUX_ERROR,
179 "%s: Multiple same specifications for %s.\n",
180 path, curr_spec->regex_str);
181 }
182 }
183 }
184 }
185 return rc;
186}
187
188/* Determine if the regular expression specification has any meta characters. */
189static void spec_hasMetaChars(struct spec *spec)
190{
191 char *c;
192 int len;
193 char *end;
194
195 c = spec->regex_str;
196 len = strlen(spec->regex_str);
197 end = c + len;
198
199 spec->hasMetaChars = 0;
200
201 /* Look at each character in the RE specification string for a
202 * meta character. Return when any meta character reached. */
203 while (c != end) {
204 switch (*c) {
205 case '.':
206 case '^':
207 case '$':
208 case '?':
209 case '*':
210 case '+':
211 case '|':
212 case '[':
213 case '(':
214 case '{':
215 spec->hasMetaChars = 1;
216 return;
217 case '\\': /* skip the next character */
218 c++;
219 break;
220 default:
221 break;
222
223 }
224 c++;
225 }
226 return;
227}
228
229static int compile_regex(struct saved_data *data, spec_t *spec, char **errbuf)
230{
231 char *reg_buf, *anchored_regex, *cp;
232 stem_t *stem_arr = data->stem_arr;
233 size_t len;
234 int regerr;
235
236 if (spec->regcomp)
237 return 0; /* already done */
238
239 data->ncomp++; /* how many compiled regexes required */
240
241 /* Skip the fixed stem. */
242 reg_buf = spec->regex_str;
243 if (spec->stem_id >= 0)
244 reg_buf += stem_arr[spec->stem_id].len;
245
246 /* Anchor the regular expression. */
247 len = strlen(reg_buf);
248 cp = anchored_regex = malloc(len + 3);
249 if (!anchored_regex)
250 return -1;
251 /* Create ^...$ regexp. */
252 *cp++ = '^';
253 cp = mempcpy(cp, reg_buf, len);
254 *cp++ = '$';
255 *cp = '\0';
256
257 /* Compile the regular expression. */
258 regerr = regcomp(&spec->regex, anchored_regex,
259 REG_EXTENDED | REG_NOSUB);
260 if (regerr != 0) {
261 size_t errsz = 0;
262 errsz = regerror(regerr, &spec->regex, NULL, 0);
263 if (errsz && errbuf)
264 *errbuf = malloc(errsz);
265 if (errbuf && *errbuf)
266 (void)regerror(regerr, &spec->regex,
267 *errbuf, errsz);
268
269 free(anchored_regex);
270 return -1;
271 }
272 free(anchored_regex);
273
274 /* Done. */
275 spec->regcomp = 1;
276
277 return 0;
278}
279
280
281static int process_line(struct selabel_handle *rec,
282 const char *path, const char *prefix,
283 char *line_buf, int pass, unsigned lineno)
284{
285 int items, len;
286 char *buf_p, *regex, *type, *context;
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, "%as %as %as", &regex, &type, &context);
301 if (items < 2) {
302 COMPAT_LOG(SELINUX_WARNING,
303 "%s: line %d is missing fields, skipping\n", path,
304 lineno);
Hiroshi Shinjia4af8472009-04-11 14:41:51 -0400305 if (items == 1)
306 free(regex);
Joshua Brindle13cd4c82008-08-19 15:30:36 -0400307 return 0;
308 } else if (items == 2) {
309 /* The type field is optional. */
310 free(context);
311 context = type;
312 type = 0;
313 }
314
315 len = get_stem_from_spec(regex);
316 if (len && prefix && strncmp(prefix, regex, len)) {
317 /* Stem of regex does not match requested prefix, discard. */
318 free(regex);
319 free(type);
320 free(context);
321 return 0;
322 }
323
324 if (pass == 1) {
325 /* On the second pass, process and store the specification in spec. */
326 char *errbuf = NULL;
327 spec_arr[nspec].stem_id = find_stem_from_spec(data, regex);
328 spec_arr[nspec].regex_str = regex;
329 if (rec->validating && compile_regex(data, &spec_arr[nspec], &errbuf)) {
330 COMPAT_LOG(SELINUX_WARNING,
331 "%s: line %d has invalid regex %s: %s\n",
332 path, lineno, regex,
333 (errbuf ? errbuf : "out of memory"));
334 }
335
336 /* Convert the type string to a mode format */
337 spec_arr[nspec].type_str = type;
338 spec_arr[nspec].mode = 0;
339 if (!type)
340 goto skip_type;
341 len = strlen(type);
342 if (type[0] != '-' || len != 2) {
343 COMPAT_LOG(SELINUX_WARNING,
344 "%s: line %d has invalid file type %s\n",
345 path, lineno, type);
346 return 0;
347 }
348 switch (type[1]) {
349 case 'b':
350 spec_arr[nspec].mode = S_IFBLK;
351 break;
352 case 'c':
353 spec_arr[nspec].mode = S_IFCHR;
354 break;
355 case 'd':
356 spec_arr[nspec].mode = S_IFDIR;
357 break;
358 case 'p':
359 spec_arr[nspec].mode = S_IFIFO;
360 break;
361 case 'l':
362 spec_arr[nspec].mode = S_IFLNK;
363 break;
364 case 's':
365 spec_arr[nspec].mode = S_IFSOCK;
366 break;
367 case '-':
368 spec_arr[nspec].mode = S_IFREG;
369 break;
370 default:
371 COMPAT_LOG(SELINUX_WARNING,
372 "%s: line %d has invalid file type %s\n",
373 path, lineno, type);
374 return 0;
375 }
376
377 skip_type:
378 spec_arr[nspec].lr.ctx_raw = context;
379
380 /* Determine if specification has
381 * any meta characters in the RE */
382 spec_hasMetaChars(&spec_arr[nspec]);
383
384 if (strcmp(context, "<<none>>") && rec->validating)
385 compat_validate(rec, &spec_arr[nspec].lr, path, lineno);
386 }
387
388 data->nspec = ++nspec;
389 if (pass == 0) {
390 free(regex);
391 if (type)
392 free(type);
393 free(context);
394 }
395 return 0;
396}
397
398static int init(struct selabel_handle *rec, 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 = NULL;
410 size_t line_len = 0;
411 unsigned int lineno, pass, i, j, maxnspec;
412 spec_t *spec_copy = NULL;
413 int status = -1, baseonly = 0;
414 struct stat sb;
415
416 /* Process arguments */
417 while (n--)
418 switch(opts[n].type) {
419 case SELABEL_OPT_PATH:
420 path = opts[n].value;
421 break;
422 case SELABEL_OPT_SUBSET:
423 prefix = opts[n].value;
424 break;
425 case SELABEL_OPT_BASEONLY:
426 baseonly = !!opts[n].value;
427 break;
428 }
429
430 /* Open the specification file. */
431 if (!path)
432 path = selinux_file_context_path();
433 if ((fp = fopen(path, "r")) == NULL)
434 return -1;
435 __fsetlocking(fp, FSETLOCKING_BYCALLER);
436
437 if (fstat(fileno(fp), &sb) < 0)
438 return -1;
439 if (!S_ISREG(sb.st_mode)) {
440 errno = EINVAL;
441 return -1;
442 }
443
444 if (!baseonly) {
445 snprintf(homedir_path, sizeof(homedir_path), "%s.homedirs",
446 path);
447 homedirfp = fopen(homedir_path, "r");
448 if (homedirfp != NULL)
449 __fsetlocking(homedirfp, FSETLOCKING_BYCALLER);
450
451 snprintf(local_path, sizeof(local_path), "%s.local", path);
452 localfp = fopen(local_path, "r");
453 if (localfp != NULL)
454 __fsetlocking(localfp, FSETLOCKING_BYCALLER);
455 }
456
457 /*
458 * Perform two passes over the specification file.
459 * The first pass counts the number of specifications and
460 * performs simple validation of the input. At the end
461 * of the first pass, the spec array is allocated.
462 * The second pass performs detailed validation of the input
463 * and fills in the spec array.
464 */
465 maxnspec = UINT_MAX / sizeof(spec_t);
466 for (pass = 0; pass < 2; pass++) {
467 lineno = 0;
468 data->nspec = 0;
469 data->ncomp = 0;
470 while (getline(&line_buf, &line_len, fp) > 0
471 && data->nspec < maxnspec) {
472 if (process_line(rec, path, prefix, line_buf,
473 pass, ++lineno) != 0)
474 goto finish;
475 }
476 if (pass == 1) {
477 status = nodups_specs(data, path);
478 if (status)
479 goto finish;
480 }
481 lineno = 0;
482 if (homedirfp)
483 while (getline(&line_buf, &line_len, homedirfp) > 0
484 && data->nspec < maxnspec) {
485 if (process_line
486 (rec, homedir_path, prefix,
487 line_buf, pass, ++lineno) != 0)
488 goto finish;
489 }
490
491 lineno = 0;
492 if (localfp)
493 while (getline(&line_buf, &line_len, localfp) > 0
494 && data->nspec < maxnspec) {
495 if (process_line
496 (rec, local_path, prefix, line_buf,
497 pass, ++lineno) != 0)
498 goto finish;
499 }
500
501 if (pass == 0) {
502 if (data->nspec == 0) {
503 status = 0;
504 goto finish;
505 }
506 if (NULL == (data->spec_arr =
507 malloc(sizeof(spec_t) * data->nspec)))
508 goto finish;
509 memset(data->spec_arr, 0, sizeof(spec_t)*data->nspec);
510 maxnspec = data->nspec;
511 rewind(fp);
512 if (homedirfp)
513 rewind(homedirfp);
514 if (localfp)
515 rewind(localfp);
516 }
517 }
518 free(line_buf);
519
520 /* Move exact pathname specifications to the end. */
521 spec_copy = malloc(sizeof(spec_t) * data->nspec);
522 if (!spec_copy)
523 goto finish;
524 j = 0;
525 for (i = 0; i < data->nspec; i++)
526 if (data->spec_arr[i].hasMetaChars)
527 memcpy(&spec_copy[j++],
528 &data->spec_arr[i], sizeof(spec_t));
529 for (i = 0; i < data->nspec; i++)
530 if (!data->spec_arr[i].hasMetaChars)
531 memcpy(&spec_copy[j++],
532 &data->spec_arr[i], sizeof(spec_t));
533 free(data->spec_arr);
534 data->spec_arr = spec_copy;
535
536 status = 0;
537finish:
538 fclose(fp);
539 if (data->spec_arr != spec_copy)
540 free(data->spec_arr);
541 if (homedirfp)
542 fclose(homedirfp);
543 if (localfp)
544 fclose(localfp);
545 return status;
546}
547
548/*
549 * Backend interface routines
550 */
Stephen Smalley070505f2010-02-16 09:29:31 -0500551static void closef(struct selabel_handle *rec)
Joshua Brindle13cd4c82008-08-19 15:30:36 -0400552{
553 struct saved_data *data = (struct saved_data *)rec->data;
554 struct spec *spec;
555 struct stem *stem;
556 unsigned int i;
557
558 for (i = 0; i < data->nspec; i++) {
559 spec = &data->spec_arr[i];
560 free(spec->regex_str);
561 free(spec->type_str);
562 free(spec->lr.ctx_raw);
563 free(spec->lr.ctx_trans);
564 regfree(&spec->regex);
565 }
566
567 for (i = 0; i < (unsigned int)data->num_stems; i++) {
568 stem = &data->stem_arr[i];
569 free(stem->buf);
570 }
571
572 if (data->spec_arr)
573 free(data->spec_arr);
574 if (data->stem_arr)
575 free(data->stem_arr);
576
577 free(data);
578}
579
580static struct selabel_lookup_rec *lookup(struct selabel_handle *rec,
581 const char *key, int type)
582{
583 struct saved_data *data = (struct saved_data *)rec->data;
584 spec_t *spec_arr = data->spec_arr;
585 int i, rc, file_stem;
586 mode_t mode = (mode_t)type;
Chad Sellers8f007922010-06-02 14:39:36 -0400587 const char *buf;
588 struct selabel_lookup_rec *ret = NULL;
589 char *clean_key = NULL;
590 const char *prev_slash, *next_slash;
591 unsigned int sofar = 0;
Joshua Brindle13cd4c82008-08-19 15:30:36 -0400592
593 if (!data->nspec) {
594 errno = ENOENT;
Chad Sellers8f007922010-06-02 14:39:36 -0400595 goto finish;
Joshua Brindle13cd4c82008-08-19 15:30:36 -0400596 }
597
Chad Sellers8f007922010-06-02 14:39:36 -0400598 /* Remove duplicate slashes */
599 if ((next_slash = strstr(key, "//"))) {
600 clean_key = malloc(strlen(key) + 1);
601 if (!clean_key)
602 goto finish;
603 prev_slash = key;
604 while (next_slash) {
605 memcpy(clean_key + sofar, prev_slash, next_slash - prev_slash);
606 sofar += next_slash - prev_slash;
607 prev_slash = next_slash + 1;
608 next_slash = strstr(prev_slash, "//");
609 }
610 strcpy(clean_key + sofar, prev_slash);
611 key = clean_key;
612 }
613
614 buf = key;
Joshua Brindle13cd4c82008-08-19 15:30:36 -0400615 file_stem = find_stem_from_file(data, &buf);
616 mode &= S_IFMT;
617
618 /*
619 * Check for matching specifications in reverse order, so that
620 * the last matching specification is used.
621 */
622 for (i = data->nspec - 1; i >= 0; i--) {
623 /* if the spec in question matches no stem or has the same
624 * stem as the file AND if the spec in question has no mode
625 * specified or if the mode matches the file mode then we do
626 * a regex check */
627 if ((spec_arr[i].stem_id == -1
628 || spec_arr[i].stem_id == file_stem)
629 && (!mode || !spec_arr[i].mode
630 || mode == spec_arr[i].mode)) {
631 if (compile_regex(data, &spec_arr[i], NULL) < 0)
Chad Sellers8f007922010-06-02 14:39:36 -0400632 goto finish;
Joshua Brindle13cd4c82008-08-19 15:30:36 -0400633 if (spec_arr[i].stem_id == -1)
634 rc = regexec(&spec_arr[i].regex, key, 0, 0, 0);
635 else
636 rc = regexec(&spec_arr[i].regex, buf, 0, 0, 0);
637
638 if (rc == 0) {
639 spec_arr[i].matches++;
640 break;
641 }
642 if (rc == REG_NOMATCH)
643 continue;
644 /* else it's an error */
Chad Sellers8f007922010-06-02 14:39:36 -0400645 goto finish;
Joshua Brindle13cd4c82008-08-19 15:30:36 -0400646 }
647 }
648
649 if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 0) {
650 /* No matching specification. */
651 errno = ENOENT;
Chad Sellers8f007922010-06-02 14:39:36 -0400652 goto finish;
Joshua Brindle13cd4c82008-08-19 15:30:36 -0400653 }
654
Chad Sellers8f007922010-06-02 14:39:36 -0400655 ret = &spec_arr[i].lr;
656
657finish:
658 free(clean_key);
659 return ret;
Joshua Brindle13cd4c82008-08-19 15:30:36 -0400660}
661
662static void stats(struct selabel_handle *rec)
663{
664 struct saved_data *data = (struct saved_data *)rec->data;
665 unsigned int i, nspec = data->nspec;
666 spec_t *spec_arr = data->spec_arr;
667
668 for (i = 0; i < nspec; i++) {
669 if (spec_arr[i].matches == 0) {
670 if (spec_arr[i].type_str) {
671 COMPAT_LOG(SELINUX_WARNING,
672 "Warning! No matches for (%s, %s, %s)\n",
673 spec_arr[i].regex_str,
674 spec_arr[i].type_str,
675 spec_arr[i].lr.ctx_raw);
676 } else {
677 COMPAT_LOG(SELINUX_WARNING,
678 "Warning! No matches for (%s, %s)\n",
679 spec_arr[i].regex_str,
680 spec_arr[i].lr.ctx_raw);
681 }
682 }
683 }
684}
685
686int selabel_file_init(struct selabel_handle *rec, struct selinux_opt *opts,
687 unsigned nopts)
688{
689 struct saved_data *data;
690
691 data = (struct saved_data *)malloc(sizeof(*data));
692 if (!data)
693 return -1;
694 memset(data, 0, sizeof(*data));
695
696 rec->data = data;
Stephen Smalley070505f2010-02-16 09:29:31 -0500697 rec->func_close = &closef;
Joshua Brindle13cd4c82008-08-19 15:30:36 -0400698 rec->func_stats = &stats;
699 rec->func_lookup = &lookup;
700
701 return init(rec, opts, nopts);
702}