1 /*
2 * The majority of this code is from Android's
3 * external/libselinux/src/android.c and upstream
4 * selinux/policycoreutils/setfiles/restore.c
5 *
6 * See selinux_restorecon(3) for details.
7 */
8
9 #include <unistd.h>
10 #include <string.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <stdbool.h>
14 #include <ctype.h>
15 #include <errno.h>
16 #include <fcntl.h>
17 #include <fts.h>
18 #include <inttypes.h>
19 #include <limits.h>
20 #include <stdint.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <sys/xattr.h>
24 #include <sys/vfs.h>
25 #include <sys/statvfs.h>
26 #include <sys/utsname.h>
27 #include <linux/magic.h>
28 #include <libgen.h>
29 #include <syslog.h>
30 #include <assert.h>
31
32 #include <selinux/selinux.h>
33 #include <selinux/context.h>
34 #include <selinux/label.h>
35 #include <selinux/restorecon.h>
36
37 #include "callbacks.h"
38 #include "selinux_internal.h"
39 #include "label_file.h"
40 #include "sha1.h"
41
42 #define STAR_COUNT 1024
43
44 static struct selabel_handle *fc_sehandle = NULL;
45 static bool selabel_no_digest;
46 static char *rootpath = NULL;
47 static int rootpathlen;
48
49 /* Information on excluded fs and directories. */
50 struct edir {
51 char *directory;
52 size_t size;
53 /* True if excluded by selinux_restorecon_set_exclude_list(3). */
54 bool caller_excluded;
55 };
56 #define CALLER_EXCLUDED true
57 static bool ignore_mounts;
58 static int exclude_non_seclabel_mounts(void);
59 static int exclude_count = 0;
60 static struct edir *exclude_lst = NULL;
61 static uint64_t fc_count = 0; /* Number of files processed so far */
62 static uint64_t efile_count; /* Estimated total number of files */
63
64 /* Store information on directories with xattr's. */
65 struct dir_xattr *dir_xattr_list;
66 static struct dir_xattr *dir_xattr_last;
67
68 /* restorecon_flags for passing to restorecon_sb() */
69 struct rest_flags {
70 bool nochange;
71 bool verbose;
72 bool progress;
73 bool mass_relabel;
74 bool set_specctx;
75 bool add_assoc;
76 bool recurse;
77 bool userealpath;
78 bool set_xdev;
79 bool abort_on_error;
80 bool syslog_changes;
81 bool log_matches;
82 bool ignore_noent;
83 bool warnonnomatch;
84 };
85
restorecon_init(void)86 static void restorecon_init(void)
87 {
88 struct selabel_handle *sehandle = NULL;
89
90 if (!fc_sehandle) {
91 sehandle = selinux_restorecon_default_handle();
92 selinux_restorecon_set_sehandle(sehandle);
93 }
94
95 efile_count = 0;
96 if (!ignore_mounts)
97 efile_count = exclude_non_seclabel_mounts();
98 }
99
100 static pthread_once_t fc_once = PTHREAD_ONCE_INIT;
101
102 /*
103 * Manage excluded directories:
104 * remove_exclude() - This removes any conflicting entries as there could be
105 * a case where a non-seclabel fs is mounted on /foo and
106 * then a seclabel fs is mounted on top of it.
107 * However if an entry has been added via
108 * selinux_restorecon_set_exclude_list(3) do not remove.
109 *
110 * add_exclude() - Add a directory/fs to be excluded from labeling. If it
111 * has already been added, then ignore.
112 *
113 * check_excluded() - Check if directory/fs is to be excluded when relabeling.
114 *
115 * file_system_count() - Calculates the number of files to be processed.
116 * The count is only used if SELINUX_RESTORECON_PROGRESS
117 * is set and a mass relabel is requested.
118 *
119 * exclude_non_seclabel_mounts() - Reads /proc/mounts to determine what
120 * non-seclabel mounts to exclude from
121 * relabeling. restorecon_init() will not
122 * call this function if the
123 * SELINUX_RESTORECON_IGNORE_MOUNTS
124 * flag is set.
125 * Setting SELINUX_RESTORECON_IGNORE_MOUNTS
126 * is useful where there is a non-seclabel fs
127 * mounted on /foo and then a seclabel fs is
128 * mounted on a directory below this.
129 */
remove_exclude(const char * directory)130 static void remove_exclude(const char *directory)
131 {
132 int i;
133
134 for (i = 0; i < exclude_count; i++) {
135 if (strcmp(directory, exclude_lst[i].directory) == 0 &&
136 !exclude_lst[i].caller_excluded) {
137 free(exclude_lst[i].directory);
138 if (i != exclude_count - 1)
139 exclude_lst[i] = exclude_lst[exclude_count - 1];
140 exclude_count--;
141 return;
142 }
143 }
144 }
145
add_exclude(const char * directory,bool who)146 static int add_exclude(const char *directory, bool who)
147 {
148 struct edir *tmp_list, *current;
149 size_t len = 0;
150 int i;
151
152 /* Check if already present. */
153 for (i = 0; i < exclude_count; i++) {
154 if (strcmp(directory, exclude_lst[i].directory) == 0)
155 return 0;
156 }
157
158 if (directory == NULL || directory[0] != '/') {
159 selinux_log(SELINUX_ERROR,
160 "Full path required for exclude: %s.\n",
161 directory);
162 errno = EINVAL;
163 return -1;
164 }
165
166 tmp_list = realloc(exclude_lst,
167 sizeof(struct edir) * (exclude_count + 1));
168 if (!tmp_list)
169 goto oom;
170
171 exclude_lst = tmp_list;
172
173 len = strlen(directory);
174 while (len > 1 && directory[len - 1] == '/')
175 len--;
176
177 current = (exclude_lst + exclude_count);
178
179 current->directory = strndup(directory, len);
180 if (!current->directory)
181 goto oom;
182
183 current->size = len;
184 current->caller_excluded = who;
185 exclude_count++;
186 return 0;
187
188 oom:
189 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
190 return -1;
191 }
192
check_excluded(const char * file)193 static int check_excluded(const char *file)
194 {
195 int i;
196
197 for (i = 0; i < exclude_count; i++) {
198 if (strncmp(file, exclude_lst[i].directory,
199 exclude_lst[i].size) == 0) {
200 if (file[exclude_lst[i].size] == 0 ||
201 file[exclude_lst[i].size] == '/')
202 return 1;
203 }
204 }
205 return 0;
206 }
207
file_system_count(char * name)208 static int file_system_count(char *name)
209 {
210 struct statvfs statvfs_buf;
211 int nfile = 0;
212
213 memset(&statvfs_buf, 0, sizeof(statvfs_buf));
214 if (!statvfs(name, &statvfs_buf))
215 nfile = statvfs_buf.f_files - statvfs_buf.f_ffree;
216
217 return nfile;
218 }
219
220 /*
221 * This is called once when selinux_restorecon() is first called.
222 * Searches /proc/mounts for all file systems that do not support extended
223 * attributes and adds them to the exclude directory table. File systems
224 * that support security labels have the seclabel option, return
225 * approximate total file count.
226 */
exclude_non_seclabel_mounts(void)227 static int exclude_non_seclabel_mounts(void)
228 {
229 struct utsname uts;
230 FILE *fp;
231 size_t len;
232 ssize_t num;
233 int index = 0, found = 0, nfile = 0;
234 char *mount_info[4];
235 char *buf = NULL, *item;
236
237 /* Check to see if the kernel supports seclabel */
238 if (uname(&uts) == 0 && strverscmp(uts.release, "2.6.30") < 0)
239 return 0;
240 if (is_selinux_enabled() <= 0)
241 return 0;
242
243 fp = fopen("/proc/mounts", "re");
244 if (!fp)
245 return 0;
246
247 while ((num = getline(&buf, &len, fp)) != -1) {
248 found = 0;
249 index = 0;
250 item = strtok(buf, " ");
251 while (item != NULL) {
252 mount_info[index] = item;
253 index++;
254 if (index == 4)
255 break;
256 item = strtok(NULL, " ");
257 }
258 if (index < 4) {
259 selinux_log(SELINUX_ERROR,
260 "/proc/mounts record \"%s\" has incorrect format.\n",
261 buf);
262 continue;
263 }
264
265 /* Remove pre-existing entry */
266 remove_exclude(mount_info[1]);
267
268 item = strtok(mount_info[3], ",");
269 while (item != NULL) {
270 if (strcmp(item, "seclabel") == 0) {
271 found = 1;
272 nfile += file_system_count(mount_info[1]);
273 break;
274 }
275 item = strtok(NULL, ",");
276 }
277
278 /* Exclude mount points without the seclabel option */
279 if (!found) {
280 if (add_exclude(mount_info[1], !CALLER_EXCLUDED) &&
281 errno == ENOMEM)
282 assert(0);
283 }
284 }
285
286 free(buf);
287 fclose(fp);
288 /* return estimated #Files + 5% for directories and hard links */
289 return nfile * 1.05;
290 }
291
292 /* Called by selinux_restorecon_xattr(3) to build a linked list of entries. */
add_xattr_entry(const char * directory,bool delete_nonmatch,bool delete_all)293 static int add_xattr_entry(const char *directory, bool delete_nonmatch,
294 bool delete_all)
295 {
296 char *sha1_buf = NULL;
297 size_t i, digest_len = 0;
298 int rc, digest_result;
299 struct dir_xattr *new_entry;
300 uint8_t *xattr_digest = NULL;
301 uint8_t *calculated_digest = NULL;
302
303 if (!directory) {
304 errno = EINVAL;
305 return -1;
306 }
307
308 selabel_get_digests_all_partial_matches(fc_sehandle, directory,
309 &calculated_digest,
310 &xattr_digest, &digest_len);
311
312 if (!xattr_digest || !digest_len) {
313 free(calculated_digest);
314 return 1;
315 }
316
317 /* Convert entry to a hex encoded string. */
318 sha1_buf = malloc(digest_len * 2 + 1);
319 if (!sha1_buf) {
320 free(xattr_digest);
321 free(calculated_digest);
322 goto oom;
323 }
324
325 for (i = 0; i < digest_len; i++)
326 sprintf((&sha1_buf[i * 2]), "%02x", xattr_digest[i]);
327
328 rc = memcmp(calculated_digest, xattr_digest, digest_len);
329 digest_result = rc ? NOMATCH : MATCH;
330
331 if ((delete_nonmatch && rc != 0) || delete_all) {
332 digest_result = rc ? DELETED_NOMATCH : DELETED_MATCH;
333 rc = removexattr(directory, RESTORECON_PARTIAL_MATCH_DIGEST);
334 if (rc) {
335 selinux_log(SELINUX_ERROR,
336 "Error: %s removing xattr \"%s\" from: %s\n",
337 strerror(errno),
338 RESTORECON_PARTIAL_MATCH_DIGEST, directory);
339 digest_result = ERROR;
340 }
341 }
342 free(xattr_digest);
343 free(calculated_digest);
344
345 /* Now add entries to link list. */
346 new_entry = malloc(sizeof(struct dir_xattr));
347 if (!new_entry) {
348 free(sha1_buf);
349 goto oom;
350 }
351 new_entry->next = NULL;
352
353 new_entry->directory = strdup(directory);
354 if (!new_entry->directory) {
355 free(new_entry);
356 free(sha1_buf);
357 goto oom;
358 }
359
360 new_entry->digest = strdup(sha1_buf);
361 if (!new_entry->digest) {
362 free(new_entry->directory);
363 free(new_entry);
364 free(sha1_buf);
365 goto oom;
366 }
367
368 new_entry->result = digest_result;
369
370 if (!dir_xattr_list) {
371 dir_xattr_list = new_entry;
372 dir_xattr_last = new_entry;
373 } else {
374 dir_xattr_last->next = new_entry;
375 dir_xattr_last = new_entry;
376 }
377
378 free(sha1_buf);
379 return 0;
380
381 oom:
382 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
383 return -1;
384 }
385
386 /*
387 * Support filespec services filespec_add(), filespec_eval() and
388 * filespec_destroy().
389 *
390 * selinux_restorecon(3) uses filespec services when the
391 * SELINUX_RESTORECON_ADD_ASSOC flag is set for adding associations between
392 * an inode and a specification.
393 */
394
395 /*
396 * The hash table of associations, hashed by inode number. Chaining is used
397 * for collisions, with elements ordered by inode number in each bucket.
398 * Each hash bucket has a dummy header.
399 */
400 #define HASH_BITS 16
401 #define HASH_BUCKETS (1 << HASH_BITS)
402 #define HASH_MASK (HASH_BUCKETS-1)
403
404 /*
405 * An association between an inode and a context.
406 */
407 typedef struct file_spec {
408 ino_t ino; /* inode number */
409 char *con; /* matched context */
410 char *file; /* full pathname */
411 struct file_spec *next; /* next association in hash bucket chain */
412 } file_spec_t;
413
414 static file_spec_t *fl_head;
415
416 /*
417 * Try to add an association between an inode and a context. If there is a
418 * different context that matched the inode, then use the first context
419 * that matched.
420 */
filespec_add(ino_t ino,const char * con,const char * file)421 static int filespec_add(ino_t ino, const char *con, const char *file)
422 {
423 file_spec_t *prevfl, *fl;
424 int h, ret;
425 struct stat64 sb;
426
427 if (!fl_head) {
428 fl_head = malloc(sizeof(file_spec_t) * HASH_BUCKETS);
429 if (!fl_head)
430 goto oom;
431 memset(fl_head, 0, sizeof(file_spec_t) * HASH_BUCKETS);
432 }
433
434 h = (ino + (ino >> HASH_BITS)) & HASH_MASK;
435 for (prevfl = &fl_head[h], fl = fl_head[h].next; fl;
436 prevfl = fl, fl = fl->next) {
437 if (ino == fl->ino) {
438 ret = lstat64(fl->file, &sb);
439 if (ret < 0 || sb.st_ino != ino) {
440 freecon(fl->con);
441 free(fl->file);
442 fl->file = strdup(file);
443 if (!fl->file)
444 goto oom;
445 fl->con = strdup(con);
446 if (!fl->con)
447 goto oom;
448 return 1;
449 }
450
451 if (strcmp(fl->con, con) == 0)
452 return 1;
453
454 selinux_log(SELINUX_ERROR,
455 "conflicting specifications for %s and %s, using %s.\n",
456 file, fl->file, fl->con);
457 free(fl->file);
458 fl->file = strdup(file);
459 if (!fl->file)
460 goto oom;
461 return 1;
462 }
463
464 if (ino > fl->ino)
465 break;
466 }
467
468 fl = malloc(sizeof(file_spec_t));
469 if (!fl)
470 goto oom;
471 fl->ino = ino;
472 fl->con = strdup(con);
473 if (!fl->con)
474 goto oom_freefl;
475 fl->file = strdup(file);
476 if (!fl->file)
477 goto oom_freefl;
478 fl->next = prevfl->next;
479 prevfl->next = fl;
480 return 0;
481
482 oom_freefl:
483 free(fl);
484 oom:
485 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
486 return -1;
487 }
488
489 /*
490 * Evaluate the association hash table distribution.
491 */
492 #ifdef DEBUG
filespec_eval(void)493 static void filespec_eval(void)
494 {
495 file_spec_t *fl;
496 int h, used, nel, len, longest;
497
498 if (!fl_head)
499 return;
500
501 used = 0;
502 longest = 0;
503 nel = 0;
504 for (h = 0; h < HASH_BUCKETS; h++) {
505 len = 0;
506 for (fl = fl_head[h].next; fl; fl = fl->next)
507 len++;
508 if (len)
509 used++;
510 if (len > longest)
511 longest = len;
512 nel += len;
513 }
514
515 selinux_log(SELINUX_INFO,
516 "filespec hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n",
517 nel, used, HASH_BUCKETS, longest);
518 }
519 #else
filespec_eval(void)520 static void filespec_eval(void)
521 {
522 }
523 #endif
524
525 /*
526 * Destroy the association hash table.
527 */
filespec_destroy(void)528 static void filespec_destroy(void)
529 {
530 file_spec_t *fl, *tmp;
531 int h;
532
533 if (!fl_head)
534 return;
535
536 for (h = 0; h < HASH_BUCKETS; h++) {
537 fl = fl_head[h].next;
538 while (fl) {
539 tmp = fl;
540 fl = fl->next;
541 freecon(tmp->con);
542 free(tmp->file);
543 free(tmp);
544 }
545 fl_head[h].next = NULL;
546 }
547 free(fl_head);
548 fl_head = NULL;
549 }
550
551 /*
552 * Called if SELINUX_RESTORECON_SET_SPECFILE_CTX is not set to check if
553 * the type components differ, updating newtypecon if so.
554 */
compare_types(char * curcon,char * newcon,char ** newtypecon)555 static int compare_types(char *curcon, char *newcon, char **newtypecon)
556 {
557 int types_differ = 0;
558 context_t cona;
559 context_t conb;
560 int rc = 0;
561
562 cona = context_new(curcon);
563 if (!cona) {
564 rc = -1;
565 goto out;
566 }
567 conb = context_new(newcon);
568 if (!conb) {
569 context_free(cona);
570 rc = -1;
571 goto out;
572 }
573
574 types_differ = strcmp(context_type_get(cona), context_type_get(conb));
575 if (types_differ) {
576 rc |= context_user_set(conb, context_user_get(cona));
577 rc |= context_role_set(conb, context_role_get(cona));
578 rc |= context_range_set(conb, context_range_get(cona));
579 if (!rc) {
580 *newtypecon = strdup(context_str(conb));
581 if (!*newtypecon) {
582 rc = -1;
583 goto err;
584 }
585 }
586 }
587
588 err:
589 context_free(cona);
590 context_free(conb);
591 out:
592 return rc;
593 }
594
restorecon_sb(const char * pathname,const struct stat * sb,struct rest_flags * flags)595 static int restorecon_sb(const char *pathname, const struct stat *sb,
596 struct rest_flags *flags)
597 {
598 char *newcon = NULL;
599 char *curcon = NULL;
600 char *newtypecon = NULL;
601 int rc;
602 bool updated = false;
603 const char *lookup_path = pathname;
604 float pc;
605
606 if (rootpath) {
607 if (strncmp(rootpath, lookup_path, rootpathlen) != 0) {
608 selinux_log(SELINUX_ERROR,
609 "%s is not located in alt_rootpath %s\n",
610 lookup_path, rootpath);
611 return -1;
612 }
613 lookup_path += rootpathlen;
614 }
615
616 if (rootpath != NULL && lookup_path[0] == '\0')
617 /* this is actually the root dir of the alt root. */
618 rc = selabel_lookup_raw(fc_sehandle, &newcon, "/",
619 sb->st_mode);
620 else
621 rc = selabel_lookup_raw(fc_sehandle, &newcon, lookup_path,
622 sb->st_mode);
623
624 if (rc < 0) {
625 if (errno == ENOENT && flags->warnonnomatch)
626 selinux_log(SELINUX_INFO,
627 "Warning no default label for %s\n",
628 lookup_path);
629
630 return 0; /* no match, but not an error */
631 }
632
633 if (flags->progress) {
634 fc_count++;
635 if (fc_count % STAR_COUNT == 0) {
636 if (flags->mass_relabel && efile_count > 0) {
637 pc = (fc_count < efile_count) ? (100.0 *
638 fc_count / efile_count) : 100;
639 fprintf(stdout, "\r%-.1f%%", (double)pc);
640 } else {
641 fprintf(stdout, "\r%" PRIu64 "k", fc_count / STAR_COUNT);
642 }
643 fflush(stdout);
644 }
645 }
646
647 if (flags->add_assoc) {
648 rc = filespec_add(sb->st_ino, newcon, pathname);
649
650 if (rc < 0) {
651 selinux_log(SELINUX_ERROR,
652 "filespec_add error: %s\n", pathname);
653 freecon(newcon);
654 return -1;
655 }
656
657 if (rc > 0) {
658 /* Already an association and it took precedence. */
659 freecon(newcon);
660 return 0;
661 }
662 }
663
664 if (flags->log_matches)
665 selinux_log(SELINUX_INFO, "%s matched by %s\n",
666 pathname, newcon);
667
668 if (lgetfilecon_raw(pathname, &curcon) < 0) {
669 if (errno != ENODATA)
670 goto err;
671
672 curcon = NULL;
673 }
674
675 if (curcon == NULL || strcmp(curcon, newcon) != 0) {
676 if (!flags->set_specctx && curcon &&
677 (is_context_customizable(curcon) > 0)) {
678 if (flags->verbose) {
679 selinux_log(SELINUX_INFO,
680 "%s not reset as customized by admin to %s\n",
681 pathname, curcon);
682 }
683 goto out;
684 }
685
686 if (!flags->set_specctx && curcon) {
687 /* If types different then update newcon. */
688 rc = compare_types(curcon, newcon, &newtypecon);
689 if (rc)
690 goto err;
691
692 if (newtypecon) {
693 freecon(newcon);
694 newcon = newtypecon;
695 } else {
696 goto out;
697 }
698 }
699
700 if (!flags->nochange) {
701 if (lsetfilecon(pathname, newcon) < 0)
702 goto err;
703 updated = true;
704 }
705
706 if (flags->verbose)
707 selinux_log(SELINUX_INFO,
708 "%s %s from %s to %s\n",
709 updated ? "Relabeled" : "Would relabel",
710 pathname, curcon, newcon);
711
712 if (flags->syslog_changes && !flags->nochange) {
713 if (curcon)
714 syslog(LOG_INFO,
715 "relabeling %s from %s to %s\n",
716 pathname, curcon, newcon);
717 else
718 syslog(LOG_INFO, "labeling %s to %s\n",
719 pathname, newcon);
720 }
721 }
722
723 out:
724 rc = 0;
725 out1:
726 freecon(curcon);
727 freecon(newcon);
728 return rc;
729 err:
730 selinux_log(SELINUX_ERROR,
731 "Could not set context for %s: %s\n",
732 pathname, strerror(errno));
733 rc = -1;
734 goto out1;
735 }
736
737 struct dir_hash_node {
738 char *path;
739 uint8_t digest[SHA1_HASH_SIZE];
740 struct dir_hash_node *next;
741 };
742 /*
743 * Returns true if the digest of all partial matched contexts is the same as
744 * the one saved by setxattr. Otherwise returns false and constructs a
745 * dir_hash_node with the newly calculated digest.
746 */
check_context_match_for_dir(const char * pathname,struct dir_hash_node ** new_node,int error)747 static bool check_context_match_for_dir(const char *pathname,
748 struct dir_hash_node **new_node,
749 int error)
750 {
751 bool status;
752 size_t digest_len = 0;
753 uint8_t *read_digest = NULL;
754 uint8_t *calculated_digest = NULL;
755
756 if (!new_node)
757 return false;
758
759 *new_node = NULL;
760
761 /* status = true if digests match, false otherwise. */
762 status = selabel_get_digests_all_partial_matches(fc_sehandle, pathname,
763 &calculated_digest,
764 &read_digest,
765 &digest_len);
766
767 if (status)
768 goto free;
769
770 /* Save digest of all matched contexts for the current directory. */
771 if (!error && calculated_digest) {
772 *new_node = calloc(1, sizeof(struct dir_hash_node));
773
774 if (!*new_node)
775 goto oom;
776
777 (*new_node)->path = strdup(pathname);
778
779 if (!(*new_node)->path) {
780 free(*new_node);
781 *new_node = NULL;
782 goto oom;
783 }
784 memcpy((*new_node)->digest, calculated_digest, digest_len);
785 (*new_node)->next = NULL;
786 }
787
788 free:
789 free(calculated_digest);
790 free(read_digest);
791 return status;
792
793 oom:
794 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
795 goto free;
796 }
797
798
799 /*
800 * Public API
801 */
802
803 /* selinux_restorecon(3) - Main function that is responsible for labeling */
selinux_restorecon(const char * pathname_orig,unsigned int restorecon_flags)804 int selinux_restorecon(const char *pathname_orig,
805 unsigned int restorecon_flags)
806 {
807 struct rest_flags flags;
808
809 flags.nochange = (restorecon_flags &
810 SELINUX_RESTORECON_NOCHANGE) ? true : false;
811 flags.verbose = (restorecon_flags &
812 SELINUX_RESTORECON_VERBOSE) ? true : false;
813 flags.progress = (restorecon_flags &
814 SELINUX_RESTORECON_PROGRESS) ? true : false;
815 flags.mass_relabel = (restorecon_flags &
816 SELINUX_RESTORECON_MASS_RELABEL) ? true : false;
817 flags.recurse = (restorecon_flags &
818 SELINUX_RESTORECON_RECURSE) ? true : false;
819 flags.set_specctx = (restorecon_flags &
820 SELINUX_RESTORECON_SET_SPECFILE_CTX) ? true : false;
821 flags.userealpath = (restorecon_flags &
822 SELINUX_RESTORECON_REALPATH) ? true : false;
823 flags.set_xdev = (restorecon_flags &
824 SELINUX_RESTORECON_XDEV) ? true : false;
825 flags.add_assoc = (restorecon_flags &
826 SELINUX_RESTORECON_ADD_ASSOC) ? true : false;
827 flags.abort_on_error = (restorecon_flags &
828 SELINUX_RESTORECON_ABORT_ON_ERROR) ? true : false;
829 flags.syslog_changes = (restorecon_flags &
830 SELINUX_RESTORECON_SYSLOG_CHANGES) ? true : false;
831 flags.log_matches = (restorecon_flags &
832 SELINUX_RESTORECON_LOG_MATCHES) ? true : false;
833 flags.ignore_noent = (restorecon_flags &
834 SELINUX_RESTORECON_IGNORE_NOENTRY) ? true : false;
835 flags.warnonnomatch = true;
836 ignore_mounts = (restorecon_flags &
837 SELINUX_RESTORECON_IGNORE_MOUNTS) ? true : false;
838 bool ignore_digest = (restorecon_flags &
839 SELINUX_RESTORECON_IGNORE_DIGEST) ? true : false;
840 bool setrestorecondigest = true;
841
842 struct stat sb;
843 struct statfs sfsb;
844 FTS *fts;
845 FTSENT *ftsent;
846 char *pathname = NULL, *pathdnamer = NULL, *pathdname, *pathbname;
847 char *paths[2] = { NULL, NULL };
848 int fts_flags, error, sverrno;
849 dev_t dev_num = 0;
850 struct dir_hash_node *current = NULL;
851 struct dir_hash_node *head = NULL;
852
853 if (flags.verbose && flags.progress)
854 flags.verbose = false;
855
856 __selinux_once(fc_once, restorecon_init);
857
858 if (!fc_sehandle)
859 return -1;
860
861 /*
862 * If selabel_no_digest = true then no digest has been requested by
863 * an external selabel_open(3) call.
864 */
865 if (selabel_no_digest ||
866 (restorecon_flags & SELINUX_RESTORECON_SKIP_DIGEST))
867 setrestorecondigest = false;
868
869 /*
870 * Convert passed-in pathname to canonical pathname by resolving
871 * realpath of containing dir, then appending last component name.
872 */
873 if (flags.userealpath) {
874 char *basename_cpy = strdup(pathname_orig);
875 if (!basename_cpy)
876 goto realpatherr;
877 pathbname = basename(basename_cpy);
878 if (!strcmp(pathbname, "/") || !strcmp(pathbname, ".") ||
879 !strcmp(pathbname, "..")) {
880 pathname = realpath(pathname_orig, NULL);
881 if (!pathname) {
882 free(basename_cpy);
883 goto realpatherr;
884 }
885 } else {
886 char *dirname_cpy = strdup(pathname_orig);
887 if (!dirname_cpy) {
888 free(basename_cpy);
889 goto realpatherr;
890 }
891 pathdname = dirname(dirname_cpy);
892 pathdnamer = realpath(pathdname, NULL);
893 free(dirname_cpy);
894 if (!pathdnamer) {
895 free(basename_cpy);
896 goto realpatherr;
897 }
898 if (!strcmp(pathdnamer, "/"))
899 error = asprintf(&pathname, "/%s", pathbname);
900 else
901 error = asprintf(&pathname, "%s/%s",
902 pathdnamer, pathbname);
903 if (error < 0) {
904 free(basename_cpy);
905 goto oom;
906 }
907 }
908 free(basename_cpy);
909 } else {
910 pathname = strdup(pathname_orig);
911 if (!pathname)
912 goto oom;
913 }
914
915 paths[0] = pathname;
916
917 if (lstat(pathname, &sb) < 0) {
918 if (flags.ignore_noent && errno == ENOENT) {
919 free(pathdnamer);
920 free(pathname);
921 return 0;
922 } else {
923 selinux_log(SELINUX_ERROR,
924 "lstat(%s) failed: %s\n",
925 pathname, strerror(errno));
926 error = -1;
927 goto cleanup;
928 }
929 }
930
931 /* Skip digest if not a directory */
932 if ((sb.st_mode & S_IFDIR) != S_IFDIR)
933 setrestorecondigest = false;
934
935 if (!flags.recurse) {
936 if (check_excluded(pathname)) {
937 error = 0;
938 goto cleanup;
939 }
940
941 error = restorecon_sb(pathname, &sb, &flags);
942 goto cleanup;
943 }
944
945 /* Obtain fs type */
946 if (statfs(pathname, &sfsb) < 0) {
947 selinux_log(SELINUX_ERROR,
948 "statfs(%s) failed: %s\n",
949 pathname, strerror(errno));
950 error = -1;
951 goto cleanup;
952 }
953
954 /* Skip digest on in-memory filesystems and /sys */
955 if (sfsb.f_type == RAMFS_MAGIC || sfsb.f_type == TMPFS_MAGIC ||
956 sfsb.f_type == SYSFS_MAGIC)
957 setrestorecondigest = false;
958
959 if (flags.set_xdev)
960 fts_flags = FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV;
961 else
962 fts_flags = FTS_PHYSICAL | FTS_NOCHDIR;
963
964 fts = fts_open(paths, fts_flags, NULL);
965 if (!fts)
966 goto fts_err;
967
968 ftsent = fts_read(fts);
969 if (!ftsent)
970 goto fts_err;
971
972 /*
973 * Keep the inode of the first device. This is because the FTS_XDEV
974 * flag tells fts not to descend into directories with different
975 * device numbers, but fts will still give back the actual directory.
976 * By saving the device number of the directory that was passed to
977 * selinux_restorecon() and then skipping all actions on any
978 * directories with a different device number when the FTS_XDEV flag
979 * is set (from http://marc.info/?l=selinux&m=124688830500777&w=2).
980 */
981 dev_num = ftsent->fts_statp->st_dev;
982
983 error = 0;
984 do {
985 /* If the FTS_XDEV flag is set and the device is different */
986 if (flags.set_xdev && ftsent->fts_statp->st_dev != dev_num)
987 continue;
988
989 switch (ftsent->fts_info) {
990 case FTS_DC:
991 selinux_log(SELINUX_ERROR,
992 "Directory cycle on %s.\n",
993 ftsent->fts_path);
994 errno = ELOOP;
995 error = -1;
996 goto out;
997 case FTS_DP:
998 continue;
999 case FTS_DNR:
1000 selinux_log(SELINUX_ERROR,
1001 "Could not read %s: %s.\n",
1002 ftsent->fts_path,
1003 strerror(ftsent->fts_errno));
1004 fts_set(fts, ftsent, FTS_SKIP);
1005 continue;
1006 case FTS_NS:
1007 selinux_log(SELINUX_ERROR,
1008 "Could not stat %s: %s.\n",
1009 ftsent->fts_path,
1010 strerror(ftsent->fts_errno));
1011 fts_set(fts, ftsent, FTS_SKIP);
1012 continue;
1013 case FTS_ERR:
1014 selinux_log(SELINUX_ERROR,
1015 "Error on %s: %s.\n",
1016 ftsent->fts_path,
1017 strerror(ftsent->fts_errno));
1018 fts_set(fts, ftsent, FTS_SKIP);
1019 continue;
1020 case FTS_D:
1021 if (sfsb.f_type == SYSFS_MAGIC &&
1022 !selabel_partial_match(fc_sehandle,
1023 ftsent->fts_path)) {
1024 fts_set(fts, ftsent, FTS_SKIP);
1025 continue;
1026 }
1027
1028 if (check_excluded(ftsent->fts_path)) {
1029 fts_set(fts, ftsent, FTS_SKIP);
1030 continue;
1031 }
1032
1033 if (setrestorecondigest) {
1034 struct dir_hash_node *new_node = NULL;
1035
1036 if (check_context_match_for_dir(ftsent->fts_path,
1037 &new_node,
1038 error) &&
1039 !ignore_digest) {
1040 selinux_log(SELINUX_INFO,
1041 "Skipping restorecon on directory(%s)\n",
1042 ftsent->fts_path);
1043 fts_set(fts, ftsent, FTS_SKIP);
1044 continue;
1045 }
1046
1047 if (new_node && !error) {
1048 if (!current) {
1049 current = new_node;
1050 head = current;
1051 } else {
1052 current->next = new_node;
1053 current = current->next;
1054 }
1055 }
1056 }
1057 /* fall through */
1058 default:
1059 error |= restorecon_sb(ftsent->fts_path,
1060 ftsent->fts_statp, &flags);
1061 if (flags.warnonnomatch)
1062 flags.warnonnomatch = false;
1063 if (error && flags.abort_on_error)
1064 goto out;
1065 break;
1066 }
1067 } while ((ftsent = fts_read(fts)) != NULL);
1068
1069 /*
1070 * Labeling successful. Write partial match digests for subdirectories.
1071 * TODO: Write digest upon FTS_DP if no error occurs in its descents.
1072 */
1073 if (setrestorecondigest && !flags.nochange && !error) {
1074 current = head;
1075 while (current != NULL) {
1076 if (setxattr(current->path,
1077 RESTORECON_PARTIAL_MATCH_DIGEST,
1078 current->digest,
1079 SHA1_HASH_SIZE, 0) < 0) {
1080 selinux_log(SELINUX_ERROR,
1081 "setxattr failed: %s: %s\n",
1082 current->path,
1083 strerror(errno));
1084 }
1085 current = current->next;
1086 }
1087 }
1088
1089 out:
1090 if (flags.progress && flags.mass_relabel)
1091 fprintf(stdout, "\r%s 100.0%%\n", pathname);
1092
1093 sverrno = errno;
1094 (void) fts_close(fts);
1095 errno = sverrno;
1096 cleanup:
1097 if (flags.add_assoc) {
1098 if (flags.verbose)
1099 filespec_eval();
1100 filespec_destroy();
1101 }
1102 free(pathdnamer);
1103 free(pathname);
1104
1105 current = head;
1106 while (current != NULL) {
1107 struct dir_hash_node *next = current->next;
1108
1109 free(current->path);
1110 free(current);
1111 current = next;
1112 }
1113 return error;
1114
1115 oom:
1116 sverrno = errno;
1117 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
1118 errno = sverrno;
1119 error = -1;
1120 goto cleanup;
1121
1122 realpatherr:
1123 sverrno = errno;
1124 selinux_log(SELINUX_ERROR,
1125 "SELinux: Could not get canonical path for %s restorecon: %s.\n",
1126 pathname_orig, strerror(errno));
1127 errno = sverrno;
1128 error = -1;
1129 goto cleanup;
1130
1131 fts_err:
1132 selinux_log(SELINUX_ERROR,
1133 "fts error while labeling %s: %s\n",
1134 paths[0], strerror(errno));
1135 error = -1;
1136 goto cleanup;
1137 }
1138
1139 /* selinux_restorecon_set_sehandle(3) is called to set the global fc handle */
selinux_restorecon_set_sehandle(struct selabel_handle * hndl)1140 void selinux_restorecon_set_sehandle(struct selabel_handle *hndl)
1141 {
1142 char **specfiles;
1143 unsigned char *fc_digest;
1144 size_t num_specfiles, fc_digest_len;
1145
1146 fc_sehandle = (struct selabel_handle *) hndl;
1147
1148 /* Check if digest requested in selabel_open(3), if so use it. */
1149 if (selabel_digest(fc_sehandle, &fc_digest, &fc_digest_len,
1150 &specfiles, &num_specfiles) < 0)
1151 selabel_no_digest = true;
1152 else
1153 selabel_no_digest = false;
1154 }
1155
1156
1157 /*
1158 * selinux_restorecon_default_handle(3) is called to set the global restorecon
1159 * handle by a process if the default params are required.
1160 */
selinux_restorecon_default_handle(void)1161 struct selabel_handle *selinux_restorecon_default_handle(void)
1162 {
1163 struct selabel_handle *sehandle;
1164
1165 struct selinux_opt fc_opts[] = {
1166 { SELABEL_OPT_DIGEST, (char *)1 }
1167 };
1168
1169 sehandle = selabel_open(SELABEL_CTX_FILE, fc_opts, 1);
1170
1171 if (!sehandle) {
1172 selinux_log(SELINUX_ERROR,
1173 "Error obtaining file context handle: %s\n",
1174 strerror(errno));
1175 return NULL;
1176 }
1177
1178 selabel_no_digest = false;
1179 return sehandle;
1180 }
1181
1182 /*
1183 * selinux_restorecon_set_exclude_list(3) is called to add additional entries
1184 * to be excluded from labeling checks.
1185 */
selinux_restorecon_set_exclude_list(const char ** exclude_list)1186 void selinux_restorecon_set_exclude_list(const char **exclude_list)
1187 {
1188 int i;
1189 struct stat sb;
1190
1191 for (i = 0; exclude_list[i]; i++) {
1192 if (lstat(exclude_list[i], &sb) < 0 && errno != EACCES) {
1193 selinux_log(SELINUX_ERROR,
1194 "lstat error on exclude path \"%s\", %s - ignoring.\n",
1195 exclude_list[i], strerror(errno));
1196 break;
1197 }
1198 if (add_exclude(exclude_list[i], CALLER_EXCLUDED) &&
1199 errno == ENOMEM)
1200 assert(0);
1201 }
1202 }
1203
1204 /* selinux_restorecon_set_alt_rootpath(3) sets an alternate rootpath. */
selinux_restorecon_set_alt_rootpath(const char * alt_rootpath)1205 int selinux_restorecon_set_alt_rootpath(const char *alt_rootpath)
1206 {
1207 int len;
1208
1209 /* This should be NULL on first use */
1210 if (rootpath)
1211 free(rootpath);
1212
1213 rootpath = strdup(alt_rootpath);
1214 if (!rootpath) {
1215 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
1216 return -1;
1217 }
1218
1219 /* trim trailing /, if present */
1220 len = strlen(rootpath);
1221 while (len && (rootpath[len - 1] == '/'))
1222 rootpath[--len] = '\0';
1223 rootpathlen = len;
1224
1225 return 0;
1226 }
1227
1228 /* selinux_restorecon_xattr(3)
1229 * Find RESTORECON_PARTIAL_MATCH_DIGEST entries.
1230 */
selinux_restorecon_xattr(const char * pathname,unsigned int xattr_flags,struct dir_xattr *** xattr_list)1231 int selinux_restorecon_xattr(const char *pathname, unsigned int xattr_flags,
1232 struct dir_xattr ***xattr_list)
1233 {
1234 bool recurse = (xattr_flags &
1235 SELINUX_RESTORECON_XATTR_RECURSE) ? true : false;
1236 bool delete_nonmatch = (xattr_flags &
1237 SELINUX_RESTORECON_XATTR_DELETE_NONMATCH_DIGESTS) ? true : false;
1238 bool delete_all = (xattr_flags &
1239 SELINUX_RESTORECON_XATTR_DELETE_ALL_DIGESTS) ? true : false;
1240 ignore_mounts = (xattr_flags &
1241 SELINUX_RESTORECON_XATTR_IGNORE_MOUNTS) ? true : false;
1242
1243 int rc, fts_flags;
1244 struct stat sb;
1245 struct statfs sfsb;
1246 struct dir_xattr *current, *next;
1247 FTS *fts;
1248 FTSENT *ftsent;
1249 char *paths[2] = { NULL, NULL };
1250
1251 __selinux_once(fc_once, restorecon_init);
1252
1253 if (!fc_sehandle)
1254 return -1;
1255
1256 if (lstat(pathname, &sb) < 0) {
1257 if (errno == ENOENT)
1258 return 0;
1259
1260 selinux_log(SELINUX_ERROR,
1261 "lstat(%s) failed: %s\n",
1262 pathname, strerror(errno));
1263 return -1;
1264 }
1265
1266 if (!recurse) {
1267 if (statfs(pathname, &sfsb) == 0) {
1268 if (sfsb.f_type == RAMFS_MAGIC ||
1269 sfsb.f_type == TMPFS_MAGIC)
1270 return 0;
1271 }
1272
1273 if (check_excluded(pathname))
1274 return 0;
1275
1276 rc = add_xattr_entry(pathname, delete_nonmatch, delete_all);
1277
1278 if (!rc && dir_xattr_list)
1279 *xattr_list = &dir_xattr_list;
1280 else if (rc == -1)
1281 return rc;
1282
1283 return 0;
1284 }
1285
1286 paths[0] = (char *)pathname;
1287 fts_flags = FTS_PHYSICAL | FTS_NOCHDIR;
1288
1289 fts = fts_open(paths, fts_flags, NULL);
1290 if (!fts) {
1291 selinux_log(SELINUX_ERROR,
1292 "fts error on %s: %s\n",
1293 paths[0], strerror(errno));
1294 return -1;
1295 }
1296
1297 while ((ftsent = fts_read(fts)) != NULL) {
1298 switch (ftsent->fts_info) {
1299 case FTS_DP:
1300 continue;
1301 case FTS_D:
1302 if (statfs(ftsent->fts_path, &sfsb) == 0) {
1303 if (sfsb.f_type == RAMFS_MAGIC ||
1304 sfsb.f_type == TMPFS_MAGIC)
1305 continue;
1306 }
1307 if (check_excluded(ftsent->fts_path)) {
1308 fts_set(fts, ftsent, FTS_SKIP);
1309 continue;
1310 }
1311
1312 rc = add_xattr_entry(ftsent->fts_path,
1313 delete_nonmatch, delete_all);
1314 if (rc == 1)
1315 continue;
1316 else if (rc == -1)
1317 goto cleanup;
1318 break;
1319 default:
1320 break;
1321 }
1322 }
1323
1324 if (dir_xattr_list)
1325 *xattr_list = &dir_xattr_list;
1326
1327 (void) fts_close(fts);
1328 return 0;
1329
1330 cleanup:
1331 rc = errno;
1332 (void) fts_close(fts);
1333 errno = rc;
1334
1335 if (dir_xattr_list) {
1336 /* Free any used memory */
1337 current = dir_xattr_list;
1338 while (current) {
1339 next = current->next;
1340 free(current->directory);
1341 free(current->digest);
1342 free(current);
1343 current = next;
1344 }
1345 }
1346 return -1;
1347 }
1348