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