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 #define HNP_ROOT_PATH "/data/app/el1/bundle/"
637 #define HNP_PUBLIC_DIR "/hnppublic"
638 #define HNP_ROOT_PATH_LEN 21
639 #define HNP_PUBLIC_DIR_LEN 10
640 #define AOT_ARK_SUFIXX "aot_compiler"
641 #define AOT_ARK_PUBLIC "public"
642 #define DATA_APP_EL1_LEN 14
643 #define AOT_ARK_SUFIXX_LEN 12
644
645 // Allow the hnp process to refresh the labels of files in the HNP_ROOT_PATH directory
is_hnp_path(const char * path)646 static bool is_hnp_path(const char *path)
647 {
648 size_t pathLen = strlen(path);
649 if ((pathLen < HNP_ROOT_PATH_LEN + 1 + HNP_PUBLIC_DIR_LEN + 1) ||
650 (strstr(path, HNP_PUBLIC_DIR) == NULL)) {
651 return false;
652 }
653
654 if (strncmp(path, HNP_ROOT_PATH, HNP_ROOT_PATH_LEN) != 0) {
655 return false;
656 }
657 return true;
658 }
659
is_all_digits(const char * str,size_t len)660 static bool is_all_digits(const char *str, size_t len) {
661 for (size_t i = 0; i < len; i++) {
662 if (!isdigit(str[i])) {
663 return false;
664 }
665 }
666 return true;
667 }
668
is_aot_path(const char * path)669 static bool is_aot_path(const char *path)
670 {
671 // only /data/app/el1/{userid}/aot_compiler or /data/app/el1/public/aot_compiler will be true
672 // length is the length of '/data/app/el1/' + 'aot_compiler' +'{userid}/', The minimum length of the userid is 1
673 if (strlen(path) < DATA_APP_EL1_LEN + 2 + AOT_ARK_SUFIXX_LEN) {
674 return false;
675 }
676 path += strlen(DATA_APP_EL1) - 1;
677 if (*path != '/') {
678 return false;
679 }
680 path++;
681 // find next '/'
682 const char *next_slash = strchr(path, '/');
683 if (next_slash == NULL) {
684 return false;
685 }
686 size_t len = next_slash - path;
687 if ((len != strlen(AOT_ARK_PUBLIC) || strncmp(path, AOT_ARK_PUBLIC, strlen(AOT_ARK_PUBLIC)) != 0) &&
688 !is_all_digits(path, len)) {
689 return false;
690 }
691 // end with aot_compiler
692 return strncmp(next_slash + 1, AOT_ARK_SUFIXX, strlen(AOT_ARK_SUFIXX)) == 0 &&
693 strlen(next_slash + 1) == strlen(AOT_ARK_SUFIXX);
694 }
695
check_path_allow_restorecon(const char * pathname)696 static bool check_path_allow_restorecon(const char *pathname)
697 {
698 if ((!strncmp(pathname, DATA_APP_EL1, sizeof(DATA_APP_EL1) - 1) && (!is_hnp_path(pathname)) &&
699 (!is_aot_path(pathname))) ||
700 !strncmp(pathname, DATA_APP_EL2, sizeof(DATA_APP_EL2) - 1) ||
701 !strncmp(pathname, DATA_APP_EL3, sizeof(DATA_APP_EL3) - 1) ||
702 !strncmp(pathname, DATA_APP_EL4, sizeof(DATA_APP_EL4) - 1) ||
703 !strncmp(pathname, DATA_ACCOUNTS_ACCOUNT_0, sizeof(DATA_ACCOUNTS_ACCOUNT_0) - 1)) {
704 return false;
705 }
706 return true;
707 }
708
709
restorecon_sb(const char * pathname,const struct stat * sb,const struct rest_flags * flags,bool first)710 static int restorecon_sb(const char *pathname, const struct stat *sb,
711 const struct rest_flags *flags, bool first)
712 {
713 char *newcon = NULL;
714 char *curcon = NULL;
715 char *newtypecon = NULL;
716 int rc;
717 const char *lookup_path = pathname;
718
719 if (!check_path_allow_restorecon(pathname)) {
720 goto out;
721 }
722
723 if (rootpath) {
724 if (strncmp(rootpath, lookup_path, rootpathlen) != 0) {
725 selinux_log(SELINUX_ERROR,
726 "%s is not located in alt_rootpath %s\n",
727 lookup_path, rootpath);
728 return -1;
729 }
730 lookup_path += rootpathlen;
731 }
732
733 if (rootpath != NULL && lookup_path[0] == '\0')
734 /* this is actually the root dir of the alt root. */
735 rc = selabel_lookup_raw(fc_sehandle, &newcon, "/",
736 sb->st_mode & S_IFMT);
737 else
738 rc = selabel_lookup_raw(fc_sehandle, &newcon, lookup_path,
739 sb->st_mode & S_IFMT);
740
741 if (rc < 0) {
742 if (errno == ENOENT) {
743 if (flags->warnonnomatch && first)
744 selinux_log(SELINUX_INFO,
745 "Warning no default label for %s\n",
746 lookup_path);
747
748 return 0; /* no match, but not an error */
749 }
750
751 return -1;
752 }
753
754 if (flags->progress) {
755 __pthread_mutex_lock(&progress_mutex);
756 fc_count++;
757 if (fc_count % STAR_COUNT == 0) {
758 if (flags->mass_relabel && efile_count > 0) {
759 float pc = (fc_count < efile_count) ? (100.0 *
760 fc_count / efile_count) : 100;
761 fprintf(stdout, "\r%-.1f%%", (double)pc);
762 } else {
763 fprintf(stdout, "\r%" PRIu64 "k", fc_count / STAR_COUNT);
764 }
765 fflush(stdout);
766 }
767 __pthread_mutex_unlock(&progress_mutex);
768 }
769
770 if (flags->add_assoc) {
771 rc = filespec_add(sb->st_ino, newcon, pathname, flags);
772
773 if (rc < 0) {
774 selinux_log(SELINUX_ERROR,
775 "filespec_add error: %s\n", pathname);
776 freecon(newcon);
777 return -1;
778 }
779
780 if (rc > 0) {
781 /* Already an association and it took precedence. */
782 freecon(newcon);
783 return 0;
784 }
785 }
786
787 if (flags->log_matches)
788 selinux_log(SELINUX_INFO, "%s matched by %s\n",
789 pathname, newcon);
790
791 if (lgetfilecon_raw(pathname, &curcon) < 0) {
792 if (errno != ENODATA)
793 goto err;
794
795 curcon = NULL;
796 }
797
798 if (curcon == NULL || strcmp(curcon, newcon) != 0) {
799 bool updated = false;
800
801 if (!flags->set_specctx && curcon &&
802 (is_context_customizable(curcon) > 0)) {
803 if (flags->verbose) {
804 selinux_log(SELINUX_INFO,
805 "%s not reset as customized by admin to %s\n",
806 pathname, curcon);
807 }
808 goto out;
809 }
810
811 if (!flags->set_specctx && curcon) {
812 /* If types different then update newcon. */
813 rc = compare_types(curcon, newcon, &newtypecon);
814 if (rc)
815 goto err;
816
817 if (newtypecon) {
818 freecon(newcon);
819 newcon = newtypecon;
820 } else {
821 goto out;
822 }
823 }
824
825 if (!flags->nochange) {
826 if (lsetfilecon(pathname, newcon) < 0)
827 goto err;
828 updated = true;
829 }
830
831 if (flags->verbose)
832 selinux_log(SELINUX_INFO,
833 "%s %s from %s to %s\n",
834 updated ? "Relabeled" : "Would relabel",
835 pathname,
836 curcon ? curcon : "<no context>",
837 newcon);
838
839 if (flags->syslog_changes && !flags->nochange) {
840 if (curcon)
841 syslog(LOG_INFO,
842 "relabeling %s from %s to %s\n",
843 pathname, curcon, newcon);
844 else
845 syslog(LOG_INFO, "labeling %s to %s\n",
846 pathname, newcon);
847 }
848 }
849
850 out:
851 rc = 0;
852 out1:
853 freecon(curcon);
854 freecon(newcon);
855 return rc;
856 err:
857 selinux_log(SELINUX_ERROR,
858 "Could not set context for %s: %m\n",
859 pathname);
860 rc = -1;
861 goto out1;
862 }
863
864 struct dir_hash_node {
865 char *path;
866 uint8_t digest[SHA1_HASH_SIZE];
867 struct dir_hash_node *next;
868 };
869 /*
870 * Returns true if the digest of all partial matched contexts is the same as
871 * the one saved by setxattr. Otherwise returns false and constructs a
872 * dir_hash_node with the newly calculated digest.
873 */
check_context_match_for_dir(const char * pathname,struct dir_hash_node ** new_node,int error)874 static bool check_context_match_for_dir(const char *pathname,
875 struct dir_hash_node **new_node,
876 int error)
877 {
878 bool status;
879 size_t digest_len = 0;
880 uint8_t *read_digest = NULL;
881 uint8_t *calculated_digest = NULL;
882
883 if (!new_node)
884 return false;
885
886 *new_node = NULL;
887
888 /* status = true if digests match, false otherwise. */
889 status = selabel_get_digests_all_partial_matches(fc_sehandle, pathname,
890 &calculated_digest,
891 &read_digest,
892 &digest_len);
893
894 if (status)
895 goto free;
896
897 /* Save digest of all matched contexts for the current directory. */
898 if (!error && calculated_digest) {
899 *new_node = calloc(1, sizeof(struct dir_hash_node));
900
901 if (!*new_node)
902 goto oom;
903
904 (*new_node)->path = strdup(pathname);
905
906 if (!(*new_node)->path) {
907 free(*new_node);
908 *new_node = NULL;
909 goto oom;
910 }
911 memcpy((*new_node)->digest, calculated_digest, digest_len);
912 (*new_node)->next = NULL;
913 }
914
915 free:
916 free(calculated_digest);
917 free(read_digest);
918 return status;
919
920 oom:
921 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
922 goto free;
923 }
924
is_in_skip_elx(const char * path)925 static bool is_in_skip_elx(const char *path) {
926 if (sizeof(skip_elx_path) == 0) {
927 return false;
928 }
929 size_t len = sizeof(skip_elx_path) / sizeof(skip_elx_path[0]);
930 for (size_t i = 0; i < len; i++) {
931 if (strncmp(path, skip_elx_path[i], strlen(skip_elx_path[i])) == 0) {
932 return true;
933 }
934 }
935 return false;
936 }
937
938 struct rest_state {
939 struct rest_flags flags;
940 dev_t dev_num;
941 struct statfs sfsb;
942 bool ignore_digest;
943 bool setrestorecondigest;
944 bool parallel;
945
946 FTS *fts;
947 FTSENT *ftsent_first;
948 struct dir_hash_node *head, *current;
949 bool abort;
950 int error;
951 long unsigned skipped_errors;
952 int saved_errno;
953 pthread_mutex_t mutex;
954 };
955
selinux_restorecon_thread(void * arg)956 static void *selinux_restorecon_thread(void *arg)
957 {
958 struct rest_state *state = arg;
959 FTS *fts = state->fts;
960 FTSENT *ftsent;
961 int error;
962 char ent_path[PATH_MAX];
963 struct stat ent_st;
964 bool first = false;
965
966 if (state->parallel)
967 pthread_mutex_lock(&state->mutex);
968
969 if (state->ftsent_first) {
970 ftsent = state->ftsent_first;
971 state->ftsent_first = NULL;
972 first = true;
973 goto loop_body;
974 }
975
976 while (((void)(errno = 0), ftsent = fts_read(fts)) != NULL) {
977 loop_body:
978 /* If the FTS_XDEV flag is set and the device is different */
979 if (state->flags.set_xdev &&
980 ftsent->fts_statp->st_dev != state->dev_num)
981 continue;
982
983 switch (ftsent->fts_info) {
984 case FTS_DC:
985 selinux_log(SELINUX_ERROR,
986 "Directory cycle on %s.\n",
987 ftsent->fts_path);
988 errno = ELOOP;
989 state->error = -1;
990 state->abort = true;
991 goto finish;
992 case FTS_DP:
993 continue;
994 case FTS_DNR:
995 error = errno;
996 errno = ftsent->fts_errno;
997 selinux_log(SELINUX_ERROR,
998 "Could not read %s: %m.\n",
999 ftsent->fts_path);
1000 errno = error;
1001 fts_set(fts, ftsent, FTS_SKIP);
1002 continue;
1003 case FTS_NS:
1004 error = errno;
1005 errno = ftsent->fts_errno;
1006 selinux_log(SELINUX_ERROR,
1007 "Could not stat %s: %m.\n",
1008 ftsent->fts_path);
1009 errno = error;
1010 fts_set(fts, ftsent, FTS_SKIP);
1011 continue;
1012 case FTS_ERR:
1013 error = errno;
1014 errno = ftsent->fts_errno;
1015 selinux_log(SELINUX_ERROR,
1016 "Error on %s: %m.\n",
1017 ftsent->fts_path);
1018 errno = error;
1019 fts_set(fts, ftsent, FTS_SKIP);
1020 continue;
1021 case FTS_D:
1022 if (state->sfsb.f_type == SYSFS_MAGIC &&
1023 !selabel_partial_match(fc_sehandle,
1024 ftsent->fts_path)) {
1025 fts_set(fts, ftsent, FTS_SKIP);
1026 continue;
1027 }
1028
1029 if (check_excluded(ftsent->fts_path)) {
1030 fts_set(fts, ftsent, FTS_SKIP);
1031 continue;
1032 }
1033
1034 if (state->setrestorecondigest) {
1035 struct dir_hash_node *new_node = NULL;
1036
1037 if (check_context_match_for_dir(ftsent->fts_path,
1038 &new_node,
1039 state->error) &&
1040 !state->ignore_digest) {
1041 selinux_log(SELINUX_INFO,
1042 "Skipping restorecon on directory(%s)\n",
1043 ftsent->fts_path);
1044 fts_set(fts, ftsent, FTS_SKIP);
1045 continue;
1046 }
1047
1048 if (new_node && !state->error) {
1049 if (!state->current) {
1050 state->current = new_node;
1051 state->head = state->current;
1052 } else {
1053 state->current->next = new_node;
1054 state->current = new_node;
1055 }
1056 }
1057 }
1058
1059 if (state->flags.skipelx && is_in_skip_elx(ftsent->fts_path)) {
1060 fts_set(fts, ftsent, FTS_SKIP);
1061 continue;
1062 }
1063
1064 enum skip_type skip_ignore_flag = skip_ignore_relabel(ftsent->fts_path);
1065 selinux_log(SELINUX_INFO,
1066 "ignore cfg parsing result %d \n",
1067 skip_ignore_flag);
1068 switch (skip_ignore_flag) {
1069 case SKIP_SELF_SUB_DIR:
1070 selinux_log(SELINUX_INFO,
1071 "Skipping restorecon on directory(%s), cause ignroe_cfg\n",
1072 ftsent->fts_path);
1073 fts_set(fts, ftsent, FTS_SKIP);
1074 continue;
1075 case SKIP_SUB_DIR:
1076 selinux_log(SELINUX_INFO,
1077 "Skipping restorecon on directory(%s) sub directory, cause ignroe_cfg\n",
1078 ftsent->fts_path);
1079 fts_set(fts, ftsent, FTS_SKIP);
1080 default:
1081 break;
1082 }
1083
1084 /* fall through */
1085 default:
1086 if (strlcpy(ent_path, ftsent->fts_path, sizeof(ent_path)) >= sizeof(ent_path)) {
1087 selinux_log(SELINUX_ERROR,
1088 "Path name too long on %s.\n",
1089 ftsent->fts_path);
1090 errno = ENAMETOOLONG;
1091 state->error = -1;
1092 state->abort = true;
1093 goto finish;
1094 }
1095
1096 ent_st = *ftsent->fts_statp;
1097 if (state->parallel)
1098 pthread_mutex_unlock(&state->mutex);
1099
1100 error = restorecon_sb(ent_path, &ent_st, &state->flags,
1101 first);
1102
1103 if (state->parallel) {
1104 pthread_mutex_lock(&state->mutex);
1105 if (state->abort)
1106 goto unlock;
1107 }
1108
1109 first = false;
1110 if (error) {
1111 if (state->flags.abort_on_error) {
1112 state->error = error;
1113 state->abort = true;
1114 goto finish;
1115 }
1116 if (state->flags.count_errors)
1117 state->skipped_errors++;
1118 else
1119 state->error = error;
1120 }
1121 break;
1122 }
1123 }
1124
1125 finish:
1126 if (!state->saved_errno)
1127 state->saved_errno = errno;
1128 unlock:
1129 if (state->parallel)
1130 pthread_mutex_unlock(&state->mutex);
1131 return NULL;
1132 }
1133
selinux_restorecon_common(const char * pathname_orig,unsigned int restorecon_flags,size_t nthreads)1134 static int selinux_restorecon_common(const char *pathname_orig,
1135 unsigned int restorecon_flags,
1136 size_t nthreads)
1137 {
1138 struct rest_state state;
1139
1140 state.flags.nochange = (restorecon_flags &
1141 SELINUX_RESTORECON_NOCHANGE) ? true : false;
1142 state.flags.verbose = (restorecon_flags &
1143 SELINUX_RESTORECON_VERBOSE) ? true : false;
1144 state.flags.progress = (restorecon_flags &
1145 SELINUX_RESTORECON_PROGRESS) ? true : false;
1146 state.flags.mass_relabel = (restorecon_flags &
1147 SELINUX_RESTORECON_MASS_RELABEL) ? true : false;
1148 state.flags.recurse = (restorecon_flags &
1149 SELINUX_RESTORECON_RECURSE) ? true : false;
1150 state.flags.set_specctx = (restorecon_flags &
1151 SELINUX_RESTORECON_SET_SPECFILE_CTX) ? true : false;
1152 state.flags.userealpath = (restorecon_flags &
1153 SELINUX_RESTORECON_REALPATH) ? true : false;
1154 state.flags.set_xdev = (restorecon_flags &
1155 SELINUX_RESTORECON_XDEV) ? true : false;
1156 state.flags.add_assoc = (restorecon_flags &
1157 SELINUX_RESTORECON_ADD_ASSOC) ? true : false;
1158 state.flags.abort_on_error = (restorecon_flags &
1159 SELINUX_RESTORECON_ABORT_ON_ERROR) ? true : false;
1160 state.flags.syslog_changes = (restorecon_flags &
1161 SELINUX_RESTORECON_SYSLOG_CHANGES) ? true : false;
1162 state.flags.log_matches = (restorecon_flags &
1163 SELINUX_RESTORECON_LOG_MATCHES) ? true : false;
1164 state.flags.ignore_noent = (restorecon_flags &
1165 SELINUX_RESTORECON_IGNORE_NOENTRY) ? true : false;
1166 state.flags.warnonnomatch = true;
1167 state.flags.conflicterror = (restorecon_flags &
1168 SELINUX_RESTORECON_CONFLICT_ERROR) ? true : false;
1169 ignore_mounts = (restorecon_flags &
1170 SELINUX_RESTORECON_IGNORE_MOUNTS) ? true : false;
1171 state.ignore_digest = (restorecon_flags &
1172 SELINUX_RESTORECON_IGNORE_DIGEST) ? true : false;
1173 state.flags.count_errors = (restorecon_flags &
1174 SELINUX_RESTORECON_COUNT_ERRORS) ? true : false;
1175 state.flags.skipelx = (restorecon_flags &
1176 SELINUX_RESTORECON_SKIPELX) ? true : false;
1177 state.setrestorecondigest = true;
1178
1179 state.head = NULL;
1180 state.current = NULL;
1181 state.abort = false;
1182 state.error = 0;
1183 state.skipped_errors = 0;
1184 state.saved_errno = 0;
1185
1186 struct stat sb;
1187 char *pathname = NULL, *pathdnamer = NULL, *pathdname, *pathbname;
1188 char *paths[2] = { NULL, NULL };
1189 int fts_flags, error;
1190 struct dir_hash_node *current = NULL;
1191
1192 if (state.flags.verbose && state.flags.progress)
1193 state.flags.verbose = false;
1194
1195 __selinux_once(fc_once, restorecon_init);
1196
1197 if (!fc_sehandle)
1198 return -1;
1199
1200 /*
1201 * If selabel_no_digest = true then no digest has been requested by
1202 * an external selabel_open(3) call.
1203 */
1204 if (selabel_no_digest ||
1205 (restorecon_flags & SELINUX_RESTORECON_SKIP_DIGEST))
1206 state.setrestorecondigest = false;
1207
1208 if (!__pthread_supported) {
1209 if (nthreads != 1) {
1210 nthreads = 1;
1211 selinux_log(SELINUX_WARNING,
1212 "Threading functionality not available, falling back to 1 thread.");
1213 }
1214 } else if (nthreads == 0) {
1215 long nproc = sysconf(_SC_NPROCESSORS_ONLN);
1216
1217 if (nproc > 0) {
1218 nthreads = nproc;
1219 } else {
1220 nthreads = 1;
1221 selinux_log(SELINUX_WARNING,
1222 "Unable to detect CPU count, falling back to 1 thread.");
1223 }
1224 }
1225
1226 /*
1227 * Convert passed-in pathname to canonical pathname by resolving
1228 * realpath of containing dir, then appending last component name.
1229 */
1230 if (state.flags.userealpath) {
1231 char *basename_cpy = strdup(pathname_orig);
1232 if (!basename_cpy)
1233 goto realpatherr;
1234 pathbname = basename(basename_cpy);
1235 if (!strcmp(pathbname, "/") || !strcmp(pathbname, ".") ||
1236 !strcmp(pathbname, "..")) {
1237 pathname = realpath(pathname_orig, NULL);
1238 if (!pathname) {
1239 free(basename_cpy);
1240 /* missing parent directory */
1241 if (state.flags.ignore_noent && errno == ENOENT) {
1242 return 0;
1243 }
1244 goto realpatherr;
1245 }
1246 } else {
1247 char *dirname_cpy = strdup(pathname_orig);
1248 if (!dirname_cpy) {
1249 free(basename_cpy);
1250 goto realpatherr;
1251 }
1252 pathdname = dirname(dirname_cpy);
1253 pathdnamer = realpath(pathdname, NULL);
1254 free(dirname_cpy);
1255 if (!pathdnamer) {
1256 free(basename_cpy);
1257 if (state.flags.ignore_noent && errno == ENOENT) {
1258 return 0;
1259 }
1260 goto realpatherr;
1261 }
1262 if (!strcmp(pathdnamer, "/"))
1263 error = asprintf(&pathname, "/%s", pathbname);
1264 else
1265 error = asprintf(&pathname, "%s/%s",
1266 pathdnamer, pathbname);
1267 if (error < 0) {
1268 free(basename_cpy);
1269 goto oom;
1270 }
1271 }
1272 free(basename_cpy);
1273 } else {
1274 pathname = strdup(pathname_orig);
1275 if (!pathname)
1276 goto oom;
1277 }
1278
1279 paths[0] = pathname;
1280
1281 if (lstat(pathname, &sb) < 0) {
1282 if (state.flags.ignore_noent && errno == ENOENT) {
1283 free(pathdnamer);
1284 free(pathname);
1285 return 0;
1286 } else {
1287 selinux_log(SELINUX_ERROR,
1288 "lstat(%s) failed: %m\n",
1289 pathname);
1290 error = -1;
1291 goto cleanup;
1292 }
1293 }
1294
1295 /* Skip digest if not a directory */
1296 if (!S_ISDIR(sb.st_mode))
1297 state.setrestorecondigest = false;
1298
1299 if (!state.flags.recurse) {
1300 if (check_excluded(pathname)) {
1301 error = 0;
1302 goto cleanup;
1303 }
1304
1305 error = restorecon_sb(pathname, &sb, &state.flags, true);
1306 goto cleanup;
1307 }
1308
1309 /* Obtain fs type */
1310 memset(&state.sfsb, 0, sizeof(state.sfsb));
1311 if (!S_ISLNK(sb.st_mode) && statfs(pathname, &state.sfsb) < 0) {
1312 selinux_log(SELINUX_ERROR,
1313 "statfs(%s) failed: %m\n",
1314 pathname);
1315 error = -1;
1316 goto cleanup;
1317 }
1318
1319 /* Skip digest on in-memory filesystems and /sys */
1320 if (state.sfsb.f_type == RAMFS_MAGIC || state.sfsb.f_type == TMPFS_MAGIC ||
1321 state.sfsb.f_type == SYSFS_MAGIC)
1322 state.setrestorecondigest = false;
1323
1324 if (state.flags.set_xdev)
1325 fts_flags = FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV;
1326 else
1327 fts_flags = FTS_PHYSICAL | FTS_NOCHDIR;
1328
1329 state.fts = fts_open(paths, fts_flags, NULL);
1330 if (!state.fts)
1331 goto fts_err;
1332
1333 state.ftsent_first = fts_read(state.fts);
1334 if (!state.ftsent_first)
1335 goto fts_err;
1336
1337 /*
1338 * Keep the inode of the first device. This is because the FTS_XDEV
1339 * flag tells fts not to descend into directories with different
1340 * device numbers, but fts will still give back the actual directory.
1341 * By saving the device number of the directory that was passed to
1342 * selinux_restorecon() and then skipping all actions on any
1343 * directories with a different device number when the FTS_XDEV flag
1344 * is set (from http://marc.info/?l=selinux&m=124688830500777&w=2).
1345 */
1346 state.dev_num = state.ftsent_first->fts_statp->st_dev;
1347
1348 bool load_ignore_path = load_ignore_cfg();
1349 if (!load_ignore_path) {
1350 selinux_log(SELINUX_ERROR, "Failed to load ignore cfg!\n");
1351 }
1352
1353 if (nthreads == 1) {
1354 state.parallel = false;
1355 selinux_restorecon_thread(&state);
1356 } else {
1357 size_t i;
1358 pthread_t self = pthread_self();
1359 pthread_t *threads = NULL;
1360
1361 pthread_mutex_init(&state.mutex, NULL);
1362
1363 threads = calloc(nthreads - 1, sizeof(*threads));
1364 if (!threads)
1365 goto oom;
1366
1367 state.parallel = true;
1368 /*
1369 * Start (nthreads - 1) threads - the main thread is going to
1370 * take part, too.
1371 */
1372 for (i = 0; i < nthreads - 1; i++) {
1373 if (pthread_create(&threads[i], NULL,
1374 selinux_restorecon_thread, &state)) {
1375 /*
1376 * If any thread fails to be created, just mark
1377 * it as such and let the successfully created
1378 * threads do the job. In the worst case the
1379 * main thread will do everything, but that's
1380 * still better than to give up.
1381 */
1382 threads[i] = self;
1383 }
1384 }
1385
1386 /* Let's join in on the fun! */
1387 selinux_restorecon_thread(&state);
1388
1389 /* Now wait for all threads to finish. */
1390 for (i = 0; i < nthreads - 1; i++) {
1391 /* Skip threads that failed to be created. */
1392 if (pthread_equal(threads[i], self))
1393 continue;
1394 pthread_join(threads[i], NULL);
1395 }
1396 free(threads);
1397
1398 pthread_mutex_destroy(&state.mutex);
1399 }
1400
1401 error = state.error;
1402 if (state.saved_errno)
1403 goto out;
1404
1405 /*
1406 * Labeling successful. Write partial match digests for subdirectories.
1407 * TODO: Write digest upon FTS_DP if no error occurs in its descents.
1408 * Note: we can't ignore errors here that we've masked due to
1409 * SELINUX_RESTORECON_COUNT_ERRORS.
1410 */
1411 if (state.setrestorecondigest && !state.flags.nochange && !error &&
1412 state.skipped_errors == 0) {
1413 current = state.head;
1414 while (current != NULL) {
1415 if (setxattr(current->path,
1416 RESTORECON_PARTIAL_MATCH_DIGEST,
1417 current->digest,
1418 SHA1_HASH_SIZE, 0) < 0) {
1419 selinux_log(SELINUX_ERROR,
1420 "setxattr failed: %s: %m\n",
1421 current->path);
1422 }
1423 current = current->next;
1424 }
1425 }
1426
1427 skipped_errors = state.skipped_errors;
1428
1429 out:
1430 if (state.flags.progress && state.flags.mass_relabel)
1431 fprintf(stdout, "\r%s 100.0%%\n", pathname);
1432
1433 (void) fts_close(state.fts);
1434 errno = state.saved_errno;
1435 cleanup:
1436 if (state.flags.add_assoc) {
1437 if (state.flags.verbose)
1438 filespec_eval();
1439 filespec_destroy();
1440 }
1441 free(pathdnamer);
1442 free(pathname);
1443
1444 current = state.head;
1445 while (current != NULL) {
1446 struct dir_hash_node *next = current->next;
1447
1448 free(current->path);
1449 free(current);
1450 current = next;
1451 }
1452 return error;
1453
1454 oom:
1455 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
1456 error = -1;
1457 goto cleanup;
1458
1459 realpatherr:
1460 selinux_log(SELINUX_ERROR,
1461 "SELinux: Could not get canonical path for %s restorecon: %m.\n",
1462 pathname_orig);
1463 error = -1;
1464 goto cleanup;
1465
1466 fts_err:
1467 selinux_log(SELINUX_ERROR,
1468 "fts error while labeling %s: %m\n",
1469 paths[0]);
1470 error = -1;
1471 goto cleanup;
1472 }
1473
1474
1475 /*
1476 * Public API
1477 */
1478
1479 /* selinux_restorecon(3) - Main function that is responsible for labeling */
selinux_restorecon(const char * pathname_orig,unsigned int restorecon_flags)1480 int selinux_restorecon(const char *pathname_orig,
1481 unsigned int restorecon_flags)
1482 {
1483 return selinux_restorecon_common(pathname_orig, restorecon_flags, 1);
1484 }
1485
1486 /* selinux_restorecon_parallel(3) - Parallel version of selinux_restorecon(3) */
selinux_restorecon_parallel(const char * pathname_orig,unsigned int restorecon_flags,size_t nthreads)1487 int selinux_restorecon_parallel(const char *pathname_orig,
1488 unsigned int restorecon_flags,
1489 size_t nthreads)
1490 {
1491 return selinux_restorecon_common(pathname_orig, restorecon_flags, nthreads);
1492 }
1493
1494 /* selinux_restorecon_set_sehandle(3) is called to set the global fc handle */
selinux_restorecon_set_sehandle(struct selabel_handle * hndl)1495 void selinux_restorecon_set_sehandle(struct selabel_handle *hndl)
1496 {
1497 char **specfiles;
1498 unsigned char *fc_digest;
1499 size_t num_specfiles, fc_digest_len;
1500
1501 fc_sehandle = hndl;
1502 if (!fc_sehandle)
1503 return;
1504
1505 /* Check if digest requested in selabel_open(3), if so use it. */
1506 if (selabel_digest(fc_sehandle, &fc_digest, &fc_digest_len,
1507 &specfiles, &num_specfiles) < 0)
1508 selabel_no_digest = true;
1509 else
1510 selabel_no_digest = false;
1511 }
1512
1513
1514 /*
1515 * selinux_restorecon_default_handle(3) is called to set the global restorecon
1516 * handle by a process if the default params are required.
1517 */
selinux_restorecon_default_handle(void)1518 struct selabel_handle *selinux_restorecon_default_handle(void)
1519 {
1520 struct selabel_handle *sehandle;
1521
1522 struct selinux_opt fc_opts[] = {
1523 { SELABEL_OPT_DIGEST, (char *)1 }
1524 };
1525
1526 sehandle = selabel_open(SELABEL_CTX_FILE, fc_opts, 1);
1527
1528 if (!sehandle) {
1529 selinux_log(SELINUX_ERROR,
1530 "Error obtaining file context handle: %m\n");
1531 return NULL;
1532 }
1533
1534 selabel_no_digest = false;
1535 return sehandle;
1536 }
1537
1538 /*
1539 * selinux_restorecon_set_exclude_list(3) is called to add additional entries
1540 * to be excluded from labeling checks.
1541 */
selinux_restorecon_set_exclude_list(const char ** exclude_list)1542 void selinux_restorecon_set_exclude_list(const char **exclude_list)
1543 {
1544 int i;
1545 struct stat sb;
1546
1547 for (i = 0; exclude_list[i]; i++) {
1548 if (lstat(exclude_list[i], &sb) < 0 && errno != EACCES) {
1549 selinux_log(SELINUX_ERROR,
1550 "lstat error on exclude path \"%s\", %m - ignoring.\n",
1551 exclude_list[i]);
1552 break;
1553 }
1554 if (add_exclude(exclude_list[i], CALLER_EXCLUDED) &&
1555 errno == ENOMEM)
1556 assert(0);
1557 }
1558 }
1559
1560 /* selinux_restorecon_set_alt_rootpath(3) sets an alternate rootpath. */
selinux_restorecon_set_alt_rootpath(const char * alt_rootpath)1561 int selinux_restorecon_set_alt_rootpath(const char *alt_rootpath)
1562 {
1563 size_t len;
1564
1565 /* This should be NULL on first use */
1566 if (rootpath)
1567 free(rootpath);
1568
1569 rootpath = strdup(alt_rootpath);
1570 if (!rootpath) {
1571 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
1572 return -1;
1573 }
1574
1575 /* trim trailing /, if present */
1576 len = strlen(rootpath);
1577 while (len && (rootpath[len - 1] == '/'))
1578 rootpath[--len] = '\0';
1579 rootpathlen = len;
1580
1581 return 0;
1582 }
1583
1584 /* selinux_restorecon_xattr(3)
1585 * Find RESTORECON_PARTIAL_MATCH_DIGEST entries.
1586 */
selinux_restorecon_xattr(const char * pathname,unsigned int xattr_flags,struct dir_xattr *** xattr_list)1587 int selinux_restorecon_xattr(const char *pathname, unsigned int xattr_flags,
1588 struct dir_xattr ***xattr_list)
1589 {
1590 bool recurse = (xattr_flags &
1591 SELINUX_RESTORECON_XATTR_RECURSE) ? true : false;
1592 bool delete_nonmatch = (xattr_flags &
1593 SELINUX_RESTORECON_XATTR_DELETE_NONMATCH_DIGESTS) ? true : false;
1594 bool delete_all = (xattr_flags &
1595 SELINUX_RESTORECON_XATTR_DELETE_ALL_DIGESTS) ? true : false;
1596 ignore_mounts = (xattr_flags &
1597 SELINUX_RESTORECON_XATTR_IGNORE_MOUNTS) ? true : false;
1598
1599 int rc, fts_flags;
1600 struct stat sb;
1601 struct statfs sfsb;
1602 struct dir_xattr *current, *next;
1603 FTS *fts;
1604 FTSENT *ftsent;
1605 char *paths[2] = { NULL, NULL };
1606
1607 __selinux_once(fc_once, restorecon_init);
1608
1609 if (!fc_sehandle)
1610 return -1;
1611
1612 if (lstat(pathname, &sb) < 0) {
1613 if (errno == ENOENT)
1614 return 0;
1615
1616 selinux_log(SELINUX_ERROR,
1617 "lstat(%s) failed: %m\n",
1618 pathname);
1619 return -1;
1620 }
1621
1622 if (!recurse) {
1623 if (statfs(pathname, &sfsb) == 0) {
1624 if (sfsb.f_type == RAMFS_MAGIC ||
1625 sfsb.f_type == TMPFS_MAGIC)
1626 return 0;
1627 }
1628
1629 if (check_excluded(pathname))
1630 return 0;
1631
1632 rc = add_xattr_entry(pathname, delete_nonmatch, delete_all);
1633
1634 if (!rc && dir_xattr_list)
1635 *xattr_list = &dir_xattr_list;
1636 else if (rc == -1)
1637 return rc;
1638
1639 return 0;
1640 }
1641
1642 paths[0] = (char *)pathname;
1643 fts_flags = FTS_PHYSICAL | FTS_NOCHDIR;
1644
1645 fts = fts_open(paths, fts_flags, NULL);
1646 if (!fts) {
1647 selinux_log(SELINUX_ERROR,
1648 "fts error on %s: %m\n",
1649 paths[0]);
1650 return -1;
1651 }
1652
1653 while ((ftsent = fts_read(fts)) != NULL) {
1654 switch (ftsent->fts_info) {
1655 case FTS_DP:
1656 continue;
1657 case FTS_D:
1658 if (statfs(ftsent->fts_path, &sfsb) == 0) {
1659 if (sfsb.f_type == RAMFS_MAGIC ||
1660 sfsb.f_type == TMPFS_MAGIC)
1661 continue;
1662 }
1663 if (check_excluded(ftsent->fts_path)) {
1664 fts_set(fts, ftsent, FTS_SKIP);
1665 continue;
1666 }
1667
1668 rc = add_xattr_entry(ftsent->fts_path,
1669 delete_nonmatch, delete_all);
1670 if (rc == 1)
1671 continue;
1672 else if (rc == -1)
1673 goto cleanup;
1674 break;
1675 default:
1676 break;
1677 }
1678 }
1679
1680 if (dir_xattr_list)
1681 *xattr_list = &dir_xattr_list;
1682
1683 (void) fts_close(fts);
1684 return 0;
1685
1686 cleanup:
1687 rc = errno;
1688 (void) fts_close(fts);
1689 errno = rc;
1690
1691 if (dir_xattr_list) {
1692 /* Free any used memory */
1693 current = dir_xattr_list;
1694 while (current) {
1695 next = current->next;
1696 free(current->directory);
1697 free(current->digest);
1698 free(current);
1699 current = next;
1700 }
1701 }
1702 return -1;
1703 }
1704
selinux_restorecon_get_skipped_errors(void)1705 long unsigned selinux_restorecon_get_skipped_errors(void)
1706 {
1707 return skipped_errors;
1708 }
1709