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