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