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
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,struct rest_flags * flags)434 static int filespec_add(ino_t ino, const char *con, const char *file,
435 struct rest_flags *flags)
436 {
437 file_spec_t *prevfl, *fl;
438 int h, ret;
439 struct stat64 sb;
440
441 __pthread_mutex_lock(&fl_mutex);
442
443 if (!fl_head) {
444 fl_head = calloc(HASH_BUCKETS, sizeof(file_spec_t));
445 if (!fl_head)
446 goto oom;
447 }
448
449 h = (ino + (ino >> HASH_BITS)) & HASH_MASK;
450 for (prevfl = &fl_head[h], fl = fl_head[h].next; fl;
451 prevfl = fl, fl = fl->next) {
452 if (ino == fl->ino) {
453 ret = lstat64(fl->file, &sb);
454 if (ret < 0 || sb.st_ino != ino) {
455 freecon(fl->con);
456 free(fl->file);
457 fl->file = strdup(file);
458 if (!fl->file)
459 goto oom;
460 fl->con = strdup(con);
461 if (!fl->con)
462 goto oom;
463 goto unlock_1;
464 }
465
466 if (strcmp(fl->con, con) == 0)
467 goto unlock_1;
468
469 selinux_log(SELINUX_ERROR,
470 "conflicting specifications for %s and %s, using %s.\n",
471 file, fl->file, fl->con);
472 free(fl->file);
473 fl->file = strdup(file);
474 if (!fl->file)
475 goto oom;
476
477 __pthread_mutex_unlock(&fl_mutex);
478
479 if (flags->conflicterror) {
480 selinux_log(SELINUX_ERROR,
481 "treating conflicting specifications as an error.\n");
482 return -1;
483 }
484 return 1;
485 }
486
487 if (ino > fl->ino)
488 break;
489 }
490
491 fl = malloc(sizeof(file_spec_t));
492 if (!fl)
493 goto oom;
494 fl->ino = ino;
495 fl->con = strdup(con);
496 if (!fl->con)
497 goto oom_freefl;
498 fl->file = strdup(file);
499 if (!fl->file)
500 goto oom_freeflcon;
501 fl->next = prevfl->next;
502 prevfl->next = fl;
503
504 __pthread_mutex_unlock(&fl_mutex);
505 return 0;
506
507 oom_freeflcon:
508 free(fl->con);
509 oom_freefl:
510 free(fl);
511 oom:
512 __pthread_mutex_unlock(&fl_mutex);
513 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
514 return -1;
515 unlock_1:
516 __pthread_mutex_unlock(&fl_mutex);
517 return 1;
518 }
519
520 /*
521 * Evaluate the association hash table distribution.
522 */
523 #ifdef DEBUG
filespec_eval(void)524 static void filespec_eval(void)
525 {
526 file_spec_t *fl;
527 int h, used, nel, len, longest;
528
529 if (!fl_head)
530 return;
531
532 used = 0;
533 longest = 0;
534 nel = 0;
535 for (h = 0; h < HASH_BUCKETS; h++) {
536 len = 0;
537 for (fl = fl_head[h].next; fl; fl = fl->next)
538 len++;
539 if (len)
540 used++;
541 if (len > longest)
542 longest = len;
543 nel += len;
544 }
545
546 selinux_log(SELINUX_INFO,
547 "filespec hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n",
548 nel, used, HASH_BUCKETS, longest);
549 }
550 #else
filespec_eval(void)551 static void filespec_eval(void)
552 {
553 }
554 #endif
555
556 /*
557 * Destroy the association hash table.
558 */
filespec_destroy(void)559 static void filespec_destroy(void)
560 {
561 file_spec_t *fl, *tmp;
562 int h;
563
564 if (!fl_head)
565 return;
566
567 for (h = 0; h < HASH_BUCKETS; h++) {
568 fl = fl_head[h].next;
569 while (fl) {
570 tmp = fl;
571 fl = fl->next;
572 freecon(tmp->con);
573 free(tmp->file);
574 free(tmp);
575 }
576 fl_head[h].next = NULL;
577 }
578 free(fl_head);
579 fl_head = NULL;
580 }
581
582 /*
583 * Called if SELINUX_RESTORECON_SET_SPECFILE_CTX is not set to check if
584 * the type components differ, updating newtypecon if so.
585 */
compare_types(const char * curcon,const char * newcon,char ** newtypecon)586 static int compare_types(const char *curcon, const char *newcon, char **newtypecon)
587 {
588 int types_differ = 0;
589 context_t cona;
590 context_t conb;
591 int rc = 0;
592
593 cona = context_new(curcon);
594 if (!cona) {
595 rc = -1;
596 goto out;
597 }
598 conb = context_new(newcon);
599 if (!conb) {
600 context_free(cona);
601 rc = -1;
602 goto out;
603 }
604
605 types_differ = strcmp(context_type_get(cona), context_type_get(conb));
606 if (types_differ) {
607 rc |= context_user_set(conb, context_user_get(cona));
608 rc |= context_role_set(conb, context_role_get(cona));
609 rc |= context_range_set(conb, context_range_get(cona));
610 if (!rc) {
611 *newtypecon = strdup(context_str(conb));
612 if (!*newtypecon) {
613 rc = -1;
614 goto err;
615 }
616 }
617 }
618
619 err:
620 context_free(cona);
621 context_free(conb);
622 out:
623 return rc;
624 }
625
626 #define DATA_APP_EL1 "/data/app/el1/"
627 #define DATA_APP_EL2 "/data/app/el2/"
628 #define DATA_APP_EL3 "/data/app/el3/"
629 #define DATA_APP_EL4 "/data/app/el4/"
630 #define DATA_ACCOUNTS_ACCOUNT_0 "/data/accounts/account_0/"
631
restorecon_sb(const char * pathname,struct rest_flags * flags,bool first)632 static int restorecon_sb(const char *pathname, struct rest_flags *flags, bool first)
633 {
634 char *newcon = NULL;
635 char *curcon = NULL;
636 char *newtypecon = NULL;
637 int fd = -1, rc;
638 struct stat stat_buf;
639 bool updated = false;
640 const char *lookup_path = pathname;
641 float pc;
642 if (!strncmp(pathname, DATA_APP_EL1, sizeof(DATA_APP_EL1) - 1) ||
643 !strncmp(pathname, DATA_APP_EL2, sizeof(DATA_APP_EL2) - 1) ||
644 !strncmp(pathname, DATA_APP_EL3, sizeof(DATA_APP_EL3) - 1) ||
645 !strncmp(pathname, DATA_APP_EL4, sizeof(DATA_APP_EL4) - 1) ||
646 !strncmp(pathname, DATA_ACCOUNTS_ACCOUNT_0, sizeof(DATA_ACCOUNTS_ACCOUNT_0) - 1)) {
647 goto out;
648 }
649 if (rootpath) {
650 if (strncmp(rootpath, lookup_path, rootpathlen) != 0) {
651 selinux_log(SELINUX_ERROR,
652 "%s is not located in alt_rootpath %s\n",
653 lookup_path, rootpath);
654 return -1;
655 }
656 lookup_path += rootpathlen;
657 }
658
659 fd = open(pathname, O_PATH | O_NOFOLLOW | O_EXCL);
660 if (fd < 0)
661 goto err;
662
663 rc = fstat(fd, &stat_buf);
664 if (rc < 0)
665 goto err;
666
667 if (rootpath != NULL && lookup_path[0] == '\0')
668 /* this is actually the root dir of the alt root. */
669 rc = selabel_lookup_raw(fc_sehandle, &newcon, "/",
670 stat_buf.st_mode);
671 else
672 rc = selabel_lookup_raw(fc_sehandle, &newcon, lookup_path,
673 stat_buf.st_mode);
674
675 if (rc < 0) {
676 if (errno == ENOENT) {
677 if (flags->warnonnomatch && first)
678 selinux_log(SELINUX_INFO,
679 "Warning no default label for %s\n",
680 lookup_path);
681
682 goto out; /* no match, but not an error */
683 }
684
685 goto err;
686 }
687
688 if (flags->progress) {
689 __pthread_mutex_lock(&progress_mutex);
690 fc_count++;
691 if (fc_count % STAR_COUNT == 0) {
692 if (flags->mass_relabel && efile_count > 0) {
693 pc = (fc_count < efile_count) ? (100.0 *
694 fc_count / efile_count) : 100;
695 fprintf(stdout, "\r%-.1f%%", (double)pc);
696 } else {
697 fprintf(stdout, "\r%" PRIu64 "k", fc_count / STAR_COUNT);
698 }
699 fflush(stdout);
700 }
701 __pthread_mutex_unlock(&progress_mutex);
702 }
703
704 if (flags->add_assoc) {
705 rc = filespec_add(stat_buf.st_ino, newcon, pathname, flags);
706
707 if (rc < 0) {
708 selinux_log(SELINUX_ERROR,
709 "filespec_add error: %s\n", pathname);
710 goto out1;
711 }
712
713 if (rc > 0) {
714 /* Already an association and it took precedence. */
715 goto out;
716 }
717 }
718
719 if (flags->log_matches)
720 selinux_log(SELINUX_INFO, "%s matched by %s\n",
721 pathname, newcon);
722
723 if (fgetfilecon_raw(fd, &curcon) < 0) {
724 if (errno != ENODATA)
725 goto err;
726
727 curcon = NULL;
728 }
729
730 if (curcon == NULL || strcmp(curcon, newcon) != 0) {
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 (fsetfilecon(fd, 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, curcon, newcon);
766
767 if (flags->syslog_changes && !flags->nochange) {
768 if (curcon)
769 syslog(LOG_INFO,
770 "relabeling %s from %s to %s\n",
771 pathname, curcon, newcon);
772 else
773 syslog(LOG_INFO, "labeling %s to %s\n",
774 pathname, newcon);
775 }
776 }
777
778 out:
779 rc = 0;
780 out1:
781 if (fd >= 0)
782 close(fd);
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
855 struct rest_state {
856 struct rest_flags flags;
857 dev_t dev_num;
858 struct statfs sfsb;
859 bool ignore_digest;
860 bool setrestorecondigest;
861 bool parallel;
862
863 FTS *fts;
864 FTSENT *ftsent_first;
865 struct dir_hash_node *head, *current;
866 bool abort;
867 int error;
868 long unsigned skipped_errors;
869 int saved_errno;
870 pthread_mutex_t mutex;
871 };
872
selinux_restorecon_thread(void * arg)873 static void *selinux_restorecon_thread(void *arg)
874 {
875 struct rest_state *state = arg;
876 FTS *fts = state->fts;
877 FTSENT *ftsent;
878 int error;
879 char ent_path[PATH_MAX];
880 bool first = false;
881
882 if (state->parallel)
883 pthread_mutex_lock(&state->mutex);
884
885 if (state->ftsent_first) {
886 ftsent = state->ftsent_first;
887 state->ftsent_first = NULL;
888 first = true;
889 goto loop_body;
890 }
891
892 while (((void)(errno = 0), ftsent = fts_read(fts)) != NULL) {
893 loop_body:
894 /* If the FTS_XDEV flag is set and the device is different */
895 if (state->flags.set_xdev &&
896 ftsent->fts_statp->st_dev != state->dev_num)
897 continue;
898
899 switch (ftsent->fts_info) {
900 case FTS_DC:
901 selinux_log(SELINUX_ERROR,
902 "Directory cycle on %s.\n",
903 ftsent->fts_path);
904 errno = ELOOP;
905 state->error = -1;
906 state->abort = true;
907 goto finish;
908 case FTS_DP:
909 continue;
910 case FTS_DNR:
911 error = errno;
912 errno = ftsent->fts_errno;
913 selinux_log(SELINUX_ERROR,
914 "Could not read %s: %m.\n",
915 ftsent->fts_path);
916 errno = error;
917 fts_set(fts, ftsent, FTS_SKIP);
918 continue;
919 case FTS_NS:
920 error = errno;
921 errno = ftsent->fts_errno;
922 selinux_log(SELINUX_ERROR,
923 "Could not stat %s: %m.\n",
924 ftsent->fts_path);
925 errno = error;
926 fts_set(fts, ftsent, FTS_SKIP);
927 continue;
928 case FTS_ERR:
929 error = errno;
930 errno = ftsent->fts_errno;
931 selinux_log(SELINUX_ERROR,
932 "Error on %s: %m.\n",
933 ftsent->fts_path);
934 errno = error;
935 fts_set(fts, ftsent, FTS_SKIP);
936 continue;
937 case FTS_D:
938 if (state->sfsb.f_type == SYSFS_MAGIC &&
939 !selabel_partial_match(fc_sehandle,
940 ftsent->fts_path)) {
941 fts_set(fts, ftsent, FTS_SKIP);
942 continue;
943 }
944
945 if (check_excluded(ftsent->fts_path)) {
946 fts_set(fts, ftsent, FTS_SKIP);
947 continue;
948 }
949
950 if (state->setrestorecondigest) {
951 struct dir_hash_node *new_node = NULL;
952
953 if (check_context_match_for_dir(ftsent->fts_path,
954 &new_node,
955 state->error) &&
956 !state->ignore_digest) {
957 selinux_log(SELINUX_INFO,
958 "Skipping restorecon on directory(%s)\n",
959 ftsent->fts_path);
960 fts_set(fts, ftsent, FTS_SKIP);
961 continue;
962 }
963
964 if (new_node && !state->error) {
965 if (!state->current) {
966 state->current = new_node;
967 state->head = state->current;
968 } else {
969 state->current->next = new_node;
970 state->current = new_node;
971 }
972 }
973 }
974 /* fall through */
975 default:
976 strcpy(ent_path, ftsent->fts_path);
977
978 if (state->parallel)
979 pthread_mutex_unlock(&state->mutex);
980
981 error = restorecon_sb(ent_path, &state->flags,
982 first);
983
984 if (state->parallel) {
985 pthread_mutex_lock(&state->mutex);
986 if (state->abort)
987 goto unlock;
988 }
989
990 first = false;
991 if (error) {
992 if (state->flags.abort_on_error) {
993 state->error = error;
994 state->abort = true;
995 goto finish;
996 }
997 if (state->flags.count_errors)
998 state->skipped_errors++;
999 else
1000 state->error = error;
1001 }
1002 break;
1003 }
1004 }
1005
1006 finish:
1007 if (!state->saved_errno)
1008 state->saved_errno = errno;
1009 unlock:
1010 if (state->parallel)
1011 pthread_mutex_unlock(&state->mutex);
1012 return NULL;
1013 }
1014
selinux_restorecon_common(const char * pathname_orig,unsigned int restorecon_flags,size_t nthreads)1015 static int selinux_restorecon_common(const char *pathname_orig,
1016 unsigned int restorecon_flags,
1017 size_t nthreads)
1018 {
1019 struct rest_state state;
1020
1021 state.flags.nochange = (restorecon_flags &
1022 SELINUX_RESTORECON_NOCHANGE) ? true : false;
1023 state.flags.verbose = (restorecon_flags &
1024 SELINUX_RESTORECON_VERBOSE) ? true : false;
1025 state.flags.progress = (restorecon_flags &
1026 SELINUX_RESTORECON_PROGRESS) ? true : false;
1027 state.flags.mass_relabel = (restorecon_flags &
1028 SELINUX_RESTORECON_MASS_RELABEL) ? true : false;
1029 state.flags.recurse = (restorecon_flags &
1030 SELINUX_RESTORECON_RECURSE) ? true : false;
1031 state.flags.set_specctx = (restorecon_flags &
1032 SELINUX_RESTORECON_SET_SPECFILE_CTX) ? true : false;
1033 state.flags.userealpath = (restorecon_flags &
1034 SELINUX_RESTORECON_REALPATH) ? true : false;
1035 state.flags.set_xdev = (restorecon_flags &
1036 SELINUX_RESTORECON_XDEV) ? true : false;
1037 state.flags.add_assoc = (restorecon_flags &
1038 SELINUX_RESTORECON_ADD_ASSOC) ? true : false;
1039 state.flags.abort_on_error = (restorecon_flags &
1040 SELINUX_RESTORECON_ABORT_ON_ERROR) ? true : false;
1041 state.flags.syslog_changes = (restorecon_flags &
1042 SELINUX_RESTORECON_SYSLOG_CHANGES) ? true : false;
1043 state.flags.log_matches = (restorecon_flags &
1044 SELINUX_RESTORECON_LOG_MATCHES) ? true : false;
1045 state.flags.ignore_noent = (restorecon_flags &
1046 SELINUX_RESTORECON_IGNORE_NOENTRY) ? true : false;
1047 state.flags.warnonnomatch = true;
1048 state.flags.conflicterror = (restorecon_flags &
1049 SELINUX_RESTORECON_CONFLICT_ERROR) ? true : false;
1050 ignore_mounts = (restorecon_flags &
1051 SELINUX_RESTORECON_IGNORE_MOUNTS) ? true : false;
1052 state.ignore_digest = (restorecon_flags &
1053 SELINUX_RESTORECON_IGNORE_DIGEST) ? true : false;
1054 state.flags.count_errors = (restorecon_flags &
1055 SELINUX_RESTORECON_COUNT_ERRORS) ? true : false;
1056 state.setrestorecondigest = true;
1057
1058 state.head = NULL;
1059 state.current = NULL;
1060 state.abort = false;
1061 state.error = 0;
1062 state.skipped_errors = 0;
1063 state.saved_errno = 0;
1064
1065 struct stat sb;
1066 char *pathname = NULL, *pathdnamer = NULL, *pathdname, *pathbname;
1067 char *paths[2] = { NULL, NULL };
1068 int fts_flags, error;
1069 struct dir_hash_node *current = NULL;
1070
1071 if (state.flags.verbose && state.flags.progress)
1072 state.flags.verbose = false;
1073
1074 __selinux_once(fc_once, restorecon_init);
1075
1076 if (!fc_sehandle)
1077 return -1;
1078
1079 /*
1080 * If selabel_no_digest = true then no digest has been requested by
1081 * an external selabel_open(3) call.
1082 */
1083 if (selabel_no_digest ||
1084 (restorecon_flags & SELINUX_RESTORECON_SKIP_DIGEST))
1085 state.setrestorecondigest = false;
1086
1087 if (!__pthread_supported) {
1088 if (nthreads != 1) {
1089 nthreads = 1;
1090 selinux_log(SELINUX_WARNING,
1091 "Threading functionality not available, falling back to 1 thread.");
1092 }
1093 } else if (nthreads == 0) {
1094 long nproc = sysconf(_SC_NPROCESSORS_ONLN);
1095
1096 if (nproc > 0) {
1097 nthreads = nproc;
1098 } else {
1099 nthreads = 1;
1100 selinux_log(SELINUX_WARNING,
1101 "Unable to detect CPU count, falling back to 1 thread.");
1102 }
1103 }
1104
1105 /*
1106 * Convert passed-in pathname to canonical pathname by resolving
1107 * realpath of containing dir, then appending last component name.
1108 */
1109 if (state.flags.userealpath) {
1110 char *basename_cpy = strdup(pathname_orig);
1111 if (!basename_cpy)
1112 goto realpatherr;
1113 pathbname = basename(basename_cpy);
1114 if (!strcmp(pathbname, "/") || !strcmp(pathbname, ".") ||
1115 !strcmp(pathbname, "..")) {
1116 pathname = realpath(pathname_orig, NULL);
1117 if (!pathname) {
1118 free(basename_cpy);
1119 goto realpatherr;
1120 }
1121 } else {
1122 char *dirname_cpy = strdup(pathname_orig);
1123 if (!dirname_cpy) {
1124 free(basename_cpy);
1125 goto realpatherr;
1126 }
1127 pathdname = dirname(dirname_cpy);
1128 pathdnamer = realpath(pathdname, NULL);
1129 free(dirname_cpy);
1130 if (!pathdnamer) {
1131 free(basename_cpy);
1132 goto realpatherr;
1133 }
1134 if (!strcmp(pathdnamer, "/"))
1135 error = asprintf(&pathname, "/%s", pathbname);
1136 else
1137 error = asprintf(&pathname, "%s/%s",
1138 pathdnamer, pathbname);
1139 if (error < 0) {
1140 free(basename_cpy);
1141 goto oom;
1142 }
1143 }
1144 free(basename_cpy);
1145 } else {
1146 pathname = strdup(pathname_orig);
1147 if (!pathname)
1148 goto oom;
1149 }
1150
1151 paths[0] = pathname;
1152
1153 if (lstat(pathname, &sb) < 0) {
1154 if (state.flags.ignore_noent && errno == ENOENT) {
1155 free(pathdnamer);
1156 free(pathname);
1157 return 0;
1158 } else {
1159 selinux_log(SELINUX_ERROR,
1160 "lstat(%s) failed: %m\n",
1161 pathname);
1162 error = -1;
1163 goto cleanup;
1164 }
1165 }
1166
1167 /* Skip digest if not a directory */
1168 if (!S_ISDIR(sb.st_mode))
1169 state.setrestorecondigest = false;
1170
1171 if (!state.flags.recurse) {
1172 if (check_excluded(pathname)) {
1173 error = 0;
1174 goto cleanup;
1175 }
1176
1177 error = restorecon_sb(pathname, &state.flags, true);
1178 goto cleanup;
1179 }
1180
1181 /* Obtain fs type */
1182 memset(&state.sfsb, 0, sizeof(state.sfsb));
1183 if (!S_ISLNK(sb.st_mode) && statfs(pathname, &state.sfsb) < 0) {
1184 selinux_log(SELINUX_ERROR,
1185 "statfs(%s) failed: %m\n",
1186 pathname);
1187 error = -1;
1188 goto cleanup;
1189 }
1190
1191 /* Skip digest on in-memory filesystems and /sys */
1192 if (state.sfsb.f_type == RAMFS_MAGIC || state.sfsb.f_type == TMPFS_MAGIC ||
1193 state.sfsb.f_type == SYSFS_MAGIC)
1194 state.setrestorecondigest = false;
1195
1196 if (state.flags.set_xdev)
1197 fts_flags = FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV;
1198 else
1199 fts_flags = FTS_PHYSICAL | FTS_NOCHDIR;
1200
1201 state.fts = fts_open(paths, fts_flags, NULL);
1202 if (!state.fts)
1203 goto fts_err;
1204
1205 state.ftsent_first = fts_read(state.fts);
1206 if (!state.ftsent_first)
1207 goto fts_err;
1208
1209 /*
1210 * Keep the inode of the first device. This is because the FTS_XDEV
1211 * flag tells fts not to descend into directories with different
1212 * device numbers, but fts will still give back the actual directory.
1213 * By saving the device number of the directory that was passed to
1214 * selinux_restorecon() and then skipping all actions on any
1215 * directories with a different device number when the FTS_XDEV flag
1216 * is set (from http://marc.info/?l=selinux&m=124688830500777&w=2).
1217 */
1218 state.dev_num = state.ftsent_first->fts_statp->st_dev;
1219
1220 if (nthreads == 1) {
1221 state.parallel = false;
1222 selinux_restorecon_thread(&state);
1223 } else {
1224 size_t i;
1225 pthread_t self = pthread_self();
1226 pthread_t *threads = NULL;
1227
1228 pthread_mutex_init(&state.mutex, NULL);
1229
1230 threads = calloc(nthreads - 1, sizeof(*threads));
1231 if (!threads)
1232 goto oom;
1233
1234 state.parallel = true;
1235 /*
1236 * Start (nthreads - 1) threads - the main thread is going to
1237 * take part, too.
1238 */
1239 for (i = 0; i < nthreads - 1; i++) {
1240 if (pthread_create(&threads[i], NULL,
1241 selinux_restorecon_thread, &state)) {
1242 /*
1243 * If any thread fails to be created, just mark
1244 * it as such and let the successfully created
1245 * threads do the job. In the worst case the
1246 * main thread will do everything, but that's
1247 * still better than to give up.
1248 */
1249 threads[i] = self;
1250 }
1251 }
1252
1253 /* Let's join in on the fun! */
1254 selinux_restorecon_thread(&state);
1255
1256 /* Now wait for all threads to finish. */
1257 for (i = 0; i < nthreads - 1; i++) {
1258 /* Skip threads that failed to be created. */
1259 if (pthread_equal(threads[i], self))
1260 continue;
1261 pthread_join(threads[i], NULL);
1262 }
1263 free(threads);
1264
1265 pthread_mutex_destroy(&state.mutex);
1266 }
1267
1268 error = state.error;
1269 if (state.saved_errno)
1270 goto out;
1271
1272 /*
1273 * Labeling successful. Write partial match digests for subdirectories.
1274 * TODO: Write digest upon FTS_DP if no error occurs in its descents.
1275 * Note: we can't ignore errors here that we've masked due to
1276 * SELINUX_RESTORECON_COUNT_ERRORS.
1277 */
1278 if (state.setrestorecondigest && !state.flags.nochange && !error &&
1279 state.skipped_errors == 0) {
1280 current = state.head;
1281 while (current != NULL) {
1282 if (setxattr(current->path,
1283 RESTORECON_PARTIAL_MATCH_DIGEST,
1284 current->digest,
1285 SHA1_HASH_SIZE, 0) < 0) {
1286 selinux_log(SELINUX_ERROR,
1287 "setxattr failed: %s: %m\n",
1288 current->path);
1289 }
1290 current = current->next;
1291 }
1292 }
1293
1294 skipped_errors = state.skipped_errors;
1295
1296 out:
1297 if (state.flags.progress && state.flags.mass_relabel)
1298 fprintf(stdout, "\r%s 100.0%%\n", pathname);
1299
1300 (void) fts_close(state.fts);
1301 errno = state.saved_errno;
1302 cleanup:
1303 if (state.flags.add_assoc) {
1304 if (state.flags.verbose)
1305 filespec_eval();
1306 filespec_destroy();
1307 }
1308 free(pathdnamer);
1309 free(pathname);
1310
1311 current = state.head;
1312 while (current != NULL) {
1313 struct dir_hash_node *next = current->next;
1314
1315 free(current->path);
1316 free(current);
1317 current = next;
1318 }
1319 return error;
1320
1321 oom:
1322 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
1323 error = -1;
1324 goto cleanup;
1325
1326 realpatherr:
1327 selinux_log(SELINUX_ERROR,
1328 "SELinux: Could not get canonical path for %s restorecon: %m.\n",
1329 pathname_orig);
1330 error = -1;
1331 goto cleanup;
1332
1333 fts_err:
1334 selinux_log(SELINUX_ERROR,
1335 "fts error while labeling %s: %m\n",
1336 paths[0]);
1337 error = -1;
1338 goto cleanup;
1339 }
1340
1341
1342 /*
1343 * Public API
1344 */
1345
1346 /* selinux_restorecon(3) - Main function that is responsible for labeling */
selinux_restorecon(const char * pathname_orig,unsigned int restorecon_flags)1347 int selinux_restorecon(const char *pathname_orig,
1348 unsigned int restorecon_flags)
1349 {
1350 return selinux_restorecon_common(pathname_orig, restorecon_flags, 1);
1351 }
1352
1353 /* selinux_restorecon_parallel(3) - Parallel version of selinux_restorecon(3) */
selinux_restorecon_parallel(const char * pathname_orig,unsigned int restorecon_flags,size_t nthreads)1354 int selinux_restorecon_parallel(const char *pathname_orig,
1355 unsigned int restorecon_flags,
1356 size_t nthreads)
1357 {
1358 return selinux_restorecon_common(pathname_orig, restorecon_flags, nthreads);
1359 }
1360
1361 /* selinux_restorecon_set_sehandle(3) is called to set the global fc handle */
selinux_restorecon_set_sehandle(struct selabel_handle * hndl)1362 void selinux_restorecon_set_sehandle(struct selabel_handle *hndl)
1363 {
1364 char **specfiles;
1365 unsigned char *fc_digest;
1366 size_t num_specfiles, fc_digest_len;
1367
1368 fc_sehandle = hndl;
1369 if (!fc_sehandle)
1370 return;
1371
1372 /* Check if digest requested in selabel_open(3), if so use it. */
1373 if (selabel_digest(fc_sehandle, &fc_digest, &fc_digest_len,
1374 &specfiles, &num_specfiles) < 0)
1375 selabel_no_digest = true;
1376 else
1377 selabel_no_digest = false;
1378 }
1379
1380
1381 /*
1382 * selinux_restorecon_default_handle(3) is called to set the global restorecon
1383 * handle by a process if the default params are required.
1384 */
selinux_restorecon_default_handle(void)1385 struct selabel_handle *selinux_restorecon_default_handle(void)
1386 {
1387 struct selabel_handle *sehandle;
1388
1389 struct selinux_opt fc_opts[] = {
1390 { SELABEL_OPT_DIGEST, (char *)1 }
1391 };
1392
1393 sehandle = selabel_open(SELABEL_CTX_FILE, fc_opts, 1);
1394
1395 if (!sehandle) {
1396 selinux_log(SELINUX_ERROR,
1397 "Error obtaining file context handle: %m\n");
1398 return NULL;
1399 }
1400
1401 selabel_no_digest = false;
1402 return sehandle;
1403 }
1404
1405 /*
1406 * selinux_restorecon_set_exclude_list(3) is called to add additional entries
1407 * to be excluded from labeling checks.
1408 */
selinux_restorecon_set_exclude_list(const char ** exclude_list)1409 void selinux_restorecon_set_exclude_list(const char **exclude_list)
1410 {
1411 int i;
1412 struct stat sb;
1413
1414 for (i = 0; exclude_list[i]; i++) {
1415 if (lstat(exclude_list[i], &sb) < 0 && errno != EACCES) {
1416 selinux_log(SELINUX_ERROR,
1417 "lstat error on exclude path \"%s\", %m - ignoring.\n",
1418 exclude_list[i]);
1419 break;
1420 }
1421 if (add_exclude(exclude_list[i], CALLER_EXCLUDED) &&
1422 errno == ENOMEM)
1423 assert(0);
1424 }
1425 }
1426
1427 /* selinux_restorecon_set_alt_rootpath(3) sets an alternate rootpath. */
selinux_restorecon_set_alt_rootpath(const char * alt_rootpath)1428 int selinux_restorecon_set_alt_rootpath(const char *alt_rootpath)
1429 {
1430 size_t len;
1431
1432 /* This should be NULL on first use */
1433 if (rootpath)
1434 free(rootpath);
1435
1436 rootpath = strdup(alt_rootpath);
1437 if (!rootpath) {
1438 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
1439 return -1;
1440 }
1441
1442 /* trim trailing /, if present */
1443 len = strlen(rootpath);
1444 while (len && (rootpath[len - 1] == '/'))
1445 rootpath[--len] = '\0';
1446 rootpathlen = len;
1447
1448 return 0;
1449 }
1450
1451 /* selinux_restorecon_xattr(3)
1452 * Find RESTORECON_PARTIAL_MATCH_DIGEST entries.
1453 */
selinux_restorecon_xattr(const char * pathname,unsigned int xattr_flags,struct dir_xattr *** xattr_list)1454 int selinux_restorecon_xattr(const char *pathname, unsigned int xattr_flags,
1455 struct dir_xattr ***xattr_list)
1456 {
1457 bool recurse = (xattr_flags &
1458 SELINUX_RESTORECON_XATTR_RECURSE) ? true : false;
1459 bool delete_nonmatch = (xattr_flags &
1460 SELINUX_RESTORECON_XATTR_DELETE_NONMATCH_DIGESTS) ? true : false;
1461 bool delete_all = (xattr_flags &
1462 SELINUX_RESTORECON_XATTR_DELETE_ALL_DIGESTS) ? true : false;
1463 ignore_mounts = (xattr_flags &
1464 SELINUX_RESTORECON_XATTR_IGNORE_MOUNTS) ? true : false;
1465
1466 int rc, fts_flags;
1467 struct stat sb;
1468 struct statfs sfsb;
1469 struct dir_xattr *current, *next;
1470 FTS *fts;
1471 FTSENT *ftsent;
1472 char *paths[2] = { NULL, NULL };
1473
1474 __selinux_once(fc_once, restorecon_init);
1475
1476 if (!fc_sehandle)
1477 return -1;
1478
1479 if (lstat(pathname, &sb) < 0) {
1480 if (errno == ENOENT)
1481 return 0;
1482
1483 selinux_log(SELINUX_ERROR,
1484 "lstat(%s) failed: %m\n",
1485 pathname);
1486 return -1;
1487 }
1488
1489 if (!recurse) {
1490 if (statfs(pathname, &sfsb) == 0) {
1491 if (sfsb.f_type == RAMFS_MAGIC ||
1492 sfsb.f_type == TMPFS_MAGIC)
1493 return 0;
1494 }
1495
1496 if (check_excluded(pathname))
1497 return 0;
1498
1499 rc = add_xattr_entry(pathname, delete_nonmatch, delete_all);
1500
1501 if (!rc && dir_xattr_list)
1502 *xattr_list = &dir_xattr_list;
1503 else if (rc == -1)
1504 return rc;
1505
1506 return 0;
1507 }
1508
1509 paths[0] = (char *)pathname;
1510 fts_flags = FTS_PHYSICAL | FTS_NOCHDIR;
1511
1512 fts = fts_open(paths, fts_flags, NULL);
1513 if (!fts) {
1514 selinux_log(SELINUX_ERROR,
1515 "fts error on %s: %m\n",
1516 paths[0]);
1517 return -1;
1518 }
1519
1520 while ((ftsent = fts_read(fts)) != NULL) {
1521 switch (ftsent->fts_info) {
1522 case FTS_DP:
1523 continue;
1524 case FTS_D:
1525 if (statfs(ftsent->fts_path, &sfsb) == 0) {
1526 if (sfsb.f_type == RAMFS_MAGIC ||
1527 sfsb.f_type == TMPFS_MAGIC)
1528 continue;
1529 }
1530 if (check_excluded(ftsent->fts_path)) {
1531 fts_set(fts, ftsent, FTS_SKIP);
1532 continue;
1533 }
1534
1535 rc = add_xattr_entry(ftsent->fts_path,
1536 delete_nonmatch, delete_all);
1537 if (rc == 1)
1538 continue;
1539 else if (rc == -1)
1540 goto cleanup;
1541 break;
1542 default:
1543 break;
1544 }
1545 }
1546
1547 if (dir_xattr_list)
1548 *xattr_list = &dir_xattr_list;
1549
1550 (void) fts_close(fts);
1551 return 0;
1552
1553 cleanup:
1554 rc = errno;
1555 (void) fts_close(fts);
1556 errno = rc;
1557
1558 if (dir_xattr_list) {
1559 /* Free any used memory */
1560 current = dir_xattr_list;
1561 while (current) {
1562 next = current->next;
1563 free(current->directory);
1564 free(current->digest);
1565 free(current);
1566 current = next;
1567 }
1568 }
1569 return -1;
1570 }
1571
selinux_restorecon_get_skipped_errors(void)1572 long unsigned selinux_restorecon_get_skipped_errors(void)
1573 {
1574 return skipped_errors;
1575 }
1576