Index: include/usage.h =================================================================== --- include/usage.h (revision 18907) +++ include/usage.h (working copy) @@ -2774,6 +2774,19 @@ #define resize_full_usage \ "Resize the screen" +#define restorecon_trivial_usage \ + "[-iFnrRv] [-e excludedir ] [-o filename ] [-f filename | pathname ]" +#define restorecon_full_usage \ + "Reset security contexts of files in pathname\n" \ + "\n -i ignore files that do not exist" \ + "\n -f infilename infilename contains a list of files to be processed by application. Use - for stdin" \ + "\n -e directory directory to exclude (repeat option for more than one directory.)" \ + "\n -R,-r change files and directories file labels recursively" \ "\n -n don't change any file labels" \ + "\n -o outfilename save list of files with incorrect context in outfilename" \ + "\n -v show changes in file labels" \ + "\n -vv show changes in file labels, if type, role, or user are changing." \ + "\n -F Force reset of context to match file_context for customizable files, or the user section, if it has changed." + #define rm_trivial_usage \ "[OPTION]... FILE..." #define rm_full_usage \ @@ -2938,6 +2951,25 @@ "[ Enforcing | Permissive | 1 | 0 ]" #define setenforce_full_usage +#define setfiles_trivial_usage \ + "[-dnpqsvW] [-o filename] [-r alt_root_path ] [-c policyfile] spec_file pathname" + +#define setfiles_full_usage \ + "reset file contexts under pathname according to spec_file:" \ + "\n -c check the validity of the contexts against the specified binary policy" \ + "\n -d show what specification matched each file" \ + "\n -l log changes in file labels to syslog" \ + "\n -n don't change any file labels" \ + "\n -q suppress no-error output" \ + "\n -r use an altenate root path" \ + "\n -e directory to exclude (repeat option for more than one directory)" \ + "\n -F Force reset of context to match file_context for customizable files" \ + "\n -o save list of files with incorrect context in filename" \ + "\n -s take a list of files from standard input instead of using a pathname on the command line" \ + "\n -v show changes in file labels, if type or role are changing" \ + "\n -vv show changes in file labels, if type, role, or user are changing." \ + "\n -W display warnings about entries that had no matching files." + #define setkeycodes_trivial_usage \ "SCANCODE KEYCODE ..." #define setkeycodes_full_usage \ Index: include/applets.h =================================================================== --- include/applets.h (revision 18907) +++ include/applets.h (working copy) @@ -269,6 +269,7 @@ USE_RENICE(APPLET(renice, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) USE_RESET(APPLET(reset, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) USE_RESIZE(APPLET(resize, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_RESTORECON(APPLET(restorecon, _BB_DIR_SBIN, _BB_SUID_NEVER)) USE_RM(APPLET_NOFORK(rm, rm, _BB_DIR_BIN, _BB_SUID_NEVER, rm)) USE_RMDIR(APPLET_NOFORK(rmdir, rmdir, _BB_DIR_BIN, _BB_SUID_NEVER, rmdir)) USE_RMMOD(APPLET(rmmod, _BB_DIR_SBIN, _BB_SUID_NEVER)) @@ -287,6 +288,7 @@ USE_SETARCH(APPLET(setarch, _BB_DIR_BIN, _BB_SUID_NEVER)) USE_SETCONSOLE(APPLET(setconsole, _BB_DIR_SBIN, _BB_SUID_NEVER)) USE_SETENFORCE(APPLET(setenforce, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_SETFILES(APPLET(setfiles, _BB_DIR_SBIN, _BB_SUID_NEVER)) USE_SETKEYCODES(APPLET(setkeycodes, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) USE_SETLOGCONS(APPLET(setlogcons, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) USE_SETSID(APPLET(setsid, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) Index: selinux/setfiles.c =================================================================== --- selinux/setfiles.c (revision 0) +++ selinux/setfiles.c (revision 0) @@ -0,0 +1,722 @@ +/* + setfiles: Based on policycoreutils 2.0.19 + policycoreutils was released under GPL 2. + Port to BusyBox by 2007 Yuichi Nakamura +*/ + +#include "libbb.h" +#define __USE_XOPEN_EXTENDED 1 /* nftw */ +#include +#if ENABLE_FEATURE_SETFILES_CHECK_OPTION +#include +#endif + +static FILE *outfile = NULL; +static int force = 0; +#define STAT_BLOCK_SIZE 1 +static int pipe_fds[2] = { -1, -1 }; +static int progress = 0; +static unsigned long long count = 0; + +#define MAX_EXCLUDES 50 +static int excludeCtr = 0; +struct edir { + char *directory; + size_t size; +}; +static struct edir excludeArray[MAX_EXCLUDES]; + +/* + * Command-line options. + */ +static char *policyfile = NULL; +static int debug = 0; +static int change = 1; +static int quiet = 0; +static int ignore_enoent; +static int verbose = 0; +static int take_log = 0; +static int warn_no_match = 0; +static char *rootpath = NULL; +static int rootpathlen = 0; +static int recurse; /* Recursive descent. */ +static int errors; + +static char *progname; + +#define SETFILES "setfiles" +#define RESTORECON "restorecon" +static int iamrestorecon; + +/* Behavior flags determined based on setfiles vs. restorecon */ +static int expand_realpath; /* Expand paths via realpath. */ +static int abort_on_error; /* Abort the file tree walk upon an error. */ +static int add_assoc; /* Track inode associations for conflict detection. */ +static int nftw_flags; /* Flags to nftw, e.g. follow links, follow mounts */ +static int matchpathcon_flags; /* Flags to matchpathcon */ + +static void qprintf(const char *fmt, ...) +{ + /*quiet,do nothing*/ +} + +static int nerr = 0; +static void inc_err(void) +{ + nerr++; + if (nerr > 9 && !debug) { + bb_error_msg_and_die("Exiting after 10 errors."); + } +} + +static int add_exclude(const char *directory) +{ + struct stat sb; + size_t len = 0; + if (directory == NULL || directory[0] != '/') { + bb_error_msg("Full path required for exclude: %s.\n", directory); + return 1; + } + if (lstat(directory, &sb)) { + bb_error_msg("Directory \"%s\" not found, ignoring.\n", directory); + return 0; + } + if ((sb.st_mode & S_IFDIR) == 0) { + bb_error_msg("\"%s\" is not a Directory: mode %o, ignoring\n", + directory, sb.st_mode); + return 0; + } + + if (excludeCtr == MAX_EXCLUDES) { + bb_error_msg("Maximum excludes %d exceeded.\n", MAX_EXCLUDES); + return 1; + } + + len = strlen(directory); + while (len > 1 && directory[len - 1] == '/') { + len--; + } + excludeArray[excludeCtr].directory = strndup(directory, len); + + if (excludeArray[excludeCtr].directory == NULL) { + bb_error_msg_and_die("Out of memory.\n"); + return 1; + } + excludeArray[excludeCtr++].size = len; + + return 0; +} + +static int exclude(const char *file) +{ + int i = 0; + for (i = 0; i < excludeCtr; i++) { + if (strncmp + (file, excludeArray[i].directory, + excludeArray[i].size) == 0) { + if (file[excludeArray[i].size] == 0 + || file[excludeArray[i].size] == '/') { + return 1; + } + } + } + return 0; +} + +static int match(const char *name, struct stat *sb, char **con) +{ + int ret; + char path[PATH_MAX + 1]; + + if (excludeCtr > 0) { + if (exclude(name)) { + return -1; + } + } + ret = lstat(name, sb); + if (ret) { + if (ignore_enoent && errno == ENOENT) + return 0; + bb_error_msg("stat(%s)", name); + return -1; + } + + if (expand_realpath) { + if (S_ISLNK(sb->st_mode)) { + char *p = NULL; + char *file_sep; + char *tmp_path = strdupa(name); + size_t len = 0; + + if (verbose > 1) + bb_error_msg("Warning! %s refers to a symbolic link, not following last component.\n", name); + + if (!tmp_path) { + bb_perror_msg("strdupa(%s)", name); + return -1; + } + file_sep = strrchr(tmp_path, '/'); + if (file_sep == tmp_path) { + file_sep++; + p = strcpy(path, ""); + } else if (file_sep) { + *file_sep = 0; + file_sep++; + p = realpath(tmp_path, path); + } else { + file_sep = tmp_path; + p = realpath("./", path); + } + if (p) + len = strlen(p); + if (!p || len + strlen(file_sep) + 2 > PATH_MAX) { + bb_perror_msg("realpath(%s) failed\n", name); + return -1; + } + p += len; + /* ensure trailing slash of directory name */ + if (len == 0 || *(p - 1) != '/') { + *p = '/'; + p++; + } + strcpy(p, file_sep); + name = path; + if (excludeCtr > 0 && exclude(name)) + return -1; + } else { + char *p; + p = realpath(name, path); + if (!p) { + bb_perror_msg("realpath(%s)", name); + return -1; + } + name = p; + if (excludeCtr > 0 && exclude(name)) + return -1; + } + } + + /* name will be what is matched in the policy */ + if (NULL != rootpath) { + if (0 != strncmp(rootpath, name, rootpathlen)) { + bb_error_msg("%s: %s is not located in %s\n", + progname, name, rootpath); + return -1; + } + name += rootpathlen; + } + + if (rootpath != NULL && name[0] == '\0') + /* this is actually the root dir of the alt root */ + return matchpathcon_index("/", sb->st_mode, con); + else + return matchpathcon_index(name, sb->st_mode, con); +} + +/* Compare two contexts to see if their differences are "significant", + * or whether the only difference is in the user. */ +static int only_changed_user(const char *a, const char *b) +{ + char *rest_a, *rest_b; /* Rest of the context after the user */ + if (force) + return 0; + if (!a || !b) + return 0; + rest_a = strchr(a, ':'); + rest_b = strchr(b, ':'); + if (!rest_a || !rest_b) + return 0; + return (strcmp(rest_a, rest_b) == 0); +} + +static int restore(const char *file) +{ + char *my_file = strdupa(file); + struct stat my_sb; + int i, j, ret; + char *context, *newcon; + int user_only_changed = 0; + size_t len = strlen(my_file); + + /* Skip the extra slashes at the beginning and end, if present. */ + if (file[0] == '/' && file[1] == '/') + my_file++; + if (len > 1 && my_file[len - 1] == '/') + my_file[len - 1] = 0; + + i = match(my_file, &my_sb, &newcon); + + if (i < 0) + /* No matching specification. */ + return 0; + + if (progress) { + count++; + if (count % 80000 == 0) { + fprintf(stdout, "\n"); + fflush(stdout); + } + if (count % 1000 == 0) { + fprintf(stdout, "*"); + fflush(stdout); + } + } + + /* + * Try to add an association between this inode and + * this specification. If there is already an association + * for this inode and it conflicts with this specification, + * then use the last matching specification. + */ + if (add_assoc) { + j = matchpathcon_filespec_add(my_sb.st_ino, i, my_file); + if (j < 0) + goto err; + + if (j != i) { + /* There was already an association and it took precedence. */ + goto out; + } + } + + if(debug) + printf("%s: %s matched by %s\n", progname, my_file, newcon); + + /* Get the current context of the file. */ + ret = lgetfilecon_raw(my_file, &context); + if (ret < 0) { + if (errno == ENODATA) { + context = NULL; + } else { + bb_perror_msg("lgetfilecon_raw on %s\n", my_file); + goto err; + } + user_only_changed = 0; + } else + user_only_changed = only_changed_user(context, newcon); + + /* + * Do not relabel the file if the matching specification is + * <> or the file is already labeled according to the + * specification. + */ + if ((strcmp(newcon, "<>") == 0) || + (context && (strcmp(context, newcon) == 0) && !force)) { + freecon(context); + goto out; + } + + if (!force && context && (is_context_customizable(context) > 0)) { + if (verbose > 1) { + bb_error_msg("%s: %s not reset customized by admin to %s\n", + progname, my_file, context); + } + freecon(context); + goto out; + } + + if (verbose) { + /* If we're just doing "-v", trim out any relabels where + * the user has changed but the role and type are the + * same. For "-vv", emit everything. */ + if (verbose > 1 || !user_only_changed) { + bb_info_msg("%s reset %s context %s->%s\n", + progname, my_file, context ?: "", newcon); + } + } + + if (take_log && !user_only_changed) { + if (context) + bb_info_msg("relabeling %s from %s to %s\n", my_file, context, newcon); + else + bb_info_msg("labeling %s to %s\n", my_file, newcon); + } + + if (outfile && !user_only_changed) + fprintf(outfile, "%s\n", my_file); + + if (context) + freecon(context); + + /* + * Do not relabel the file if -n was used. + */ + if (!change || user_only_changed) + goto out; + + /* + * Relabel the file to the specified context. + */ + ret = lsetfilecon(my_file, newcon); + if (ret) { + bb_perror_msg("lsetfileconon(%s,%s)", my_file, newcon); + goto out; + } + out: + freecon(newcon); + return 0; + err: + freecon(newcon); + return -1; +} + +/* + * Apply the last matching specification to a file. + * This function is called by nftw on each file during + * the directory traversal. + */ +static int apply_spec(const char *file, + const struct stat *sb_unused __attribute__ ((unused)), + int flag, struct FTW *s_unused __attribute__ ((unused))) +{ + char buf[STAT_BLOCK_SIZE]; + if (pipe_fds[0] != -1 + && read(pipe_fds[0], buf, STAT_BLOCK_SIZE) != STAT_BLOCK_SIZE) { + bb_error_msg("Read error on pipe."); + pipe_fds[0] = -1; + } + + if (flag == FTW_DNR) { + bb_error_msg("%s: unable to read directory %s", progname, file); + return 0; + } + + errors |= restore(file); + if (abort_on_error && errors) + return -1; + return 0; +} + + +static int canoncon(const char *path, unsigned lineno, char **contextp) +{ + char *context = *contextp, *tmpcon; + int valid = 1; + char err_msg[]="%s: line %u has invalid context %s"; + +#if ENABLE_FEATURE_SETFILES_CHECK_OPTION + if (policyfile) { + valid = (sepol_check_context(context) >= 0); + if (!valid) { + /* Exit immediately if we're in checking mode. */ + bb_error_msg_and_die(err_msg, path, lineno, context); + } else { + return !valid; + } + } +#endif + + if (security_canonicalize_context_raw(context, &tmpcon) < 0) { + if (errno != ENOENT) { + valid = 0; + inc_err(); + } + } else { + free(context); + *contextp = tmpcon; + } + + if (!valid) { + bb_error_msg(err_msg, path, lineno, context); + } + + return !valid; +} + +static int pre_stat(const char *file_unused __attribute__ ((unused)), + const struct stat *sb_unused __attribute__ ((unused)), + int flag_unused __attribute__ ((unused)), + struct FTW *s_unused __attribute__ ((unused))) +{ + char buf[STAT_BLOCK_SIZE]; + if (write(pipe_fds[1], buf, STAT_BLOCK_SIZE) != STAT_BLOCK_SIZE) { + bb_error_msg_and_die("Error writing to stat pipe, child exiting."); + } + return 0; +} + +static int process_one(char *name) +{ + struct stat sb; + int rc; + + rc = lstat(name, &sb); + if (rc < 0) { + if (ignore_enoent && errno == ENOENT) + return 0; + bb_perror_msg("stat(%s)", name); + goto err; + } + + if (S_ISDIR(sb.st_mode) && recurse) { + if (pipe(pipe_fds) < 0) { + bb_perror_msg("pipe on %s", name); + goto err; + } + rc = fork(); + if (rc < 0) { + bb_error_msg("fork on %s", name); + goto err; + } + if (rc == 0) { + /* Child: pre-stat the files. */ + close(pipe_fds[0]); + nftw(name, pre_stat, 1024, nftw_flags); + exit(0); + } + /* Parent: Check and label the files. */ + rc = 0; + close(pipe_fds[1]); + if (nftw(name, apply_spec, 1024, nftw_flags)) { + bb_error_msg("error while labeling %s\n", name); + goto err; + } + } else { + rc = restore(name); + if (rc) + goto err; + } + +out: + if (add_assoc) { + if (quiet) + set_matchpathcon_printf(&qprintf); + matchpathcon_filespec_eval(); + set_matchpathcon_printf(NULL); + matchpathcon_filespec_destroy(); + } + + return rc; + +err: + rc = -1; + goto out; +} + +#define OPT_c (1<<0) +#define OPT_d (1<<1) +#define OPT_e (1<<2) +#define OPT_f (1<<3) +#define OPT_i (1<<4) +#define OPT_l (1<<5) +#define OPT_n (1<<6) +#define OPT_p (1<<7) +#define OPT_q (1<<8) +#define OPT_r (1<<9) +#define OPT_s (1<<10) +#define OPT_v (1<<11) +#define OPT_o (1<<12) +#define OPT_F (1<<13) +#define OPT_R (1<<14) +#define OPT_W (1<<15) + +int setfiles_main(int argc, char **argv); +int setfiles_main(int argc, char **argv) +{ + struct stat sb; + int rc, i = 0; + char *input_filename = NULL; + int use_input_file = 0; + char *buf = NULL; + size_t buf_len; + char *base; + int flags; + llist_t *exclude_dir = NULL; + char *out_filename = NULL; + + memset(excludeArray, 0, sizeof(excludeArray)); + + progname = xstrdup(argv[0]); + + base = basename(progname); + + if (!strcmp(base, SETFILES)) { + /* + * setfiles: + * Recursive descent, + * Does not expand paths via realpath, + * Aborts on errors during the file tree walk, + * Try to track inode associations for conflict detection, + * Does not follow mounts, + * Validates all file contexts at init time. + */ + iamrestorecon = 0; + recurse = 1; + expand_realpath = 0; + abort_on_error = 1; + add_assoc = 1; + nftw_flags = FTW_PHYS | FTW_MOUNT; + matchpathcon_flags = MATCHPATHCON_VALIDATE | MATCHPATHCON_NOTRANS; + } else { + /* + * restorecon: + * No recursive descent unless -r/-R, + * Expands paths via realpath, + * Do not abort on errors during the file tree walk, + * Do not try to track inode associations for conflict detection, + * Follows mounts, + * Does lazy validation of contexts upon use. + */ + iamrestorecon = 1; + recurse = 0; + expand_realpath = 1; + abort_on_error = 0; + add_assoc = 0; + nftw_flags = FTW_PHYS; + matchpathcon_flags = MATCHPATHCON_NOTRANS; + /* restorecon only */ + selinux_or_die(); + } + + set_matchpathcon_flags(matchpathcon_flags); + + opt_complementary = "e::vvv--p:p--v"; + if (iamrestorecon) { + flags = getopt32(argc, argv, "c:de:f:ilnpqrsvo:FRW", &policyfile, &exclude_dir, &input_filename, &out_filename, &verbose); + } else { + /*setfiles*/ + flags = getopt32(argc, argv, "c:de:f:ilnpqr:svo:FRW", &policyfile, &exclude_dir, &input_filename, &rootpath, &out_filename, &verbose); + } + + if (flags & OPT_c) { +#if ENABLE_FEATURE_SETFILES_CHECK_OPTION + FILE *policystream; + if (iamrestorecon) + bb_show_usage(); + policystream = xfopen(policyfile, "r"); + + if (sepol_set_policydb_from_file(policystream) < 0) { + bb_error_msg_and_die("sepol_set_policydb_from_file on %s", policyfile); + } + fclose(policystream); + + /* Only process the specified file_contexts file, not + any .homedirs or .local files, and do not perform + context translations. */ + set_matchpathcon_flags(MATCHPATHCON_BASEONLY | + MATCHPATHCON_NOTRANS | + MATCHPATHCON_VALIDATE); + +#else + bb_error_msg_and_die("-c is not supported"); +#endif + } + + if (flags & OPT_d) { + debug = 1; + } + + if (flags & OPT_e) { + if (exclude_dir == NULL){ + bb_show_usage(); + } + + while (exclude_dir) { + if (add_exclude(llist_pop(&exclude_dir))) + exit(1); + } + } + + if (flags & OPT_i) { + ignore_enoent = 1; + } + + if (flags & OPT_l) { + take_log = 1; + } + + if (flags & OPT_F) { + force = 1; + } + + if (flags & OPT_n) { + change = 0; + } + + if (flags & OPT_o) { + if (strcmp(out_filename, "-") == 0) { + outfile = stdout; + } else { + outfile = xfopen(out_filename, "w"); + } + } + + if (iamrestorecon) { + if (flags & (OPT_r | OPT_R)) + recurse = 1; + } else { + if (flags & OPT_R) + bb_show_usage(); + if (flags & OPT_r) + rootpathlen = strlen(rootpath); + } + + if (flags & OPT_s) { + use_input_file = 1; + input_filename = xstrdup("-"); + add_assoc = 0; + } + + if (flags & OPT_p) { + progress = 1; + } + + if (flags & OPT_W) { + warn_no_match = 1; + } + + if (!iamrestorecon) { + /* Use our own invalid context checking function so that + we can support either checking against the active policy or + checking against a binary policy file. */ + set_matchpathcon_canoncon(&canoncon); + if (argc == 1) + bb_show_usage(); + if (stat(argv[optind], &sb) < 0) { + bb_perror_msg_and_die(argv[optind]); + } + if (!S_ISREG(sb.st_mode)) { + bb_error_msg_and_die("spec file %s is not a regular file.", argv[optind]); + } + + /* Load the file contexts configuration and check it. */ + rc = matchpathcon_init(argv[optind]); + if (rc < 0) { + bb_perror_msg_and_die(argv[optind]); + } + + optind++; + + if (nerr) + exit(1); + } + + if (use_input_file) { + FILE *f = stdin; + ssize_t len; + if (strcmp(input_filename, "-") != 0) + f = xfopen(input_filename, "r"); + + while ((len = getline(&buf, &buf_len, f)) > 0) { + buf[len - 1] = 0; + errors |= process_one(buf); + } + if (strcmp(input_filename, "-") != 0) + fclose(f); + } else { + if (optind >= argc) + bb_show_usage(); + for (i = optind; i < argc; i++) { + errors |= process_one(argv[i]); + } + } + + if (warn_no_match) + matchpathcon_checkmatches(argv[0]); + + if (outfile) + fclose(outfile); + + exit(errors); +} + Index: selinux/restorecon.c =================================================================== --- selinux/restorecon.c (revision 0) +++ selinux/restorecon.c (revision 0) @@ -0,0 +1,11 @@ +/* + restorecon + By Yuchi Nakamura +*/ + +int setfiles_main(int argc, char **argv); +int restorecon_main(int argc, char **argv); + +int restorecon_main(int argc, char **argv) { + return setfiles_main(argc,argv); +} Index: selinux/Config.in =================================================================== --- selinux/Config.in (revision 18907) +++ selinux/Config.in (working copy) @@ -49,6 +49,13 @@ Enable support to get default security context of the specified path from the file contexts configuration. +config RESTORECON + bool "restorecon" + default n + depends on SELINUX && SETFILES + help + Enable support to relabel files. The feature is almost the same as setfiles, but usage is a little different. + config RUNCON bool "runcon" default n @@ -78,5 +85,20 @@ help Enable support to modify the mode SELinux is running in. +config SETFILES + bool "setfiles" + default n + depends on SELINUX + help + Enable support to modify to relabel files. + +config FEATURE_SETFILES_CHECK_OPTION + bool "Enable check option" + default n + depends on SETFILES + help + Support "-c" option(check the validity of the contexts against + the specified binary policy) for setfiles. It requires libsepol. + endmenu Index: selinux/Kbuild =================================================================== --- selinux/Kbuild (revision 18907) +++ selinux/Kbuild (working copy) @@ -11,6 +11,8 @@ lib-$(CONFIG_GETSEBOOL) += getsebool.o lib-$(CONFIG_LOAD_POLICY) += load_policy.o lib-$(CONFIG_MATCHPATHCON) += matchpathcon.o +lib-$(CONFIG_RESTORECON) += restorecon.o lib-$(CONFIG_RUNCON) += runcon.o lib-$(CONFIG_SELINUXENABLED) += selinuxenabled.o lib-$(CONFIG_SETENFORCE) += setenforce.o +lib-$(CONFIG_SETFILES) += setfiles.o