• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * The majority of this code is from Android's
3  * external/libselinux/src/android.c and upstream
4  * selinux/policycoreutils/setfiles/restore.c
5  *
6  * See selinux_restorecon(3) for details.
7  */
8 
9 #include <unistd.h>
10 #include <string.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <stdbool.h>
14 #include <ctype.h>
15 #include <errno.h>
16 #include <fcntl.h>
17 #include <include/fts.h>
18 #include <inttypes.h>
19 #include <limits.h>
20 #include <stdint.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <sys/xattr.h>
24 #include <sys/vfs.h>
25 #include <sys/statvfs.h>
26 #include <sys/utsname.h>
27 #include <linux/magic.h>
28 #include <libgen.h>
29 #include <syslog.h>
30 #include <assert.h>
31 
32 #include <selinux/selinux.h>
33 #include <selinux/context.h>
34 #include <selinux/label.h>
35 #include <selinux/restorecon.h>
36 
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,struct rest_flags * flags)434 static int filespec_add(ino_t ino, const char *con, const char *file,
435 			struct rest_flags *flags)
436 {
437 	file_spec_t *prevfl, *fl;
438 	int h, ret;
439 	struct stat64 sb;
440 
441 	__pthread_mutex_lock(&fl_mutex);
442 
443 	if (!fl_head) {
444 		fl_head = calloc(HASH_BUCKETS, sizeof(file_spec_t));
445 		if (!fl_head)
446 			goto oom;
447 	}
448 
449 	h = (ino + (ino >> HASH_BITS)) & HASH_MASK;
450 	for (prevfl = &fl_head[h], fl = fl_head[h].next; fl;
451 	     prevfl = fl, fl = fl->next) {
452 		if (ino == fl->ino) {
453 			ret = lstat64(fl->file, &sb);
454 			if (ret < 0 || sb.st_ino != ino) {
455 				freecon(fl->con);
456 				free(fl->file);
457 				fl->file = strdup(file);
458 				if (!fl->file)
459 					goto oom;
460 				fl->con = strdup(con);
461 				if (!fl->con)
462 					goto oom;
463 				goto unlock_1;
464 			}
465 
466 			if (strcmp(fl->con, con) == 0)
467 				goto unlock_1;
468 
469 			selinux_log(SELINUX_ERROR,
470 				"conflicting specifications for %s and %s, using %s.\n",
471 				file, fl->file, fl->con);
472 			free(fl->file);
473 			fl->file = strdup(file);
474 			if (!fl->file)
475 				goto oom;
476 
477 			__pthread_mutex_unlock(&fl_mutex);
478 
479 			if (flags->conflicterror) {
480 				selinux_log(SELINUX_ERROR,
481 				"treating conflicting specifications as an error.\n");
482 				return -1;
483 			}
484 			return 1;
485 		}
486 
487 		if (ino > fl->ino)
488 			break;
489 	}
490 
491 	fl = malloc(sizeof(file_spec_t));
492 	if (!fl)
493 		goto oom;
494 	fl->ino = ino;
495 	fl->con = strdup(con);
496 	if (!fl->con)
497 		goto oom_freefl;
498 	fl->file = strdup(file);
499 	if (!fl->file)
500 		goto oom_freeflcon;
501 	fl->next = prevfl->next;
502 	prevfl->next = fl;
503 
504 	__pthread_mutex_unlock(&fl_mutex);
505 	return 0;
506 
507 oom_freeflcon:
508 	free(fl->con);
509 oom_freefl:
510 	free(fl);
511 oom:
512 	__pthread_mutex_unlock(&fl_mutex);
513 	selinux_log(SELINUX_ERROR, "%s:  Out of memory\n", __func__);
514 	return -1;
515 unlock_1:
516 	__pthread_mutex_unlock(&fl_mutex);
517 	return 1;
518 }
519 
520 /*
521  * Evaluate the association hash table distribution.
522  */
523 #ifdef DEBUG
filespec_eval(void)524 static void filespec_eval(void)
525 {
526 	file_spec_t *fl;
527 	int h, used, nel, len, longest;
528 
529 	if (!fl_head)
530 		return;
531 
532 	used = 0;
533 	longest = 0;
534 	nel = 0;
535 	for (h = 0; h < HASH_BUCKETS; h++) {
536 		len = 0;
537 		for (fl = fl_head[h].next; fl; fl = fl->next)
538 			len++;
539 		if (len)
540 			used++;
541 		if (len > longest)
542 			longest = len;
543 		nel += len;
544 	}
545 
546 	selinux_log(SELINUX_INFO,
547 		     "filespec hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n",
548 		     nel, used, HASH_BUCKETS, longest);
549 }
550 #else
filespec_eval(void)551 static void filespec_eval(void)
552 {
553 }
554 #endif
555 
556 /*
557  * Destroy the association hash table.
558  */
filespec_destroy(void)559 static void filespec_destroy(void)
560 {
561 	file_spec_t *fl, *tmp;
562 	int h;
563 
564 	if (!fl_head)
565 		return;
566 
567 	for (h = 0; h < HASH_BUCKETS; h++) {
568 		fl = fl_head[h].next;
569 		while (fl) {
570 			tmp = fl;
571 			fl = fl->next;
572 			freecon(tmp->con);
573 			free(tmp->file);
574 			free(tmp);
575 		}
576 		fl_head[h].next = NULL;
577 	}
578 	free(fl_head);
579 	fl_head = NULL;
580 }
581 
582 /*
583  * Called if SELINUX_RESTORECON_SET_SPECFILE_CTX is not set to check if
584  * the type components differ, updating newtypecon if so.
585  */
compare_types(const char * curcon,const char * newcon,char ** newtypecon)586 static int compare_types(const char *curcon, const char *newcon, char **newtypecon)
587 {
588 	int types_differ = 0;
589 	context_t cona;
590 	context_t conb;
591 	int rc = 0;
592 
593 	cona = context_new(curcon);
594 	if (!cona) {
595 		rc = -1;
596 		goto out;
597 	}
598 	conb = context_new(newcon);
599 	if (!conb) {
600 		context_free(cona);
601 		rc = -1;
602 		goto out;
603 	}
604 
605 	types_differ = strcmp(context_type_get(cona), context_type_get(conb));
606 	if (types_differ) {
607 		rc |= context_user_set(conb, context_user_get(cona));
608 		rc |= context_role_set(conb, context_role_get(cona));
609 		rc |= context_range_set(conb, context_range_get(cona));
610 		if (!rc) {
611 			*newtypecon = strdup(context_str(conb));
612 			if (!*newtypecon) {
613 				rc = -1;
614 				goto err;
615 			}
616 		}
617 	}
618 
619 err:
620 	context_free(cona);
621 	context_free(conb);
622 out:
623 	return rc;
624 }
625 
626 #define DATA_APP_EL1 "/data/app/el1/"
627 #define DATA_APP_EL2 "/data/app/el2/"
628 #define DATA_APP_EL3 "/data/app/el3/"
629 #define DATA_APP_EL4 "/data/app/el4/"
630 #define DATA_ACCOUNTS_ACCOUNT_0 "/data/accounts/account_0/"
631 
restorecon_sb(const char * pathname,struct rest_flags * flags,bool first)632 static int restorecon_sb(const char *pathname, struct rest_flags *flags, bool first)
633 {
634 	char *newcon = NULL;
635 	char *curcon = NULL;
636 	char *newtypecon = NULL;
637 	int fd = -1, rc;
638 	struct stat stat_buf;
639 	bool updated = false;
640 	const char *lookup_path = pathname;
641 	float pc;
642 	if (!strncmp(pathname, DATA_APP_EL1, sizeof(DATA_APP_EL1) - 1) ||
643 		!strncmp(pathname, DATA_APP_EL2, sizeof(DATA_APP_EL2) - 1) ||
644 		!strncmp(pathname, DATA_APP_EL3, sizeof(DATA_APP_EL3) - 1) ||
645 		!strncmp(pathname, DATA_APP_EL4, sizeof(DATA_APP_EL4) - 1) ||
646 		!strncmp(pathname, DATA_ACCOUNTS_ACCOUNT_0, sizeof(DATA_ACCOUNTS_ACCOUNT_0) - 1)) {
647 		goto out;
648 	}
649 	if (rootpath) {
650 		if (strncmp(rootpath, lookup_path, rootpathlen) != 0) {
651 			selinux_log(SELINUX_ERROR,
652 				    "%s is not located in alt_rootpath %s\n",
653 				    lookup_path, rootpath);
654 			return -1;
655 		}
656 		lookup_path += rootpathlen;
657 	}
658 
659 	fd = open(pathname, O_PATH | O_NOFOLLOW | O_EXCL);
660 	if (fd < 0)
661 		goto err;
662 
663 	rc = fstat(fd, &stat_buf);
664 	if (rc < 0)
665 		goto err;
666 
667 	if (rootpath != NULL && lookup_path[0] == '\0')
668 		/* this is actually the root dir of the alt root. */
669 		rc = selabel_lookup_raw(fc_sehandle, &newcon, "/",
670 						    stat_buf.st_mode);
671 	else
672 		rc = selabel_lookup_raw(fc_sehandle, &newcon, lookup_path,
673 						    stat_buf.st_mode);
674 
675 	if (rc < 0) {
676 		if (errno == ENOENT) {
677 			if (flags->warnonnomatch && first)
678 				selinux_log(SELINUX_INFO,
679 					    "Warning no default label for %s\n",
680 					    lookup_path);
681 
682 			goto out; /* no match, but not an error */
683 		}
684 
685 		goto err;
686 	}
687 
688 	if (flags->progress) {
689 		__pthread_mutex_lock(&progress_mutex);
690 		fc_count++;
691 		if (fc_count % STAR_COUNT == 0) {
692 			if (flags->mass_relabel && efile_count > 0) {
693 				pc = (fc_count < efile_count) ? (100.0 *
694 					     fc_count / efile_count) : 100;
695 				fprintf(stdout, "\r%-.1f%%", (double)pc);
696 			} else {
697 				fprintf(stdout, "\r%" PRIu64 "k", fc_count / STAR_COUNT);
698 			}
699 			fflush(stdout);
700 		}
701 		__pthread_mutex_unlock(&progress_mutex);
702 	}
703 
704 	if (flags->add_assoc) {
705 		rc = filespec_add(stat_buf.st_ino, newcon, pathname, flags);
706 
707 		if (rc < 0) {
708 			selinux_log(SELINUX_ERROR,
709 				    "filespec_add error: %s\n", pathname);
710 			goto out1;
711 		}
712 
713 		if (rc > 0) {
714 			/* Already an association and it took precedence. */
715 			goto out;
716 		}
717 	}
718 
719 	if (flags->log_matches)
720 		selinux_log(SELINUX_INFO, "%s matched by %s\n",
721 			    pathname, newcon);
722 
723 	if (fgetfilecon_raw(fd, &curcon) < 0) {
724 		if (errno != ENODATA)
725 			goto err;
726 
727 		curcon = NULL;
728 	}
729 
730 	if (curcon == NULL || strcmp(curcon, newcon) != 0) {
731 		if (!flags->set_specctx && curcon &&
732 				    (is_context_customizable(curcon) > 0)) {
733 			if (flags->verbose) {
734 				selinux_log(SELINUX_INFO,
735 				 "%s not reset as customized by admin to %s\n",
736 							    pathname, curcon);
737 			}
738 			goto out;
739 		}
740 
741 		if (!flags->set_specctx && curcon) {
742 			/* If types different then update newcon. */
743 			rc = compare_types(curcon, newcon, &newtypecon);
744 			if (rc)
745 				goto err;
746 
747 			if (newtypecon) {
748 				freecon(newcon);
749 				newcon = newtypecon;
750 			} else {
751 				goto out;
752 			}
753 		}
754 
755 		if (!flags->nochange) {
756 			if (fsetfilecon(fd, newcon) < 0)
757 				goto err;
758 			updated = true;
759 		}
760 
761 		if (flags->verbose)
762 			selinux_log(SELINUX_INFO,
763 				    "%s %s from %s to %s\n",
764 				    updated ? "Relabeled" : "Would relabel",
765 				    pathname, curcon, newcon);
766 
767 		if (flags->syslog_changes && !flags->nochange) {
768 			if (curcon)
769 				syslog(LOG_INFO,
770 					    "relabeling %s from %s to %s\n",
771 					    pathname, curcon, newcon);
772 			else
773 				syslog(LOG_INFO, "labeling %s to %s\n",
774 					    pathname, newcon);
775 		}
776 	}
777 
778 out:
779 	rc = 0;
780 out1:
781 	if (fd >= 0)
782 		close(fd);
783 	freecon(curcon);
784 	freecon(newcon);
785 	return rc;
786 err:
787 	selinux_log(SELINUX_ERROR,
788 		    "Could not set context for %s:  %m\n",
789 		    pathname);
790 	rc = -1;
791 	goto out1;
792 }
793 
794 struct dir_hash_node {
795 	char *path;
796 	uint8_t digest[SHA1_HASH_SIZE];
797 	struct dir_hash_node *next;
798 };
799 /*
800  * Returns true if the digest of all partial matched contexts is the same as
801  * the one saved by setxattr. Otherwise returns false and constructs a
802  * dir_hash_node with the newly calculated digest.
803  */
check_context_match_for_dir(const char * pathname,struct dir_hash_node ** new_node,int error)804 static bool check_context_match_for_dir(const char *pathname,
805 					struct dir_hash_node **new_node,
806 					int error)
807 {
808 	bool status;
809 	size_t digest_len = 0;
810 	uint8_t *read_digest = NULL;
811 	uint8_t *calculated_digest = NULL;
812 
813 	if (!new_node)
814 		return false;
815 
816 	*new_node = NULL;
817 
818 	/* status = true if digests match, false otherwise. */
819 	status = selabel_get_digests_all_partial_matches(fc_sehandle, pathname,
820 							 &calculated_digest,
821 							 &read_digest,
822 							 &digest_len);
823 
824 	if (status)
825 		goto free;
826 
827 	/* Save digest of all matched contexts for the current directory. */
828 	if (!error && calculated_digest) {
829 		*new_node = calloc(1, sizeof(struct dir_hash_node));
830 
831 		if (!*new_node)
832 			goto oom;
833 
834 		(*new_node)->path = strdup(pathname);
835 
836 		if (!(*new_node)->path) {
837 			free(*new_node);
838 			*new_node = NULL;
839 			goto oom;
840 		}
841 		memcpy((*new_node)->digest, calculated_digest, digest_len);
842 		(*new_node)->next = NULL;
843 	}
844 
845 free:
846 	free(calculated_digest);
847 	free(read_digest);
848 	return status;
849 
850 oom:
851 	selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
852 	goto free;
853 }
854 
855 struct rest_state {
856 	struct rest_flags flags;
857 	dev_t dev_num;
858 	struct statfs sfsb;
859 	bool ignore_digest;
860 	bool setrestorecondigest;
861 	bool parallel;
862 
863 	FTS *fts;
864 	FTSENT *ftsent_first;
865 	struct dir_hash_node *head, *current;
866 	bool abort;
867 	int error;
868 	long unsigned skipped_errors;
869 	int saved_errno;
870 	pthread_mutex_t mutex;
871 };
872 
selinux_restorecon_thread(void * arg)873 static void *selinux_restorecon_thread(void *arg)
874 {
875 	struct rest_state *state = arg;
876 	FTS *fts = state->fts;
877 	FTSENT *ftsent;
878 	int error;
879 	char ent_path[PATH_MAX];
880 	bool first = false;
881 
882 	if (state->parallel)
883 		pthread_mutex_lock(&state->mutex);
884 
885 	if (state->ftsent_first) {
886 		ftsent = state->ftsent_first;
887 		state->ftsent_first = NULL;
888 		first = true;
889 		goto loop_body;
890 	}
891 
892 	while (((void)(errno = 0), ftsent = fts_read(fts)) != NULL) {
893 loop_body:
894 		/* If the FTS_XDEV flag is set and the device is different */
895 		if (state->flags.set_xdev &&
896 		    ftsent->fts_statp->st_dev != state->dev_num)
897 			continue;
898 
899 		switch (ftsent->fts_info) {
900 		case FTS_DC:
901 			selinux_log(SELINUX_ERROR,
902 				    "Directory cycle on %s.\n",
903 				    ftsent->fts_path);
904 			errno = ELOOP;
905 			state->error = -1;
906 			state->abort = true;
907 			goto finish;
908 		case FTS_DP:
909 			continue;
910 		case FTS_DNR:
911 			error = errno;
912 			errno = ftsent->fts_errno;
913 			selinux_log(SELINUX_ERROR,
914 				    "Could not read %s: %m.\n",
915 				    ftsent->fts_path);
916 			errno = error;
917 			fts_set(fts, ftsent, FTS_SKIP);
918 			continue;
919 		case FTS_NS:
920 			error = errno;
921 			errno = ftsent->fts_errno;
922 			selinux_log(SELINUX_ERROR,
923 				    "Could not stat %s: %m.\n",
924 				    ftsent->fts_path);
925 			errno = error;
926 			fts_set(fts, ftsent, FTS_SKIP);
927 			continue;
928 		case FTS_ERR:
929 			error = errno;
930 			errno = ftsent->fts_errno;
931 			selinux_log(SELINUX_ERROR,
932 				    "Error on %s: %m.\n",
933 				    ftsent->fts_path);
934 			errno = error;
935 			fts_set(fts, ftsent, FTS_SKIP);
936 			continue;
937 		case FTS_D:
938 			if (state->sfsb.f_type == SYSFS_MAGIC &&
939 			    !selabel_partial_match(fc_sehandle,
940 			    ftsent->fts_path)) {
941 				fts_set(fts, ftsent, FTS_SKIP);
942 				continue;
943 			}
944 
945 			if (check_excluded(ftsent->fts_path)) {
946 				fts_set(fts, ftsent, FTS_SKIP);
947 				continue;
948 			}
949 
950 			if (state->setrestorecondigest) {
951 				struct dir_hash_node *new_node = NULL;
952 
953 				if (check_context_match_for_dir(ftsent->fts_path,
954 								&new_node,
955 								state->error) &&
956 								!state->ignore_digest) {
957 					selinux_log(SELINUX_INFO,
958 						"Skipping restorecon on directory(%s)\n",
959 						    ftsent->fts_path);
960 					fts_set(fts, ftsent, FTS_SKIP);
961 					continue;
962 				}
963 
964 				if (new_node && !state->error) {
965 					if (!state->current) {
966 						state->current = new_node;
967 						state->head = state->current;
968 					} else {
969 						state->current->next = new_node;
970 						state->current = new_node;
971 					}
972 				}
973 			}
974 			/* fall through */
975 		default:
976 			strcpy(ent_path, ftsent->fts_path);
977 
978 			if (state->parallel)
979 				pthread_mutex_unlock(&state->mutex);
980 
981 			error = restorecon_sb(ent_path, &state->flags,
982 					      first);
983 
984 			if (state->parallel) {
985 				pthread_mutex_lock(&state->mutex);
986 				if (state->abort)
987 					goto unlock;
988 			}
989 
990 			first = false;
991 			if (error) {
992 				if (state->flags.abort_on_error) {
993 					state->error = error;
994 					state->abort = true;
995 					goto finish;
996 				}
997 				if (state->flags.count_errors)
998 					state->skipped_errors++;
999 				else
1000 					state->error = error;
1001 			}
1002 			break;
1003 		}
1004 	}
1005 
1006 finish:
1007 	if (!state->saved_errno)
1008 		state->saved_errno = errno;
1009 unlock:
1010 	if (state->parallel)
1011 		pthread_mutex_unlock(&state->mutex);
1012 	return NULL;
1013 }
1014 
selinux_restorecon_common(const char * pathname_orig,unsigned int restorecon_flags,size_t nthreads)1015 static int selinux_restorecon_common(const char *pathname_orig,
1016 				     unsigned int restorecon_flags,
1017 				     size_t nthreads)
1018 {
1019 	struct rest_state state;
1020 
1021 	state.flags.nochange = (restorecon_flags &
1022 		    SELINUX_RESTORECON_NOCHANGE) ? true : false;
1023 	state.flags.verbose = (restorecon_flags &
1024 		    SELINUX_RESTORECON_VERBOSE) ? true : false;
1025 	state.flags.progress = (restorecon_flags &
1026 		    SELINUX_RESTORECON_PROGRESS) ? true : false;
1027 	state.flags.mass_relabel = (restorecon_flags &
1028 		    SELINUX_RESTORECON_MASS_RELABEL) ? true : false;
1029 	state.flags.recurse = (restorecon_flags &
1030 		    SELINUX_RESTORECON_RECURSE) ? true : false;
1031 	state.flags.set_specctx = (restorecon_flags &
1032 		    SELINUX_RESTORECON_SET_SPECFILE_CTX) ? true : false;
1033 	state.flags.userealpath = (restorecon_flags &
1034 		   SELINUX_RESTORECON_REALPATH) ? true : false;
1035 	state.flags.set_xdev = (restorecon_flags &
1036 		   SELINUX_RESTORECON_XDEV) ? true : false;
1037 	state.flags.add_assoc = (restorecon_flags &
1038 		   SELINUX_RESTORECON_ADD_ASSOC) ? true : false;
1039 	state.flags.abort_on_error = (restorecon_flags &
1040 		   SELINUX_RESTORECON_ABORT_ON_ERROR) ? true : false;
1041 	state.flags.syslog_changes = (restorecon_flags &
1042 		   SELINUX_RESTORECON_SYSLOG_CHANGES) ? true : false;
1043 	state.flags.log_matches = (restorecon_flags &
1044 		   SELINUX_RESTORECON_LOG_MATCHES) ? true : false;
1045 	state.flags.ignore_noent = (restorecon_flags &
1046 		   SELINUX_RESTORECON_IGNORE_NOENTRY) ? true : false;
1047 	state.flags.warnonnomatch = true;
1048 	state.flags.conflicterror = (restorecon_flags &
1049 		   SELINUX_RESTORECON_CONFLICT_ERROR) ? true : false;
1050 	ignore_mounts = (restorecon_flags &
1051 		   SELINUX_RESTORECON_IGNORE_MOUNTS) ? true : false;
1052 	state.ignore_digest = (restorecon_flags &
1053 		    SELINUX_RESTORECON_IGNORE_DIGEST) ? true : false;
1054 	state.flags.count_errors = (restorecon_flags &
1055 		    SELINUX_RESTORECON_COUNT_ERRORS) ? true : false;
1056 	state.setrestorecondigest = true;
1057 
1058 	state.head = NULL;
1059 	state.current = NULL;
1060 	state.abort = false;
1061 	state.error = 0;
1062 	state.skipped_errors = 0;
1063 	state.saved_errno = 0;
1064 
1065 	struct stat sb;
1066 	char *pathname = NULL, *pathdnamer = NULL, *pathdname, *pathbname;
1067 	char *paths[2] = { NULL, NULL };
1068 	int fts_flags, error;
1069 	struct dir_hash_node *current = NULL;
1070 
1071 	if (state.flags.verbose && state.flags.progress)
1072 		state.flags.verbose = false;
1073 
1074 	__selinux_once(fc_once, restorecon_init);
1075 
1076 	if (!fc_sehandle)
1077 		return -1;
1078 
1079 	/*
1080 	 * If selabel_no_digest = true then no digest has been requested by
1081 	 * an external selabel_open(3) call.
1082 	 */
1083 	if (selabel_no_digest ||
1084 	    (restorecon_flags & SELINUX_RESTORECON_SKIP_DIGEST))
1085 		state.setrestorecondigest = false;
1086 
1087 	if (!__pthread_supported) {
1088 		if (nthreads != 1) {
1089 			nthreads = 1;
1090 			selinux_log(SELINUX_WARNING,
1091 				"Threading functionality not available, falling back to 1 thread.");
1092 		}
1093 	} else if (nthreads == 0) {
1094 		long nproc = sysconf(_SC_NPROCESSORS_ONLN);
1095 
1096 		if (nproc > 0) {
1097 			nthreads = nproc;
1098 		} else {
1099 			nthreads = 1;
1100 			selinux_log(SELINUX_WARNING,
1101 				"Unable to detect CPU count, falling back to 1 thread.");
1102 		}
1103 	}
1104 
1105 	/*
1106 	 * Convert passed-in pathname to canonical pathname by resolving
1107 	 * realpath of containing dir, then appending last component name.
1108 	 */
1109 	if (state.flags.userealpath) {
1110 		char *basename_cpy = strdup(pathname_orig);
1111 		if (!basename_cpy)
1112 			goto realpatherr;
1113 		pathbname = basename(basename_cpy);
1114 		if (!strcmp(pathbname, "/") || !strcmp(pathbname, ".") ||
1115 					    !strcmp(pathbname, "..")) {
1116 			pathname = realpath(pathname_orig, NULL);
1117 			if (!pathname) {
1118 				free(basename_cpy);
1119 				goto realpatherr;
1120 			}
1121 		} else {
1122 			char *dirname_cpy = strdup(pathname_orig);
1123 			if (!dirname_cpy) {
1124 				free(basename_cpy);
1125 				goto realpatherr;
1126 			}
1127 			pathdname = dirname(dirname_cpy);
1128 			pathdnamer = realpath(pathdname, NULL);
1129 			free(dirname_cpy);
1130 			if (!pathdnamer) {
1131 				free(basename_cpy);
1132 				goto realpatherr;
1133 			}
1134 			if (!strcmp(pathdnamer, "/"))
1135 				error = asprintf(&pathname, "/%s", pathbname);
1136 			else
1137 				error = asprintf(&pathname, "%s/%s",
1138 						    pathdnamer, pathbname);
1139 			if (error < 0) {
1140 				free(basename_cpy);
1141 				goto oom;
1142 			}
1143 		}
1144 		free(basename_cpy);
1145 	} else {
1146 		pathname = strdup(pathname_orig);
1147 		if (!pathname)
1148 			goto oom;
1149 	}
1150 
1151 	paths[0] = pathname;
1152 
1153 	if (lstat(pathname, &sb) < 0) {
1154 		if (state.flags.ignore_noent && errno == ENOENT) {
1155 			free(pathdnamer);
1156 			free(pathname);
1157 			return 0;
1158 		} else {
1159 			selinux_log(SELINUX_ERROR,
1160 				    "lstat(%s) failed: %m\n",
1161 				    pathname);
1162 			error = -1;
1163 			goto cleanup;
1164 		}
1165 	}
1166 
1167 	/* Skip digest if not a directory */
1168 	if (!S_ISDIR(sb.st_mode))
1169 		state.setrestorecondigest = false;
1170 
1171 	if (!state.flags.recurse) {
1172 		if (check_excluded(pathname)) {
1173 			error = 0;
1174 			goto cleanup;
1175 		}
1176 
1177 		error = restorecon_sb(pathname, &state.flags, true);
1178 		goto cleanup;
1179 	}
1180 
1181 	/* Obtain fs type */
1182 	memset(&state.sfsb, 0, sizeof(state.sfsb));
1183 	if (!S_ISLNK(sb.st_mode) && statfs(pathname, &state.sfsb) < 0) {
1184 		selinux_log(SELINUX_ERROR,
1185 			    "statfs(%s) failed: %m\n",
1186 			    pathname);
1187 		error = -1;
1188 		goto cleanup;
1189 	}
1190 
1191 	/* Skip digest on in-memory filesystems and /sys */
1192 	if (state.sfsb.f_type == RAMFS_MAGIC || state.sfsb.f_type == TMPFS_MAGIC ||
1193 	    state.sfsb.f_type == SYSFS_MAGIC)
1194 		state.setrestorecondigest = false;
1195 
1196 	if (state.flags.set_xdev)
1197 		fts_flags = FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV;
1198 	else
1199 		fts_flags = FTS_PHYSICAL | FTS_NOCHDIR;
1200 
1201 	state.fts = fts_open(paths, fts_flags, NULL);
1202 	if (!state.fts)
1203 		goto fts_err;
1204 
1205 	state.ftsent_first = fts_read(state.fts);
1206 	if (!state.ftsent_first)
1207 		goto fts_err;
1208 
1209 	/*
1210 	 * Keep the inode of the first device. This is because the FTS_XDEV
1211 	 * flag tells fts not to descend into directories with different
1212 	 * device numbers, but fts will still give back the actual directory.
1213 	 * By saving the device number of the directory that was passed to
1214 	 * selinux_restorecon() and then skipping all actions on any
1215 	 * directories with a different device number when the FTS_XDEV flag
1216 	 * is set (from http://marc.info/?l=selinux&m=124688830500777&w=2).
1217 	 */
1218 	state.dev_num = state.ftsent_first->fts_statp->st_dev;
1219 
1220 	if (nthreads == 1) {
1221 		state.parallel = false;
1222 		selinux_restorecon_thread(&state);
1223 	} else {
1224 		size_t i;
1225 		pthread_t self = pthread_self();
1226 		pthread_t *threads = NULL;
1227 
1228 		pthread_mutex_init(&state.mutex, NULL);
1229 
1230 		threads = calloc(nthreads - 1, sizeof(*threads));
1231 		if (!threads)
1232 			goto oom;
1233 
1234 		state.parallel = true;
1235 		/*
1236 		 * Start (nthreads - 1) threads - the main thread is going to
1237 		 * take part, too.
1238 		 */
1239 		for (i = 0; i < nthreads - 1; i++) {
1240 			if (pthread_create(&threads[i], NULL,
1241 					   selinux_restorecon_thread, &state)) {
1242 				/*
1243 				 * If any thread fails to be created, just mark
1244 				 * it as such and let the successfully created
1245 				 * threads do the job. In the worst case the
1246 				 * main thread will do everything, but that's
1247 				 * still better than to give up.
1248 				 */
1249 				threads[i] = self;
1250 			}
1251 		}
1252 
1253 		/* Let's join in on the fun! */
1254 		selinux_restorecon_thread(&state);
1255 
1256 		/* Now wait for all threads to finish. */
1257 		for (i = 0; i < nthreads - 1; i++) {
1258 			/* Skip threads that failed to be created. */
1259 			if (pthread_equal(threads[i], self))
1260 				continue;
1261 			pthread_join(threads[i], NULL);
1262 		}
1263 		free(threads);
1264 
1265 		pthread_mutex_destroy(&state.mutex);
1266 	}
1267 
1268 	error = state.error;
1269 	if (state.saved_errno)
1270 		goto out;
1271 
1272 	/*
1273 	 * Labeling successful. Write partial match digests for subdirectories.
1274 	 * TODO: Write digest upon FTS_DP if no error occurs in its descents.
1275 	 * Note: we can't ignore errors here that we've masked due to
1276 	 * SELINUX_RESTORECON_COUNT_ERRORS.
1277 	 */
1278 	if (state.setrestorecondigest && !state.flags.nochange && !error &&
1279 	    state.skipped_errors == 0) {
1280 		current = state.head;
1281 		while (current != NULL) {
1282 			if (setxattr(current->path,
1283 			    RESTORECON_PARTIAL_MATCH_DIGEST,
1284 			    current->digest,
1285 			    SHA1_HASH_SIZE, 0) < 0) {
1286 				selinux_log(SELINUX_ERROR,
1287 					    "setxattr failed: %s: %m\n",
1288 					    current->path);
1289 			}
1290 			current = current->next;
1291 		}
1292 	}
1293 
1294 	skipped_errors = state.skipped_errors;
1295 
1296 out:
1297 	if (state.flags.progress && state.flags.mass_relabel)
1298 		fprintf(stdout, "\r%s 100.0%%\n", pathname);
1299 
1300 	(void) fts_close(state.fts);
1301 	errno = state.saved_errno;
1302 cleanup:
1303 	if (state.flags.add_assoc) {
1304 		if (state.flags.verbose)
1305 			filespec_eval();
1306 		filespec_destroy();
1307 	}
1308 	free(pathdnamer);
1309 	free(pathname);
1310 
1311 	current = state.head;
1312 	while (current != NULL) {
1313 		struct dir_hash_node *next = current->next;
1314 
1315 		free(current->path);
1316 		free(current);
1317 		current = next;
1318 	}
1319 	return error;
1320 
1321 oom:
1322 	selinux_log(SELINUX_ERROR, "%s:  Out of memory\n", __func__);
1323 	error = -1;
1324 	goto cleanup;
1325 
1326 realpatherr:
1327 	selinux_log(SELINUX_ERROR,
1328 		    "SELinux: Could not get canonical path for %s restorecon: %m.\n",
1329 		    pathname_orig);
1330 	error = -1;
1331 	goto cleanup;
1332 
1333 fts_err:
1334 	selinux_log(SELINUX_ERROR,
1335 		    "fts error while labeling %s: %m\n",
1336 		    paths[0]);
1337 	error = -1;
1338 	goto cleanup;
1339 }
1340 
1341 
1342 /*
1343  * Public API
1344  */
1345 
1346 /* selinux_restorecon(3) - Main function that is responsible for labeling */
selinux_restorecon(const char * pathname_orig,unsigned int restorecon_flags)1347 int selinux_restorecon(const char *pathname_orig,
1348 		       unsigned int restorecon_flags)
1349 {
1350 	return selinux_restorecon_common(pathname_orig, restorecon_flags, 1);
1351 }
1352 
1353 /* selinux_restorecon_parallel(3) - Parallel version of selinux_restorecon(3) */
selinux_restorecon_parallel(const char * pathname_orig,unsigned int restorecon_flags,size_t nthreads)1354 int selinux_restorecon_parallel(const char *pathname_orig,
1355 				unsigned int restorecon_flags,
1356 				size_t nthreads)
1357 {
1358 	return selinux_restorecon_common(pathname_orig, restorecon_flags, nthreads);
1359 }
1360 
1361 /* selinux_restorecon_set_sehandle(3) is called to set the global fc handle */
selinux_restorecon_set_sehandle(struct selabel_handle * hndl)1362 void selinux_restorecon_set_sehandle(struct selabel_handle *hndl)
1363 {
1364 	char **specfiles;
1365 	unsigned char *fc_digest;
1366 	size_t num_specfiles, fc_digest_len;
1367 
1368 	fc_sehandle = hndl;
1369 	if (!fc_sehandle)
1370 		return;
1371 
1372 	/* Check if digest requested in selabel_open(3), if so use it. */
1373 	if (selabel_digest(fc_sehandle, &fc_digest, &fc_digest_len,
1374 				   &specfiles, &num_specfiles) < 0)
1375 		selabel_no_digest = true;
1376 	else
1377 		selabel_no_digest = false;
1378 }
1379 
1380 
1381 /*
1382  * selinux_restorecon_default_handle(3) is called to set the global restorecon
1383  * handle by a process if the default params are required.
1384  */
selinux_restorecon_default_handle(void)1385 struct selabel_handle *selinux_restorecon_default_handle(void)
1386 {
1387 	struct selabel_handle *sehandle;
1388 
1389 	struct selinux_opt fc_opts[] = {
1390 		{ SELABEL_OPT_DIGEST, (char *)1 }
1391 	};
1392 
1393 	sehandle = selabel_open(SELABEL_CTX_FILE, fc_opts, 1);
1394 
1395 	if (!sehandle) {
1396 		selinux_log(SELINUX_ERROR,
1397 			    "Error obtaining file context handle: %m\n");
1398 		return NULL;
1399 	}
1400 
1401 	selabel_no_digest = false;
1402 	return sehandle;
1403 }
1404 
1405 /*
1406  * selinux_restorecon_set_exclude_list(3) is called to add additional entries
1407  * to be excluded from labeling checks.
1408  */
selinux_restorecon_set_exclude_list(const char ** exclude_list)1409 void selinux_restorecon_set_exclude_list(const char **exclude_list)
1410 {
1411 	int i;
1412 	struct stat sb;
1413 
1414 	for (i = 0; exclude_list[i]; i++) {
1415 		if (lstat(exclude_list[i], &sb) < 0 && errno != EACCES) {
1416 			selinux_log(SELINUX_ERROR,
1417 				    "lstat error on exclude path \"%s\", %m - ignoring.\n",
1418 				    exclude_list[i]);
1419 			break;
1420 		}
1421 		if (add_exclude(exclude_list[i], CALLER_EXCLUDED) &&
1422 		    errno == ENOMEM)
1423 			assert(0);
1424 	}
1425 }
1426 
1427 /* selinux_restorecon_set_alt_rootpath(3) sets an alternate rootpath. */
selinux_restorecon_set_alt_rootpath(const char * alt_rootpath)1428 int selinux_restorecon_set_alt_rootpath(const char *alt_rootpath)
1429 {
1430 	size_t len;
1431 
1432 	/* This should be NULL on first use */
1433 	if (rootpath)
1434 		free(rootpath);
1435 
1436 	rootpath = strdup(alt_rootpath);
1437 	if (!rootpath) {
1438 		selinux_log(SELINUX_ERROR, "%s:  Out of memory\n", __func__);
1439 		return -1;
1440 	}
1441 
1442 	/* trim trailing /, if present */
1443 	len = strlen(rootpath);
1444 	while (len && (rootpath[len - 1] == '/'))
1445 		rootpath[--len] = '\0';
1446 	rootpathlen = len;
1447 
1448 	return 0;
1449 }
1450 
1451 /* selinux_restorecon_xattr(3)
1452  * Find RESTORECON_PARTIAL_MATCH_DIGEST entries.
1453  */
selinux_restorecon_xattr(const char * pathname,unsigned int xattr_flags,struct dir_xattr *** xattr_list)1454 int selinux_restorecon_xattr(const char *pathname, unsigned int xattr_flags,
1455 			     struct dir_xattr ***xattr_list)
1456 {
1457 	bool recurse = (xattr_flags &
1458 	    SELINUX_RESTORECON_XATTR_RECURSE) ? true : false;
1459 	bool delete_nonmatch = (xattr_flags &
1460 	    SELINUX_RESTORECON_XATTR_DELETE_NONMATCH_DIGESTS) ? true : false;
1461 	bool delete_all = (xattr_flags &
1462 	    SELINUX_RESTORECON_XATTR_DELETE_ALL_DIGESTS) ? true : false;
1463 	ignore_mounts = (xattr_flags &
1464 	   SELINUX_RESTORECON_XATTR_IGNORE_MOUNTS) ? true : false;
1465 
1466 	int rc, fts_flags;
1467 	struct stat sb;
1468 	struct statfs sfsb;
1469 	struct dir_xattr *current, *next;
1470 	FTS *fts;
1471 	FTSENT *ftsent;
1472 	char *paths[2] = { NULL, NULL };
1473 
1474 	__selinux_once(fc_once, restorecon_init);
1475 
1476 	if (!fc_sehandle)
1477 		return -1;
1478 
1479 	if (lstat(pathname, &sb) < 0) {
1480 		if (errno == ENOENT)
1481 			return 0;
1482 
1483 		selinux_log(SELINUX_ERROR,
1484 			    "lstat(%s) failed: %m\n",
1485 			    pathname);
1486 		return -1;
1487 	}
1488 
1489 	if (!recurse) {
1490 		if (statfs(pathname, &sfsb) == 0) {
1491 			if (sfsb.f_type == RAMFS_MAGIC ||
1492 			    sfsb.f_type == TMPFS_MAGIC)
1493 				return 0;
1494 		}
1495 
1496 		if (check_excluded(pathname))
1497 			return 0;
1498 
1499 		rc = add_xattr_entry(pathname, delete_nonmatch, delete_all);
1500 
1501 		if (!rc && dir_xattr_list)
1502 			*xattr_list = &dir_xattr_list;
1503 		else if (rc == -1)
1504 			return rc;
1505 
1506 		return 0;
1507 	}
1508 
1509 	paths[0] = (char *)pathname;
1510 	fts_flags = FTS_PHYSICAL | FTS_NOCHDIR;
1511 
1512 	fts = fts_open(paths, fts_flags, NULL);
1513 	if (!fts) {
1514 		selinux_log(SELINUX_ERROR,
1515 			    "fts error on %s: %m\n",
1516 			    paths[0]);
1517 		return -1;
1518 	}
1519 
1520 	while ((ftsent = fts_read(fts)) != NULL) {
1521 		switch (ftsent->fts_info) {
1522 		case FTS_DP:
1523 			continue;
1524 		case FTS_D:
1525 			if (statfs(ftsent->fts_path, &sfsb) == 0) {
1526 				if (sfsb.f_type == RAMFS_MAGIC ||
1527 				    sfsb.f_type == TMPFS_MAGIC)
1528 					continue;
1529 			}
1530 			if (check_excluded(ftsent->fts_path)) {
1531 				fts_set(fts, ftsent, FTS_SKIP);
1532 				continue;
1533 			}
1534 
1535 			rc = add_xattr_entry(ftsent->fts_path,
1536 					     delete_nonmatch, delete_all);
1537 			if (rc == 1)
1538 				continue;
1539 			else if (rc == -1)
1540 				goto cleanup;
1541 			break;
1542 		default:
1543 			break;
1544 		}
1545 	}
1546 
1547 	if (dir_xattr_list)
1548 		*xattr_list = &dir_xattr_list;
1549 
1550 	(void) fts_close(fts);
1551 	return 0;
1552 
1553 cleanup:
1554 	rc = errno;
1555 	(void) fts_close(fts);
1556 	errno = rc;
1557 
1558 	if (dir_xattr_list) {
1559 		/* Free any used memory */
1560 		current = dir_xattr_list;
1561 		while (current) {
1562 			next = current->next;
1563 			free(current->directory);
1564 			free(current->digest);
1565 			free(current);
1566 			current = next;
1567 		}
1568 	}
1569 	return -1;
1570 }
1571 
selinux_restorecon_get_skipped_errors(void)1572 long unsigned selinux_restorecon_get_skipped_errors(void)
1573 {
1574 	return skipped_errors;
1575 }
1576