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