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