1 #include "restore.h"
2 #include <glob.h>
3 #include <selinux/context.h>
4 
5 #define SKIP -2
6 #define ERR -1
7 #define MAX_EXCLUDES 1000
8 
9 /*
10  * The hash table of associations, hashed by inode number.
11  * Chaining is used for collisions, with elements ordered
12  * by inode number in each bucket.  Each hash bucket has a dummy
13  * header.
14  */
15 #define HASH_BITS 16
16 #define HASH_BUCKETS (1 << HASH_BITS)
17 #define HASH_MASK (HASH_BUCKETS-1)
18 
19 /*
20  * An association between an inode and a context.
21  */
22 typedef struct file_spec {
23 	ino_t ino;		/* inode number */
24 	char *con;		/* matched context */
25 	char *file;		/* full pathname */
26 	struct file_spec *next;	/* next association in hash bucket chain */
27 } file_spec_t;
28 
29 struct edir {
30 	char *directory;
31 	size_t size;
32 };
33 
34 
35 static file_spec_t *fl_head;
36 static int filespec_add(ino_t ino, const security_context_t con, const char *file);
37 struct restore_opts *r_opts = NULL;
38 static void filespec_destroy(void);
39 static void filespec_eval(void);
40 static int excludeCtr = 0;
41 static struct edir excludeArray[MAX_EXCLUDES];
42 
remove_exclude(const char * directory)43 void remove_exclude(const char *directory)
44 {
45 	int i = 0;
46 	for (i = 0; i < excludeCtr; i++) {
47 		if (strcmp(directory, excludeArray[i].directory) == 0) {
48 			free(excludeArray[i].directory);
49 			if (i != excludeCtr-1)
50 				excludeArray[i] = excludeArray[excludeCtr-1];
51 			excludeCtr--;
52 			return;
53 		}
54 	}
55 	return;
56 }
57 
restore_init(struct restore_opts * opts)58 void restore_init(struct restore_opts *opts)
59 {
60 	r_opts = opts;
61 	struct selinux_opt selinux_opts[] = {
62 		{ SELABEL_OPT_VALIDATE, r_opts->selabel_opt_validate },
63 		{ SELABEL_OPT_PATH, r_opts->selabel_opt_path }
64 	};
65 	r_opts->hnd = selabel_open(SELABEL_CTX_FILE, selinux_opts, 2);
66 	if (!r_opts->hnd) {
67 		perror(r_opts->selabel_opt_path);
68 		exit(1);
69 	}
70 }
71 
restore_finish()72 void restore_finish()
73 {
74 	int i;
75 	for (i = 0; i < excludeCtr; i++) {
76 		free(excludeArray[i].directory);
77 	}
78 }
79 
match(const char * name,struct stat * sb,char ** con)80 static int match(const char *name, struct stat *sb, char **con)
81 {
82 	if (!(r_opts->hard_links) && !S_ISDIR(sb->st_mode) && (sb->st_nlink > 1)) {
83 		fprintf(stderr, "Warning! %s refers to a file with more than one hard link, not fixing hard links.\n",
84 					name);
85 		return -1;
86 	}
87 
88 	if (NULL != r_opts->rootpath) {
89 		if (0 != strncmp(r_opts->rootpath, name, r_opts->rootpathlen)) {
90 			fprintf(stderr, "%s:  %s is not located in %s\n",
91 				r_opts->progname, name, r_opts->rootpath);
92 			return -1;
93 		}
94 		name += r_opts->rootpathlen;
95 	}
96 
97 	if (r_opts->rootpath != NULL && name[0] == '\0')
98 		/* this is actually the root dir of the alt root */
99 		return selabel_lookup_raw(r_opts->hnd, con, "/", sb->st_mode);
100 	else
101 		return selabel_lookup_raw(r_opts->hnd, con, name, sb->st_mode);
102 }
restore(FTSENT * ftsent,int recurse)103 static int restore(FTSENT *ftsent, int recurse)
104 {
105 	char *my_file = strdupa(ftsent->fts_path);
106 	int ret = -1;
107 	security_context_t curcon = NULL, newcon = NULL;
108 	float progress;
109 	if (match(my_file, ftsent->fts_statp, &newcon) < 0) {
110 		if ((errno == ENOENT) && ((!recurse) || (r_opts->verbose)))
111 			fprintf(stderr, "%s:  Warning no default label for %s\n", r_opts->progname, my_file);
112 
113 		/* Check for no matching specification. */
114 		return (errno == ENOENT) ? 0 : -1;
115 	}
116 
117 	if (r_opts->progress) {
118 		r_opts->count++;
119 		if (r_opts->count % STAR_COUNT == 0) {
120 			if (r_opts->progress == 1) {
121 				fprintf(stdout, "\r%luk", (size_t) r_opts->count / STAR_COUNT );
122 			} else {
123 				if (r_opts->nfile > 0) {
124 					progress = (r_opts->count < r_opts->nfile) ? (100.0 * r_opts->count / r_opts->nfile) : 100;
125 					fprintf(stdout, "\r%-.1f%%", progress);
126 				}
127 			}
128 			fflush(stdout);
129 		}
130 	}
131 
132 	/*
133 	 * Try to add an association between this inode and
134 	 * this specification.  If there is already an association
135 	 * for this inode and it conflicts with this specification,
136 	 * then use the last matching specification.
137 	 */
138 	if (r_opts->add_assoc) {
139 		ret = filespec_add(ftsent->fts_statp->st_ino, newcon, my_file);
140 		if (ret < 0)
141 			goto err;
142 
143 		if (ret > 0)
144 			/* There was already an association and it took precedence. */
145 			goto out;
146 	}
147 
148 	if (r_opts->debug) {
149 		printf("%s:  %s matched by %s\n", r_opts->progname, my_file, newcon);
150 	}
151 
152 	/*
153 	 * Do not relabel if their is no default specification for this file
154 	 */
155 
156 	if (strcmp(newcon, "<<none>>") == 0) {
157 		goto out;
158 	}
159 
160 	/* Get the current context of the file. */
161 	ret = lgetfilecon_raw(ftsent->fts_accpath, &curcon);
162 	if (ret < 0) {
163 		if (errno == ENODATA) {
164 			curcon = NULL;
165 		} else {
166 			fprintf(stderr, "%s get context on %s failed: '%s'\n",
167 				r_opts->progname, my_file, strerror(errno));
168 			goto err;
169 		}
170 	}
171 
172 	/* lgetfilecon returns number of characters and ret needs to be reset
173 	 * to 0.
174 	 */
175 	ret = 0;
176 
177 	/*
178 	 * Do not relabel the file if the file is already labeled according to
179 	 * the specification.
180 	 */
181 	if (curcon && (strcmp(curcon, newcon) == 0)) {
182 		goto out;
183 	}
184 
185 	if (!r_opts->force && curcon && (is_context_customizable(curcon) > 0)) {
186 		if (r_opts->verbose > 1) {
187 			fprintf(stderr,
188 				"%s: %s not reset customized by admin to %s\n",
189 				r_opts->progname, my_file, curcon);
190 		}
191 		goto out;
192 	}
193 
194 	/*
195 	 *  Do not change label unless this is a force or the type is different
196 	 */
197 	if (!r_opts->force && curcon) {
198 		int types_differ = 0;
199 		context_t cona;
200 		context_t conb;
201 		int err = 0;
202 		cona = context_new(curcon);
203 		if (! cona) {
204 			goto out;
205 		}
206 		conb = context_new(newcon);
207 		if (! conb) {
208 			context_free(cona);
209 			goto out;
210 		}
211 
212 		types_differ = strcmp(context_type_get(cona), context_type_get(conb));
213 		if (types_differ) {
214 			err |= context_user_set(conb, context_user_get(cona));
215 			err |= context_role_set(conb, context_role_get(cona));
216 			err |= context_range_set(conb, context_range_get(cona));
217 			if (!err) {
218 				freecon(newcon);
219 				newcon = strdup(context_str(conb));
220 			}
221 		}
222 		context_free(cona);
223 		context_free(conb);
224 
225 		if (!types_differ || err) {
226 			goto out;
227 		}
228 	}
229 
230 	if (r_opts->verbose) {
231 		printf("%s reset %s context %s->%s\n",
232 		       r_opts->progname, my_file, curcon ?: "", newcon);
233 	}
234 
235 	if (r_opts->logging && r_opts->change) {
236 		if (curcon)
237 			syslog(LOG_INFO, "relabeling %s from %s to %s\n",
238 			       my_file, curcon, newcon);
239 		else
240 			syslog(LOG_INFO, "labeling %s to %s\n",
241 			       my_file, newcon);
242 	}
243 
244 	if (r_opts->outfile)
245 		fprintf(r_opts->outfile, "%s\n", my_file);
246 
247 	/*
248 	 * Do not relabel the file if -n was used.
249 	 */
250 	if (!r_opts->change)
251 		goto out;
252 
253 	/*
254 	 * Relabel the file to the specified context.
255 	 */
256 	ret = lsetfilecon(ftsent->fts_accpath, newcon);
257 	if (ret) {
258 		fprintf(stderr, "%s set context %s->%s failed:'%s'\n",
259 			r_opts->progname, my_file, newcon, strerror(errno));
260 		goto skip;
261 	}
262 	ret = 0;
263 out:
264 	freecon(curcon);
265 	freecon(newcon);
266 	return ret;
267 skip:
268 	freecon(curcon);
269 	freecon(newcon);
270 	return SKIP;
271 err:
272 	freecon(curcon);
273 	freecon(newcon);
274 	return ERR;
275 }
276 /*
277  * Apply the last matching specification to a file.
278  * This function is called by fts on each file during
279  * the directory traversal.
280  */
apply_spec(FTSENT * ftsent,int recurse)281 static int apply_spec(FTSENT *ftsent, int recurse)
282 {
283 	if (ftsent->fts_info == FTS_DNR) {
284 		fprintf(stderr, "%s:  unable to read directory %s\n",
285 			r_opts->progname, ftsent->fts_path);
286 		return SKIP;
287 	}
288 
289 	int rc = restore(ftsent, recurse);
290 	if (rc == ERR) {
291 		if (!r_opts->abort_on_error)
292 			return SKIP;
293 	}
294 	return rc;
295 }
296 
297 #include <sys/statvfs.h>
298 
process_one(char * name,int recurse_this_path)299 static int process_one(char *name, int recurse_this_path)
300 {
301 	int rc = 0;
302 	const char *namelist[2] = {name, NULL};
303 	dev_t dev_num = 0;
304 	FTS *fts_handle = NULL;
305 	FTSENT *ftsent = NULL;
306 
307 	if (r_opts == NULL){
308 		fprintf(stderr,
309 			"Must call initialize first!");
310 		goto err;
311 	}
312 
313 	fts_handle = fts_open((char **)namelist, r_opts->fts_flags, NULL);
314 	if (fts_handle  == NULL) {
315 		fprintf(stderr,
316 			"%s: error while labeling %s:  %s\n",
317 			r_opts->progname, namelist[0], strerror(errno));
318 		goto err;
319 	}
320 
321 
322 	ftsent = fts_read(fts_handle);
323 	if (ftsent == NULL) {
324 		fprintf(stderr,
325 			"%s: error while labeling %s:  %s\n",
326 			r_opts->progname, namelist[0], strerror(errno));
327 		goto err;
328 	}
329 
330 	/* Keep the inode of the first one. */
331 	dev_num = ftsent->fts_statp->st_dev;
332 
333 	do {
334 		rc = 0;
335 		/* Skip the post order nodes. */
336 		if (ftsent->fts_info == FTS_DP)
337 			continue;
338 		/* If the XDEV flag is set and the device is different */
339 		if (ftsent->fts_statp->st_dev != dev_num &&
340 		    FTS_XDEV == (r_opts->fts_flags & FTS_XDEV))
341 			continue;
342 		if (excludeCtr > 0) {
343 			if (exclude(ftsent->fts_path)) {
344 				fts_set(fts_handle, ftsent, FTS_SKIP);
345 				continue;
346 			}
347 		}
348 
349 		rc = apply_spec(ftsent, recurse_this_path);
350 		if (rc == SKIP)
351 			fts_set(fts_handle, ftsent, FTS_SKIP);
352 		if (rc == ERR)
353 			goto err;
354 		if (!recurse_this_path)
355 			break;
356 	} while ((ftsent = fts_read(fts_handle)) != NULL);
357 
358 out:
359 	if (r_opts->add_assoc) {
360 		if (!r_opts->quiet)
361 			filespec_eval();
362 		filespec_destroy();
363 	}
364 	if (fts_handle)
365 		fts_close(fts_handle);
366 	return rc;
367 
368 err:
369 	rc = -1;
370 	goto out;
371 }
372 
process_glob(char * name,int recurse)373 int process_glob(char *name, int recurse) {
374 	glob_t globbuf;
375 	size_t i = 0;
376 	int errors;
377 	memset(&globbuf, 0, sizeof(globbuf));
378 	errors = glob(name, GLOB_TILDE | GLOB_PERIOD | GLOB_NOCHECK | GLOB_BRACE, NULL, &globbuf);
379 	if (errors)
380 		return errors;
381 
382 	for (i = 0; i < globbuf.gl_pathc; i++) {
383 		int len = strlen(globbuf.gl_pathv[i]) -2;
384 		if (len > 0 && strcmp(&globbuf.gl_pathv[i][len--], "/.") == 0)
385 			continue;
386 		if (len > 0 && strcmp(&globbuf.gl_pathv[i][len], "/..") == 0)
387 			continue;
388 		int rc = process_one_realpath(globbuf.gl_pathv[i], recurse);
389 		if (rc < 0)
390 			errors = rc;
391 	}
392 	globfree(&globbuf);
393 	return errors;
394 }
395 
process_one_realpath(char * name,int recurse)396 int process_one_realpath(char *name, int recurse)
397 {
398 	int rc = 0;
399 	char *p;
400 	struct stat64 sb;
401 
402 	if (r_opts == NULL){
403 		fprintf(stderr,
404 			"Must call initialize first!");
405 		return -1;
406 	}
407 
408 	if (!r_opts->expand_realpath) {
409 		return process_one(name, recurse);
410 	} else {
411 		rc = lstat64(name, &sb);
412 		if (rc < 0) {
413 			if (r_opts->ignore_enoent && errno == ENOENT)
414 				return 0;
415 			fprintf(stderr, "%s:  lstat(%s) failed:  %s\n",
416 				r_opts->progname, name,	strerror(errno));
417 			return -1;
418 		}
419 
420 		if (S_ISLNK(sb.st_mode)) {
421 			char path[PATH_MAX + 1];
422 
423 			rc = realpath_not_final(name, path);
424 			if (rc < 0)
425 				return rc;
426 			rc = process_one(path, 0);
427 			if (rc < 0)
428 				return rc;
429 
430 			p = realpath(name, NULL);
431 			if (p) {
432 				rc = process_one(p, recurse);
433 				free(p);
434 			}
435 			return rc;
436 		} else {
437 			p = realpath(name, NULL);
438 			if (!p) {
439 				fprintf(stderr, "realpath(%s) failed %s\n", name,
440 					strerror(errno));
441 				return -1;
442 			}
443 			rc = process_one(p, recurse);
444 			free(p);
445 			return rc;
446 		}
447 	}
448 }
449 
exclude(const char * file)450 int exclude(const char *file)
451 {
452 	int i = 0;
453 	for (i = 0; i < excludeCtr; i++) {
454 		if (strncmp
455 		    (file, excludeArray[i].directory,
456 		     excludeArray[i].size) == 0) {
457 			if (file[excludeArray[i].size] == 0
458 			    || file[excludeArray[i].size] == '/') {
459 				return 1;
460 			}
461 		}
462 	}
463 	return 0;
464 }
465 
add_exclude(const char * directory)466 int add_exclude(const char *directory)
467 {
468 	size_t len = 0;
469 
470 	if (directory == NULL || directory[0] != '/') {
471 		fprintf(stderr, "Full path required for exclude: %s.\n",
472 			directory);
473 		return 1;
474 	}
475 	if (excludeCtr == MAX_EXCLUDES) {
476 		fprintf(stderr, "Maximum excludes %d exceeded.\n",
477 			MAX_EXCLUDES);
478 		return 1;
479 	}
480 
481 	len = strlen(directory);
482 	while (len > 1 && directory[len - 1] == '/') {
483 		len--;
484 	}
485 	excludeArray[excludeCtr].directory = strndup(directory, len);
486 
487 	if (excludeArray[excludeCtr].directory == NULL) {
488 		fprintf(stderr, "Out of memory.\n");
489 		return 1;
490 	}
491 	excludeArray[excludeCtr++].size = len;
492 
493 	return 0;
494 }
495 
496 /*
497  * Evaluate the association hash table distribution.
498  */
filespec_eval(void)499 static void filespec_eval(void)
500 {
501 	file_spec_t *fl;
502 	int h, used, nel, len, longest;
503 
504 	if (!fl_head)
505 		return;
506 
507 	used = 0;
508 	longest = 0;
509 	nel = 0;
510 	for (h = 0; h < HASH_BUCKETS; h++) {
511 		len = 0;
512 		for (fl = fl_head[h].next; fl; fl = fl->next) {
513 			len++;
514 		}
515 		if (len)
516 			used++;
517 		if (len > longest)
518 			longest = len;
519 		nel += len;
520 	}
521 
522 	if (r_opts->verbose > 1)
523 		printf
524 		    ("%s:  hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n",
525 		     __FUNCTION__, nel, used, HASH_BUCKETS, longest);
526 }
527 
528 /*
529  * Destroy the association hash table.
530  */
filespec_destroy(void)531 static void filespec_destroy(void)
532 {
533 	file_spec_t *fl, *tmp;
534 	int h;
535 
536 	if (!fl_head)
537 		return;
538 
539 	for (h = 0; h < HASH_BUCKETS; h++) {
540 		fl = fl_head[h].next;
541 		while (fl) {
542 			tmp = fl;
543 			fl = fl->next;
544 			freecon(tmp->con);
545 			free(tmp->file);
546 			free(tmp);
547 		}
548 		fl_head[h].next = NULL;
549 	}
550 	free(fl_head);
551 	fl_head = NULL;
552 }
553 /*
554  * Try to add an association between an inode and a context.
555  * If there is a different context that matched the inode,
556  * then use the first context that matched.
557  */
filespec_add(ino_t ino,const security_context_t con,const char * file)558 static int filespec_add(ino_t ino, const security_context_t con, const char *file)
559 {
560 	file_spec_t *prevfl, *fl;
561 	int h, ret;
562 	struct stat64 sb;
563 
564 	if (!fl_head) {
565 		fl_head = malloc(sizeof(file_spec_t) * HASH_BUCKETS);
566 		if (!fl_head)
567 			goto oom;
568 		memset(fl_head, 0, sizeof(file_spec_t) * HASH_BUCKETS);
569 	}
570 
571 	h = (ino + (ino >> HASH_BITS)) & HASH_MASK;
572 	for (prevfl = &fl_head[h], fl = fl_head[h].next; fl;
573 	     prevfl = fl, fl = fl->next) {
574 		if (ino == fl->ino) {
575 			ret = lstat64(fl->file, &sb);
576 			if (ret < 0 || sb.st_ino != ino) {
577 				freecon(fl->con);
578 				free(fl->file);
579 				fl->file = strdup(file);
580 				if (!fl->file)
581 					goto oom;
582 				fl->con = strdup(con);
583 				if (!fl->con)
584 					goto oom;
585 				return 1;
586 			}
587 
588 			if (strcmp(fl->con, con) == 0)
589 				return 1;
590 
591 			fprintf(stderr,
592 				"%s:  conflicting specifications for %s and %s, using %s.\n",
593 				__FUNCTION__, file, fl->file, fl->con);
594 			free(fl->file);
595 			fl->file = strdup(file);
596 			if (!fl->file)
597 				goto oom;
598 			return 1;
599 		}
600 
601 		if (ino > fl->ino)
602 			break;
603 	}
604 
605 	fl = malloc(sizeof(file_spec_t));
606 	if (!fl)
607 		goto oom;
608 	fl->ino = ino;
609 	fl->con = strdup(con);
610 	if (!fl->con)
611 		goto oom_freefl;
612 	fl->file = strdup(file);
613 	if (!fl->file)
614 		goto oom_freefl;
615 	fl->next = prevfl->next;
616 	prevfl->next = fl;
617 	return 0;
618       oom_freefl:
619 	free(fl);
620       oom:
621 	fprintf(stderr,
622 		"%s:  insufficient memory for file label entry for %s\n",
623 		__FUNCTION__, file);
624 	return -1;
625 }
626 
627 #include <sys/utsname.h>
file_system_count(char * name)628 int file_system_count(char *name) {
629 	struct statvfs statvfs_buf;
630 	int nfile = 0;
631 	memset(&statvfs_buf, 0, sizeof(statvfs_buf));
632 	if (!statvfs(name, &statvfs_buf)) {
633 		nfile = statvfs_buf.f_files - statvfs_buf.f_ffree;
634 	}
635 	return nfile;
636 }
637 
638 /*
639    Search /proc/mounts for all file systems that do not support extended
640    attributes and add them to the exclude directory table.  File systems
641    that support security labels have the seclabel option, return total file count
642 */
exclude_non_seclabel_mounts()643 int exclude_non_seclabel_mounts()
644 {
645 	struct utsname uts;
646 	FILE *fp;
647 	size_t len;
648 	ssize_t num;
649 	int index = 0, found = 0;
650 	char *mount_info[4];
651 	char *buf = NULL, *item;
652 	int nfile = 0;
653 	/* Check to see if the kernel supports seclabel */
654 	if (uname(&uts) == 0 && strverscmp(uts.release, "2.6.30") < 0)
655 		return 0;
656 	if (is_selinux_enabled() <= 0)
657 		return 0;
658 
659 	fp = fopen("/proc/mounts", "r");
660 	if (!fp)
661 		return 0;
662 
663 	while ((num = getline(&buf, &len, fp)) != -1) {
664 		found = 0;
665 		index = 0;
666 		item = strtok(buf, " ");
667 		while (item != NULL) {
668 			mount_info[index] = item;
669 			if (index == 3)
670 				break;
671 			index++;
672 			item = strtok(NULL, " ");
673 		}
674 		if (index < 3) {
675 			fprintf(stderr,
676 				"/proc/mounts record \"%s\" has incorrect format.\n",
677 				buf);
678 			continue;
679 		}
680 
681 		/* remove pre-existing entry */
682 		remove_exclude(mount_info[1]);
683 
684 		item = strtok(mount_info[3], ",");
685 		while (item != NULL) {
686 			if (strcmp(item, "seclabel") == 0) {
687 				found = 1;
688 				nfile += file_system_count(mount_info[1]);
689 				break;
690 			}
691 			item = strtok(NULL, ",");
692 		}
693 
694 		/* exclude mount points without the seclabel option */
695 		if (!found)
696 			add_exclude(mount_info[1]);
697 	}
698 
699 	free(buf);
700 	fclose(fp);
701 	/* return estimated #Files + 5% for directories and hard links */
702 	return nfile * 1.05;
703 }
704 
705