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