1 #include "restore.h"
2 #include <unistd.h>
3 #include <fcntl.h>
4 #include <stdio_ext.h>
5 #include <ctype.h>
6 #include <regex.h>
7 #include <sys/vfs.h>
8 #include <libgen.h>
9 #ifdef USE_AUDIT
10 #include <libaudit.h>
11 
12 #ifndef AUDIT_FS_RELABEL
13 #define AUDIT_FS_RELABEL 2309
14 #endif
15 #endif
16 
17 static char *policyfile;
18 static int warn_no_match;
19 static int null_terminated;
20 static int request_digest;
21 static struct restore_opts r_opts;
22 static int nerr;
23 
24 #define STAT_BLOCK_SIZE 1
25 
26 /* setfiles will abort its operation after reaching the
27  * following number of errors (e.g. invalid contexts),
28  * unless it is used in "debug" mode (-d option).
29  */
30 #ifndef ABORT_ON_ERRORS
31 #define ABORT_ON_ERRORS	10
32 #endif
33 
34 #define SETFILES "setfiles"
35 #define RESTORECON "restorecon"
36 static int iamrestorecon;
37 
38 /* Behavior flags determined based on setfiles vs. restorecon */
39 static int ctx_validate; /* Validate contexts */
40 static const char *altpath; /* Alternate path to file_contexts */
41 
usage(const char * const name)42 static __attribute__((__noreturn__)) void usage(const char *const name)
43 {
44 	if (iamrestorecon) {
45 		fprintf(stderr,
46 			"usage:  %s [-iIDFmnprRv0] [-e excludedir] pathname...\n"
47 			"usage:  %s [-iIDFmnprRv0] [-e excludedir] -f filename\n",
48 			name, name);
49 	} else {
50 		fprintf(stderr,
51 			"usage:  %s [-diIDlmnpqvFW] [-e excludedir] [-r alt_root_path] spec_file pathname...\n"
52 			"usage:  %s [-diIDlmnpqvFW] [-e excludedir] [-r alt_root_path] spec_file -f filename\n"
53 			"usage:  %s -s [-diIDlmnpqvFW] spec_file\n"
54 			"usage:  %s -c policyfile spec_file\n",
55 			name, name, name, name);
56 	}
57 	exit(-1);
58 }
59 
inc_err(void)60 void inc_err(void)
61 {
62 	nerr++;
63 	if (nerr > ABORT_ON_ERRORS - 1 && !r_opts.debug) {
64 		fprintf(stderr, "Exiting after %d errors.\n", ABORT_ON_ERRORS);
65 		exit(-1);
66 	}
67 }
68 
set_rootpath(const char * arg)69 void set_rootpath(const char *arg)
70 {
71 	if (strlen(arg) == 1 && strncmp(arg, "/", 1) == 0) {
72 		fprintf(stderr, "%s:  invalid alt_rootpath: %s\n",
73 			r_opts.progname, arg);
74 		exit(-1);
75 	}
76 
77 	r_opts.rootpath = strdup(arg);
78 	if (!r_opts.rootpath) {
79 		fprintf(stderr,
80 			"%s:  insufficient memory for r_opts.rootpath\n",
81 			r_opts.progname);
82 		exit(-1);
83 	}
84 }
85 
canoncon(char ** contextp)86 int canoncon(char **contextp)
87 {
88 	char *context = *contextp, *tmpcon;
89 	int rc = 0;
90 
91 	if (policyfile) {
92 		if (sepol_check_context(context) < 0) {
93 			fprintf(stderr, "invalid context %s\n", context);
94 			exit(-1);
95 		}
96 	} else if (security_canonicalize_context_raw(context, &tmpcon) == 0) {
97 		free(context);
98 		*contextp = tmpcon;
99 	} else if (errno != ENOENT) {
100 		rc = -1;
101 		inc_err();
102 	}
103 
104 	return rc;
105 }
106 
107 #ifndef USE_AUDIT
maybe_audit_mass_relabel(int mass_relabel,int mass_relabel_errs)108 static void maybe_audit_mass_relabel(int mass_relabel __attribute__((unused)),
109 				int mass_relabel_errs __attribute__((unused)))
110 {
111 #else
112 static void maybe_audit_mass_relabel(int mass_relabel, int mass_relabel_errs)
113 {
114 	int audit_fd = -1;
115 	int rc = 0;
116 
117 	if (!mass_relabel)		/* only audit a forced full relabel */
118 		return;
119 
120 	audit_fd = audit_open();
121 
122 	if (audit_fd < 0) {
123 		fprintf(stderr, "Error connecting to audit system.\n");
124 		exit(-1);
125 	}
126 
127 	rc = audit_log_user_message(audit_fd, AUDIT_FS_RELABEL,
128 				    "op=mass relabel",
129 				    NULL, NULL, NULL, !mass_relabel_errs);
130 	if (rc <= 0) {
131 		fprintf(stderr, "Error sending audit message: %s.\n",
132 			strerror(errno));
133 		/* exit(-1); -- don't exit atm. as fix for eff_cap isn't
134 		 * in most kernels.
135 		 */
136 	}
137 	audit_close(audit_fd);
138 #endif
139 }
140 
141 static int __attribute__ ((format(printf, 2, 3)))
142 log_callback(int type, const char *fmt, ...)
143 {
144 	int rc;
145 	FILE *out;
146 	va_list ap;
147 
148 	if (type == SELINUX_INFO) {
149 		out = stdout;
150 	} else {
151 		out = stderr;
152 		fflush(stdout);
153 		fprintf(out, "%s: ", r_opts.progname);
154 	}
155 	va_start(ap, fmt);
156 	rc = vfprintf(out, fmt, ap);
157 	va_end(ap);
158 	return rc;
159 }
160 
161 int main(int argc, char **argv)
162 {
163 	struct stat sb;
164 	int opt, i = 0;
165 	const char *input_filename = NULL;
166 	int use_input_file = 0;
167 	char *buf = NULL;
168 	size_t buf_len;
169 	const char *base;
170 	int errors = 0;
171 	const char *ropts = "e:f:hiIDlmno:pqrsvFRW0";
172 	const char *sopts = "c:de:f:hiIDlmno:pqr:svFR:W0";
173 	const char *opts;
174 	union selinux_callback cb;
175 
176 	/* Initialize variables */
177 	memset(&r_opts, 0, sizeof(r_opts));
178 	altpath = NULL;
179 	null_terminated = 0;
180 	warn_no_match = 0;
181 	request_digest = 0;
182 	policyfile = NULL;
183 	nerr = 0;
184 
185 	r_opts.progname = strdup(argv[0]);
186 	if (!r_opts.progname) {
187 		fprintf(stderr, "%s:  Out of memory!\n", argv[0]);
188 		exit(-1);
189 	}
190 	base = basename(r_opts.progname);
191 
192 	if (!strcmp(base, SETFILES)) {
193 		/*
194 		 * setfiles:
195 		 * Recursive descent,
196 		 * Does not expand paths via realpath,
197 		 * Aborts on errors during the file tree walk,
198 		 * Try to track inode associations for conflict detection,
199 		 * Does not follow mounts (sets SELINUX_RESTORECON_XDEV),
200 		 * Validates all file contexts at init time.
201 		 */
202 		iamrestorecon = 0;
203 		r_opts.recurse = SELINUX_RESTORECON_RECURSE;
204 		r_opts.userealpath = 0; /* SELINUX_RESTORECON_REALPATH */
205 		r_opts.abort_on_error = SELINUX_RESTORECON_ABORT_ON_ERROR;
206 		r_opts.add_assoc = SELINUX_RESTORECON_ADD_ASSOC;
207 		/* FTS_PHYSICAL and FTS_NOCHDIR are always set by selinux_restorecon(3) */
208 		r_opts.xdev = SELINUX_RESTORECON_XDEV;
209 		r_opts.ignore_mounts = 0; /* SELINUX_RESTORECON_IGNORE_MOUNTS */
210 		ctx_validate = 1;
211 		opts = sopts;
212 	} else {
213 		/*
214 		 * restorecon:
215 		 * No recursive descent unless -r/-R,
216 		 * Expands paths via realpath,
217 		 * Do not abort on errors during the file tree walk,
218 		 * Do not try to track inode associations for conflict detection,
219 		 * Follows mounts,
220 		 * Does lazy validation of contexts upon use.
221 		 */
222 		if (strcmp(base, RESTORECON))
223 			fprintf(stderr, "Executed with unrecognized name (%s), defaulting to %s behavior.\n",
224 				base, RESTORECON);
225 
226 		iamrestorecon = 1;
227 		r_opts.recurse = 0;
228 		r_opts.userealpath = SELINUX_RESTORECON_REALPATH;
229 		r_opts.abort_on_error = 0;
230 		r_opts.add_assoc = 0;
231 		r_opts.xdev = 0;
232 		r_opts.ignore_mounts = 0;
233 		ctx_validate = 0;
234 		opts = ropts;
235 
236 		/* restorecon only:  silent exit if no SELinux.
237 		 * Allows unconditional execution by scripts.
238 		 */
239 		if (is_selinux_enabled() <= 0)
240 			exit(0);
241 	}
242 
243 	/* Process any options. */
244 	while ((opt = getopt(argc, argv, opts)) > 0) {
245 		switch (opt) {
246 		case 'c':
247 			{
248 				FILE *policystream;
249 
250 				if (iamrestorecon)
251 					usage(argv[0]);
252 
253 				policyfile = optarg;
254 
255 				policystream = fopen(policyfile, "r");
256 				if (!policystream) {
257 					fprintf(stderr,
258 						"Error opening %s: %s\n",
259 						policyfile, strerror(errno));
260 					exit(-1);
261 				}
262 				__fsetlocking(policystream,
263 					      FSETLOCKING_BYCALLER);
264 
265 				if (sepol_set_policydb_from_file(policystream)
266 									< 0) {
267 					fprintf(stderr,
268 						"Error reading policy %s: %s\n",
269 						policyfile, strerror(errno));
270 					exit(-1);
271 				}
272 				fclose(policystream);
273 
274 				ctx_validate = 1;
275 				break;
276 			}
277 		case 'e':
278 			if (lstat(optarg, &sb) < 0 && errno != EACCES) {
279 				fprintf(stderr, "Can't stat exclude path \"%s\", %s - ignoring.\n",
280 					optarg, strerror(errno));
281 				break;
282 			}
283 			add_exclude(optarg);
284 			break;
285 		case 'f':
286 			use_input_file = 1;
287 			input_filename = optarg;
288 			break;
289 		case 'd':
290 			if (iamrestorecon)
291 				usage(argv[0]);
292 			r_opts.debug = 1;
293 			r_opts.log_matches =
294 					   SELINUX_RESTORECON_LOG_MATCHES;
295 			break;
296 		case 'i':
297 			r_opts.ignore_noent =
298 					   SELINUX_RESTORECON_IGNORE_NOENTRY;
299 			break;
300 		case 'I': /* Force label check by ignoring directory digest. */
301 			r_opts.ignore_digest =
302 					   SELINUX_RESTORECON_IGNORE_DIGEST;
303 			request_digest = 1;
304 			break;
305 		case 'D': /*
306 			   * Request file_contexts digest in selabel_open
307 			   * This will effectively enable usage of the
308 			   * security.restorecon_last extended attribute.
309 			   */
310 			request_digest = 1;
311 			break;
312 		case 'l':
313 			r_opts.syslog_changes =
314 					   SELINUX_RESTORECON_SYSLOG_CHANGES;
315 			break;
316 		case 'F':
317 			r_opts.set_specctx =
318 					   SELINUX_RESTORECON_SET_SPECFILE_CTX;
319 			break;
320 		case 'm':
321 			r_opts.ignore_mounts =
322 					   SELINUX_RESTORECON_IGNORE_MOUNTS;
323 			break;
324 		case 'n':
325 			r_opts.nochange = SELINUX_RESTORECON_NOCHANGE;
326 			break;
327 		case 'o': /* Deprecated */
328 			fprintf(stderr, "%s: -o option no longer supported\n",
329 				r_opts.progname);
330 			break;
331 		case 'q':
332 			/* Deprecated - Was only used to say whether print
333 			 * filespec_eval() params. Now uses verbose flag.
334 			 */
335 			break;
336 		case 'R':
337 		case 'r':
338 			if (iamrestorecon) {
339 				r_opts.recurse = SELINUX_RESTORECON_RECURSE;
340 				break;
341 			}
342 
343 			if (lstat(optarg, &sb) < 0 && errno != EACCES) {
344 				fprintf(stderr,
345 					"Can't stat alt_root_path \"%s\", %s\n",
346 					optarg, strerror(errno));
347 				exit(-1);
348 			}
349 
350 			if (r_opts.rootpath) {
351 				fprintf(stderr,
352 					"%s: only one -r can be specified\n",
353 					argv[0]);
354 				exit(-1);
355 			}
356 			set_rootpath(optarg);
357 			break;
358 		case 's':
359 			use_input_file = 1;
360 			input_filename = "-";
361 			r_opts.add_assoc = 0;
362 			break;
363 		case 'v':
364 			if (r_opts.progress) {
365 				fprintf(stderr,
366 					"Progress and Verbose mutually exclusive\n");
367 				usage(argv[0]);
368 			}
369 			r_opts.verbose = SELINUX_RESTORECON_VERBOSE;
370 			break;
371 		case 'p':
372 			if (r_opts.verbose) {
373 				fprintf(stderr,
374 					"Progress and Verbose mutually exclusive\n");
375 				usage(argv[0]);
376 			}
377 			r_opts.progress = SELINUX_RESTORECON_PROGRESS;
378 			break;
379 		case 'W':
380 			warn_no_match = 1; /* Print selabel_stats() */
381 			break;
382 		case '0':
383 			null_terminated = 1;
384 			break;
385 		case 'h':
386 		case '?':
387 			usage(argv[0]);
388 		}
389 	}
390 
391 	for (i = optind; i < argc; i++) {
392 		if (!strcmp(argv[i], "/"))
393 			r_opts.mass_relabel = SELINUX_RESTORECON_MASS_RELABEL;
394 	}
395 
396 	cb.func_log = log_callback;
397 	selinux_set_callback(SELINUX_CB_LOG, cb);
398 
399 	if (!iamrestorecon) {
400 		if (policyfile) {
401 			if (optind != (argc - 1))
402 				usage(argv[0]);
403 		} else if (use_input_file) {
404 			if (optind != (argc - 1)) {
405 				/* Cannot mix with pathname arguments. */
406 				usage(argv[0]);
407 			}
408 		} else {
409 			if (optind > (argc - 2))
410 				usage(argv[0]);
411 		}
412 
413 		/* Use our own invalid context checking function so that
414 		 * we can support either checking against the active policy or
415 		 * checking against a binary policy file.
416 		 */
417 		cb.func_validate = canoncon;
418 		selinux_set_callback(SELINUX_CB_VALIDATE, cb);
419 
420 		if (stat(argv[optind], &sb) < 0) {
421 			perror(argv[optind]);
422 			exit(-1);
423 		}
424 		if (!S_ISREG(sb.st_mode)) {
425 			fprintf(stderr, "%s:  spec file %s is not a regular file.\n",
426 				argv[0], argv[optind]);
427 			exit(-1);
428 		}
429 
430 		altpath = argv[optind];
431 		optind++;
432 	} else if (argc == 1)
433 		usage(argv[0]);
434 
435 	/* Set selabel_open options. */
436 	r_opts.selabel_opt_validate = (ctx_validate ? (char *)1 : NULL);
437 	r_opts.selabel_opt_digest = (request_digest ? (char *)1 : NULL);
438 	r_opts.selabel_opt_path = altpath;
439 
440 	if (nerr)
441 		exit(-1);
442 
443 	restore_init(&r_opts);
444 
445 	if (use_input_file) {
446 		FILE *f = stdin;
447 		ssize_t len;
448 		int delim;
449 
450 		if (strcmp(input_filename, "-") != 0)
451 			f = fopen(input_filename, "r");
452 
453 		if (f == NULL) {
454 			fprintf(stderr, "Unable to open %s: %s\n",
455 				input_filename,
456 				strerror(errno));
457 			usage(argv[0]);
458 		}
459 		__fsetlocking(f, FSETLOCKING_BYCALLER);
460 
461 		delim = (null_terminated != 0) ? '\0' : '\n';
462 		while ((len = getdelim(&buf, &buf_len, delim, f)) > 0) {
463 			buf[len - 1] = 0;
464 			if (!strcmp(buf, "/"))
465 				r_opts.mass_relabel = SELINUX_RESTORECON_MASS_RELABEL;
466 			errors |= process_glob(buf, &r_opts) < 0;
467 		}
468 		if (strcmp(input_filename, "-") != 0)
469 			fclose(f);
470 	} else {
471 		for (i = optind; i < argc; i++)
472 			errors |= process_glob(argv[i], &r_opts) < 0;
473 	}
474 
475 	maybe_audit_mass_relabel(r_opts.mass_relabel, errors);
476 
477 	if (warn_no_match)
478 		selabel_stats(r_opts.hnd);
479 
480 	selabel_close(r_opts.hnd);
481 	restore_finish();
482 
483 	if (r_opts.progress)
484 		fprintf(stdout, "\n");
485 
486 	exit(errors ? -1 : 0);
487 }
488