source: MondoRescue/branches/3.3/mindi-busybox/selinux/setfiles.c@ 3803

Last change on this file since 3803 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

  • Property svn:eol-style set to native
File size: 17.5 KB
Line 
1/*
2 setfiles: based on policycoreutils 2.0.19
3 policycoreutils was released under GPL 2.
4 Port to BusyBox (c) 2007 by Yuichi Nakamura <ynakam@hitachisoft.jp>
5*/
6
7//usage:#define setfiles_trivial_usage
8//usage: "[-dnpqsvW] [-e DIR]... [-o FILE] [-r alt_root_path]"
9//usage: IF_FEATURE_SETFILES_CHECK_OPTION(
10//usage: " [-c policyfile] spec_file"
11//usage: )
12//usage: " pathname"
13//usage:#define setfiles_full_usage "\n\n"
14//usage: "Reset file contexts under pathname according to spec_file\n"
15//usage: IF_FEATURE_SETFILES_CHECK_OPTION(
16//usage: "\n -c FILE Check the validity of the contexts against the specified binary policy"
17//usage: )
18//usage: "\n -d Show which specification matched each file"
19//usage: "\n -l Log changes in file labels to syslog"
20//TODO: log to syslog is not yet implemented, it goes to stdout only now
21//usage: "\n -n Don't change any file labels"
22//usage: "\n -q Suppress warnings"
23//usage: "\n -r DIR Use an alternate root path"
24//usage: "\n -e DIR Exclude DIR"
25//usage: "\n -F Force reset of context to match file_context for customizable files"
26//usage: "\n -o FILE Save list of files with incorrect context"
27//usage: "\n -s Take a list of files from stdin (instead of command line)"
28//usage: "\n -v Show changes in file labels, if type or role are changing"
29//usage: "\n -vv Show changes in file labels, if type, role, or user are changing"
30//usage: "\n -W Display warnings about entries that had no matching files"
31//usage:
32//usage:#define restorecon_trivial_usage
33//usage: "[-iFnRv] [-e EXCLUDEDIR]... [-o FILE] [-f FILE]"
34//usage:#define restorecon_full_usage "\n\n"
35//usage: "Reset security contexts of files in pathname\n"
36//usage: "\n -i Ignore files that don't exist"
37//usage: "\n -f FILE File with list of files to process"
38//usage: "\n -e DIR Directory to exclude"
39//usage: "\n -R,-r Recurse"
40//usage: "\n -n Don't change any file labels"
41//usage: "\n -o FILE Save list of files with incorrect context"
42//usage: "\n -v Verbose"
43//usage: "\n -vv Show changed labels"
44//usage: "\n -F Force reset of context to match file_context"
45//usage: "\n for customizable files, or the user section,"
46//usage: "\n if it has changed"
47
48#include "libbb.h"
49#if ENABLE_FEATURE_SETFILES_CHECK_OPTION
50#include <sepol/sepol.h>
51#endif
52
53#define MAX_EXCLUDES 50
54
55struct edir {
56 char *directory;
57 size_t size;
58};
59
60struct globals {
61 FILE *outfile;
62 char *policyfile;
63 char *rootpath;
64 int rootpathlen;
65 unsigned count;
66 int excludeCtr;
67 int errors;
68 int verbose; /* getopt32 uses it, has to be int */
69 smallint recurse; /* Recursive descent */
70 smallint follow_mounts;
71 /* Behavior flags determined based on setfiles vs. restorecon */
72 smallint expand_realpath; /* Expand paths via realpath */
73 smallint abort_on_error; /* Abort the file tree walk upon an error */
74 int add_assoc; /* Track inode associations for conflict detection */
75 int matchpathcon_flags; /* Flags to matchpathcon */
76 dev_t dev_id; /* Device id where target file exists */
77 int nerr;
78 struct edir excludeArray[MAX_EXCLUDES];
79} FIX_ALIASING;
80#define G (*(struct globals*)bb_common_bufsiz1)
81void BUG_setfiles_globals_too_big(void);
82#define INIT_G() do { \
83 setup_common_bufsiz(); \
84 if (sizeof(G) > COMMON_BUFSIZE) \
85 BUG_setfiles_globals_too_big(); \
86 /* memset(&G, 0, sizeof(G)); - already is */ \
87} while (0)
88#define outfile (G.outfile )
89#define policyfile (G.policyfile )
90#define rootpath (G.rootpath )
91#define rootpathlen (G.rootpathlen )
92#define count (G.count )
93#define excludeCtr (G.excludeCtr )
94#define errors (G.errors )
95#define verbose (G.verbose )
96#define recurse (G.recurse )
97#define follow_mounts (G.follow_mounts )
98#define expand_realpath (G.expand_realpath )
99#define abort_on_error (G.abort_on_error )
100#define add_assoc (G.add_assoc )
101#define matchpathcon_flags (G.matchpathcon_flags)
102#define dev_id (G.dev_id )
103#define nerr (G.nerr )
104#define excludeArray (G.excludeArray )
105
106/* Must match getopt32 string! */
107enum {
108 OPT_d = (1 << 0),
109 OPT_e = (1 << 1),
110 OPT_f = (1 << 2),
111 OPT_i = (1 << 3),
112 OPT_l = (1 << 4),
113 OPT_n = (1 << 5),
114 OPT_p = (1 << 6),
115 OPT_q = (1 << 7),
116 OPT_r = (1 << 8),
117 OPT_s = (1 << 9),
118 OPT_v = (1 << 10),
119 OPT_o = (1 << 11),
120 OPT_F = (1 << 12),
121 OPT_W = (1 << 13),
122 OPT_c = (1 << 14), /* c only for setfiles */
123 OPT_R = (1 << 14), /* R only for restorecon */
124};
125#define FLAG_d_debug (option_mask32 & OPT_d)
126#define FLAG_e (option_mask32 & OPT_e)
127#define FLAG_f (option_mask32 & OPT_f)
128#define FLAG_i_ignore_enoent (option_mask32 & OPT_i)
129#define FLAG_l_take_log (option_mask32 & OPT_l)
130#define FLAG_n_dry_run (option_mask32 & OPT_n)
131#define FLAG_p_progress (option_mask32 & OPT_p)
132#define FLAG_q_quiet (option_mask32 & OPT_q)
133#define FLAG_r (option_mask32 & OPT_r)
134#define FLAG_s (option_mask32 & OPT_s)
135#define FLAG_v (option_mask32 & OPT_v)
136#define FLAG_o (option_mask32 & OPT_o)
137#define FLAG_F_force (option_mask32 & OPT_F)
138#define FLAG_W_warn_no_match (option_mask32 & OPT_W)
139#define FLAG_c (option_mask32 & OPT_c)
140#define FLAG_R (option_mask32 & OPT_R)
141
142
143static void qprintf(const char *fmt UNUSED_PARAM, ...)
144{
145 /* quiet, do nothing */
146}
147
148static void inc_err(void)
149{
150 nerr++;
151 if (nerr > 9 && !FLAG_d_debug) {
152 bb_error_msg_and_die("exiting after 10 errors");
153 }
154}
155
156static void add_exclude(const char *directory)
157{
158 struct stat sb;
159 size_t len;
160
161 if (directory == NULL || directory[0] != '/') {
162 bb_error_msg_and_die("full path required for exclude: %s", directory);
163 }
164 if (lstat(directory, &sb)) {
165 bb_error_msg("directory \"%s\" not found, ignoring", directory);
166 return;
167 }
168 if ((sb.st_mode & S_IFDIR) == 0) {
169 bb_error_msg("\"%s\" is not a directory: mode %o, ignoring",
170 directory, sb.st_mode);
171 return;
172 }
173 if (excludeCtr == MAX_EXCLUDES) {
174 bb_error_msg_and_die("maximum excludes %d exceeded", MAX_EXCLUDES);
175 }
176
177 len = strlen(directory);
178 while (len > 1 && directory[len - 1] == '/') {
179 len--;
180 }
181 excludeArray[excludeCtr].directory = xstrndup(directory, len);
182 excludeArray[excludeCtr++].size = len;
183}
184
185static bool exclude(const char *file)
186{
187 int i = 0;
188 for (i = 0; i < excludeCtr; i++) {
189 if (strncmp(file, excludeArray[i].directory,
190 excludeArray[i].size) == 0) {
191 if (file[excludeArray[i].size] == '\0'
192 || file[excludeArray[i].size] == '/') {
193 return 1;
194 }
195 }
196 }
197 return 0;
198}
199
200static int match(const char *name, struct stat *sb, char **con)
201{
202 int ret;
203 char path[PATH_MAX + 1];
204 char *tmp_path = xstrdup(name);
205
206 if (excludeCtr > 0 && exclude(name)) {
207 goto err;
208 }
209 ret = lstat(name, sb);
210 if (ret) {
211 if (FLAG_i_ignore_enoent && errno == ENOENT) {
212 free(tmp_path);
213 return 0;
214 }
215 bb_error_msg("stat(%s)", name);
216 goto err;
217 }
218
219 if (expand_realpath) {
220 if (S_ISLNK(sb->st_mode)) {
221 char *p = NULL;
222 char *file_sep;
223
224 size_t len = 0;
225
226 if (verbose > 1)
227 bb_error_msg("warning! %s refers to a symbolic link, not following last component", name);
228
229 file_sep = strrchr(tmp_path, '/');
230 if (file_sep == tmp_path) {
231 file_sep++;
232 path[0] = '\0';
233 p = path;
234 } else if (file_sep) {
235 *file_sep++ = '\0';
236 p = realpath(tmp_path, path);
237 } else {
238 file_sep = tmp_path;
239 p = realpath("./", path);
240 }
241 if (p)
242 len = strlen(p);
243 if (!p || len + strlen(file_sep) + 2 > PATH_MAX) {
244 bb_perror_msg("realpath(%s) failed", name);
245 goto err;
246 }
247 p += len;
248 /* ensure trailing slash of directory name */
249 if (len == 0 || p[-1] != '/') {
250 *p++ = '/';
251 }
252 strcpy(p, file_sep);
253 name = path;
254 if (excludeCtr > 0 && exclude(name))
255 goto err;
256 } else {
257 char *p;
258 p = realpath(name, path);
259 if (!p) {
260 bb_perror_msg("realpath(%s)", name);
261 goto err;
262 }
263 name = p;
264 if (excludeCtr > 0 && exclude(name))
265 goto err;
266 }
267 }
268
269 /* name will be what is matched in the policy */
270 if (NULL != rootpath) {
271 if (0 != strncmp(rootpath, name, rootpathlen)) {
272 bb_error_msg("%s is not located in %s",
273 name, rootpath);
274 goto err;
275 }
276 name += rootpathlen;
277 }
278
279 free(tmp_path);
280 if (rootpath != NULL && name[0] == '\0')
281 /* this is actually the root dir of the alt root */
282 return matchpathcon_index("/", sb->st_mode, con);
283 return matchpathcon_index(name, sb->st_mode, con);
284 err:
285 free(tmp_path);
286 return -1;
287}
288
289/* Compare two contexts to see if their differences are "significant",
290 * or whether the only difference is in the user. */
291static bool only_changed_user(const char *a, const char *b)
292{
293 if (FLAG_F_force)
294 return 0;
295 if (!a || !b)
296 return 0;
297 a = strchr(a, ':'); /* Rest of the context after the user */
298 b = strchr(b, ':');
299 if (!a || !b)
300 return 0;
301 return (strcmp(a, b) == 0);
302}
303
304static int restore(const char *file)
305{
306 char *my_file;
307 struct stat my_sb;
308 int i, j, ret;
309 char *context = NULL;
310 char *newcon = NULL;
311 bool user_only_changed = 0;
312 int retval = 0;
313
314 my_file = bb_simplify_path(file);
315
316 i = match(my_file, &my_sb, &newcon);
317
318 if (i < 0) /* No matching specification. */
319 goto out;
320
321 if (FLAG_p_progress) {
322 count++;
323 if (count % 0x400 == 0) { /* every 1024 times */
324 count = (count % (80*0x400));
325 if (count == 0)
326 bb_putchar('\n');
327 bb_putchar('*');
328 fflush_all();
329 }
330 }
331
332 /*
333 * Try to add an association between this inode and
334 * this specification. If there is already an association
335 * for this inode and it conflicts with this specification,
336 * then use the last matching specification.
337 */
338 if (add_assoc) {
339 j = matchpathcon_filespec_add(my_sb.st_ino, i, my_file);
340 if (j < 0)
341 goto err;
342
343 if (j != i) {
344 /* There was already an association and it took precedence. */
345 goto out;
346 }
347 }
348
349 if (FLAG_d_debug)
350 printf("%s: %s matched by %s\n", applet_name, my_file, newcon);
351
352 /* Get the current context of the file. */
353 ret = lgetfilecon_raw(my_file, &context);
354 if (ret < 0) {
355 if (errno == ENODATA) {
356 context = NULL; /* paranoia */
357 } else {
358 bb_perror_msg("lgetfilecon_raw on %s", my_file);
359 goto err;
360 }
361 user_only_changed = 0;
362 } else
363 user_only_changed = only_changed_user(context, newcon);
364
365 /*
366 * Do not relabel the file if the matching specification is
367 * <<none>> or the file is already labeled according to the
368 * specification.
369 */
370 if ((strcmp(newcon, "<<none>>") == 0)
371 || (context && (strcmp(context, newcon) == 0) && !FLAG_F_force)) {
372 goto out;
373 }
374
375 if (!FLAG_F_force && context && (is_context_customizable(context) > 0)) {
376 if (verbose > 1) {
377 bb_error_msg("skipping %s. %s is customizable_types",
378 my_file, context);
379 }
380 goto out;
381 }
382
383 if (verbose) {
384 /* If we're just doing "-v", trim out any relabels where
385 * the user has changed but the role and type are the
386 * same. For "-vv", emit everything. */
387 if (verbose > 1 || !user_only_changed) {
388 printf("%s: reset %s context %s->%s\n",
389 applet_name, my_file, context ? context : "", newcon);
390 }
391 }
392
393 if (FLAG_l_take_log && !user_only_changed) {
394 if (context)
395 printf("relabeling %s from %s to %s\n", my_file, context, newcon);
396 else
397 printf("labeling %s to %s\n", my_file, newcon);
398 }
399
400 if (outfile && !user_only_changed)
401 fprintf(outfile, "%s\n", my_file);
402
403 /*
404 * Do not relabel the file if -n was used.
405 */
406 if (FLAG_n_dry_run || user_only_changed)
407 goto out;
408
409 /*
410 * Relabel the file to the specified context.
411 */
412 ret = lsetfilecon(my_file, newcon);
413 if (ret) {
414 bb_perror_msg("lsetfileconon(%s,%s)", my_file, newcon);
415 goto err;
416 }
417
418 out:
419 freecon(context);
420 freecon(newcon);
421 free(my_file);
422 return retval;
423 err:
424 retval--; /* -1 */
425 goto out;
426}
427
428/*
429 * Apply the last matching specification to a file.
430 * This function is called by recursive_action on each file during
431 * the directory traversal.
432 */
433static int FAST_FUNC apply_spec(
434 const char *file,
435 struct stat *sb,
436 void *userData UNUSED_PARAM,
437 int depth UNUSED_PARAM)
438{
439 if (!follow_mounts) {
440 /* setfiles does not process across different mount points */
441 if (sb->st_dev != dev_id) {
442 return SKIP;
443 }
444 }
445 errors |= restore(file);
446 if (abort_on_error && errors)
447 return FALSE;
448 return TRUE;
449}
450
451
452static int canoncon(const char *path, unsigned lineno, char **contextp)
453{
454 static const char err_msg[] ALIGN1 = "%s: line %u has invalid context %s";
455
456 char *tmpcon;
457 char *context = *contextp;
458 int invalid = 0;
459
460#if ENABLE_FEATURE_SETFILES_CHECK_OPTION
461 if (policyfile) {
462 if (sepol_check_context(context) >= 0)
463 return 0;
464 /* Exit immediately if we're in checking mode. */
465 bb_error_msg_and_die(err_msg, path, lineno, context);
466 }
467#endif
468
469 if (security_canonicalize_context_raw(context, &tmpcon) < 0) {
470 if (errno != ENOENT) {
471 invalid = 1;
472 inc_err();
473 }
474 } else {
475 free(context);
476 *contextp = tmpcon;
477 }
478
479 if (invalid) {
480 bb_error_msg(err_msg, path, lineno, context);
481 }
482
483 return invalid;
484}
485
486static int process_one(char *name)
487{
488 struct stat sb;
489 int rc;
490
491 rc = lstat(name, &sb);
492 if (rc < 0) {
493 if (FLAG_i_ignore_enoent && errno == ENOENT)
494 return 0;
495 bb_perror_msg("stat(%s)", name);
496 goto err;
497 }
498 dev_id = sb.st_dev;
499
500 if (S_ISDIR(sb.st_mode) && recurse) {
501 if (recursive_action(name,
502 ACTION_RECURSE,
503 apply_spec,
504 apply_spec,
505 NULL, 0) != TRUE
506 ) {
507 bb_error_msg("error while labeling %s", name);
508 goto err;
509 }
510 } else {
511 rc = restore(name);
512 if (rc)
513 goto err;
514 }
515
516 out:
517 if (add_assoc) {
518 if (FLAG_q_quiet)
519 set_matchpathcon_printf(&qprintf);
520 matchpathcon_filespec_eval();
521 set_matchpathcon_printf(NULL);
522 matchpathcon_filespec_destroy();
523 }
524
525 return rc;
526
527 err:
528 rc = -1;
529 goto out;
530}
531
532int setfiles_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
533int setfiles_main(int argc UNUSED_PARAM, char **argv)
534{
535 struct stat sb;
536 int rc, i = 0;
537 const char *input_filename = NULL;
538 char *buf = NULL;
539 size_t buf_len;
540 int flags;
541 llist_t *exclude_dir = NULL;
542 char *out_filename = NULL;
543
544 INIT_G();
545
546 if (applet_name[0] == 's') { /* "setfiles" */
547 /*
548 * setfiles:
549 * Recursive descent,
550 * Does not expand paths via realpath,
551 * Aborts on errors during the file tree walk,
552 * Try to track inode associations for conflict detection,
553 * Does not follow mounts,
554 * Validates all file contexts at init time.
555 */
556 recurse = 1;
557 abort_on_error = 1;
558 add_assoc = 1;
559 /* follow_mounts = 0; - already is */
560 matchpathcon_flags = MATCHPATHCON_VALIDATE | MATCHPATHCON_NOTRANS;
561 } else {
562 /*
563 * restorecon:
564 * No recursive descent unless -r/-R,
565 * Expands paths via realpath,
566 * Do not abort on errors during the file tree walk,
567 * Do not try to track inode associations for conflict detection,
568 * Follows mounts,
569 * Does lazy validation of contexts upon use.
570 */
571 expand_realpath = 1;
572 follow_mounts = 1;
573 matchpathcon_flags = MATCHPATHCON_NOTRANS;
574 /* restorecon only */
575 selinux_or_die();
576 }
577
578 set_matchpathcon_flags(matchpathcon_flags);
579
580 opt_complementary = "e::vv:v--p:p--v:v--q:q--v";
581 /* Option order must match OPT_x definitions! */
582 if (applet_name[0] == 'r') { /* restorecon */
583 flags = getopt32(argv, "de:f:ilnpqrsvo:FWR",
584 &exclude_dir, &input_filename, &out_filename, &verbose);
585 } else { /* setfiles */
586 flags = getopt32(argv, "de:f:ilnpqr:svo:FW"
587 IF_FEATURE_SETFILES_CHECK_OPTION("c:"),
588 &exclude_dir, &input_filename, &rootpath, &out_filename,
589 IF_FEATURE_SETFILES_CHECK_OPTION(&policyfile,)
590 &verbose);
591 }
592 argv += optind;
593
594#if ENABLE_FEATURE_SETFILES_CHECK_OPTION
595 if ((applet_name[0] == 's') && (flags & OPT_c)) {
596 FILE *policystream;
597
598 policystream = xfopen_for_read(policyfile);
599 if (sepol_set_policydb_from_file(policystream) < 0) {
600 bb_error_msg_and_die("sepol_set_policydb_from_file on %s", policyfile);
601 }
602 fclose(policystream);
603
604 /* Only process the specified file_contexts file, not
605 * any .homedirs or .local files, and do not perform
606 * context translations. */
607 set_matchpathcon_flags(MATCHPATHCON_BASEONLY |
608 MATCHPATHCON_NOTRANS |
609 MATCHPATHCON_VALIDATE);
610 }
611#endif
612
613 while (exclude_dir)
614 add_exclude(llist_pop(&exclude_dir));
615
616 if (flags & OPT_o) {
617 outfile = stdout;
618 if (NOT_LONE_CHAR(out_filename, '-')) {
619 outfile = xfopen_for_write(out_filename);
620 }
621 }
622 if (applet_name[0] == 'r') { /* restorecon */
623 if (flags & (OPT_r | OPT_R))
624 recurse = 1;
625 } else { /* setfiles */
626 if (flags & OPT_r)
627 rootpathlen = strlen(rootpath);
628 }
629 if (flags & OPT_s) {
630 input_filename = "-";
631 add_assoc = 0;
632 }
633
634 if (applet_name[0] == 's') { /* setfiles */
635 /* Use our own invalid context checking function so that
636 * we can support either checking against the active policy or
637 * checking against a binary policy file. */
638 set_matchpathcon_canoncon(&canoncon);
639 if (!argv[0])
640 bb_show_usage();
641 xstat(argv[0], &sb);
642 if (!S_ISREG(sb.st_mode)) {
643 bb_error_msg_and_die("spec file %s is not a regular file", argv[0]);
644 }
645 /* Load the file contexts configuration and check it. */
646 rc = matchpathcon_init(argv[0]);
647 if (rc < 0) {
648 bb_simple_perror_msg_and_die(argv[0]);
649 }
650 if (nerr)
651 exit(EXIT_FAILURE);
652 argv++;
653 }
654
655 if (input_filename) {
656 ssize_t len;
657 FILE *f = stdin;
658
659 if (NOT_LONE_CHAR(input_filename, '-'))
660 f = xfopen_for_read(input_filename);
661 while ((len = getline(&buf, &buf_len, f)) > 0) {
662 buf[len - 1] = '\0';
663 errors |= process_one(buf);
664 }
665 if (ENABLE_FEATURE_CLEAN_UP)
666 fclose_if_not_stdin(f);
667 } else {
668 if (!argv[0])
669 bb_show_usage();
670 for (i = 0; argv[i]; i++) {
671 errors |= process_one(argv[i]);
672 }
673 }
674
675 if (FLAG_W_warn_no_match)
676 matchpathcon_checkmatches(argv[0]);
677
678 if (ENABLE_FEATURE_CLEAN_UP && outfile)
679 fclose(outfile);
680
681 return errors;
682}
Note: See TracBrowser for help on using the repository browser.