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