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