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