From 85ddc626be5fd202f04aa8ced398b5119174b556 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Sat, 20 Jan 2007 16:10:43 +0100 Subject: [PATCH] cp, mv, install: add SELinux support, but unlike with the Red Hat patch, mv and cp do not provide the "-Z context" option. * src/copy.c: Include . (restore_default_fscreatecon): New function. (copy_reg): Make cp --preserve=context work for existing destination. (copy_internal): Likewise for new destinations. * src/copy.h (cp_options) [preserve_security_context]: New member. * src/cp.c: Include . (selinux_enabled): New global. (usage): Mention new --preserve=context option. (PRESERVE_CONTEXT): Define/use. (decode_preserve_arg): Handle PRESERVE_CONTEXT. (main): Remove an obsolete comment. If --preserve=context is specified on a system without SELinux enabled, give a diagnostic and fail. * src/mv.c: Include . Set x->preserve_security_context if SELinux is enabled. * src/install.c: Accept new "-Z, --context=C" option. Accept --preserve-context option (but not -P option). Accept alternate spelling: --preserve_context, for now. Include and "quotearg.h". (selinux_enabled, use_default_selinux_context): New globals. (PRESERVE_CONTEXT_OPTION): Define. (cp_option_init): Default: do not preserve security context. (setdefaultfilecon): New function. (main): Honor new options. * src/Makefile.am (mv_LDADD, cp_LDADD, ginstall_LDADD): Add $(LIB_SELINUX). --- ChangeLog-selinux | 31 +++++++++++++++- src/Makefile.am | 6 ++-- src/copy.c | 79 ++++++++++++++++++++++++++++++++++++++++- src/copy.h | 5 ++- src/cp.c | 30 ++++++++++++---- src/install.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/mv.c | 4 +++ 7 files changed, 243 insertions(+), 15 deletions(-) diff --git a/ChangeLog-selinux b/ChangeLog-selinux index 05200e3..de9cc2f 100644 --- a/ChangeLog-selinux +++ b/ChangeLog-selinux @@ -1,4 +1,33 @@ -2007-01-13 Jim Meyering +2007-01-20 Jim Meyering + + cp, mv, install: add SELinux support, but unlike with the Red Hat + patch, mv and cp do not provide the "-Z context" option. + * src/copy.c: Include . + (restore_default_fscreatecon): New function. + (copy_reg): Make cp --preserve=context work for existing destination. + (copy_internal): Likewise for new destinations. + * src/copy.h (cp_options) [preserve_security_context]: New member. + * src/cp.c: Include . + (selinux_enabled): New global. + (usage): Mention new --preserve=context option. + (PRESERVE_CONTEXT): Define/use. + (decode_preserve_arg): Handle PRESERVE_CONTEXT. + (main): Remove an obsolete comment. + If --preserve=context is specified on a system without SELinux + enabled, give a diagnostic and fail. + * src/mv.c: Include . + Set x->preserve_security_context if SELinux is enabled. + * src/install.c: Accept new "-Z, --context=C" option. + Accept --preserve-context option (but not -P option). + Accept alternate spelling: --preserve_context, for now. + Include and "quotearg.h". + (selinux_enabled, use_default_selinux_context): New globals. + (PRESERVE_CONTEXT_OPTION): Define. + (cp_option_init): Default: do not preserve security context. + (setdefaultfilecon): New function. + (main): Honor new options. + * src/Makefile.am (mv_LDADD, cp_LDADD, ginstall_LDADD): + Add $(LIB_SELINUX). * tests/misc/selinux [VERBOSE]: Print version info for each of the tested tools, not just ls. diff --git a/src/Makefile.am b/src/Makefile.am index 3f65a1e..c999c6e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -61,9 +61,9 @@ LDADD = ../lib/libcoreutils.a $(LIBINTL) ../lib/libcoreutils.a # for eaccess in lib/euidaccess.c. chcon_LDADD = $(LDADD) $(LIB_SELINUX) -cp_LDADD = $(LDADD) $(LIB_EACCESS) -ginstall_LDADD = $(LDADD) $(LIB_EACCESS) -mv_LDADD = $(LDADD) $(LIB_EACCESS) +cp_LDADD = $(LDADD) $(LIB_EACCESS) $(LIB_SELINUX) +ginstall_LDADD = $(LDADD) $(LIB_EACCESS) $(LIB_SELINUX) +mv_LDADD = $(LDADD) $(LIB_EACCESS) $(LIB_SELINUX) pathchk_LDADD = $(LDADD) $(LIB_EACCESS) rm_LDADD = $(LDADD) $(LIB_EACCESS) test_LDADD = $(LDADD) $(LIB_EACCESS) diff --git a/src/copy.c b/src/copy.c index 786de2f..f60fa55 100644 --- a/src/copy.c +++ b/src/copy.c @@ -21,6 +21,7 @@ #include #include #include +#include #if HAVE_HURD_H # include @@ -298,6 +299,36 @@ copy_reg (char const *src_name, char const *dst_name, { dest_desc = open (dst_name, O_WRONLY | O_TRUNC | O_BINARY); + /* When using cp --preserve=context to copy to an existing destination, + use the default context rather than that of the source. Why? + 1) the src context may prohibit writing, and + 2) because it's more consistent to use the same context + that is used when the destination file doesn't already exist. */ + if (x->preserve_security_context && 0 <= dest_desc) + { + security_context_t con; + if (getfscreatecon (&con) < 0) + { + error (0, errno, _("failed to get file system create context")); + return_val = false; + goto close_src_desc; + } + + if (con) + { + if (fsetfilecon (dest_desc, con) < 0) + { + error (0, errno, + _("failed to set the security context of %s to %s"), + quote_n (0, dst_name), quote_n (1, con)); + return_val = false; + freecon (con); + goto close_src_desc; + } + freecon(con); + } + } + if (dest_desc < 0 && x->unlink_dest_after_failed_open) { if (unlink (dst_name) != 0) @@ -1001,6 +1032,15 @@ emit_verbose (char const *src, char const *dst, char const *backup_dst_name) putchar ('\n'); } +/* A wrapper around "setfscreatecon (NULL)" that exits upon failure. */ +static void +restore_default_fscreatecon_or_die (void) +{ + if (setfscreatecon (NULL) != 0) + error (EXIT_FAILURE, errno, + _("failed to restore the default file creation context")); +} + /* Copy the file SRC_NAME to the file DST_NAME. The files may be of any type. NEW_DST should be true if the file DST_NAME cannot exist because its parent directory was just created; NEW_DST should @@ -1349,7 +1389,7 @@ copy_internal (char const *src_name, char const *dst_name, if (x->move_mode && src_sb.st_nlink == 1) { - earlier_file = src_to_dest_lookup (src_sb.st_ino, src_sb.st_dev); + earlier_file = src_to_dest_lookup (src_sb.st_ino, src_sb.st_dev); } else if ((x->preserve_links && (1 < src_sb.st_nlink @@ -1539,6 +1579,37 @@ copy_internal (char const *src_name, char const *dst_name, delayed_ok = true; + if (x->preserve_security_context) + { + security_context_t con; + + if (0 <= lgetfilecon (src_name, &con)) + { + if (setfscreatecon (con) < 0) + { + error (0, errno, + _("failed to set default file creation context to %s"), + quote (con)); + if (x->require_preserve) + { + freecon (con); + return false; + } + } + freecon (con); + } + else + { + if (errno != ENOTSUP && errno != ENODATA) + { + error (0, errno, + _("failed to get security context of %s"), + quote (src_name)); + return false; + } + } + } + /* In certain modes (cp's --symbolic-link), and for certain file types (symlinks and hard links) it doesn't make sense to preserve metadata, or it's possible to preserve only some of it. @@ -1768,6 +1839,9 @@ copy_internal (char const *src_name, char const *dst_name, } } + if (x->preserve_security_context) + restore_default_fscreatecon_or_die (); + /* There's no need to preserve timestamps or permissions. */ preserve_metadata = false; @@ -1901,6 +1975,9 @@ copy_internal (char const *src_name, char const *dst_name, un_backup: + if (x->preserve_security_context) + restore_default_fscreatecon_or_die (); + /* We have failed to create the destination file. If we've just added a dev/ino entry via the remember_copied call above (i.e., unless we've just failed to create a hard link), diff --git a/src/copy.h b/src/copy.h index c815baf..eab6c86 100644 --- a/src/copy.h +++ b/src/copy.h @@ -1,5 +1,5 @@ /* core functions for copying files and directories - Copyright (C) 89, 90, 91, 1995-2005 Free Software Foundation. + Copyright (C) 89, 90, 91, 1995-2007 Free Software Foundation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -127,6 +127,9 @@ struct cp_options bool preserve_ownership; bool preserve_mode; bool preserve_timestamps; + /* If true, attempt to preserve the SELinux security context, too. + Set this only if the kernel is SELinux enabled. */ + bool preserve_security_context; /* Enabled for mv, and for cp by the --preserve=links option. If true, attempt to preserve in the destination files any diff --git a/src/cp.c b/src/cp.c index 1fffbd7..c63e047 100644 --- a/src/cp.c +++ b/src/cp.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "system.h" #include "argmatch.h" @@ -85,6 +86,9 @@ enum /* The invocation name of this program. */ char *program_name; +/* True if the kernel is SELinux enabled. */ +static bool selinux_enabled; + /* If true, the command "cp x/e_file e_dir" uses "e_dir/x/e_file" as its destination instead of the usual "e_dir/e_file." */ static bool parents_option = false; @@ -191,7 +195,7 @@ Mandatory arguments to long options are mandatory for short options too.\n\ -p same as --preserve=mode,ownership,timestamps\n\ --preserve[=ATTR_LIST] preserve the specified attributes (default:\n\ mode,ownership,timestamps), if possible\n\ - additional attributes: links, all\n\ + additional attributes: context, links, all\n\ "), stdout); fputs (_("\ --no-preserve=ATTR_LIST don't preserve the specified attributes\n\ @@ -749,6 +753,7 @@ cp_option_init (struct cp_options *x) x->preserve_links = false; x->preserve_mode = false; x->preserve_timestamps = false; + x->preserve_security_context = false; x->require_preserve = false; x->recursive = false; @@ -777,18 +782,19 @@ decode_preserve_arg (char const *arg, struct cp_options *x, bool on_off) PRESERVE_TIMESTAMPS, PRESERVE_OWNERSHIP, PRESERVE_LINK, + PRESERVE_CONTEXT, PRESERVE_ALL }; static enum File_attribute const preserve_vals[] = { PRESERVE_MODE, PRESERVE_TIMESTAMPS, - PRESERVE_OWNERSHIP, PRESERVE_LINK, PRESERVE_ALL + PRESERVE_OWNERSHIP, PRESERVE_LINK, PRESERVE_CONTEXT, PRESERVE_ALL }; /* Valid arguments to the `--preserve' option. */ static char const* const preserve_args[] = { "mode", "timestamps", - "ownership", "links", "all", NULL + "ownership", "links", "context", "all", NULL }; ARGMATCH_VERIFY (preserve_args, preserve_vals); @@ -824,11 +830,17 @@ decode_preserve_arg (char const *arg, struct cp_options *x, bool on_off) x->preserve_links = on_off; break; + case PRESERVE_CONTEXT: + x->preserve_security_context = on_off; + break; + case PRESERVE_ALL: x->preserve_mode = on_off; x->preserve_timestamps = on_off; x->preserve_ownership = on_off; x->preserve_links = on_off; + if (selinux_enabled) + x->preserve_security_context = on_off; break; default: @@ -862,6 +874,7 @@ main (int argc, char **argv) atexit (close_stdout); + selinux_enabled = (0 < is_selinux_enabled ()); cp_option_init (&x); /* FIXME: consider not calling getenv for SIMPLE_BACKUP_SUFFIX unless @@ -1048,9 +1061,6 @@ main (int argc, char **argv) x.dereference = DEREF_ALWAYS; } - /* The key difference between -d (--no-dereference) and not is the version - of `stat' to call. */ - if (x.recursive) x.copy_as_regular = copy_contents; @@ -1059,6 +1069,14 @@ main (int argc, char **argv) if (x.unlink_dest_after_failed_open & (x.hard_link | x.symbolic_link)) x.unlink_dest_before_opening = true; + if (x.preserve_security_context) + { + if (!selinux_enabled) + error (EXIT_FAILURE, 0, + _("cannot preserve security context " + "without an SELinux-enabled kernel")); + } + /* Allocate space for remembering copied and created files. */ hash_init (); diff --git a/src/install.c b/src/install.c index 6f85a24..f6152f3 100644 --- a/src/install.c +++ b/src/install.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "system.h" #include "backupfile.h" @@ -35,6 +36,7 @@ #include "mkdir-p.h" #include "modechange.h" #include "quote.h" +#include "quotearg.h" #include "savewd.h" #include "stat-time.h" #include "utimens.h" @@ -49,6 +51,9 @@ # include #endif +static int selinux_enabled = 0; +static bool use_default_selinux_context = true; + #if ! HAVE_ENDGRENT # define endgrent() ((void) 0) #endif @@ -121,15 +126,28 @@ static bool strip_files; /* If true, install a directory instead of a regular file. */ static bool dir_arg; +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + PRESERVE_CONTEXT_OPTION = CHAR_MAX + 1 +}; + static struct option const long_options[] = { {"backup", optional_argument, NULL, 'b'}, + {GETOPT_SELINUX_CONTEXT_OPTION_DECL}, {"directory", no_argument, NULL, 'd'}, {"group", required_argument, NULL, 'g'}, {"mode", required_argument, NULL, 'm'}, {"no-target-directory", no_argument, NULL, 'T'}, {"owner", required_argument, NULL, 'o'}, {"preserve-timestamps", no_argument, NULL, 'p'}, + {"preserve-context", no_argument, NULL, PRESERVE_CONTEXT_OPTION}, + /* Continue silent support for --preserve_context until Jan 2008. FIXME-obs + After that, FIXME-obs: warn in, say, late 2008, and disable altogether + a year or two later. */ + {"preserve_context", no_argument, NULL, PRESERVE_CONTEXT_OPTION}, {"strip", no_argument, NULL, 's'}, {"suffix", required_argument, NULL, 'S'}, {"target-directory", required_argument, NULL, 't'}, @@ -169,11 +187,47 @@ cp_option_init (struct cp_options *x) x->stdin_tty = false; x->update = false; + x->preserve_security_context = false; x->verbose = false; x->dest_info = NULL; x->src_info = NULL; } +/* Modify file context to match the specified policy. + If an error occurs the file will remain with the default directory + context. */ +static void +setdefaultfilecon (char const *file) +{ + struct stat st; + security_context_t scontext = NULL; + if (selinux_enabled != 1) + { + /* Indicate no context found. */ + return; + } + if (lstat (file, &st) != 0) + return; + + /* If there's an error determining the context, or it has none, + return to allow default context */ + if ((matchpathcon (file, st.st_mode, &scontext) != 0) || + (strcmp (scontext, "<>") == 0)) + { + if (scontext != NULL) + freecon (scontext); + return; + } + + if (lsetfilecon (file, scontext) < 0 && errno != ENOTSUP) + error (0, errno, + _("warning: %s: failed to change context to %s"), + quotearg_colon (file), scontext); + + freecon (scontext); + return; +} + /* FILE is the last operand of this command. Return true if FILE is a directory. But report an error there is a problem accessing FILE, or if FILE does not exist but would have to refer to an existing @@ -222,6 +276,9 @@ main (int argc, char **argv) bool no_target_directory = false; int n_files; char **file; + security_context_t scontext = NULL; + /* set iff kernel has extra selinux system calls */ + selinux_enabled = (0 < is_selinux_enabled ()); initialize_main (&argc, &argv); program_name = argv[0]; @@ -243,7 +300,7 @@ main (int argc, char **argv) we'll actually use backup_suffix_string. */ backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX"); - while ((optc = getopt_long (argc, argv, "bcsDdg:m:o:pt:TvS:", long_options, + while ((optc = getopt_long (argc, argv, "bcsDdg:m:o:pt:TvS:Z:", long_options, NULL)) != -1) { switch (optc) @@ -305,6 +362,27 @@ main (int argc, char **argv) case 'T': no_target_directory = true; break; + + case PRESERVE_CONTEXT_OPTION: + if ( ! selinux_enabled) + { + error (0, 0, _("Warning: ignoring --preserve-context; " + "this kernel is not SELinux-enabled.")); + break; + } + x.preserve_security_context = true; + use_default_selinux_context = false; + break; + case 'Z': + if ( ! selinux_enabled) + { + error (0, 0, _("Warning: ignoring --context (-Z); " + "this kernel is not SELinux-enabled.")); + break; + } + scontext = optarg; + use_default_selinux_context = false; + break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: @@ -320,6 +398,11 @@ main (int argc, char **argv) error (EXIT_FAILURE, 0, _("target directory not allowed when installing a directory")); + if (x.preserve_security_context && scontext != NULL) + error (EXIT_FAILURE, 0, + _("cannot force target context to %s and preserve it"), + quote (scontext)); + if (backup_suffix_string) simple_backup_suffix = xstrdup (backup_suffix_string); @@ -328,6 +411,11 @@ main (int argc, char **argv) version_control_string) : no_backups); + if (scontext && setfscreatecon (scontext) < 0) + error (EXIT_FAILURE, errno, + _("failed to set default file creation context to %s"), + quote (scontext)); + n_files = argc - optind; file = argv + optind; @@ -503,6 +591,7 @@ copy_file (const char *from, const char *to, const struct cp_options *x) static bool change_attributes (char const *name) { + bool ok = false; /* chown must precede chmod because on some systems, chown clears the set[ug]id bits for non-superusers, resulting in incorrect permissions. @@ -521,9 +610,12 @@ change_attributes (char const *name) else if (chmod (name, mode) != 0) error (0, errno, _("cannot change permissions of %s"), quote (name)); else - return true; + ok = true; + + if (use_default_selinux_context) + setdefaultfilecon (name); - return false; + return ok; } /* Set the timestamps of file TO to match those of file FROM. @@ -687,6 +779,11 @@ Mandatory arguments to long options are mandatory for short options too.\n\ -T, --no-target-directory treat DEST as a normal file\n\ -v, --verbose print the name of each directory as it is created\n\ "), stdout); + fputs (_("\ + --preserve-context preserve SELinux security context\n\ + -Z, --context=CONTEXT set SELinux security context of files and directories\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ diff --git a/src/mv.c b/src/mv.c index 2ca60d0..90387f7 100644 --- a/src/mv.c +++ b/src/mv.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "system.h" #include "argmatch.h" @@ -113,6 +114,8 @@ rm_option_init (struct rm_options *x) static void cp_option_init (struct cp_options *x) { + bool selinux_enabled = (0 < is_selinux_enabled ()); + x->copy_as_regular = false; /* FIXME: maybe make this an option */ x->dereference = DEREF_NEVER; x->unlink_dest_before_opening = false; @@ -126,6 +129,7 @@ cp_option_init (struct cp_options *x) x->preserve_links = true; x->preserve_mode = true; x->preserve_timestamps = true; + x->preserve_security_context = selinux_enabled; x->require_preserve = false; /* FIXME: maybe make this an option */ x->recursive = true; x->sparse_mode = SPARSE_AUTO; /* FIXME: maybe make this an option */ -- 2.7.4