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