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