1 /*
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 <assert.h>
12 #include <fcntl.h>
13 #include <stdarg.h>
14 #include <string.h>
15 #include <stdio.h>
16 #include <stdio_ext.h>
17 #include <ctype.h>
18 #include <errno.h>
19 #include <limits.h>
20 #include <stdint.h>
21 #include <pcre.h>
22
23 #include <linux/limits.h>
24
25 #include <sys/mman.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <unistd.h>
29 #include "callbacks.h"
30 #include "label_internal.h"
31 #include "label_file.h"
32
33 /*
34 * Internals, mostly moved over from matchpathcon.c
35 */
36
37 /* return the length of the text that is the stem of a file name */
get_stem_from_file_name(const char * const buf)38 static int get_stem_from_file_name(const char *const buf)
39 {
40 const char *tmp = strchr(buf + 1, '/');
41
42 if (!tmp)
43 return 0;
44 return tmp - buf;
45 }
46
47 /* find the stem of a file name, returns the index into stem_arr (or -1 if
48 * there is no match - IE for a file in the root directory or a regex that is
49 * too complex for us). Makes buf point to the text AFTER the stem. */
find_stem_from_file(struct saved_data * data,const char ** buf)50 static int find_stem_from_file(struct saved_data *data, const char **buf)
51 {
52 int i;
53 int stem_len = get_stem_from_file_name(*buf);
54
55 if (!stem_len)
56 return -1;
57 for (i = 0; i < data->num_stems; i++) {
58 if (stem_len == data->stem_arr[i].len
59 && !strncmp(*buf, data->stem_arr[i].buf, stem_len)) {
60 *buf += stem_len;
61 return i;
62 }
63 }
64 return -1;
65 }
66
67 /*
68 * Warn about duplicate specifications.
69 */
nodups_specs(struct saved_data * data,const char * path)70 static int nodups_specs(struct saved_data *data, const char *path)
71 {
72 int rc = 0;
73 unsigned int ii, jj;
74 struct spec *curr_spec, *spec_arr = data->spec_arr;
75
76 for (ii = 0; ii < data->nspec; ii++) {
77 curr_spec = &spec_arr[ii];
78 for (jj = ii + 1; jj < data->nspec; jj++) {
79 if ((!strcmp(spec_arr[jj].regex_str, curr_spec->regex_str))
80 && (!spec_arr[jj].mode || !curr_spec->mode
81 || spec_arr[jj].mode == curr_spec->mode)) {
82 rc = -1;
83 errno = EINVAL;
84 if (strcmp(spec_arr[jj].lr.ctx_raw, curr_spec->lr.ctx_raw)) {
85 COMPAT_LOG
86 (SELINUX_ERROR,
87 "%s: Multiple different specifications for %s (%s and %s).\n",
88 path, curr_spec->regex_str,
89 spec_arr[jj].lr.ctx_raw,
90 curr_spec->lr.ctx_raw);
91 } else {
92 COMPAT_LOG
93 (SELINUX_ERROR,
94 "%s: Multiple same specifications for %s.\n",
95 path, curr_spec->regex_str);
96 }
97 }
98 }
99 }
100 return rc;
101 }
102
compile_regex(struct saved_data * data,struct spec * spec,const char ** errbuf)103 static int compile_regex(struct saved_data *data, struct spec *spec, const char **errbuf)
104 {
105 const char *tmperrbuf;
106 char *reg_buf, *anchored_regex, *cp;
107 struct stem *stem_arr = data->stem_arr;
108 size_t len;
109 int erroff;
110
111 if (spec->regcomp)
112 return 0; /* already done */
113
114 /* Skip the fixed stem. */
115 reg_buf = spec->regex_str;
116 if (spec->stem_id >= 0)
117 reg_buf += stem_arr[spec->stem_id].len;
118
119 /* Anchor the regular expression. */
120 len = strlen(reg_buf);
121 cp = anchored_regex = malloc(len + 3);
122 if (!anchored_regex)
123 return -1;
124
125 /* Create ^...$ regexp. */
126 *cp++ = '^';
127 cp = mempcpy(cp, reg_buf, len);
128 *cp++ = '$';
129 *cp = '\0';
130
131 /* Compile the regular expression. */
132 spec->regex = pcre_compile(anchored_regex, PCRE_DOTALL, &tmperrbuf, &erroff, NULL);
133 free(anchored_regex);
134 if (!spec->regex) {
135 if (errbuf)
136 *errbuf=tmperrbuf;
137 return -1;
138 }
139
140 spec->sd = pcre_study(spec->regex, 0, &tmperrbuf);
141 if (!spec->sd && tmperrbuf) {
142 if (errbuf)
143 *errbuf=tmperrbuf;
144 return -1;
145 }
146
147 /* Done. */
148 spec->regcomp = 1;
149
150 return 0;
151 }
152
process_line(struct selabel_handle * rec,const char * path,const char * prefix,char * line_buf,unsigned lineno)153 static int process_line(struct selabel_handle *rec,
154 const char *path, const char *prefix,
155 char *line_buf, unsigned lineno)
156 {
157 int items, len, rc;
158 char *buf_p, *regex, *type, *context;
159 struct saved_data *data = (struct saved_data *)rec->data;
160 struct spec *spec_arr;
161 unsigned int nspec = data->nspec;
162 const char *errbuf = NULL;
163
164 len = strlen(line_buf);
165 if (line_buf[len - 1] == '\n')
166 line_buf[len - 1] = 0;
167 buf_p = line_buf;
168 while (isspace(*buf_p))
169 buf_p++;
170 /* Skip comment lines and empty lines. */
171 if (*buf_p == '#' || *buf_p == 0)
172 return 0;
173 items = sscanf(line_buf, "%ms %ms %ms", ®ex, &type, &context);
174 if (items < 2) {
175 COMPAT_LOG(SELINUX_WARNING,
176 "%s: line %u is missing fields, skipping\n", path,
177 lineno);
178 if (items == 1)
179 free(regex);
180 return 0;
181 } else if (items == 2) {
182 /* The type field is optional. */
183 free(context);
184 context = type;
185 type = 0;
186 }
187
188 len = get_stem_from_spec(regex);
189 if (len && prefix && strncmp(prefix, regex, len)) {
190 /* Stem of regex does not match requested prefix, discard. */
191 free(regex);
192 free(type);
193 free(context);
194 return 0;
195 }
196
197 rc = grow_specs(data);
198 if (rc)
199 return rc;
200
201 spec_arr = data->spec_arr;
202
203 /* process and store the specification in spec. */
204 spec_arr[nspec].stem_id = find_stem_from_spec(data, regex);
205 spec_arr[nspec].regex_str = regex;
206 if (rec->validating && compile_regex(data, &spec_arr[nspec], &errbuf)) {
207 COMPAT_LOG(SELINUX_WARNING, "%s: line %u has invalid regex %s: %s\n",
208 path, lineno, regex, (errbuf ? errbuf : "out of memory"));
209 }
210
211 /* Convert the type string to a mode format */
212 spec_arr[nspec].type_str = type;
213 spec_arr[nspec].mode = 0;
214 if (type) {
215 mode_t mode = string_to_mode(type);
216 if (mode == (mode_t)-1) {
217 COMPAT_LOG(SELINUX_WARNING, "%s: line %u has invalid file type %s\n",
218 path, lineno, type);
219 mode = 0;
220 }
221 spec_arr[nspec].mode = mode;
222 }
223
224 spec_arr[nspec].lr.ctx_raw = context;
225
226 /* Determine if specification has
227 * any meta characters in the RE */
228 spec_hasMetaChars(&spec_arr[nspec]);
229
230 if (strcmp(context, "<<none>>") && rec->validating)
231 compat_validate(rec, &spec_arr[nspec].lr, path, lineno);
232
233 data->nspec = ++nspec;
234
235 return 0;
236 }
237
load_mmap(struct selabel_handle * rec,const char * path,struct stat * sb)238 static int load_mmap(struct selabel_handle *rec, const char *path, struct stat *sb)
239 {
240 struct saved_data *data = (struct saved_data *)rec->data;
241 char mmap_path[PATH_MAX + 1];
242 int mmapfd;
243 int rc;
244 struct stat mmap_stat;
245 char *addr;
246 size_t len;
247 int stem_map_len, *stem_map;
248 struct mmap_area *mmap_area;
249
250 uint32_t i;
251 uint32_t *magic;
252 uint32_t *section_len;
253 uint32_t *plen;
254
255 rc = snprintf(mmap_path, sizeof(mmap_path), "%s.bin", path);
256 if (rc >= (int)sizeof(mmap_path))
257 return -1;
258
259 mmapfd = open(mmap_path, O_RDONLY | O_CLOEXEC);
260 if (mmapfd < 0)
261 return -1;
262
263 rc = fstat(mmapfd, &mmap_stat);
264 if (rc < 0) {
265 close(mmapfd);
266 return -1;
267 }
268
269 /* if mmap is old, ignore it */
270 if (mmap_stat.st_mtime < sb->st_mtime) {
271 close(mmapfd);
272 return -1;
273 }
274
275 if (mmap_stat.st_mtime == sb->st_mtime &&
276 mmap_stat.st_mtim.tv_nsec < sb->st_mtim.tv_nsec) {
277 close(mmapfd);
278 return -1;
279 }
280
281 /* ok, read it in... */
282 len = mmap_stat.st_size;
283 len += (sysconf(_SC_PAGE_SIZE) - 1);
284 len &= ~(sysconf(_SC_PAGE_SIZE) - 1);
285
286 mmap_area = malloc(sizeof(*mmap_area));
287 if (!mmap_area) {
288 close(mmapfd);
289 return -1;
290 }
291
292 addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, mmapfd, 0);
293 close(mmapfd);
294 if (addr == MAP_FAILED) {
295 free(mmap_area);
296 perror("mmap");
297 return -1;
298 }
299
300 /* save where we mmap'd the file to cleanup on close() */
301 mmap_area->addr = addr;
302 mmap_area->len = len;
303 mmap_area->next = data->mmap_areas;
304 data->mmap_areas = mmap_area;
305
306 /* check if this looks like an fcontext file */
307 magic = (uint32_t *)addr;
308 if (*magic != SELINUX_MAGIC_COMPILED_FCONTEXT)
309 return -1;
310 addr += sizeof(uint32_t);
311
312 /* check if this version is higher than we understand */
313 section_len = (uint32_t *)addr;
314 if (*section_len > SELINUX_COMPILED_FCONTEXT_MAX_VERS)
315 return -1;
316 addr += sizeof(uint32_t);
317
318 if (*section_len >= SELINUX_COMPILED_FCONTEXT_PCRE_VERS) {
319 len = strlen(pcre_version());
320 plen = (uint32_t *)addr;
321 if (*plen > mmap_area->len)
322 return -1; /* runs off the end of the map */
323 if (len != *plen)
324 return -1; /* pcre version length mismatch */
325 addr += sizeof(uint32_t);
326 if (memcmp((char *)addr, pcre_version(), len))
327 return -1; /* pcre version content mismatch */
328 if (addr + *plen >= (char *)mmap_area->addr + mmap_area->len)
329 return -1; /* Buffer over-run */
330 addr += *plen;
331 }
332
333 /* allocate the stems_data array */
334 section_len = (uint32_t *)addr;
335 addr += sizeof(uint32_t);
336
337 /*
338 * map indexed by the stem # in the mmap file and contains the stem
339 * number in the data stem_arr
340 */
341 stem_map_len = *section_len;
342 stem_map = calloc(stem_map_len, sizeof(*stem_map));
343 if (!stem_map)
344 return -1;
345
346 for (i = 0; i < *section_len; i++) {
347 char *buf;
348 uint32_t stem_len;
349 int newid;
350
351 /* the length does not inlude the nul */
352 plen = (uint32_t *)addr;
353 addr += sizeof(uint32_t);
354
355 stem_len = *plen;
356 buf = (char *)addr;
357 addr += (stem_len + 1); // +1 is the nul
358
359 /* store the mapping between old and new */
360 newid = find_stem(data, buf, stem_len);
361 if (newid < 0) {
362 newid = store_stem(data, buf, stem_len);
363 if (newid < 0) {
364 rc = newid;
365 goto err;
366 }
367 data->stem_arr[newid].from_mmap = 1;
368 }
369 stem_map[i] = newid;
370 }
371
372 /* allocate the regex array */
373 section_len = (uint32_t *)addr;
374 addr += sizeof(*section_len);
375
376 for (i = 0; i < *section_len; i++) {
377 struct spec *spec;
378 int32_t stem_id;
379
380 rc = grow_specs(data);
381 if (rc < 0)
382 goto err;
383
384 spec = &data->spec_arr[data->nspec];
385 spec->from_mmap = 1;
386 spec->regcomp = 1;
387
388 plen = (uint32_t *)addr;
389 addr += sizeof(uint32_t);
390 rc = -1;
391 spec->lr.ctx_raw = strdup((char *)addr);
392 if (!spec->lr.ctx_raw)
393 goto err;
394
395 if (addr + *plen >= (char *)mmap_area->addr + mmap_area->len)
396 return -1;
397 addr += *plen;
398
399 plen = (uint32_t *)addr;
400 addr += sizeof(uint32_t);
401 spec->regex_str = (char *)addr;
402 if (addr + *plen >= (char *)mmap_area->addr + mmap_area->len)
403 return -1;
404 addr += *plen;
405
406 spec->mode = *(mode_t *)addr;
407 addr += sizeof(mode_t);
408
409 /* map the stem id from the mmap file to the data->stem_arr */
410 stem_id = *(int32_t *)addr;
411 if (stem_id == -1 || stem_id >= stem_map_len)
412 spec->stem_id = -1;
413 else
414 spec->stem_id = stem_map[stem_id];
415 addr += sizeof(int32_t);
416
417 /* retrieve the hasMetaChars bit */
418 spec->hasMetaChars = *(uint32_t *)addr;
419 addr += sizeof(uint32_t);
420
421 plen = (uint32_t *)addr;
422 addr += sizeof(uint32_t);
423 spec->regex = (pcre *)addr;
424 if (addr + *plen >= (char *)mmap_area->addr + mmap_area->len)
425 return -1;
426 addr += *plen;
427
428 plen = (uint32_t *)addr;
429 addr += sizeof(uint32_t);
430 spec->lsd.study_data = (void *)addr;
431 spec->lsd.flags |= PCRE_EXTRA_STUDY_DATA;
432 if (addr + *plen >= (char *)mmap_area->addr + mmap_area->len)
433 return -1;
434 addr += *plen;
435
436 data->nspec++;
437 }
438 /* win */
439 rc = 0;
440 err:
441 free(stem_map);
442
443 return rc;
444 }
445
process_file(const char * path,const char * suffix,struct selabel_handle * rec,const char * prefix)446 static int process_file(const char *path, const char *suffix, struct selabel_handle *rec, const char *prefix)
447 {
448 FILE *fp;
449 struct stat sb;
450 unsigned int lineno;
451 size_t line_len;
452 char *line_buf = NULL;
453 int rc;
454 char stack_path[PATH_MAX + 1];
455
456 /* append the path suffix if we have one */
457 if (suffix) {
458 rc = snprintf(stack_path, sizeof(stack_path), "%s.%s", path, suffix);
459 if (rc >= (int)sizeof(stack_path)) {
460 errno = ENAMETOOLONG;
461 return -1;
462 }
463 path = stack_path;
464 }
465
466 /* Open the specification file. */
467 if ((fp = fopen(path, "r")) == NULL)
468 return -1;
469 __fsetlocking(fp, FSETLOCKING_BYCALLER);
470
471 if (fstat(fileno(fp), &sb) < 0)
472 return -1;
473 if (!S_ISREG(sb.st_mode)) {
474 errno = EINVAL;
475 return -1;
476 }
477
478 rc = load_mmap(rec, path, &sb);
479 if (rc == 0)
480 goto out;
481
482 /*
483 * The do detailed validation of the input and fill the spec array
484 */
485 lineno = 0;
486 while (getline(&line_buf, &line_len, fp) > 0) {
487 rc = process_line(rec, path, prefix, line_buf, ++lineno);
488 if (rc)
489 return rc;
490 }
491 out:
492 free(line_buf);
493 fclose(fp);
494
495 return 0;
496 }
497
init(struct selabel_handle * rec,struct selinux_opt * opts,unsigned n)498 static int init(struct selabel_handle *rec, struct selinux_opt *opts,
499 unsigned n)
500 {
501 struct saved_data *data = (struct saved_data *)rec->data;
502 const char *path = NULL;
503 const char *prefix = NULL;
504 char subs_file[PATH_MAX + 1];
505 int status = -1, baseonly = 0;
506
507 /* Process arguments */
508 while (n--)
509 switch(opts[n].type) {
510 case SELABEL_OPT_PATH:
511 path = opts[n].value;
512 break;
513 case SELABEL_OPT_SUBSET:
514 prefix = opts[n].value;
515 break;
516 case SELABEL_OPT_BASEONLY:
517 baseonly = !!opts[n].value;
518 break;
519 }
520
521 /* Process local and distribution substitution files */
522 if (!path) {
523 rec->dist_subs = selabel_subs_init(selinux_file_context_subs_dist_path(), rec->dist_subs);
524 rec->subs = selabel_subs_init(selinux_file_context_subs_path(), rec->subs);
525 path = selinux_file_context_path();
526 } else {
527 snprintf(subs_file, sizeof(subs_file), "%s.subs_dist", path);
528 rec->dist_subs = selabel_subs_init(subs_file, rec->dist_subs);
529 snprintf(subs_file, sizeof(subs_file), "%s.subs", path);
530 rec->subs = selabel_subs_init(subs_file, rec->subs);
531 }
532
533 rec->spec_file = strdup(path);
534
535 /*
536 * The do detailed validation of the input and fill the spec array
537 */
538 status = process_file(path, NULL, rec, prefix);
539 if (status)
540 goto finish;
541
542 if (rec->validating) {
543 status = nodups_specs(data, path);
544 if (status)
545 goto finish;
546 }
547
548 if (!baseonly) {
549 status = process_file(path, "homedirs", rec, prefix);
550 if (status && errno != ENOENT)
551 goto finish;
552
553 status = process_file(path, "local", rec, prefix);
554 if (status && errno != ENOENT)
555 goto finish;
556 }
557
558 status = sort_specs(data);
559
560 status = 0;
561 finish:
562 if (status)
563 free(data->spec_arr);
564 return status;
565 }
566
567 /*
568 * Backend interface routines
569 */
closef(struct selabel_handle * rec)570 static void closef(struct selabel_handle *rec)
571 {
572 struct saved_data *data = (struct saved_data *)rec->data;
573 struct mmap_area *area, *last_area;
574 struct spec *spec;
575 struct stem *stem;
576 unsigned int i;
577
578 for (i = 0; i < data->nspec; i++) {
579 spec = &data->spec_arr[i];
580 free(spec->lr.ctx_trans);
581 free(spec->lr.ctx_raw);
582 if (spec->from_mmap)
583 continue;
584 free(spec->regex_str);
585 free(spec->type_str);
586 if (spec->regcomp) {
587 pcre_free(spec->regex);
588 pcre_free_study(spec->sd);
589 }
590 }
591
592 for (i = 0; i < (unsigned int)data->num_stems; i++) {
593 stem = &data->stem_arr[i];
594 if (stem->from_mmap)
595 continue;
596 free(stem->buf);
597 }
598
599 if (data->spec_arr)
600 free(data->spec_arr);
601 if (data->stem_arr)
602 free(data->stem_arr);
603
604 area = data->mmap_areas;
605 while (area) {
606 munmap(area->addr, area->len);
607 last_area = area;
608 area = area->next;
609 free(last_area);
610 }
611 free(data);
612 }
613
lookup(struct selabel_handle * rec,const char * key,int type)614 static struct selabel_lookup_rec *lookup(struct selabel_handle *rec,
615 const char *key, int type)
616 {
617 struct saved_data *data = (struct saved_data *)rec->data;
618 struct spec *spec_arr = data->spec_arr;
619 int i, rc, file_stem;
620 mode_t mode = (mode_t)type;
621 const char *buf;
622 struct selabel_lookup_rec *ret = NULL;
623 char *clean_key = NULL;
624 const char *prev_slash, *next_slash;
625 unsigned int sofar = 0;
626
627 if (!data->nspec) {
628 errno = ENOENT;
629 goto finish;
630 }
631
632 /* Remove duplicate slashes */
633 if ((next_slash = strstr(key, "//"))) {
634 clean_key = malloc(strlen(key) + 1);
635 if (!clean_key)
636 goto finish;
637 prev_slash = key;
638 while (next_slash) {
639 memcpy(clean_key + sofar, prev_slash, next_slash - prev_slash);
640 sofar += next_slash - prev_slash;
641 prev_slash = next_slash + 1;
642 next_slash = strstr(prev_slash, "//");
643 }
644 strcpy(clean_key + sofar, prev_slash);
645 key = clean_key;
646 }
647
648 buf = key;
649 file_stem = find_stem_from_file(data, &buf);
650 mode &= S_IFMT;
651
652 /*
653 * Check for matching specifications in reverse order, so that
654 * the last matching specification is used.
655 */
656 for (i = data->nspec - 1; i >= 0; i--) {
657 struct spec *spec = &spec_arr[i];
658 /* if the spec in question matches no stem or has the same
659 * stem as the file AND if the spec in question has no mode
660 * specified or if the mode matches the file mode then we do
661 * a regex check */
662 if ((spec->stem_id == -1 || spec->stem_id == file_stem) &&
663 (!mode || !spec->mode || mode == spec->mode)) {
664 if (compile_regex(data, spec, NULL) < 0)
665 goto finish;
666 if (spec->stem_id == -1)
667 rc = pcre_exec(spec->regex, get_pcre_extra(spec), key, strlen(key), 0, 0, NULL, 0);
668 else
669 rc = pcre_exec(spec->regex, get_pcre_extra(spec), buf, strlen(buf), 0, 0, NULL, 0);
670
671 if (rc == 0) {
672 spec->matches++;
673 break;
674 } else if (rc == PCRE_ERROR_NOMATCH)
675 continue;
676
677 errno = ENOENT;
678 /* else it's an error */
679 goto finish;
680 }
681 }
682
683 if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 0) {
684 /* No matching specification. */
685 errno = ENOENT;
686 goto finish;
687 }
688
689 errno = 0;
690 ret = &spec_arr[i].lr;
691
692 finish:
693 free(clean_key);
694 return ret;
695 }
696
stats(struct selabel_handle * rec)697 static void stats(struct selabel_handle *rec)
698 {
699 struct saved_data *data = (struct saved_data *)rec->data;
700 unsigned int i, nspec = data->nspec;
701 struct spec *spec_arr = data->spec_arr;
702
703 for (i = 0; i < nspec; i++) {
704 if (spec_arr[i].matches == 0) {
705 if (spec_arr[i].type_str) {
706 COMPAT_LOG(SELINUX_WARNING,
707 "Warning! No matches for (%s, %s, %s)\n",
708 spec_arr[i].regex_str,
709 spec_arr[i].type_str,
710 spec_arr[i].lr.ctx_raw);
711 } else {
712 COMPAT_LOG(SELINUX_WARNING,
713 "Warning! No matches for (%s, %s)\n",
714 spec_arr[i].regex_str,
715 spec_arr[i].lr.ctx_raw);
716 }
717 }
718 }
719 }
720
selabel_file_init(struct selabel_handle * rec,struct selinux_opt * opts,unsigned nopts)721 int selabel_file_init(struct selabel_handle *rec, struct selinux_opt *opts,
722 unsigned nopts)
723 {
724 struct saved_data *data;
725
726 data = (struct saved_data *)malloc(sizeof(*data));
727 if (!data)
728 return -1;
729 memset(data, 0, sizeof(*data));
730
731 rec->data = data;
732 rec->func_close = &closef;
733 rec->func_stats = &stats;
734 rec->func_lookup = &lookup;
735
736 return init(rec, opts, nopts);
737 }
738