1 /*
2 * File contexts backend for labeling system
3 *
4 * Author : Eamon Walsh <ewalsh@tycho.nsa.gov>
5 * Author : Stephen Smalley <stephen.smalley.work@gmail.com>
6 */
7
8 #include <assert.h>
9 #include <fcntl.h>
10 #include <stdarg.h>
11 #include <string.h>
12 #include <stdio.h>
13 #include <ctype.h>
14 #include <errno.h>
15 #include <limits.h>
16 #include <stdint.h>
17 #include <unistd.h>
18 #include <sys/mman.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21
22 #include "hashtab.h"
23 #include "callbacks.h"
24 #include "label_internal.h"
25 #include "label_file.h"
26
27 /* controls the shrink multiple of the hashtab length */
28 #define SHRINK_MULTIS 1
29
30 struct chkdups_key {
31 char *regex;
32 unsigned int mode;
33 };
34
35 /*
36 * Internals, mostly moved over from matchpathcon.c
37 */
38
39 /* return the length of the text that is the stem of a file name */
get_stem_from_file_name(const char * const buf)40 static int get_stem_from_file_name(const char *const buf)
41 {
42 const char *tmp = strchr(buf + 1, '/');
43
44 if (!tmp)
45 return 0;
46 return tmp - buf;
47 }
48
49 /* find the stem of a file name, returns the index into stem_arr (or -1 if
50 * there is no match - IE for a file in the root directory or a regex that is
51 * too complex for us). */
find_stem_from_file(struct saved_data * data,const char * key)52 static int find_stem_from_file(struct saved_data *data, const char *key)
53 {
54 int i;
55 int stem_len = get_stem_from_file_name(key);
56
57 if (!stem_len)
58 return -1;
59 for (i = 0; i < data->num_stems; i++) {
60 if (stem_len == data->stem_arr[i].len
61 && !strncmp(key, data->stem_arr[i].buf, stem_len)) {
62 return i;
63 }
64 }
65 return -1;
66 }
67
68 /*
69 * hash calculation and key comparison of hash table
70 */
71 ignore_unsigned_overflow_
symhash(hashtab_t h,const_hashtab_key_t key)72 static unsigned int symhash(hashtab_t h, const_hashtab_key_t key)
73 {
74 const struct chkdups_key *k = (const struct chkdups_key *)key;
75 const char *p = NULL;
76 size_t size;
77 unsigned int val = 0;
78
79 size = strlen(k->regex);
80 for (p = k->regex; ((size_t) (p - k->regex)) < size; p++)
81 val =
82 ((val << 4) | (val >> (8 * sizeof(unsigned int) - 4))) ^ (*p);
83 return val % h->size;
84 }
85
symcmp(hashtab_t h,const_hashtab_key_t key1,const_hashtab_key_t key2)86 static int symcmp(hashtab_t h
87 __attribute__ ((unused)), const_hashtab_key_t key1,
88 const_hashtab_key_t key2)
89 {
90 const struct chkdups_key *a = (const struct chkdups_key *)key1;
91 const struct chkdups_key *b = (const struct chkdups_key *)key2;
92
93 return strcmp(a->regex, b->regex) || (a->mode && b->mode && a->mode != b->mode);
94 }
95
destroy_chkdups_key(hashtab_key_t key)96 static int destroy_chkdups_key(hashtab_key_t key)
97 {
98 free(key);
99
100 return 0;
101 }
102
103 /*
104 * Warn about duplicate specifications.
105 */
nodups_specs(struct saved_data * data,const char * path)106 static int nodups_specs(struct saved_data *data, const char *path)
107 {
108 int rc = 0, ret = 0;
109 unsigned int ii;
110 struct spec *curr_spec, *spec_arr = data->spec_arr;
111 struct chkdups_key *new = NULL;
112 unsigned int hashtab_len = (data->nspec / SHRINK_MULTIS) ? data->nspec / SHRINK_MULTIS : 1;
113
114 hashtab_t hash_table = hashtab_create(symhash, symcmp, hashtab_len);
115 if (!hash_table) {
116 rc = -1;
117 COMPAT_LOG(SELINUX_ERROR, "%s: hashtab create failed.\n", path);
118 return rc;
119 }
120 for (ii = 0; ii < data->nspec; ii++) {
121 new = (struct chkdups_key *)malloc(sizeof(struct chkdups_key));
122 if (!new) {
123 rc = -1;
124 hashtab_destroy_key(hash_table, destroy_chkdups_key);
125 COMPAT_LOG(SELINUX_ERROR, "%s: hashtab key create failed.\n", path);
126 return rc;
127 }
128 new->regex = spec_arr[ii].regex_str;
129 new->mode = spec_arr[ii].mode;
130 ret = hashtab_insert(hash_table, (hashtab_key_t)new, &spec_arr[ii]);
131 if (ret == HASHTAB_SUCCESS)
132 continue;
133 if (ret == HASHTAB_PRESENT) {
134 curr_spec =
135 (struct spec *)hashtab_search(hash_table, (hashtab_key_t)new);
136 rc = -1;
137 errno = EINVAL;
138 free(new);
139 if (strcmp(spec_arr[ii].lr.ctx_raw, curr_spec->lr.ctx_raw)) {
140 COMPAT_LOG
141 (SELINUX_ERROR,
142 "%s: Multiple different specifications for %s (%s and %s).\n",
143 path, curr_spec->regex_str,
144 spec_arr[ii].lr.ctx_raw,
145 curr_spec->lr.ctx_raw);
146 } else {
147 COMPAT_LOG
148 (SELINUX_ERROR,
149 "%s: Multiple same specifications for %s.\n",
150 path, curr_spec->regex_str);
151 }
152 }
153 if (ret == HASHTAB_OVERFLOW) {
154 rc = -1;
155 free(new);
156 COMPAT_LOG
157 (SELINUX_ERROR,
158 "%s: hashtab happen memory error.\n",
159 path);
160 break;
161 }
162 }
163
164 hashtab_destroy_key(hash_table, destroy_chkdups_key);
165
166 return rc;
167 }
168
process_text_file(FILE * fp,const char * prefix,struct selabel_handle * rec,const char * path)169 static int process_text_file(FILE *fp, const char *prefix,
170 struct selabel_handle *rec, const char *path)
171 {
172 int rc;
173 size_t line_len;
174 unsigned int lineno = 0;
175 char *line_buf = NULL;
176
177 while (getline(&line_buf, &line_len, fp) > 0) {
178 rc = process_line(rec, path, prefix, line_buf, ++lineno);
179 if (rc)
180 goto out;
181 }
182 rc = 0;
183 out:
184 free(line_buf);
185 return rc;
186 }
187
load_mmap(FILE * fp,size_t len,struct selabel_handle * rec,const char * path)188 static int load_mmap(FILE *fp, size_t len, struct selabel_handle *rec,
189 const char *path)
190 {
191 struct saved_data *data = (struct saved_data *)rec->data;
192 int rc;
193 char *addr, *str_buf;
194 int *stem_map;
195 struct mmap_area *mmap_area;
196 uint32_t i, magic, version;
197 uint32_t entry_len, stem_map_len, regex_array_len;
198 const char *reg_version;
199 const char *reg_arch;
200 char reg_arch_matches = 0;
201
202 mmap_area = malloc(sizeof(*mmap_area));
203 if (!mmap_area) {
204 return -1;
205 }
206
207 addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
208 if (addr == MAP_FAILED) {
209 free(mmap_area);
210 perror("mmap");
211 return -1;
212 }
213
214 /* save where we mmap'd the file to cleanup on close() */
215 mmap_area->addr = mmap_area->next_addr = addr;
216 mmap_area->len = mmap_area->next_len = len;
217 mmap_area->next = data->mmap_areas;
218 data->mmap_areas = mmap_area;
219
220 /* check if this looks like an fcontext file */
221 rc = next_entry(&magic, mmap_area, sizeof(uint32_t));
222 if (rc < 0 || magic != SELINUX_MAGIC_COMPILED_FCONTEXT)
223 return -1;
224
225 /* check if this version is higher than we understand */
226 rc = next_entry(&version, mmap_area, sizeof(uint32_t));
227 if (rc < 0 || version > SELINUX_COMPILED_FCONTEXT_MAX_VERS)
228 return -1;
229
230 reg_version = regex_version();
231 if (!reg_version)
232 return -1;
233
234 reg_arch = regex_arch_string();
235 if (!reg_arch)
236 return -1;
237
238 if (version >= SELINUX_COMPILED_FCONTEXT_PCRE_VERS) {
239
240 len = strlen(reg_version);
241
242 rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t));
243 if (rc < 0)
244 return -1;
245
246 /* Check version lengths */
247 if (len != entry_len)
248 return -1;
249
250 /* Check if regex version mismatch */
251 str_buf = malloc(entry_len + 1);
252 if (!str_buf)
253 return -1;
254
255 rc = next_entry(str_buf, mmap_area, entry_len);
256 if (rc < 0) {
257 free(str_buf);
258 return -1;
259 }
260
261 str_buf[entry_len] = '\0';
262 if ((strcmp(str_buf, reg_version) != 0)) {
263 COMPAT_LOG(SELINUX_ERROR,
264 "Regex version mismatch, expected: %s actual: %s\n",
265 reg_version, str_buf);
266 free(str_buf);
267 return -1;
268 }
269 free(str_buf);
270
271 if (version >= SELINUX_COMPILED_FCONTEXT_REGEX_ARCH) {
272 len = strlen(reg_arch);
273
274 rc = next_entry(&entry_len, mmap_area,
275 sizeof(uint32_t));
276 if (rc < 0)
277 return -1;
278
279 /* Check arch string lengths */
280 if (len != entry_len) {
281 /*
282 * Skip the entry and conclude that we have
283 * a mismatch, which is not fatal.
284 */
285 next_entry(NULL, mmap_area, entry_len);
286 goto end_arch_check;
287 }
288
289 /* Check if arch string mismatch */
290 str_buf = malloc(entry_len + 1);
291 if (!str_buf)
292 return -1;
293
294 rc = next_entry(str_buf, mmap_area, entry_len);
295 if (rc < 0) {
296 free(str_buf);
297 return -1;
298 }
299
300 str_buf[entry_len] = '\0';
301 reg_arch_matches = strcmp(str_buf, reg_arch) == 0;
302 free(str_buf);
303 }
304 }
305 end_arch_check:
306
307 /* allocate the stems_data array */
308 rc = next_entry(&stem_map_len, mmap_area, sizeof(uint32_t));
309 if (rc < 0)
310 return -1;
311
312 /*
313 * map indexed by the stem # in the mmap file and contains the stem
314 * number in the data stem_arr
315 */
316 stem_map = calloc(stem_map_len, sizeof(*stem_map));
317 if (!stem_map)
318 return -1;
319
320 for (i = 0; i < stem_map_len; i++) {
321 char *buf;
322 uint32_t stem_len;
323 int newid;
324
325 /* the length does not include the nul */
326 rc = next_entry(&stem_len, mmap_area, sizeof(uint32_t));
327 if (rc < 0 || !stem_len) {
328 rc = -1;
329 goto out;
330 }
331
332 /* Check for stem_len wrap around. */
333 if (stem_len < UINT32_MAX) {
334 buf = (char *)mmap_area->next_addr;
335 /* Check if over-run before null check. */
336 rc = next_entry(NULL, mmap_area, (stem_len + 1));
337 if (rc < 0)
338 goto out;
339
340 if (buf[stem_len] != '\0') {
341 rc = -1;
342 goto out;
343 }
344 } else {
345 rc = -1;
346 goto out;
347 }
348
349 /* store the mapping between old and new */
350 newid = find_stem(data, buf, stem_len);
351 if (newid < 0) {
352 newid = store_stem(data, buf, stem_len);
353 if (newid < 0) {
354 rc = newid;
355 goto out;
356 }
357 data->stem_arr[newid].from_mmap = 1;
358 }
359 stem_map[i] = newid;
360 }
361
362 /* allocate the regex array */
363 rc = next_entry(®ex_array_len, mmap_area, sizeof(uint32_t));
364 if (rc < 0 || !regex_array_len) {
365 rc = -1;
366 goto out;
367 }
368
369 for (i = 0; i < regex_array_len; i++) {
370 struct spec *spec;
371 int32_t stem_id, meta_chars;
372 uint32_t mode = 0, prefix_len = 0;
373
374 rc = grow_specs(data);
375 if (rc < 0)
376 goto out;
377
378 spec = &data->spec_arr[data->nspec];
379 spec->from_mmap = 1;
380
381 /* Process context */
382 rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t));
383 if (rc < 0 || !entry_len) {
384 rc = -1;
385 goto out;
386 }
387
388 str_buf = malloc(entry_len);
389 if (!str_buf) {
390 rc = -1;
391 goto out;
392 }
393 rc = next_entry(str_buf, mmap_area, entry_len);
394 if (rc < 0) {
395 free(str_buf);
396 goto out;
397 }
398
399 if (str_buf[entry_len - 1] != '\0') {
400 free(str_buf);
401 rc = -1;
402 goto out;
403 }
404 spec->lr.ctx_raw = str_buf;
405
406 if (strcmp(spec->lr.ctx_raw, "<<none>>") && rec->validating) {
407 if (selabel_validate(&spec->lr) < 0) {
408 selinux_log(SELINUX_ERROR,
409 "%s: context %s is invalid\n",
410 path, spec->lr.ctx_raw);
411 goto out;
412 }
413 }
414
415 /* Process regex string */
416 rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t));
417 if (rc < 0 || !entry_len) {
418 rc = -1;
419 goto out;
420 }
421
422 spec->regex_str = (char *)mmap_area->next_addr;
423 rc = next_entry(NULL, mmap_area, entry_len);
424 if (rc < 0)
425 goto out;
426
427 if (spec->regex_str[entry_len - 1] != '\0') {
428 rc = -1;
429 goto out;
430 }
431
432 /* Process mode */
433 if (version >= SELINUX_COMPILED_FCONTEXT_MODE)
434 rc = next_entry(&mode, mmap_area, sizeof(uint32_t));
435 else
436 rc = next_entry(&mode, mmap_area, sizeof(mode_t));
437 if (rc < 0)
438 goto out;
439
440 spec->mode = mode;
441
442 /* map the stem id from the mmap file to the data->stem_arr */
443 rc = next_entry(&stem_id, mmap_area, sizeof(int32_t));
444 if (rc < 0)
445 goto out;
446
447 if (stem_id < 0 || stem_id >= (int32_t)stem_map_len)
448 spec->stem_id = -1;
449 else
450 spec->stem_id = stem_map[stem_id];
451
452 /* retrieve the hasMetaChars bit */
453 rc = next_entry(&meta_chars, mmap_area, sizeof(uint32_t));
454 if (rc < 0)
455 goto out;
456
457 spec->hasMetaChars = meta_chars;
458 /* and prefix length for use by selabel_lookup_best_match */
459 if (version >= SELINUX_COMPILED_FCONTEXT_PREFIX_LEN) {
460 rc = next_entry(&prefix_len, mmap_area,
461 sizeof(uint32_t));
462 if (rc < 0)
463 goto out;
464
465 spec->prefix_len = prefix_len;
466 }
467
468 rc = regex_load_mmap(mmap_area, &spec->regex, reg_arch_matches,
469 &spec->regex_compiled);
470 if (rc < 0)
471 goto out;
472
473 __pthread_mutex_init(&spec->regex_lock, NULL);
474 data->nspec++;
475 }
476
477 rc = 0;
478 out:
479 free(stem_map);
480
481 return rc;
482 }
483
484 struct file_details {
485 const char *suffix;
486 struct stat sb;
487 };
488
rolling_append(char * current,const char * suffix,size_t max)489 static char *rolling_append(char *current, const char *suffix, size_t max)
490 {
491 size_t size;
492 size_t suffix_size;
493 size_t current_size;
494
495 if (!suffix)
496 return current;
497
498 current_size = strlen(current);
499 suffix_size = strlen(suffix);
500
501 size = current_size + suffix_size;
502 if (size < current_size || size < suffix_size)
503 return NULL;
504
505 /* ensure space for the '.' and the '\0' characters. */
506 if (size >= (SIZE_MAX - 2))
507 return NULL;
508
509 size += 2;
510
511 if (size > max)
512 return NULL;
513
514 /* Append any given suffix */
515 char *to = current + current_size;
516 *to++ = '.';
517 strcpy(to, suffix);
518
519 return current;
520 }
521
fcontext_is_binary(FILE * fp)522 static int fcontext_is_binary(FILE *fp)
523 {
524 uint32_t magic;
525 int rc;
526
527 size_t len = fread(&magic, sizeof(magic), 1, fp);
528
529 rc = fseek(fp, 0L, SEEK_SET);
530 if (rc == -1)
531 return -1;
532
533 return (len && (magic == SELINUX_MAGIC_COMPILED_FCONTEXT));
534 }
535
536 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
537
open_file(const char * path,const char * suffix,char * save_path,size_t len,struct stat * sb,bool open_oldest)538 static FILE *open_file(const char *path, const char *suffix,
539 char *save_path, size_t len, struct stat *sb, bool open_oldest)
540 {
541 unsigned int i;
542 int rc;
543 char stack_path[len];
544 struct file_details *found = NULL;
545
546 /*
547 * Rolling append of suffix. Try to open with path.suffix then the
548 * next as path.suffix.suffix and so forth.
549 */
550 struct file_details fdetails[2] = {
551 { .suffix = suffix },
552 { .suffix = "bin" }
553 };
554
555 rc = snprintf(stack_path, sizeof(stack_path), "%s", path);
556 if (rc >= (int) sizeof(stack_path)) {
557 errno = ENAMETOOLONG;
558 return NULL;
559 }
560
561 for (i = 0; i < ARRAY_SIZE(fdetails); i++) {
562
563 /* This handles the case if suffix is null */
564 path = rolling_append(stack_path, fdetails[i].suffix,
565 sizeof(stack_path));
566 if (!path)
567 return NULL;
568
569 rc = stat(path, &fdetails[i].sb);
570 if (rc)
571 continue;
572
573 /* first file thing found, just take it */
574 if (!found) {
575 strcpy(save_path, path);
576 found = &fdetails[i];
577 continue;
578 }
579
580 /*
581 * Keep picking the newest file found. Where "newest"
582 * includes equality. This provides a precedence on
583 * secondary suffixes even when the timestamp is the
584 * same. Ie choose file_contexts.bin over file_contexts
585 * even if the time stamp is the same. Invert this logic
586 * on open_oldest set to true. The idea is that if the
587 * newest file failed to process, we can attempt to
588 * process the oldest. The logic here is subtle and depends
589 * on the array ordering in fdetails for the case when time
590 * stamps are the same.
591 */
592 if (open_oldest ^
593 (fdetails[i].sb.st_mtime >= found->sb.st_mtime)) {
594 found = &fdetails[i];
595 strcpy(save_path, path);
596 }
597 }
598
599 if (!found) {
600 errno = ENOENT;
601 return NULL;
602 }
603
604 memcpy(sb, &found->sb, sizeof(*sb));
605 return fopen(save_path, "re");
606 }
607
process_file(const char * path,const char * suffix,struct selabel_handle * rec,const char * prefix,struct selabel_digest * digest)608 static int process_file(const char *path, const char *suffix,
609 struct selabel_handle *rec,
610 const char *prefix, struct selabel_digest *digest)
611 {
612 int rc;
613 unsigned int i;
614 struct stat sb;
615 FILE *fp = NULL;
616 char found_path[PATH_MAX];
617
618 /*
619 * On the first pass open the newest modified file. If it fails to
620 * process, then the second pass shall open the oldest file. If both
621 * passes fail, then it's a fatal error.
622 */
623 for (i = 0; i < 2; i++) {
624 fp = open_file(path, suffix, found_path, sizeof(found_path),
625 &sb, i > 0);
626 if (fp == NULL)
627 return -1;
628
629 rc = fcontext_is_binary(fp);
630 if (rc < 0) {
631 (void) fclose(fp);
632 return -1;
633 }
634
635 rc = rc ?
636 load_mmap(fp, sb.st_size, rec, found_path) :
637 process_text_file(fp, prefix, rec, found_path);
638 if (!rc)
639 rc = digest_add_specfile(digest, fp, NULL, sb.st_size,
640 found_path);
641
642 fclose(fp);
643
644 if (!rc)
645 return 0;
646 }
647 return -1;
648 }
649
selabel_subs_fini(struct selabel_sub * ptr)650 static void selabel_subs_fini(struct selabel_sub *ptr)
651 {
652 struct selabel_sub *next;
653
654 while (ptr) {
655 next = ptr->next;
656 free(ptr->src);
657 free(ptr->dst);
658 free(ptr);
659 ptr = next;
660 }
661 }
662
selabel_sub(struct selabel_sub * ptr,const char * src)663 static char *selabel_sub(struct selabel_sub *ptr, const char *src)
664 {
665 char *dst = NULL;
666 int len;
667
668 while (ptr) {
669 if (strncmp(src, ptr->src, ptr->slen) == 0 ) {
670 if (src[ptr->slen] == '/' ||
671 src[ptr->slen] == 0) {
672 if ((src[ptr->slen] == '/') &&
673 (strcmp(ptr->dst, "/") == 0))
674 len = ptr->slen + 1;
675 else
676 len = ptr->slen;
677 if (asprintf(&dst, "%s%s", ptr->dst, &src[len]) < 0)
678 return NULL;
679 return dst;
680 }
681 }
682 ptr = ptr->next;
683 }
684 return NULL;
685 }
686
687 #if !defined(BUILD_HOST) && !defined(ANDROID)
selabel_subs_init(const char * path,struct selabel_digest * digest,struct selabel_sub ** out_subs)688 static int selabel_subs_init(const char *path, struct selabel_digest *digest,
689 struct selabel_sub **out_subs)
690 {
691 char buf[1024];
692 FILE *cfg = fopen(path, "re");
693 struct selabel_sub *list = NULL, *sub = NULL;
694 struct stat sb;
695 int status = -1;
696
697 *out_subs = NULL;
698 if (!cfg) {
699 /* If the file does not exist, it is not fatal */
700 return (errno == ENOENT) ? 0 : -1;
701 }
702
703 if (fstat(fileno(cfg), &sb) < 0)
704 goto out;
705
706 while (fgets_unlocked(buf, sizeof(buf) - 1, cfg)) {
707 char *ptr = NULL;
708 char *src = buf;
709 char *dst = NULL;
710
711 while (*src && isspace((unsigned char)*src))
712 src++;
713 if (src[0] == '#') continue;
714 ptr = src;
715 while (*ptr && ! isspace((unsigned char)*ptr))
716 ptr++;
717 *ptr++ = '\0';
718 if (! *src) continue;
719
720 dst = ptr;
721 while (*dst && isspace((unsigned char)*dst))
722 dst++;
723 ptr = dst;
724 while (*ptr && ! isspace((unsigned char)*ptr))
725 ptr++;
726 *ptr = '\0';
727 if (! *dst)
728 continue;
729
730 sub = calloc(1, sizeof(*sub));
731 if (! sub)
732 goto err;
733
734 sub->src = strdup(src);
735 if (! sub->src)
736 goto err;
737
738 sub->dst = strdup(dst);
739 if (! sub->dst)
740 goto err;
741
742 sub->slen = strlen(src);
743 sub->next = list;
744 list = sub;
745 sub = NULL;
746 }
747
748 if (digest_add_specfile(digest, cfg, NULL, sb.st_size, path) < 0)
749 goto err;
750
751 *out_subs = list;
752 status = 0;
753
754 out:
755 fclose(cfg);
756 return status;
757 err:
758 if (sub)
759 free(sub->src);
760 free(sub);
761 while (list) {
762 sub = list->next;
763 free(list->src);
764 free(list->dst);
765 free(list);
766 list = sub;
767 }
768 goto out;
769 }
770 #endif
771
selabel_sub_key(struct saved_data * data,const char * key)772 static char *selabel_sub_key(struct saved_data *data, const char *key)
773 {
774 char *ptr = NULL;
775 char *dptr = NULL;
776
777 ptr = selabel_sub(data->subs, key);
778 if (ptr) {
779 dptr = selabel_sub(data->dist_subs, ptr);
780 if (dptr) {
781 free(ptr);
782 ptr = dptr;
783 }
784 } else {
785 ptr = selabel_sub(data->dist_subs, key);
786 }
787 if (ptr)
788 return ptr;
789
790 return NULL;
791 }
792
793 static void closef(struct selabel_handle *rec);
794
795 #ifdef OHOS_FC_INIT
init(struct selabel_handle * rec,const struct selinux_opt * opts,unsigned n)796 static int init(struct selabel_handle *rec, const struct selinux_opt *opts, unsigned n)
797 {
798 struct saved_data *data = (struct saved_data *)rec->data;
799 const char *prefix = NULL;
800 int status = -1;
801 size_t path_nums = 0;
802 size_t opt_nums = n;
803
804 while (n--) {
805 switch (opts[n].type) {
806 case SELABEL_OPT_PATH:
807 path_nums++;
808 break;
809 default:
810 break;
811 }
812 }
813
814 if (path_nums == 0) {
815 selinux_log(SELINUX_ERROR, "No specific file_contexts provided\n");
816 goto finish;
817 }
818
819 rec->spec_file = (char **)calloc(path_nums, sizeof(char *));
820 if (rec->spec_file == NULL) {
821 goto finish;
822 }
823 rec->spec_file_nums = path_nums;
824 size_t i = 0;
825 n = opt_nums;
826 while (n--) {
827 if (opts[n].type == SELABEL_OPT_PATH) {
828 rec->spec_file[i] = strdup(opts[n].value);
829 if (rec->spec_file[i] == NULL) {
830 goto finish;
831 }
832 i++;
833 }
834 }
835
836 for (int path_index = 0; path_index < rec->spec_file_nums; path_index++) {
837 status = process_file(rec->spec_file[path_index], NULL, rec, prefix, rec->digest);
838 if (status) {
839 goto finish;
840 }
841
842 if (rec->validating) {
843 status = nodups_specs(data, rec->spec_file[path_index]);
844 if (status) {
845 goto finish;
846 }
847 }
848 }
849
850 digest_gen_hash(rec->digest);
851
852 status = sort_specs(data);
853
854 finish:
855 if (status)
856 closef(rec);
857
858 return status;
859 }
860 #else
init(struct selabel_handle * rec,const struct selinux_opt * opts,unsigned n)861 static int init(struct selabel_handle *rec, const struct selinux_opt *opts,
862 unsigned n)
863 {
864 struct saved_data *data = (struct saved_data *)rec->data;
865 const char *path = NULL;
866 const char *prefix = NULL;
867 int status = -1, baseonly = 0;
868
869 /* Process arguments */
870 while (n) {
871 n--;
872 switch(opts[n].type) {
873 case SELABEL_OPT_PATH:
874 path = opts[n].value;
875 break;
876 case SELABEL_OPT_SUBSET:
877 prefix = opts[n].value;
878 break;
879 case SELABEL_OPT_BASEONLY:
880 baseonly = !!opts[n].value;
881 break;
882 case SELABEL_OPT_UNUSED:
883 case SELABEL_OPT_VALIDATE:
884 case SELABEL_OPT_DIGEST:
885 break;
886 default:
887 errno = EINVAL;
888 return -1;
889 }
890 }
891
892 #if !defined(BUILD_HOST) && !defined(ANDROID)
893 char subs_file[PATH_MAX + 1];
894 /* Process local and distribution substitution files */
895 if (!path) {
896 status = selabel_subs_init(
897 selinux_file_context_subs_dist_path(),
898 rec->digest, &data->dist_subs);
899 if (status)
900 goto finish;
901 status = selabel_subs_init(selinux_file_context_subs_path(),
902 rec->digest, &data->subs);
903 if (status)
904 goto finish;
905 path = selinux_file_context_path();
906 } else {
907 snprintf(subs_file, sizeof(subs_file), "%s.subs_dist", path);
908 status = selabel_subs_init(subs_file, rec->digest,
909 &data->dist_subs);
910 if (status)
911 goto finish;
912 snprintf(subs_file, sizeof(subs_file), "%s.subs", path);
913 status = selabel_subs_init(subs_file, rec->digest,
914 &data->subs);
915 if (status)
916 goto finish;
917 }
918
919 #endif
920
921 if (!path)
922 goto finish;
923
924 rec->spec_file = strdup(path);
925
926 /*
927 * The do detailed validation of the input and fill the spec array
928 */
929 status = process_file(path, NULL, rec, prefix, rec->digest);
930 if (status)
931 goto finish;
932
933 if (rec->validating) {
934 status = nodups_specs(data, path);
935 if (status)
936 goto finish;
937 }
938
939 if (!baseonly) {
940 status = process_file(path, "homedirs", rec, prefix,
941 rec->digest);
942 if (status && errno != ENOENT)
943 goto finish;
944
945 status = process_file(path, "local", rec, prefix,
946 rec->digest);
947 if (status && errno != ENOENT)
948 goto finish;
949 }
950
951 digest_gen_hash(rec->digest);
952
953 status = sort_specs(data);
954
955 finish:
956 if (status)
957 closef(rec);
958
959 return status;
960 }
961 #endif
962
963 /*
964 * Backend interface routines
965 */
closef(struct selabel_handle * rec)966 static void closef(struct selabel_handle *rec)
967 {
968 struct saved_data *data = (struct saved_data *)rec->data;
969 struct mmap_area *area, *last_area;
970 struct spec *spec;
971 struct stem *stem;
972 unsigned int i;
973
974 if (!data)
975 return;
976
977 selabel_subs_fini(data->subs);
978 selabel_subs_fini(data->dist_subs);
979
980 for (i = 0; i < data->nspec; i++) {
981 spec = &data->spec_arr[i];
982 free(spec->lr.ctx_trans);
983 free(spec->lr.ctx_raw);
984 regex_data_free(spec->regex);
985 __pthread_mutex_destroy(&spec->regex_lock);
986 if (spec->from_mmap)
987 continue;
988 free(spec->regex_str);
989 free(spec->type_str);
990 }
991
992 for (i = 0; i < (unsigned int)data->num_stems; i++) {
993 stem = &data->stem_arr[i];
994 if (stem->from_mmap)
995 continue;
996 free(stem->buf);
997 }
998
999 if (data->spec_arr)
1000 free(data->spec_arr);
1001 if (data->stem_arr)
1002 free(data->stem_arr);
1003
1004 area = data->mmap_areas;
1005 while (area) {
1006 munmap(area->addr, area->len);
1007 last_area = area;
1008 area = area->next;
1009 free(last_area);
1010 }
1011 free(data);
1012 }
1013
1014 // Finds all the matches of |key| in the given context. Returns the result in
1015 // the allocated array and updates the match count. If match_count is NULL,
1016 // stops early once the 1st match is found.
lookup_all(struct selabel_handle * rec,const char * key,int type,bool partial,size_t * match_count)1017 static struct spec **lookup_all(struct selabel_handle *rec,
1018 const char *key,
1019 int type,
1020 bool partial,
1021 size_t *match_count)
1022 {
1023 struct saved_data *data = (struct saved_data *)rec->data;
1024 struct spec *spec_arr = data->spec_arr;
1025 int i, rc, file_stem;
1026 size_t len;
1027 mode_t mode = (mode_t)type;
1028 char *clean_key = NULL;
1029 const char *prev_slash, *next_slash;
1030 unsigned int sofar = 0;
1031 char *sub = NULL;
1032
1033 struct spec **result = NULL;
1034 if (match_count) {
1035 *match_count = 0;
1036 result = calloc(data->nspec, sizeof(struct spec*));
1037 } else {
1038 result = calloc(1, sizeof(struct spec*));
1039 }
1040 if (!result) {
1041 selinux_log(SELINUX_ERROR, "Failed to allocate %zu bytes of data\n",
1042 data->nspec * sizeof(struct spec*));
1043 goto finish;
1044 }
1045
1046 if (!data->nspec) {
1047 errno = ENOENT;
1048 goto finish;
1049 }
1050
1051 /* Remove duplicate slashes */
1052 if ((next_slash = strstr(key, "//"))) {
1053 clean_key = (char *) malloc(strlen(key) + 1);
1054 if (!clean_key)
1055 goto finish;
1056 prev_slash = key;
1057 while (next_slash) {
1058 memcpy(clean_key + sofar, prev_slash, next_slash - prev_slash);
1059 sofar += next_slash - prev_slash;
1060 prev_slash = next_slash + 1;
1061 next_slash = strstr(prev_slash, "//");
1062 }
1063 strcpy(clean_key + sofar, prev_slash);
1064 key = clean_key;
1065 }
1066
1067 /* remove trailing slash */
1068 len = strlen(key);
1069 if (len == 0) {
1070 errno = EINVAL;
1071 goto finish;
1072 }
1073
1074 if (len > 1 && key[len - 1] == '/') {
1075 /* reuse clean_key from above if available */
1076 if (!clean_key) {
1077 clean_key = (char *) malloc(len);
1078 if (!clean_key)
1079 goto finish;
1080
1081 memcpy(clean_key, key, len - 1);
1082 }
1083
1084 clean_key[len - 1] = '\0';
1085 key = clean_key;
1086 }
1087
1088 sub = selabel_sub_key(data, key);
1089 if (sub)
1090 key = sub;
1091
1092 file_stem = find_stem_from_file(data, key);
1093 mode &= S_IFMT;
1094
1095 /*
1096 * Check for matching specifications in reverse order, so that
1097 * the last matching specification is used.
1098 */
1099 for (i = data->nspec - 1; i >= 0; i--) {
1100 struct spec *spec = &spec_arr[i];
1101 /* if the spec in question matches no stem or has the same
1102 * stem as the file AND if the spec in question has no mode
1103 * specified or if the mode matches the file mode then we do
1104 * a regex check */
1105 bool stem_matches = spec->stem_id == -1 || spec->stem_id == file_stem;
1106 // Don't check the stem if we want to find partial matches.
1107 // Otherwise the case "/abc/efg/(/.*)?" will be considered
1108 //a miss for "/abc".
1109 if ((partial || stem_matches) &&
1110 (!mode || !spec->mode || mode == spec->mode)) {
1111 if (compile_regex(spec, NULL) < 0)
1112 goto finish;
1113 rc = regex_match(spec->regex, key, partial);
1114 if (rc == REGEX_MATCH || (partial && rc == REGEX_MATCH_PARTIAL)) {
1115 if (rc == REGEX_MATCH) {
1116 #ifdef __ATOMIC_RELAXED
1117 __atomic_store_n(&spec->any_matches,
1118 true, __ATOMIC_RELAXED);
1119 #else
1120 #error "Please use a compiler that supports __atomic builtins"
1121 #endif
1122 }
1123
1124 if (strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 0) {
1125 errno = ENOENT;
1126 goto finish;
1127 }
1128
1129 if (match_count) {
1130 result[*match_count] = spec;
1131 *match_count += 1;
1132 // Continue to find all the matches.
1133 continue;
1134 }
1135 result[0] = spec;
1136 break;
1137 }
1138
1139 if (rc == REGEX_NO_MATCH)
1140 continue;
1141
1142 errno = ENOENT;
1143 /* else it's an error */
1144 goto finish;
1145 }
1146 }
1147 if (!result[0])
1148 errno = ENOENT;
1149
1150 finish:
1151 free(clean_key);
1152 free(sub);
1153 if (result && !result[0]) {
1154 free(result);
1155 result = NULL;
1156 }
1157 return result;
1158 }
1159
lookup_common(struct selabel_handle * rec,const char * key,int type,bool partial)1160 static struct spec *lookup_common(struct selabel_handle *rec,
1161 const char *key,
1162 int type,
1163 bool partial) {
1164 struct spec **matches = lookup_all(rec, key, type, partial, NULL);
1165 if (!matches) {
1166 return NULL;
1167 }
1168 struct spec *result = matches[0];
1169 free(matches);
1170 return result;
1171 }
1172
1173 /*
1174 * Returns true if the digest of all partial matched contexts is the same as
1175 * the one saved by setxattr, otherwise returns false. The length of the SHA1
1176 * digest will always be returned. The caller must free any returned digests.
1177 */
get_digests_all_partial_matches(struct selabel_handle * rec,const char * pathname,uint8_t ** calculated_digest,uint8_t ** xattr_digest,size_t * digest_len)1178 static bool get_digests_all_partial_matches(struct selabel_handle *rec,
1179 const char *pathname,
1180 uint8_t **calculated_digest,
1181 uint8_t **xattr_digest,
1182 size_t *digest_len)
1183 {
1184 uint8_t read_digest[SHA1_HASH_SIZE];
1185 ssize_t read_size = getxattr(pathname, RESTORECON_PARTIAL_MATCH_DIGEST,
1186 read_digest, SHA1_HASH_SIZE
1187 #ifdef __APPLE__
1188 , 0, 0
1189 #endif /* __APPLE __ */
1190 );
1191 uint8_t hash_digest[SHA1_HASH_SIZE];
1192 bool status = selabel_hash_all_partial_matches(rec, pathname,
1193 hash_digest);
1194
1195 *xattr_digest = NULL;
1196 *calculated_digest = NULL;
1197 *digest_len = SHA1_HASH_SIZE;
1198
1199 if (read_size == SHA1_HASH_SIZE) {
1200 *xattr_digest = calloc(1, SHA1_HASH_SIZE + 1);
1201 if (!*xattr_digest)
1202 goto oom;
1203
1204 memcpy(*xattr_digest, read_digest, SHA1_HASH_SIZE);
1205 }
1206
1207 if (status) {
1208 *calculated_digest = calloc(1, SHA1_HASH_SIZE + 1);
1209 if (!*calculated_digest)
1210 goto oom;
1211
1212 memcpy(*calculated_digest, hash_digest, SHA1_HASH_SIZE);
1213 }
1214
1215 if (status && read_size == SHA1_HASH_SIZE &&
1216 memcmp(read_digest, hash_digest, SHA1_HASH_SIZE) == 0)
1217 return true;
1218
1219 return false;
1220
1221 oom:
1222 selinux_log(SELINUX_ERROR, "SELinux: %s: Out of memory\n", __func__);
1223 return false;
1224 }
1225
hash_all_partial_matches(struct selabel_handle * rec,const char * key,uint8_t * digest)1226 static bool hash_all_partial_matches(struct selabel_handle *rec, const char *key, uint8_t *digest)
1227 {
1228 assert(digest);
1229
1230 size_t total_matches;
1231 struct spec **matches = lookup_all(rec, key, 0, true, &total_matches);
1232 if (!matches) {
1233 return false;
1234 }
1235
1236 Sha1Context context;
1237 Sha1Initialise(&context);
1238 size_t i;
1239 for (i = 0; i < total_matches; i++) {
1240 char* regex_str = matches[i]->regex_str;
1241 mode_t mode = matches[i]->mode;
1242 char* ctx_raw = matches[i]->lr.ctx_raw;
1243
1244 Sha1Update(&context, regex_str, strlen(regex_str) + 1);
1245 Sha1Update(&context, &mode, sizeof(mode_t));
1246 Sha1Update(&context, ctx_raw, strlen(ctx_raw) + 1);
1247 }
1248
1249 SHA1_HASH sha1_hash;
1250 Sha1Finalise(&context, &sha1_hash);
1251 memcpy(digest, sha1_hash.bytes, SHA1_HASH_SIZE);
1252
1253 free(matches);
1254 return true;
1255 }
1256
lookup(struct selabel_handle * rec,const char * key,int type)1257 static struct selabel_lookup_rec *lookup(struct selabel_handle *rec,
1258 const char *key, int type)
1259 {
1260 struct spec *spec;
1261
1262 spec = lookup_common(rec, key, type, false);
1263 if (spec)
1264 return &spec->lr;
1265 return NULL;
1266 }
1267
partial_match(struct selabel_handle * rec,const char * key)1268 static bool partial_match(struct selabel_handle *rec, const char *key)
1269 {
1270 return lookup_common(rec, key, 0, true) ? true : false;
1271 }
1272
lookup_best_match(struct selabel_handle * rec,const char * key,const char ** aliases,int type)1273 static struct selabel_lookup_rec *lookup_best_match(struct selabel_handle *rec,
1274 const char *key,
1275 const char **aliases,
1276 int type)
1277 {
1278 size_t n, i;
1279 int best = -1;
1280 struct spec **specs;
1281 size_t prefix_len = 0;
1282 struct selabel_lookup_rec *lr = NULL;
1283
1284 if (!aliases || !aliases[0])
1285 return lookup(rec, key, type);
1286
1287 for (n = 0; aliases[n]; n++)
1288 ;
1289
1290 specs = calloc(n+1, sizeof(struct spec *));
1291 if (!specs)
1292 return NULL;
1293 specs[0] = lookup_common(rec, key, type, false);
1294 if (specs[0]) {
1295 if (!specs[0]->hasMetaChars) {
1296 /* exact match on key */
1297 lr = &specs[0]->lr;
1298 goto out;
1299 }
1300 best = 0;
1301 prefix_len = specs[0]->prefix_len;
1302 }
1303 for (i = 1; i <= n; i++) {
1304 specs[i] = lookup_common(rec, aliases[i-1], type, false);
1305 if (specs[i]) {
1306 if (!specs[i]->hasMetaChars) {
1307 /* exact match on alias */
1308 lr = &specs[i]->lr;
1309 goto out;
1310 }
1311 if (specs[i]->prefix_len > prefix_len) {
1312 best = i;
1313 prefix_len = specs[i]->prefix_len;
1314 }
1315 }
1316 }
1317
1318 if (best >= 0) {
1319 /* longest fixed prefix match on key or alias */
1320 lr = &specs[best]->lr;
1321 } else {
1322 errno = ENOENT;
1323 }
1324
1325 out:
1326 free(specs);
1327 return lr;
1328 }
1329
incomp(const struct spec * spec1,const struct spec * spec2,const char * reason,int i,int j)1330 static enum selabel_cmp_result incomp(const struct spec *spec1, const struct spec *spec2, const char *reason, int i, int j)
1331 {
1332 selinux_log(SELINUX_INFO,
1333 "selabel_cmp: mismatched %s on entry %d: (%s, %x, %s) vs entry %d: (%s, %x, %s)\n",
1334 reason,
1335 i, spec1->regex_str, spec1->mode, spec1->lr.ctx_raw,
1336 j, spec2->regex_str, spec2->mode, spec2->lr.ctx_raw);
1337 return SELABEL_INCOMPARABLE;
1338 }
1339
cmp(const struct selabel_handle * h1,const struct selabel_handle * h2)1340 static enum selabel_cmp_result cmp(const struct selabel_handle *h1,
1341 const struct selabel_handle *h2)
1342 {
1343 const struct saved_data *data1 = (const struct saved_data *)h1->data;
1344 const struct saved_data *data2 = (const struct saved_data *)h2->data;
1345 unsigned int i, nspec1 = data1->nspec, j, nspec2 = data2->nspec;
1346 const struct spec *spec_arr1 = data1->spec_arr, *spec_arr2 = data2->spec_arr;
1347 const struct stem *stem_arr1 = data1->stem_arr, *stem_arr2 = data2->stem_arr;
1348 bool skipped1 = false, skipped2 = false;
1349
1350 i = 0;
1351 j = 0;
1352 while (i < nspec1 && j < nspec2) {
1353 const struct spec *spec1 = &spec_arr1[i];
1354 const struct spec *spec2 = &spec_arr2[j];
1355
1356 /*
1357 * Because sort_specs() moves exact pathnames to the
1358 * end, we might need to skip over additional regex
1359 * entries that only exist in one of the configurations.
1360 */
1361 if (!spec1->hasMetaChars && spec2->hasMetaChars) {
1362 j++;
1363 skipped2 = true;
1364 continue;
1365 }
1366
1367 if (spec1->hasMetaChars && !spec2->hasMetaChars) {
1368 i++;
1369 skipped1 = true;
1370 continue;
1371 }
1372
1373 if (spec1->regex && spec2->regex) {
1374 if (regex_cmp(spec1->regex, spec2->regex) == SELABEL_INCOMPARABLE){
1375 return incomp(spec1, spec2, "regex", i, j);
1376 }
1377 } else {
1378 if (strcmp(spec1->regex_str, spec2->regex_str))
1379 return incomp(spec1, spec2, "regex_str", i, j);
1380 }
1381
1382 if (spec1->mode != spec2->mode)
1383 return incomp(spec1, spec2, "mode", i, j);
1384
1385 if (spec1->stem_id == -1 && spec2->stem_id != -1)
1386 return incomp(spec1, spec2, "stem_id", i, j);
1387 if (spec2->stem_id == -1 && spec1->stem_id != -1)
1388 return incomp(spec1, spec2, "stem_id", i, j);
1389 if (spec1->stem_id != -1 && spec2->stem_id != -1) {
1390 const struct stem *stem1 = &stem_arr1[spec1->stem_id];
1391 const struct stem *stem2 = &stem_arr2[spec2->stem_id];
1392 if (stem1->len != stem2->len ||
1393 strncmp(stem1->buf, stem2->buf, stem1->len))
1394 return incomp(spec1, spec2, "stem", i, j);
1395 }
1396
1397 if (strcmp(spec1->lr.ctx_raw, spec2->lr.ctx_raw))
1398 return incomp(spec1, spec2, "ctx_raw", i, j);
1399
1400 i++;
1401 j++;
1402 }
1403
1404 if ((skipped1 || i < nspec1) && !skipped2)
1405 return SELABEL_SUPERSET;
1406 if ((skipped2 || j < nspec2) && !skipped1)
1407 return SELABEL_SUBSET;
1408 if (skipped1 && skipped2)
1409 return SELABEL_INCOMPARABLE;
1410 return SELABEL_EQUAL;
1411 }
1412
1413
stats(struct selabel_handle * rec)1414 static void stats(struct selabel_handle *rec)
1415 {
1416 struct saved_data *data = (struct saved_data *)rec->data;
1417 unsigned int i, nspec = data->nspec;
1418 struct spec *spec_arr = data->spec_arr;
1419 bool any_matches;
1420
1421 for (i = 0; i < nspec; i++) {
1422 #ifdef __ATOMIC_RELAXED
1423 any_matches = __atomic_load_n(&spec_arr[i].any_matches, __ATOMIC_RELAXED);
1424 #else
1425 #error "Please use a compiler that supports __atomic builtins"
1426 #endif
1427 if (!any_matches) {
1428 if (spec_arr[i].type_str) {
1429 COMPAT_LOG(SELINUX_WARNING,
1430 "Warning! No matches for (%s, %s, %s)\n",
1431 spec_arr[i].regex_str,
1432 spec_arr[i].type_str,
1433 spec_arr[i].lr.ctx_raw);
1434 } else {
1435 COMPAT_LOG(SELINUX_WARNING,
1436 "Warning! No matches for (%s, %s)\n",
1437 spec_arr[i].regex_str,
1438 spec_arr[i].lr.ctx_raw);
1439 }
1440 }
1441 }
1442 }
1443
selabel_file_init(struct selabel_handle * rec,const struct selinux_opt * opts,unsigned nopts)1444 int selabel_file_init(struct selabel_handle *rec,
1445 const struct selinux_opt *opts,
1446 unsigned nopts)
1447 {
1448 struct saved_data *data;
1449
1450 data = (struct saved_data *)calloc(1, sizeof(*data));
1451 if (!data)
1452 return -1;
1453
1454 rec->data = data;
1455 rec->func_close = &closef;
1456 rec->func_stats = &stats;
1457 rec->func_lookup = &lookup;
1458 rec->func_partial_match = &partial_match;
1459 rec->func_get_digests_all_partial_matches =
1460 &get_digests_all_partial_matches;
1461 rec->func_hash_all_partial_matches = &hash_all_partial_matches;
1462 rec->func_lookup_best_match = &lookup_best_match;
1463 rec->func_cmp = &cmp;
1464
1465 return init(rec, opts, nopts);
1466 }
1467