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