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 static pthread_mutex_t progress_mutex = PTHREAD_MUTEX_INITIALIZER;
64
65 /* Store information on directories with xattr's. */
66 static struct dir_xattr *dir_xattr_list;
67 static struct dir_xattr *dir_xattr_last;
68
69 /* restorecon_flags for passing to restorecon_sb() */
70 struct rest_flags {
71 bool nochange;
72 bool verbose;
73 bool progress;
74 bool mass_relabel;
75 bool set_specctx;
76 bool add_assoc;
77 bool recurse;
78 bool userealpath;
79 bool set_xdev;
80 bool abort_on_error;
81 bool syslog_changes;
82 bool log_matches;
83 bool ignore_noent;
84 bool warnonnomatch;
85 bool conflicterror;
86 };
87
restorecon_init(void)88 static void restorecon_init(void)
89 {
90 struct selabel_handle *sehandle = NULL;
91
92 if (!fc_sehandle) {
93 sehandle = selinux_restorecon_default_handle();
94 selinux_restorecon_set_sehandle(sehandle);
95 }
96
97 efile_count = 0;
98 if (!ignore_mounts)
99 efile_count = exclude_non_seclabel_mounts();
100 }
101
102 static pthread_once_t fc_once = PTHREAD_ONCE_INIT;
103
104 /*
105 * Manage excluded directories:
106 * remove_exclude() - This removes any conflicting entries as there could be
107 * a case where a non-seclabel fs is mounted on /foo and
108 * then a seclabel fs is mounted on top of it.
109 * However if an entry has been added via
110 * selinux_restorecon_set_exclude_list(3) do not remove.
111 *
112 * add_exclude() - Add a directory/fs to be excluded from labeling. If it
113 * has already been added, then ignore.
114 *
115 * check_excluded() - Check if directory/fs is to be excluded when relabeling.
116 *
117 * file_system_count() - Calculates the number of files to be processed.
118 * The count is only used if SELINUX_RESTORECON_PROGRESS
119 * is set and a mass relabel is requested.
120 *
121 * exclude_non_seclabel_mounts() - Reads /proc/mounts to determine what
122 * non-seclabel mounts to exclude from
123 * relabeling. restorecon_init() will not
124 * call this function if the
125 * SELINUX_RESTORECON_IGNORE_MOUNTS
126 * flag is set.
127 * Setting SELINUX_RESTORECON_IGNORE_MOUNTS
128 * is useful where there is a non-seclabel fs
129 * mounted on /foo and then a seclabel fs is
130 * mounted on a directory below this.
131 */
remove_exclude(const char * directory)132 static void remove_exclude(const char *directory)
133 {
134 int i;
135
136 for (i = 0; i < exclude_count; i++) {
137 if (strcmp(directory, exclude_lst[i].directory) == 0 &&
138 !exclude_lst[i].caller_excluded) {
139 free(exclude_lst[i].directory);
140 if (i != exclude_count - 1)
141 exclude_lst[i] = exclude_lst[exclude_count - 1];
142 exclude_count--;
143 return;
144 }
145 }
146 }
147
add_exclude(const char * directory,bool who)148 static int add_exclude(const char *directory, bool who)
149 {
150 struct edir *tmp_list, *current;
151 size_t len = 0;
152 int i;
153
154 /* Check if already present. */
155 for (i = 0; i < exclude_count; i++) {
156 if (strcmp(directory, exclude_lst[i].directory) == 0)
157 return 0;
158 }
159
160 if (directory == NULL || directory[0] != '/') {
161 selinux_log(SELINUX_ERROR,
162 "Full path required for exclude: %s.\n",
163 directory);
164 errno = EINVAL;
165 return -1;
166 }
167
168 tmp_list = realloc(exclude_lst,
169 sizeof(struct edir) * (exclude_count + 1));
170 if (!tmp_list)
171 goto oom;
172
173 exclude_lst = tmp_list;
174
175 len = strlen(directory);
176 while (len > 1 && directory[len - 1] == '/')
177 len--;
178
179 current = (exclude_lst + exclude_count);
180
181 current->directory = strndup(directory, len);
182 if (!current->directory)
183 goto oom;
184
185 current->size = len;
186 current->caller_excluded = who;
187 exclude_count++;
188 return 0;
189
190 oom:
191 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
192 return -1;
193 }
194
check_excluded(const char * file)195 static int check_excluded(const char *file)
196 {
197 int i;
198
199 for (i = 0; i < exclude_count; i++) {
200 if (strncmp(file, exclude_lst[i].directory,
201 exclude_lst[i].size) == 0) {
202 if (file[exclude_lst[i].size] == 0 ||
203 file[exclude_lst[i].size] == '/')
204 return 1;
205 }
206 }
207 return 0;
208 }
209
file_system_count(char * name)210 static int file_system_count(char *name)
211 {
212 struct statvfs statvfs_buf;
213 int nfile = 0;
214
215 memset(&statvfs_buf, 0, sizeof(statvfs_buf));
216 if (!statvfs(name, &statvfs_buf))
217 nfile = statvfs_buf.f_files - statvfs_buf.f_ffree;
218
219 return nfile;
220 }
221
222 /*
223 * This is called once when selinux_restorecon() is first called.
224 * Searches /proc/mounts for all file systems that do not support extended
225 * attributes and adds them to the exclude directory table. File systems
226 * that support security labels have the seclabel option, return
227 * approximate total file count.
228 */
exclude_non_seclabel_mounts(void)229 static int exclude_non_seclabel_mounts(void)
230 {
231 struct utsname uts;
232 FILE *fp;
233 size_t len;
234 int index = 0, found = 0, nfile = 0;
235 char *mount_info[4];
236 char *buf = NULL, *item;
237
238 /* Check to see if the kernel supports seclabel */
239 if (uname(&uts) == 0 && strverscmp(uts.release, "2.6.30") < 0)
240 return 0;
241 if (is_selinux_enabled() <= 0)
242 return 0;
243
244 fp = fopen("/proc/mounts", "re");
245 if (!fp)
246 return 0;
247
248 while (getline(&buf, &len, fp) != -1) {
249 found = 0;
250 index = 0;
251 item = strtok(buf, " ");
252 while (item != NULL) {
253 mount_info[index] = item;
254 index++;
255 if (index == 4)
256 break;
257 item = strtok(NULL, " ");
258 }
259 if (index < 4) {
260 selinux_log(SELINUX_ERROR,
261 "/proc/mounts record \"%s\" has incorrect format.\n",
262 buf);
263 continue;
264 }
265
266 /* Remove pre-existing entry */
267 remove_exclude(mount_info[1]);
268
269 item = strtok(mount_info[3], ",");
270 while (item != NULL) {
271 if (strcmp(item, "seclabel") == 0) {
272 found = 1;
273 nfile += file_system_count(mount_info[1]);
274 break;
275 }
276 item = strtok(NULL, ",");
277 }
278
279 /* Exclude mount points without the seclabel option */
280 if (!found) {
281 if (add_exclude(mount_info[1], !CALLER_EXCLUDED) &&
282 errno == ENOMEM)
283 assert(0);
284 }
285 }
286
287 free(buf);
288 fclose(fp);
289 /* return estimated #Files + 5% for directories and hard links */
290 return nfile * 1.05;
291 }
292
293 /* 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)294 static int add_xattr_entry(const char *directory, bool delete_nonmatch,
295 bool delete_all)
296 {
297 char *sha1_buf = NULL;
298 size_t i, digest_len = 0;
299 int rc, digest_result;
300 bool match;
301 struct dir_xattr *new_entry;
302 uint8_t *xattr_digest = NULL;
303 uint8_t *calculated_digest = NULL;
304
305 if (!directory) {
306 errno = EINVAL;
307 return -1;
308 }
309
310 match = selabel_get_digests_all_partial_matches(fc_sehandle, directory,
311 &calculated_digest, &xattr_digest,
312 &digest_len);
313
314 if (!xattr_digest || !digest_len) {
315 free(calculated_digest);
316 return 1;
317 }
318
319 /* Convert entry to a hex encoded string. */
320 sha1_buf = malloc(digest_len * 2 + 1);
321 if (!sha1_buf) {
322 free(xattr_digest);
323 free(calculated_digest);
324 goto oom;
325 }
326
327 for (i = 0; i < digest_len; i++)
328 sprintf((&sha1_buf[i * 2]), "%02x", xattr_digest[i]);
329
330 digest_result = match ? MATCH : NOMATCH;
331
332 if ((delete_nonmatch && !match) || delete_all) {
333 digest_result = match ? DELETED_MATCH : DELETED_NOMATCH;
334 rc = removexattr(directory, RESTORECON_PARTIAL_MATCH_DIGEST);
335 if (rc) {
336 selinux_log(SELINUX_ERROR,
337 "Error: %m removing xattr \"%s\" from: %s\n",
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 static pthread_mutex_t fl_mutex = PTHREAD_MUTEX_INITIALIZER;
416
417 /*
418 * Try to add an association between an inode and a context. If there is a
419 * different context that matched the inode, then use the first context
420 * that matched.
421 */
filespec_add(ino_t ino,const char * con,const char * file,struct rest_flags * flags)422 static int filespec_add(ino_t ino, const char *con, const char *file,
423 struct rest_flags *flags)
424 {
425 file_spec_t *prevfl, *fl;
426 int h, ret;
427 struct stat64 sb;
428
429 __pthread_mutex_lock(&fl_mutex);
430
431 if (!fl_head) {
432 fl_head = calloc(HASH_BUCKETS, sizeof(file_spec_t));
433 if (!fl_head)
434 goto oom;
435 }
436
437 h = (ino + (ino >> HASH_BITS)) & HASH_MASK;
438 for (prevfl = &fl_head[h], fl = fl_head[h].next; fl;
439 prevfl = fl, fl = fl->next) {
440 if (ino == fl->ino) {
441 ret = lstat64(fl->file, &sb);
442 if (ret < 0 || sb.st_ino != ino) {
443 freecon(fl->con);
444 free(fl->file);
445 fl->file = strdup(file);
446 if (!fl->file)
447 goto oom;
448 fl->con = strdup(con);
449 if (!fl->con)
450 goto oom;
451 goto unlock_1;
452 }
453
454 if (strcmp(fl->con, con) == 0)
455 goto unlock_1;
456
457 selinux_log(SELINUX_ERROR,
458 "conflicting specifications for %s and %s, using %s.\n",
459 file, fl->file, fl->con);
460 free(fl->file);
461 fl->file = strdup(file);
462 if (!fl->file)
463 goto oom;
464
465 __pthread_mutex_unlock(&fl_mutex);
466
467 if (flags->conflicterror) {
468 selinux_log(SELINUX_ERROR,
469 "treating conflicting specifications as an error.\n");
470 return -1;
471 }
472 return 1;
473 }
474
475 if (ino > fl->ino)
476 break;
477 }
478
479 fl = malloc(sizeof(file_spec_t));
480 if (!fl)
481 goto oom;
482 fl->ino = ino;
483 fl->con = strdup(con);
484 if (!fl->con)
485 goto oom_freefl;
486 fl->file = strdup(file);
487 if (!fl->file)
488 goto oom_freefl;
489 fl->next = prevfl->next;
490 prevfl->next = fl;
491
492 __pthread_mutex_unlock(&fl_mutex);
493 return 0;
494
495 oom_freefl:
496 free(fl);
497 oom:
498 __pthread_mutex_unlock(&fl_mutex);
499 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
500 return -1;
501 unlock_1:
502 __pthread_mutex_unlock(&fl_mutex);
503 return 1;
504 }
505
506 /*
507 * Evaluate the association hash table distribution.
508 */
509 #ifdef DEBUG
filespec_eval(void)510 static void filespec_eval(void)
511 {
512 file_spec_t *fl;
513 int h, used, nel, len, longest;
514
515 if (!fl_head)
516 return;
517
518 used = 0;
519 longest = 0;
520 nel = 0;
521 for (h = 0; h < HASH_BUCKETS; h++) {
522 len = 0;
523 for (fl = fl_head[h].next; fl; fl = fl->next)
524 len++;
525 if (len)
526 used++;
527 if (len > longest)
528 longest = len;
529 nel += len;
530 }
531
532 selinux_log(SELINUX_INFO,
533 "filespec hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n",
534 nel, used, HASH_BUCKETS, longest);
535 }
536 #else
filespec_eval(void)537 static void filespec_eval(void)
538 {
539 }
540 #endif
541
542 /*
543 * Destroy the association hash table.
544 */
filespec_destroy(void)545 static void filespec_destroy(void)
546 {
547 file_spec_t *fl, *tmp;
548 int h;
549
550 if (!fl_head)
551 return;
552
553 for (h = 0; h < HASH_BUCKETS; h++) {
554 fl = fl_head[h].next;
555 while (fl) {
556 tmp = fl;
557 fl = fl->next;
558 freecon(tmp->con);
559 free(tmp->file);
560 free(tmp);
561 }
562 fl_head[h].next = NULL;
563 }
564 free(fl_head);
565 fl_head = NULL;
566 }
567
568 /*
569 * Called if SELINUX_RESTORECON_SET_SPECFILE_CTX is not set to check if
570 * the type components differ, updating newtypecon if so.
571 */
compare_types(char * curcon,char * newcon,char ** newtypecon)572 static int compare_types(char *curcon, char *newcon, char **newtypecon)
573 {
574 int types_differ = 0;
575 context_t cona;
576 context_t conb;
577 int rc = 0;
578
579 cona = context_new(curcon);
580 if (!cona) {
581 rc = -1;
582 goto out;
583 }
584 conb = context_new(newcon);
585 if (!conb) {
586 context_free(cona);
587 rc = -1;
588 goto out;
589 }
590
591 types_differ = strcmp(context_type_get(cona), context_type_get(conb));
592 if (types_differ) {
593 rc |= context_user_set(conb, context_user_get(cona));
594 rc |= context_role_set(conb, context_role_get(cona));
595 rc |= context_range_set(conb, context_range_get(cona));
596 if (!rc) {
597 *newtypecon = strdup(context_str(conb));
598 if (!*newtypecon) {
599 rc = -1;
600 goto err;
601 }
602 }
603 }
604
605 err:
606 context_free(cona);
607 context_free(conb);
608 out:
609 return rc;
610 }
611
restorecon_sb(const char * pathname,const struct stat * sb,struct rest_flags * flags,bool first)612 static int restorecon_sb(const char *pathname, const struct stat *sb,
613 struct rest_flags *flags, bool first)
614 {
615 char *newcon = NULL;
616 char *curcon = NULL;
617 char *newtypecon = NULL;
618 int rc;
619 bool updated = false;
620 const char *lookup_path = pathname;
621 float pc;
622
623 if (rootpath) {
624 if (strncmp(rootpath, lookup_path, rootpathlen) != 0) {
625 selinux_log(SELINUX_ERROR,
626 "%s is not located in alt_rootpath %s\n",
627 lookup_path, rootpath);
628 return -1;
629 }
630 lookup_path += rootpathlen;
631 }
632
633 if (rootpath != NULL && lookup_path[0] == '\0')
634 /* this is actually the root dir of the alt root. */
635 rc = selabel_lookup_raw(fc_sehandle, &newcon, "/",
636 sb->st_mode);
637 else
638 rc = selabel_lookup_raw(fc_sehandle, &newcon, lookup_path,
639 sb->st_mode);
640
641 if (rc < 0) {
642 if (errno == ENOENT && flags->warnonnomatch && first)
643 selinux_log(SELINUX_INFO,
644 "Warning no default label for %s\n",
645 lookup_path);
646
647 return 0; /* no match, but not an error */
648 }
649
650 if (flags->progress) {
651 __pthread_mutex_lock(&progress_mutex);
652 fc_count++;
653 if (fc_count % STAR_COUNT == 0) {
654 if (flags->mass_relabel && efile_count > 0) {
655 pc = (fc_count < efile_count) ? (100.0 *
656 fc_count / efile_count) : 100;
657 fprintf(stdout, "\r%-.1f%%", (double)pc);
658 } else {
659 fprintf(stdout, "\r%" PRIu64 "k", fc_count / STAR_COUNT);
660 }
661 fflush(stdout);
662 }
663 __pthread_mutex_unlock(&progress_mutex);
664 }
665
666 if (flags->add_assoc) {
667 rc = filespec_add(sb->st_ino, newcon, pathname, flags);
668
669 if (rc < 0) {
670 selinux_log(SELINUX_ERROR,
671 "filespec_add error: %s\n", pathname);
672 freecon(newcon);
673 return -1;
674 }
675
676 if (rc > 0) {
677 /* Already an association and it took precedence. */
678 freecon(newcon);
679 return 0;
680 }
681 }
682
683 if (flags->log_matches)
684 selinux_log(SELINUX_INFO, "%s matched by %s\n",
685 pathname, newcon);
686
687 if (lgetfilecon_raw(pathname, &curcon) < 0) {
688 if (errno != ENODATA)
689 goto err;
690
691 curcon = NULL;
692 }
693
694 if (curcon == NULL || strcmp(curcon, newcon) != 0) {
695 if (!flags->set_specctx && curcon &&
696 (is_context_customizable(curcon) > 0)) {
697 if (flags->verbose) {
698 selinux_log(SELINUX_INFO,
699 "%s not reset as customized by admin to %s\n",
700 pathname, curcon);
701 }
702 goto out;
703 }
704
705 if (!flags->set_specctx && curcon) {
706 /* If types different then update newcon. */
707 rc = compare_types(curcon, newcon, &newtypecon);
708 if (rc)
709 goto err;
710
711 if (newtypecon) {
712 freecon(newcon);
713 newcon = newtypecon;
714 } else {
715 goto out;
716 }
717 }
718
719 if (!flags->nochange) {
720 if (lsetfilecon(pathname, newcon) < 0)
721 goto err;
722 updated = true;
723 }
724
725 if (flags->verbose)
726 selinux_log(SELINUX_INFO,
727 "%s %s from %s to %s\n",
728 updated ? "Relabeled" : "Would relabel",
729 pathname, curcon, newcon);
730
731 if (flags->syslog_changes && !flags->nochange) {
732 if (curcon)
733 syslog(LOG_INFO,
734 "relabeling %s from %s to %s\n",
735 pathname, curcon, newcon);
736 else
737 syslog(LOG_INFO, "labeling %s to %s\n",
738 pathname, newcon);
739 }
740 }
741
742 out:
743 rc = 0;
744 out1:
745 freecon(curcon);
746 freecon(newcon);
747 return rc;
748 err:
749 selinux_log(SELINUX_ERROR,
750 "Could not set context for %s: %m\n",
751 pathname);
752 rc = -1;
753 goto out1;
754 }
755
756 struct dir_hash_node {
757 char *path;
758 uint8_t digest[SHA1_HASH_SIZE];
759 struct dir_hash_node *next;
760 };
761 /*
762 * Returns true if the digest of all partial matched contexts is the same as
763 * the one saved by setxattr. Otherwise returns false and constructs a
764 * dir_hash_node with the newly calculated digest.
765 */
check_context_match_for_dir(const char * pathname,struct dir_hash_node ** new_node,int error)766 static bool check_context_match_for_dir(const char *pathname,
767 struct dir_hash_node **new_node,
768 int error)
769 {
770 bool status;
771 size_t digest_len = 0;
772 uint8_t *read_digest = NULL;
773 uint8_t *calculated_digest = NULL;
774
775 if (!new_node)
776 return false;
777
778 *new_node = NULL;
779
780 /* status = true if digests match, false otherwise. */
781 status = selabel_get_digests_all_partial_matches(fc_sehandle, pathname,
782 &calculated_digest,
783 &read_digest,
784 &digest_len);
785
786 if (status)
787 goto free;
788
789 /* Save digest of all matched contexts for the current directory. */
790 if (!error && calculated_digest) {
791 *new_node = calloc(1, sizeof(struct dir_hash_node));
792
793 if (!*new_node)
794 goto oom;
795
796 (*new_node)->path = strdup(pathname);
797
798 if (!(*new_node)->path) {
799 free(*new_node);
800 *new_node = NULL;
801 goto oom;
802 }
803 memcpy((*new_node)->digest, calculated_digest, digest_len);
804 (*new_node)->next = NULL;
805 }
806
807 free:
808 free(calculated_digest);
809 free(read_digest);
810 return status;
811
812 oom:
813 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
814 goto free;
815 }
816
817 struct rest_state {
818 struct rest_flags flags;
819 dev_t dev_num;
820 struct statfs sfsb;
821 bool ignore_digest;
822 bool setrestorecondigest;
823 bool parallel;
824
825 FTS *fts;
826 FTSENT *ftsent_first;
827 struct dir_hash_node *head, *current;
828 bool abort;
829 int error;
830 int saved_errno;
831 pthread_mutex_t mutex;
832 };
833
selinux_restorecon_thread(void * arg)834 static void *selinux_restorecon_thread(void *arg)
835 {
836 struct rest_state *state = arg;
837 FTS *fts = state->fts;
838 FTSENT *ftsent;
839 int error;
840 char ent_path[PATH_MAX];
841 struct stat ent_st;
842 bool first = false;
843
844 if (state->parallel)
845 pthread_mutex_lock(&state->mutex);
846
847 if (state->ftsent_first) {
848 ftsent = state->ftsent_first;
849 state->ftsent_first = NULL;
850 first = true;
851 goto loop_body;
852 }
853
854 while (((void)(errno = 0), ftsent = fts_read(fts)) != NULL) {
855 loop_body:
856 /* If the FTS_XDEV flag is set and the device is different */
857 if (state->flags.set_xdev &&
858 ftsent->fts_statp->st_dev != state->dev_num)
859 continue;
860
861 switch (ftsent->fts_info) {
862 case FTS_DC:
863 selinux_log(SELINUX_ERROR,
864 "Directory cycle on %s.\n",
865 ftsent->fts_path);
866 errno = ELOOP;
867 state->error = -1;
868 state->abort = true;
869 goto finish;
870 case FTS_DP:
871 continue;
872 case FTS_DNR:
873 error = errno;
874 errno = ftsent->fts_errno;
875 selinux_log(SELINUX_ERROR,
876 "Could not read %s: %m.\n",
877 ftsent->fts_path);
878 errno = error;
879 fts_set(fts, ftsent, FTS_SKIP);
880 continue;
881 case FTS_NS:
882 error = errno;
883 errno = ftsent->fts_errno;
884 selinux_log(SELINUX_ERROR,
885 "Could not stat %s: %m.\n",
886 ftsent->fts_path);
887 errno = error;
888 fts_set(fts, ftsent, FTS_SKIP);
889 continue;
890 case FTS_ERR:
891 error = errno;
892 errno = ftsent->fts_errno;
893 selinux_log(SELINUX_ERROR,
894 "Error on %s: %m.\n",
895 ftsent->fts_path);
896 errno = error;
897 fts_set(fts, ftsent, FTS_SKIP);
898 continue;
899 case FTS_D:
900 if (state->sfsb.f_type == SYSFS_MAGIC &&
901 !selabel_partial_match(fc_sehandle,
902 ftsent->fts_path)) {
903 fts_set(fts, ftsent, FTS_SKIP);
904 continue;
905 }
906
907 if (check_excluded(ftsent->fts_path)) {
908 fts_set(fts, ftsent, FTS_SKIP);
909 continue;
910 }
911
912 if (state->setrestorecondigest) {
913 struct dir_hash_node *new_node = NULL;
914
915 if (check_context_match_for_dir(ftsent->fts_path,
916 &new_node,
917 state->error) &&
918 !state->ignore_digest) {
919 selinux_log(SELINUX_INFO,
920 "Skipping restorecon on directory(%s)\n",
921 ftsent->fts_path);
922 fts_set(fts, ftsent, FTS_SKIP);
923 continue;
924 }
925
926 if (new_node && !state->error) {
927 if (!state->current) {
928 state->current = new_node;
929 state->head = state->current;
930 } else {
931 state->current->next = new_node;
932 state->current = new_node;
933 }
934 }
935 }
936 /* fall through */
937 default:
938 strcpy(ent_path, ftsent->fts_path);
939 ent_st = *ftsent->fts_statp;
940 if (state->parallel)
941 pthread_mutex_unlock(&state->mutex);
942
943 error = restorecon_sb(ent_path, &ent_st, &state->flags,
944 first);
945
946 if (state->parallel) {
947 pthread_mutex_lock(&state->mutex);
948 if (state->abort)
949 goto unlock;
950 }
951
952 state->error |= error;
953 first = false;
954 if (error && state->flags.abort_on_error) {
955 state->abort = true;
956 goto finish;
957 }
958 break;
959 }
960 }
961
962 finish:
963 if (!state->saved_errno)
964 state->saved_errno = errno;
965 unlock:
966 if (state->parallel)
967 pthread_mutex_unlock(&state->mutex);
968 return NULL;
969 }
970
selinux_restorecon_common(const char * pathname_orig,unsigned int restorecon_flags,size_t nthreads)971 static int selinux_restorecon_common(const char *pathname_orig,
972 unsigned int restorecon_flags,
973 size_t nthreads)
974 {
975 struct rest_state state;
976
977 state.flags.nochange = (restorecon_flags &
978 SELINUX_RESTORECON_NOCHANGE) ? true : false;
979 state.flags.verbose = (restorecon_flags &
980 SELINUX_RESTORECON_VERBOSE) ? true : false;
981 state.flags.progress = (restorecon_flags &
982 SELINUX_RESTORECON_PROGRESS) ? true : false;
983 state.flags.mass_relabel = (restorecon_flags &
984 SELINUX_RESTORECON_MASS_RELABEL) ? true : false;
985 state.flags.recurse = (restorecon_flags &
986 SELINUX_RESTORECON_RECURSE) ? true : false;
987 state.flags.set_specctx = (restorecon_flags &
988 SELINUX_RESTORECON_SET_SPECFILE_CTX) ? true : false;
989 state.flags.userealpath = (restorecon_flags &
990 SELINUX_RESTORECON_REALPATH) ? true : false;
991 state.flags.set_xdev = (restorecon_flags &
992 SELINUX_RESTORECON_XDEV) ? true : false;
993 state.flags.add_assoc = (restorecon_flags &
994 SELINUX_RESTORECON_ADD_ASSOC) ? true : false;
995 state.flags.abort_on_error = (restorecon_flags &
996 SELINUX_RESTORECON_ABORT_ON_ERROR) ? true : false;
997 state.flags.syslog_changes = (restorecon_flags &
998 SELINUX_RESTORECON_SYSLOG_CHANGES) ? true : false;
999 state.flags.log_matches = (restorecon_flags &
1000 SELINUX_RESTORECON_LOG_MATCHES) ? true : false;
1001 state.flags.ignore_noent = (restorecon_flags &
1002 SELINUX_RESTORECON_IGNORE_NOENTRY) ? true : false;
1003 state.flags.warnonnomatch = true;
1004 state.flags.conflicterror = (restorecon_flags &
1005 SELINUX_RESTORECON_CONFLICT_ERROR) ? true : false;
1006 ignore_mounts = (restorecon_flags &
1007 SELINUX_RESTORECON_IGNORE_MOUNTS) ? true : false;
1008 state.ignore_digest = (restorecon_flags &
1009 SELINUX_RESTORECON_IGNORE_DIGEST) ? true : false;
1010 state.setrestorecondigest = true;
1011
1012 state.head = NULL;
1013 state.current = NULL;
1014 state.abort = false;
1015 state.error = 0;
1016 state.saved_errno = 0;
1017
1018 struct stat sb;
1019 char *pathname = NULL, *pathdnamer = NULL, *pathdname, *pathbname;
1020 char *paths[2] = { NULL, NULL };
1021 int fts_flags, error, sverrno;
1022 struct dir_hash_node *current = NULL;
1023
1024 if (state.flags.verbose && state.flags.progress)
1025 state.flags.verbose = false;
1026
1027 __selinux_once(fc_once, restorecon_init);
1028
1029 if (!fc_sehandle)
1030 return -1;
1031
1032 /*
1033 * If selabel_no_digest = true then no digest has been requested by
1034 * an external selabel_open(3) call.
1035 */
1036 if (selabel_no_digest ||
1037 (restorecon_flags & SELINUX_RESTORECON_SKIP_DIGEST))
1038 state.setrestorecondigest = false;
1039
1040 if (!__pthread_supported) {
1041 if (nthreads != 1) {
1042 nthreads = 1;
1043 selinux_log(SELINUX_WARNING,
1044 "Threading functionality not available, falling back to 1 thread.");
1045 }
1046 } else if (nthreads == 0) {
1047 long nproc = sysconf(_SC_NPROCESSORS_ONLN);
1048
1049 if (nproc > 0) {
1050 nthreads = nproc;
1051 } else {
1052 nthreads = 1;
1053 selinux_log(SELINUX_WARNING,
1054 "Unable to detect CPU count, falling back to 1 thread.");
1055 }
1056 }
1057
1058 /*
1059 * Convert passed-in pathname to canonical pathname by resolving
1060 * realpath of containing dir, then appending last component name.
1061 */
1062 if (state.flags.userealpath) {
1063 char *basename_cpy = strdup(pathname_orig);
1064 if (!basename_cpy)
1065 goto realpatherr;
1066 pathbname = basename(basename_cpy);
1067 if (!strcmp(pathbname, "/") || !strcmp(pathbname, ".") ||
1068 !strcmp(pathbname, "..")) {
1069 pathname = realpath(pathname_orig, NULL);
1070 if (!pathname) {
1071 free(basename_cpy);
1072 goto realpatherr;
1073 }
1074 } else {
1075 char *dirname_cpy = strdup(pathname_orig);
1076 if (!dirname_cpy) {
1077 free(basename_cpy);
1078 goto realpatherr;
1079 }
1080 pathdname = dirname(dirname_cpy);
1081 pathdnamer = realpath(pathdname, NULL);
1082 free(dirname_cpy);
1083 if (!pathdnamer) {
1084 free(basename_cpy);
1085 goto realpatherr;
1086 }
1087 if (!strcmp(pathdnamer, "/"))
1088 error = asprintf(&pathname, "/%s", pathbname);
1089 else
1090 error = asprintf(&pathname, "%s/%s",
1091 pathdnamer, pathbname);
1092 if (error < 0) {
1093 free(basename_cpy);
1094 goto oom;
1095 }
1096 }
1097 free(basename_cpy);
1098 } else {
1099 pathname = strdup(pathname_orig);
1100 if (!pathname)
1101 goto oom;
1102 }
1103
1104 paths[0] = pathname;
1105
1106 if (lstat(pathname, &sb) < 0) {
1107 if (state.flags.ignore_noent && errno == ENOENT) {
1108 free(pathdnamer);
1109 free(pathname);
1110 return 0;
1111 } else {
1112 selinux_log(SELINUX_ERROR,
1113 "lstat(%s) failed: %m\n",
1114 pathname);
1115 error = -1;
1116 goto cleanup;
1117 }
1118 }
1119
1120 /* Skip digest if not a directory */
1121 if (!S_ISDIR(sb.st_mode))
1122 state.setrestorecondigest = false;
1123
1124 if (!state.flags.recurse) {
1125 if (check_excluded(pathname)) {
1126 error = 0;
1127 goto cleanup;
1128 }
1129
1130 error = restorecon_sb(pathname, &sb, &state.flags, true);
1131 goto cleanup;
1132 }
1133
1134 /* Obtain fs type */
1135 memset(&state.sfsb, 0, sizeof(state.sfsb));
1136 if (!S_ISLNK(sb.st_mode) && statfs(pathname, &state.sfsb) < 0) {
1137 selinux_log(SELINUX_ERROR,
1138 "statfs(%s) failed: %m\n",
1139 pathname);
1140 error = -1;
1141 goto cleanup;
1142 }
1143
1144 /* Skip digest on in-memory filesystems and /sys */
1145 if (state.sfsb.f_type == RAMFS_MAGIC || state.sfsb.f_type == TMPFS_MAGIC ||
1146 state.sfsb.f_type == SYSFS_MAGIC)
1147 state.setrestorecondigest = false;
1148
1149 if (state.flags.set_xdev)
1150 fts_flags = FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV;
1151 else
1152 fts_flags = FTS_PHYSICAL | FTS_NOCHDIR;
1153
1154 state.fts = fts_open(paths, fts_flags, NULL);
1155 if (!state.fts)
1156 goto fts_err;
1157
1158 state.ftsent_first = fts_read(state.fts);
1159 if (!state.ftsent_first)
1160 goto fts_err;
1161
1162 /*
1163 * Keep the inode of the first device. This is because the FTS_XDEV
1164 * flag tells fts not to descend into directories with different
1165 * device numbers, but fts will still give back the actual directory.
1166 * By saving the device number of the directory that was passed to
1167 * selinux_restorecon() and then skipping all actions on any
1168 * directories with a different device number when the FTS_XDEV flag
1169 * is set (from http://marc.info/?l=selinux&m=124688830500777&w=2).
1170 */
1171 state.dev_num = state.ftsent_first->fts_statp->st_dev;
1172
1173 if (nthreads == 1) {
1174 state.parallel = false;
1175 selinux_restorecon_thread(&state);
1176 } else {
1177 size_t i;
1178 pthread_t self = pthread_self();
1179 pthread_t *threads = NULL;
1180
1181 pthread_mutex_init(&state.mutex, NULL);
1182
1183 threads = calloc(nthreads - 1, sizeof(*threads));
1184 if (!threads)
1185 goto oom;
1186
1187 state.parallel = true;
1188 /*
1189 * Start (nthreads - 1) threads - the main thread is going to
1190 * take part, too.
1191 */
1192 for (i = 0; i < nthreads - 1; i++) {
1193 if (pthread_create(&threads[i], NULL,
1194 selinux_restorecon_thread, &state)) {
1195 /*
1196 * If any thread fails to be created, just mark
1197 * it as such and let the successfully created
1198 * threads do the job. In the worst case the
1199 * main thread will do everything, but that's
1200 * still better than to give up.
1201 */
1202 threads[i] = self;
1203 }
1204 }
1205
1206 /* Let's join in on the fun! */
1207 selinux_restorecon_thread(&state);
1208
1209 /* Now wait for all threads to finish. */
1210 for (i = 0; i < nthreads - 1; i++) {
1211 /* Skip threads that failed to be created. */
1212 if (pthread_equal(threads[i], self))
1213 continue;
1214 pthread_join(threads[i], NULL);
1215 }
1216 free(threads);
1217
1218 pthread_mutex_destroy(&state.mutex);
1219 }
1220
1221 error = state.error;
1222 if (state.saved_errno)
1223 goto out;
1224
1225 /*
1226 * Labeling successful. Write partial match digests for subdirectories.
1227 * TODO: Write digest upon FTS_DP if no error occurs in its descents.
1228 */
1229 if (state.setrestorecondigest && !state.flags.nochange && !error) {
1230 current = state.head;
1231 while (current != NULL) {
1232 if (setxattr(current->path,
1233 RESTORECON_PARTIAL_MATCH_DIGEST,
1234 current->digest,
1235 SHA1_HASH_SIZE, 0) < 0) {
1236 selinux_log(SELINUX_ERROR,
1237 "setxattr failed: %s: %m\n",
1238 current->path);
1239 }
1240 current = current->next;
1241 }
1242 }
1243
1244 out:
1245 if (state.flags.progress && state.flags.mass_relabel)
1246 fprintf(stdout, "\r%s 100.0%%\n", pathname);
1247
1248 (void) fts_close(state.fts);
1249 errno = state.saved_errno;
1250 cleanup:
1251 if (state.flags.add_assoc) {
1252 if (state.flags.verbose)
1253 filespec_eval();
1254 filespec_destroy();
1255 }
1256 free(pathdnamer);
1257 free(pathname);
1258
1259 current = state.head;
1260 while (current != NULL) {
1261 struct dir_hash_node *next = current->next;
1262
1263 free(current->path);
1264 free(current);
1265 current = next;
1266 }
1267 return error;
1268
1269 oom:
1270 sverrno = errno;
1271 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
1272 errno = sverrno;
1273 error = -1;
1274 goto cleanup;
1275
1276 realpatherr:
1277 sverrno = errno;
1278 selinux_log(SELINUX_ERROR,
1279 "SELinux: Could not get canonical path for %s restorecon: %m.\n",
1280 pathname_orig);
1281 errno = sverrno;
1282 error = -1;
1283 goto cleanup;
1284
1285 fts_err:
1286 selinux_log(SELINUX_ERROR,
1287 "fts error while labeling %s: %m\n",
1288 paths[0]);
1289 error = -1;
1290 goto cleanup;
1291 }
1292
1293
1294 /*
1295 * Public API
1296 */
1297
1298 /* selinux_restorecon(3) - Main function that is responsible for labeling */
selinux_restorecon(const char * pathname_orig,unsigned int restorecon_flags)1299 int selinux_restorecon(const char *pathname_orig,
1300 unsigned int restorecon_flags)
1301 {
1302 return selinux_restorecon_common(pathname_orig, restorecon_flags, 1);
1303 }
1304
1305 /* selinux_restorecon_parallel(3) - Parallel version of selinux_restorecon(3) */
selinux_restorecon_parallel(const char * pathname_orig,unsigned int restorecon_flags,size_t nthreads)1306 int selinux_restorecon_parallel(const char *pathname_orig,
1307 unsigned int restorecon_flags,
1308 size_t nthreads)
1309 {
1310 return selinux_restorecon_common(pathname_orig, restorecon_flags, nthreads);
1311 }
1312
1313 /* selinux_restorecon_set_sehandle(3) is called to set the global fc handle */
selinux_restorecon_set_sehandle(struct selabel_handle * hndl)1314 void selinux_restorecon_set_sehandle(struct selabel_handle *hndl)
1315 {
1316 char **specfiles;
1317 unsigned char *fc_digest;
1318 size_t num_specfiles, fc_digest_len;
1319
1320 fc_sehandle = hndl;
1321 if (!fc_sehandle)
1322 return;
1323
1324 /* Check if digest requested in selabel_open(3), if so use it. */
1325 if (selabel_digest(fc_sehandle, &fc_digest, &fc_digest_len,
1326 &specfiles, &num_specfiles) < 0)
1327 selabel_no_digest = true;
1328 else
1329 selabel_no_digest = false;
1330 }
1331
1332
1333 /*
1334 * selinux_restorecon_default_handle(3) is called to set the global restorecon
1335 * handle by a process if the default params are required.
1336 */
selinux_restorecon_default_handle(void)1337 struct selabel_handle *selinux_restorecon_default_handle(void)
1338 {
1339 struct selabel_handle *sehandle;
1340
1341 struct selinux_opt fc_opts[] = {
1342 { SELABEL_OPT_DIGEST, (char *)1 }
1343 };
1344
1345 sehandle = selabel_open(SELABEL_CTX_FILE, fc_opts, 1);
1346
1347 if (!sehandle) {
1348 selinux_log(SELINUX_ERROR,
1349 "Error obtaining file context handle: %m\n");
1350 return NULL;
1351 }
1352
1353 selabel_no_digest = false;
1354 return sehandle;
1355 }
1356
1357 /*
1358 * selinux_restorecon_set_exclude_list(3) is called to add additional entries
1359 * to be excluded from labeling checks.
1360 */
selinux_restorecon_set_exclude_list(const char ** exclude_list)1361 void selinux_restorecon_set_exclude_list(const char **exclude_list)
1362 {
1363 int i;
1364 struct stat sb;
1365
1366 for (i = 0; exclude_list[i]; i++) {
1367 if (lstat(exclude_list[i], &sb) < 0 && errno != EACCES) {
1368 selinux_log(SELINUX_ERROR,
1369 "lstat error on exclude path \"%s\", %m - ignoring.\n",
1370 exclude_list[i]);
1371 break;
1372 }
1373 if (add_exclude(exclude_list[i], CALLER_EXCLUDED) &&
1374 errno == ENOMEM)
1375 assert(0);
1376 }
1377 }
1378
1379 /* selinux_restorecon_set_alt_rootpath(3) sets an alternate rootpath. */
selinux_restorecon_set_alt_rootpath(const char * alt_rootpath)1380 int selinux_restorecon_set_alt_rootpath(const char *alt_rootpath)
1381 {
1382 int len;
1383
1384 /* This should be NULL on first use */
1385 if (rootpath)
1386 free(rootpath);
1387
1388 rootpath = strdup(alt_rootpath);
1389 if (!rootpath) {
1390 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
1391 return -1;
1392 }
1393
1394 /* trim trailing /, if present */
1395 len = strlen(rootpath);
1396 while (len && (rootpath[len - 1] == '/'))
1397 rootpath[--len] = '\0';
1398 rootpathlen = len;
1399
1400 return 0;
1401 }
1402
1403 /* selinux_restorecon_xattr(3)
1404 * Find RESTORECON_PARTIAL_MATCH_DIGEST entries.
1405 */
selinux_restorecon_xattr(const char * pathname,unsigned int xattr_flags,struct dir_xattr *** xattr_list)1406 int selinux_restorecon_xattr(const char *pathname, unsigned int xattr_flags,
1407 struct dir_xattr ***xattr_list)
1408 {
1409 bool recurse = (xattr_flags &
1410 SELINUX_RESTORECON_XATTR_RECURSE) ? true : false;
1411 bool delete_nonmatch = (xattr_flags &
1412 SELINUX_RESTORECON_XATTR_DELETE_NONMATCH_DIGESTS) ? true : false;
1413 bool delete_all = (xattr_flags &
1414 SELINUX_RESTORECON_XATTR_DELETE_ALL_DIGESTS) ? true : false;
1415 ignore_mounts = (xattr_flags &
1416 SELINUX_RESTORECON_XATTR_IGNORE_MOUNTS) ? true : false;
1417
1418 int rc, fts_flags;
1419 struct stat sb;
1420 struct statfs sfsb;
1421 struct dir_xattr *current, *next;
1422 FTS *fts;
1423 FTSENT *ftsent;
1424 char *paths[2] = { NULL, NULL };
1425
1426 __selinux_once(fc_once, restorecon_init);
1427
1428 if (!fc_sehandle)
1429 return -1;
1430
1431 if (lstat(pathname, &sb) < 0) {
1432 if (errno == ENOENT)
1433 return 0;
1434
1435 selinux_log(SELINUX_ERROR,
1436 "lstat(%s) failed: %m\n",
1437 pathname);
1438 return -1;
1439 }
1440
1441 if (!recurse) {
1442 if (statfs(pathname, &sfsb) == 0) {
1443 if (sfsb.f_type == RAMFS_MAGIC ||
1444 sfsb.f_type == TMPFS_MAGIC)
1445 return 0;
1446 }
1447
1448 if (check_excluded(pathname))
1449 return 0;
1450
1451 rc = add_xattr_entry(pathname, delete_nonmatch, delete_all);
1452
1453 if (!rc && dir_xattr_list)
1454 *xattr_list = &dir_xattr_list;
1455 else if (rc == -1)
1456 return rc;
1457
1458 return 0;
1459 }
1460
1461 paths[0] = (char *)pathname;
1462 fts_flags = FTS_PHYSICAL | FTS_NOCHDIR;
1463
1464 fts = fts_open(paths, fts_flags, NULL);
1465 if (!fts) {
1466 selinux_log(SELINUX_ERROR,
1467 "fts error on %s: %m\n",
1468 paths[0]);
1469 return -1;
1470 }
1471
1472 while ((ftsent = fts_read(fts)) != NULL) {
1473 switch (ftsent->fts_info) {
1474 case FTS_DP:
1475 continue;
1476 case FTS_D:
1477 if (statfs(ftsent->fts_path, &sfsb) == 0) {
1478 if (sfsb.f_type == RAMFS_MAGIC ||
1479 sfsb.f_type == TMPFS_MAGIC)
1480 continue;
1481 }
1482 if (check_excluded(ftsent->fts_path)) {
1483 fts_set(fts, ftsent, FTS_SKIP);
1484 continue;
1485 }
1486
1487 rc = add_xattr_entry(ftsent->fts_path,
1488 delete_nonmatch, delete_all);
1489 if (rc == 1)
1490 continue;
1491 else if (rc == -1)
1492 goto cleanup;
1493 break;
1494 default:
1495 break;
1496 }
1497 }
1498
1499 if (dir_xattr_list)
1500 *xattr_list = &dir_xattr_list;
1501
1502 (void) fts_close(fts);
1503 return 0;
1504
1505 cleanup:
1506 rc = errno;
1507 (void) fts_close(fts);
1508 errno = rc;
1509
1510 if (dir_xattr_list) {
1511 /* Free any used memory */
1512 current = dir_xattr_list;
1513 while (current) {
1514 next = current->next;
1515 free(current->directory);
1516 free(current->digest);
1517 free(current);
1518 current = next;
1519 }
1520 }
1521 return -1;
1522 }
1523