From 397137b8158298cae14efae330abc57ba11630a5 Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Mon, 23 Jul 2007 14:03:30 +0000 Subject: setfiles,restorecon: new SELinux applets by Yuichi Nakamura --- selinux/Config.in | 26 ++ selinux/Kbuild | 2 + selinux/setfiles.c | 702 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 730 insertions(+) create mode 100644 selinux/setfiles.c (limited to 'selinux') diff --git a/selinux/Config.in b/selinux/Config.in index 42bca66d6..77f89c798 100644 --- a/selinux/Config.in +++ b/selinux/Config.in @@ -49,6 +49,14 @@ config MATCHPATHCON 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 + 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 +86,23 @@ config SETENFORCE 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. + Notice: If you built libselinux with -D_FILE_OFFSET_BITS=64, + (It is default in libselinux's Makefile), you _must_ enable + CONFIG_LFS. + +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. Requires libsepol. + endmenu diff --git a/selinux/Kbuild b/selinux/Kbuild index 09cae4d2d..08f49c867 100644 --- a/selinux/Kbuild +++ b/selinux/Kbuild @@ -14,3 +14,5 @@ lib-$(CONFIG_MATCHPATHCON) += matchpathcon.o lib-$(CONFIG_RUNCON) += runcon.o lib-$(CONFIG_SELINUXENABLED) += selinuxenabled.o lib-$(CONFIG_SETENFORCE) += setenforce.o +lib-$(CONFIG_SETFILES) += setfiles.o +lib-$(CONFIG_RESTORECON) += setfiles.o diff --git a/selinux/setfiles.c b/selinux/setfiles.c new file mode 100644 index 000000000..42207fcfb --- /dev/null +++ b/selinux/setfiles.c @@ -0,0 +1,702 @@ +/* + setfiles: based on policycoreutils 2.0.19 + policycoreutils was released under GPL 2. + Port to BusyBox by 2007 Yuichi Nakamura +*/ + +#include "libbb.h" +#if ENABLE_FEATURE_SETFILES_CHECK_OPTION +#include +#endif + +#define MAX_EXCLUDES 50 + +struct edir { + char *directory; + size_t size; +}; + +struct globals { + FILE *outfile; + char *policyfile; + char *rootpath; + int rootpathlen; + unsigned count; + int excludeCtr; + int errors; + int verbose; /* getopt32 uses it, has to be int */ + //smallint force; + //smallint progress; + //smallint debug; + //smallint dry_run; + //smallint quiet; + //smallint ignore_enoent; + //smallint take_log; + //smallint warn_no_match; + smallint recurse; /* Recursive descent */ + smallint follow_mounts; + /* Behavior flags determined based on setfiles vs. restorecon */ + smallint expand_realpath; /* Expand paths via realpath */ + smallint abort_on_error; /* Abort the file tree walk upon an error */ + int add_assoc; /* Track inode associations for conflict detection */ + int matchpathcon_flags; /* Flags to matchpathcon */ + dev_t dev_id; /* Device id where target file exists */ + int nerr; + struct edir excludeArray[MAX_EXCLUDES]; +}; + +#define G (*(struct globals*)&bb_common_bufsiz1) +void BUG_setfiles_globals_too_big(void); +#define INIT_G() do { \ + if (sizeof(G) > COMMON_BUFSIZE) \ + BUG_setfiles_globals_too_big(); \ + /* memset(&G, 0, sizeof(G)); - already is */ \ +} while (0) +#define outfile (G.outfile ) +#define policyfile (G.policyfile ) +#define rootpath (G.rootpath ) +#define rootpathlen (G.rootpathlen ) +#define count (G.count ) +#define excludeCtr (G.excludeCtr ) +#define errors (G.errors ) +#define verbose (G.verbose ) +//#define force (G.force ) +//#define progress (G.progress ) +//#define debug (G.debug ) +//#define dry_run (G.dry_run ) +//#define quiet (G.quiet ) +//#define ignore_enoent (G.ignore_enoent ) +//#define take_log (G.take_log ) +//#define warn_no_match (G.warn_no_match ) +#define recurse (G.recurse ) +#define follow_mounts (G.follow_mounts ) +#define expand_realpath (G.expand_realpath ) +#define abort_on_error (G.abort_on_error ) +#define add_assoc (G.add_assoc ) +#define matchpathcon_flags (G.matchpathcon_flags) +#define dev_id (G.dev_id ) +#define nerr (G.nerr ) +#define excludeArray (G.excludeArray ) + +/* Must match getopt32 string! */ +enum { + OPT_d = (1 << 0), + OPT_e = (1 << 1), + OPT_f = (1 << 2), + OPT_i = (1 << 3), + OPT_l = (1 << 4), + OPT_n = (1 << 5), + OPT_p = (1 << 6), + OPT_q = (1 << 7), + OPT_r = (1 << 8), + OPT_s = (1 << 9), + OPT_v = (1 << 10), + OPT_o = (1 << 11), + OPT_F = (1 << 12), + OPT_W = (1 << 13), + OPT_c = (1 << 14), /* c only for setfiles */ + OPT_R = (1 << 14), /* R only for restorecon */ +}; +#define FLAG_d_debug (option_mask32 & OPT_d) +#define FLAG_e (option_mask32 & OPT_e) +#define FLAG_f (option_mask32 & OPT_f) +#define FLAG_i_ignore_enoent (option_mask32 & OPT_i) +#define FLAG_l_take_log (option_mask32 & OPT_l) +#define FLAG_n_dry_run (option_mask32 & OPT_n) +#define FLAG_p_progress (option_mask32 & OPT_p) +#define FLAG_q_quiet (option_mask32 & OPT_q) +#define FLAG_r (option_mask32 & OPT_r) +#define FLAG_s (option_mask32 & OPT_s) +#define FLAG_v (option_mask32 & OPT_v) +#define FLAG_o (option_mask32 & OPT_o) +#define FLAG_F_force (option_mask32 & OPT_F) +#define FLAG_W_warn_no_match (option_mask32 & OPT_W) +#define FLAG_c (option_mask32 & OPT_c) +#define FLAG_R (option_mask32 & OPT_R) + + +static void qprintf(const char *fmt, ...) +{ + /* quiet, do nothing */ +} + +static void inc_err(void) +{ + nerr++; + if (nerr > 9 && !FLAG_d_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", directory); + return 1; + } + if (lstat(directory, &sb)) { + bb_error_msg("directory \"%s\" not found, ignoring", directory); + return 0; + } + if ((sb.st_mode & S_IFDIR) == 0) { + bb_error_msg("\"%s\" is not a directory: mode %o, ignoring", + directory, sb.st_mode); + return 0; + } + if (excludeCtr == MAX_EXCLUDES) { + bb_error_msg("maximum excludes %d exceeded", MAX_EXCLUDES); + return 1; + } + + len = strlen(directory); + while (len > 1 && directory[len - 1] == '/') { + len--; + } + excludeArray[excludeCtr].directory = xstrndup(directory, len); + 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]; + char *tmp_path = xstrdup(name); + + if (excludeCtr > 0) { + if (exclude(name)) { + goto err; + } + } + ret = lstat(name, sb); + if (ret) { + if (FLAG_i_ignore_enoent && errno == ENOENT) { + free(tmp_path); + return 0; + } + bb_error_msg("stat(%s)", name); + goto err; + } + + if (expand_realpath) { + if (S_ISLNK(sb->st_mode)) { + char *p = NULL; + char *file_sep; + + size_t len = 0; + + if (verbose > 1) + bb_error_msg("warning! %s refers to a symbolic link, not following last component", name); + + file_sep = strrchr(tmp_path, '/'); + if (file_sep == tmp_path) { + file_sep++; + p[0] = '\0'; + p = path; + } else if (file_sep) { + *file_sep++ = '\0'; + 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", name); + goto err; + } + p += len; + /* ensure trailing slash of directory name */ + if (len == 0 || p[-1] != '/') { + *p++ = '/'; + } + strcpy(p, file_sep); + name = path; + if (excludeCtr > 0 && exclude(name)) + goto err; + + } else { + char *p; + p = realpath(name, path); + if (!p) { + bb_perror_msg("realpath(%s)", name); + goto err; + } + name = p; + if (excludeCtr > 0 && exclude(name)) + goto err; + } + } + + /* name will be what is matched in the policy */ + if (NULL != rootpath) { + if (0 != strncmp(rootpath, name, rootpathlen)) { + bb_error_msg("%s is not located in %s", + name, rootpath); + goto err; + } + name += rootpathlen; + } + + free(tmp_path); + if (rootpath != NULL && name[0] == '\0') + /* this is actually the root dir of the alt root */ + return matchpathcon_index("/", sb->st_mode, con); + return matchpathcon_index(name, sb->st_mode, con); + err: + free(tmp_path); + return -1; +} + +/* 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) +{ + if (FLAG_F_force) + return 0; + if (!a || !b) + return 0; + a = strchr(a, ':'); /* Rest of the context after the user */ + b = strchr(b, ':'); + if (!a || !b) + return 0; + return (strcmp(a, b) == 0); +} + +static int restore(const char *file) +{ + char *my_file = xstrdup(file); + char *my_file_orig = my_file; + struct stat my_sb; + int i, j, ret; + char *context = NULL; + char *newcon = NULL; + int user_only_changed = 0; + size_t len = strlen(my_file); + int retval = 0; + + /* 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. */ + goto out; + + + if (FLAG_p_progress) { + count++; + if (count % 0x400 == 0) { /* every 1024 times */ + count = (count % (80*0x400)); + if (count == 0) + fputc('\n', stdout); + fputc('*', 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 (FLAG_d_debug) + printf("%s: %s matched by %s\n", applet_name, my_file, newcon); + + /* Get the current context of the file. */ + ret = lgetfilecon_raw(my_file, &context); + if (ret < 0) { + if (errno == ENODATA) { + context = NULL; /* paranoia */ + } else { + bb_perror_msg("lgetfilecon_raw on %s", 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) && !FLAG_F_force)) { + goto out; + } + + if (!FLAG_F_force && context && (is_context_customizable(context) > 0)) { + if (verbose > 1) { + bb_error_msg("skipping %s. %s is customizable_types", + my_file, 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", + applet_name, my_file, context ?: "", newcon); + } + } + + if (FLAG_l_take_log && !user_only_changed) { + if (context) + bb_info_msg("relabeling %s from %s to %s", my_file, context, newcon); + else + bb_info_msg("labeling %s to %s", my_file, newcon); + } + + if (outfile && !user_only_changed) + fprintf(outfile, "%s\n", my_file); + + /* + * Do not relabel the file if -n was used. + */ + if (FLAG_n_dry_run || 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 err; + } + + out: + freecon(context); + freecon(newcon); + free(my_file_orig); + return retval; + err: + retval--; /* -1 */ + goto out; +} + +/* + * Apply the last matching specification to a file. + * This function is called by recursive_action on each file during + * the directory traversal. + */ +static int apply_spec(const char *file, + struct stat *sb, void *userData, int depth) +{ + if (!follow_mounts) { + /* setfiles does not process across different mount points */ + if (sb->st_dev != dev_id) { + return SKIP; + } + } + errors |= restore(file); + if (abort_on_error && errors) + return FALSE; + return TRUE; +} + + +static int canoncon(const char *path, unsigned lineno, char **contextp) +{ + static const char err_msg[] = "%s: line %u has invalid context %s"; + + char *tmpcon; + char *context = *contextp; + int invalid = 0; + +#if ENABLE_FEATURE_SETFILES_CHECK_OPTION + if (policyfile) { + if (sepol_check_context(context) >= 0) + return 0; + /* Exit immediately if we're in checking mode. */ + bb_error_msg_and_die(err_msg, path, lineno, context); + } +#endif + + if (security_canonicalize_context_raw(context, &tmpcon) < 0) { + if (errno != ENOENT) { + invalid = 1; + inc_err(); + } + } else { + free(context); + *contextp = tmpcon; + } + + if (invalid) { + bb_error_msg(err_msg, path, lineno, context); + } + + return invalid; +} + +static int process_one(char *name) +{ + struct stat sb; + int rc; + + rc = lstat(name, &sb); + if (rc < 0) { + if (FLAG_i_ignore_enoent && errno == ENOENT) + return 0; + bb_perror_msg("stat(%s)", name); + goto err; + } + dev_id = sb.st_dev; + + if (S_ISDIR(sb.st_mode) && recurse) { + if (recursive_action(name, + ACTION_RECURSE, + apply_spec, + apply_spec, + NULL, 0) != TRUE) { + bb_error_msg("error while labeling %s", name); + goto err; + } + } else { + rc = restore(name); + if (rc) + goto err; + } + + out: + if (add_assoc) { + if (FLAG_q_quiet) + set_matchpathcon_printf(&qprintf); + matchpathcon_filespec_eval(); + set_matchpathcon_printf(NULL); + matchpathcon_filespec_destroy(); + } + + return rc; + + err: + rc = -1; + goto out; +} + +int setfiles_main(int argc, char **argv); +int setfiles_main(int argc, char **argv) +{ + struct stat sb; + int rc, i = 0; + const char *input_filename = NULL; + int use_input_file = 0; + char *buf = NULL; + size_t buf_len; + int flags; + llist_t *exclude_dir = NULL; + char *out_filename = NULL; + + INIT_G(); + + if (applet_name[0] == 's') { /* "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. + */ + recurse = 1; + abort_on_error = 1; + add_assoc = 1; + /* follow_mounts = 0; - already is */ + 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. + */ + expand_realpath = 1; + follow_mounts = 1; + matchpathcon_flags = MATCHPATHCON_NOTRANS; + /* restorecon only */ + selinux_or_die(); + } + + set_matchpathcon_flags(matchpathcon_flags); + + opt_complementary = "e::vv:v--p:p--v"; + /* Option order must match OPT_x definitions! */ + if (applet_name[0] == 'r') { /* restorecon */ + flags = getopt32(argc, argv, "de:f:ilnpqrsvo:FWR", + &exclude_dir, &input_filename, &out_filename, &verbose); + } else { /* setfiles */ + flags = getopt32(argc, argv, "de:f:ilnpqr:svo:FW" + USE_FEATURE_SETFILES_CHECK_OPTION("c:"), + &exclude_dir, &input_filename, &rootpath, &out_filename, + USE_FEATURE_SETFILES_CHECK_OPTION(&policyfile,) + &verbose); + } + +#if ENABLE_FEATURE_SETFILES_CHECK_OPTION + if ((applet_name[0] == 's') && (flags & OPT_c)) { + FILE *policystream; + + 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); + } +#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) { + // dry_run = 1; + //} + if (flags & OPT_o) { + outfile = stdout; + if (NOT_LONE_CHAR(out_filename, '-')) { + outfile = xfopen(out_filename, "w"); + } + } + //if (flags & OPT_q) { + // quiet = 1; + //} + if (applet_name[0] == 'r') { /* restorecon */ + if (flags & (OPT_r | OPT_R)) + recurse = 1; + } else { /* setfiles */ + if (flags & OPT_r) + rootpathlen = strlen(rootpath); + } + if (flags & OPT_s) { + use_input_file = 1; + input_filename = "-"; + add_assoc = 0; + } + //if (flags & OPT_p) { + // progress = 1; + //} + //if (flags & OPT_W) { + // warn_no_match = 1; + //} + + if (applet_name[0] == 's') { /* setfiles */ + /* 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("%s", 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("%s", argv[optind]); + } + + optind++; + + if (nerr) + exit(1); + } + + if (use_input_file) { + ssize_t len; + FILE *f = stdin; + + if (NOT_LONE_CHAR(input_filename, '-')) + f = xfopen(input_filename, "r"); + while ((len = getline(&buf, &buf_len, f)) > 0) { + buf[len - 1] = '\0'; + errors |= process_one(buf); + } + if (ENABLE_FEATURE_CLEAN_UP) + fclose_if_not_stdin(f); + } else { + if (optind >= argc) + bb_show_usage(); + for (i = optind; i < argc; i++) { + errors |= process_one(argv[i]); + } + } + + if (FLAG_W_warn_no_match) + matchpathcon_checkmatches(argv[0]); + + if (ENABLE_FEATURE_CLEAN_UP && outfile) + fclose(outfile); + + return errors; +} -- cgit v1.2.3