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