• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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