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