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 [-diIDlmnpqvCEFWT] [-e excludedir] [-r alt_root_path] [-c policyfile] spec_file pathname...\n"
44 "usage: %s [-diIDlmnpqvCEFWT] [-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:svCEFR:W0T:";
154 const char *opts;
155 union selinux_callback cb;
156 long unsigned skipped_errors;
157
158 /* Initialize variables */
159 memset(&r_opts, 0, sizeof(r_opts));
160 altpath = NULL;
161 null_terminated = 0;
162 warn_no_match = 0;
163 request_digest = 0;
164 policyfile = NULL;
165 skipped_errors = 0;
166
167 if (!argv[0]) {
168 fprintf(stderr, "Called without required program name!\n");
169 exit(-1);
170 }
171 r_opts.progname = strdup(argv[0]);
172 if (!r_opts.progname) {
173 fprintf(stderr, "%s: Out of memory!\n", argv[0]);
174 exit(-1);
175 }
176 base = basename(r_opts.progname);
177
178 if (!strcmp(base, SETFILES)) {
179 /*
180 * setfiles:
181 * Recursive descent,
182 * Does not expand paths via realpath,
183 * Try to track inode associations for conflict detection,
184 * Does not follow mounts (sets SELINUX_RESTORECON_XDEV),
185 * Validates all file contexts at init time.
186 */
187 iamrestorecon = 0;
188 r_opts.recurse = SELINUX_RESTORECON_RECURSE;
189 r_opts.userealpath = 0; /* SELINUX_RESTORECON_REALPATH */
190 r_opts.add_assoc = SELINUX_RESTORECON_ADD_ASSOC;
191 /* FTS_PHYSICAL and FTS_NOCHDIR are always set by selinux_restorecon(3) */
192 r_opts.xdev = SELINUX_RESTORECON_XDEV;
193 r_opts.ignore_mounts = 0; /* SELINUX_RESTORECON_IGNORE_MOUNTS */
194 ctx_validate = 1;
195 opts = sopts;
196 } else {
197 /*
198 * restorecon:
199 * No recursive descent unless -r/-R,
200 * Expands paths via realpath,
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 policyfile = optarg;
233
234 policystream = fopen(policyfile, "r");
235 if (!policystream) {
236 fprintf(stderr,
237 "Error opening %s: %s\n",
238 policyfile, strerror(errno));
239 exit(-1);
240 }
241 __fsetlocking(policystream,
242 FSETLOCKING_BYCALLER);
243
244 if (sepol_set_policydb_from_file(policystream)
245 < 0) {
246 fprintf(stderr,
247 "Error reading policy %s: %s\n",
248 policyfile, strerror(errno));
249 exit(-1);
250 }
251 fclose(policystream);
252
253 ctx_validate = 1;
254 break;
255 }
256 case 'e':
257 if (lstat(optarg, &sb) < 0 && errno != EACCES) {
258 fprintf(stderr, "Can't stat exclude path \"%s\", %s - ignoring.\n",
259 optarg, strerror(errno));
260 break;
261 }
262 add_exclude(optarg);
263 break;
264 case 'f':
265 use_input_file = 1;
266 input_filename = optarg;
267 break;
268 case 'd':
269 r_opts.debug = 1;
270 r_opts.log_matches =
271 SELINUX_RESTORECON_LOG_MATCHES;
272 break;
273 case 'i':
274 r_opts.ignore_noent =
275 SELINUX_RESTORECON_IGNORE_NOENTRY;
276 break;
277 case 'I': /* Force label check by ignoring directory digest. */
278 r_opts.ignore_digest =
279 SELINUX_RESTORECON_IGNORE_DIGEST;
280 request_digest = 1;
281 break;
282 case 'D': /*
283 * Request file_contexts digest in selabel_open
284 * This will effectively enable usage of the
285 * security.restorecon_last extended attribute.
286 */
287 request_digest = 1;
288 break;
289 case 'l':
290 r_opts.syslog_changes =
291 SELINUX_RESTORECON_SYSLOG_CHANGES;
292 break;
293 case 'C':
294 r_opts.count_errors = SELINUX_RESTORECON_COUNT_ERRORS;
295 break;
296 case 'E':
297 r_opts.conflict_error =
298 SELINUX_RESTORECON_CONFLICT_ERROR;
299 break;
300 case 'F':
301 r_opts.set_specctx =
302 SELINUX_RESTORECON_SET_SPECFILE_CTX;
303 break;
304 case 'm':
305 r_opts.ignore_mounts =
306 SELINUX_RESTORECON_IGNORE_MOUNTS;
307 break;
308 case 'n':
309 r_opts.nochange = SELINUX_RESTORECON_NOCHANGE;
310 break;
311 case 'o': /* Deprecated */
312 fprintf(stderr, "%s: -o option no longer supported\n",
313 r_opts.progname);
314 break;
315 case 'q':
316 /* Deprecated - Was only used to say whether print
317 * filespec_eval() params. Now uses verbose flag.
318 */
319 break;
320 case 'R':
321 case 'r':
322 if (iamrestorecon) {
323 r_opts.recurse = SELINUX_RESTORECON_RECURSE;
324 break;
325 }
326
327 if (lstat(optarg, &sb) < 0 && errno != EACCES) {
328 fprintf(stderr,
329 "Can't stat alt_root_path \"%s\", %s\n",
330 optarg, strerror(errno));
331 exit(-1);
332 }
333
334 if (r_opts.rootpath) {
335 fprintf(stderr,
336 "%s: only one -r can be specified\n",
337 argv[0]);
338 exit(-1);
339 }
340 set_rootpath(optarg);
341 break;
342 case 's':
343 use_input_file = 1;
344 input_filename = "-";
345 r_opts.add_assoc = 0;
346 break;
347 case 'v':
348 if (r_opts.progress) {
349 fprintf(stderr,
350 "Progress and Verbose mutually exclusive\n");
351 usage(argv[0]);
352 }
353 r_opts.verbose = SELINUX_RESTORECON_VERBOSE;
354 break;
355 case 'p':
356 if (r_opts.verbose) {
357 fprintf(stderr,
358 "Progress and Verbose mutually exclusive\n");
359 usage(argv[0]);
360 }
361 r_opts.progress = SELINUX_RESTORECON_PROGRESS;
362 break;
363 case 'W':
364 warn_no_match = 1; /* Print selabel_stats() */
365 break;
366 case '0':
367 null_terminated = 1;
368 break;
369 case 'x':
370 r_opts.xdev = SELINUX_RESTORECON_XDEV;
371 break;
372 case 'T':
373 nthreads = strtoull(optarg, &endptr, 10);
374 if (*optarg == '\0' || *endptr != '\0')
375 usage(argv[0]);
376 break;
377 case 'h':
378 case '?':
379 usage(argv[0]);
380 }
381 }
382
383 for (i = optind; i < argc; i++) {
384 if (!strcmp(argv[i], "/"))
385 r_opts.mass_relabel = SELINUX_RESTORECON_MASS_RELABEL;
386 }
387
388 cb.func_log = log_callback;
389 selinux_set_callback(SELINUX_CB_LOG, cb);
390
391 if (!iamrestorecon) {
392 if (policyfile) {
393 if (optind > (argc - 1))
394 usage(argv[0]);
395 } else if (use_input_file) {
396 if (optind != (argc - 1)) {
397 /* Cannot mix with pathname arguments. */
398 usage(argv[0]);
399 }
400 } else {
401 if (optind > (argc - 2))
402 usage(argv[0]);
403 }
404
405 /* Use our own invalid context checking function so that
406 * we can support either checking against the active policy or
407 * checking against a binary policy file.
408 */
409 cb.func_validate = canoncon;
410 selinux_set_callback(SELINUX_CB_VALIDATE, cb);
411
412 if (stat(argv[optind], &sb) < 0) {
413 perror(argv[optind]);
414 exit(-1);
415 }
416 if (!S_ISREG(sb.st_mode)) {
417 fprintf(stderr, "%s: spec file %s is not a regular file.\n",
418 argv[0], argv[optind]);
419 exit(-1);
420 }
421
422 altpath = argv[optind];
423 optind++;
424 } else if (argc < 2)
425 usage(argv[0]);
426
427 /* Set selabel_open options. */
428 r_opts.selabel_opt_validate = (ctx_validate ? (char *)1 : NULL);
429 r_opts.selabel_opt_digest = (request_digest ? (char *)1 : NULL);
430 r_opts.selabel_opt_path = altpath;
431
432 restore_init(&r_opts);
433
434 if (use_input_file) {
435 FILE *f = stdin;
436 ssize_t len;
437 int delim;
438
439 if (strcmp(input_filename, "-") != 0)
440 f = fopen(input_filename, "r");
441
442 if (f == NULL) {
443 fprintf(stderr, "Unable to open %s: %s\n",
444 input_filename,
445 strerror(errno));
446 usage(argv[0]);
447 }
448 __fsetlocking(f, FSETLOCKING_BYCALLER);
449
450 delim = (null_terminated != 0) ? '\0' : '\n';
451 while ((len = getdelim(&buf, &buf_len, delim, f)) > 0) {
452 buf[len - 1] = 0;
453 if (!strcmp(buf, "/"))
454 r_opts.mass_relabel = SELINUX_RESTORECON_MASS_RELABEL;
455 errors |= process_glob(buf, &r_opts, nthreads,
456 &skipped_errors) < 0;
457 }
458 if (strcmp(input_filename, "-") != 0)
459 fclose(f);
460 } else {
461 for (i = optind; i < argc; i++)
462 errors |= process_glob(argv[i], &r_opts, nthreads,
463 &skipped_errors) < 0;
464 }
465
466 maybe_audit_mass_relabel(r_opts.mass_relabel, errors);
467
468 if (warn_no_match)
469 selabel_stats(r_opts.hnd);
470
471 selabel_close(r_opts.hnd);
472 restore_finish();
473
474 if (r_opts.progress)
475 fprintf(stdout, "\n");
476
477 exit(errors ? -1 : skipped_errors ? 1 : 0);
478 }
479