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