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