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