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