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