From 085cace1805308589c6211429068f68047be0b0e Mon Sep 17 00:00:00 2001 From: Pavel Raiskup Date: Sun, 18 Nov 2012 22:28:01 +0200 Subject: [PATCH] Add SELinux context store/restore/list support. * gnulib.modules: Add selinux-at. * src/Makefile.am (tar_LDADD): Add LIB_SELINUX. * src/create.c (start_header, dump_file0): Handle selinux contexts. * src/extract.c (delayed_set_stat) : New member. (delayed_link) : New member. (set_stat, delay_set_stat) (apply_nonancestor_delayed_set_stat): Handle selinux contexts. * src/tar.c: New options: "--selinux", "--no-selinux". (tar_stat_destroy): Free cntx_name. * src/tar.h (tar_stat_info) : New member. * src/xattrs.c (xattrs_selinux_get) (xattrs_selinux_set): New functions. (xattrs_print_char): Honor selinux_context_option. (xattrs_print): Print selinux context. * src/xheader.c: Handle new keyword "RHT.security.selinux". * tests/Makefile.am: Add new tests. * tests/testsuite.at: Likewise. * tests/selacl01.at: New test. * tests/selnx01.at: New test. --- gnulib.modules | 1 + src/Makefile.am | 2 +- src/create.c | 7 ++++ src/extract.c | 14 +++++++ src/tar.c | 21 ++++++++++ src/tar.h | 2 + src/xattrs.c | 68 ++++++++++++++++++++++++++++++++- src/xheader.c | 20 ++++++++++ tests/Makefile.am | 2 + tests/selacl01.at | 63 ++++++++++++++++++++++++++++++ tests/selnx01.at | 95 ++++++++++++++++++++++++++++++++++++++++++++++ tests/testsuite.at | 16 ++++++++ 12 files changed, 309 insertions(+), 2 deletions(-) create mode 100644 tests/selacl01.at create mode 100644 tests/selnx01.at diff --git a/gnulib.modules b/gnulib.modules index 7ecdd20..a3e0f25 100644 --- a/gnulib.modules +++ b/gnulib.modules @@ -50,6 +50,7 @@ root-uid rpmatch safe-read savedir +selinux-at setenv snprintf stat-time diff --git a/src/Makefile.am b/src/Makefile.am index 27c28be..782df19 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -49,4 +49,4 @@ INCLUDES = -I$(top_srcdir)/gnu -I../ -I../gnu -I$(top_srcdir)/lib -I../lib LDADD = ../lib/libtar.a ../gnu/libgnu.a $(LIBINTL) $(LIBICONV) -tar_LDADD = $(LIBS) $(LDADD) $(LIB_CLOCK_GETTIME) $(LIB_EACCESS) +tar_LDADD = $(LIBS) $(LDADD) $(LIB_CLOCK_GETTIME) $(LIB_EACCESS) $(LIB_SELINUX) diff --git a/src/create.c b/src/create.c index 37a5808..36ca30e 100644 --- a/src/create.c +++ b/src/create.c @@ -953,6 +953,8 @@ start_header (struct tar_stat_info *st) if (st->acls_d_ptr) xheader_store ("SCHILY.acl.default", st, NULL); } + if ((selinux_context_option > 0) && st->cntx_name) + xheader_store ("RHT.security.selinux", st, NULL); if (xattrs_option > 0) { size_t scan_xattr = 0; @@ -1742,6 +1744,7 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p) struct stat final_stat; xattrs_acls_get (parentfd, name, st, 0, !is_dir); + xattrs_selinux_get (parentfd, name, st, fd); xattrs_xattrs_get (parentfd, name, st, fd); if (is_dir) @@ -1862,6 +1865,7 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p) if (NAME_FIELD_SIZE - (archive_format == OLDGNU_FORMAT) < size) write_long_link (st); + xattrs_selinux_get (parentfd, name, st, 0); xattrs_xattrs_get (parentfd, name, st, 0); block_ordinal = current_block_ordinal (); @@ -1885,18 +1889,21 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p) { type = CHRTYPE; xattrs_acls_get (parentfd, name, st, 0, true); + xattrs_selinux_get (parentfd, name, st, 0); xattrs_xattrs_get (parentfd, name, st, 0); } else if (S_ISBLK (st->stat.st_mode)) { type = BLKTYPE; xattrs_acls_get (parentfd, name, st, 0, true); + xattrs_selinux_get (parentfd, name, st, 0); xattrs_xattrs_get (parentfd, name, st, 0); } else if (S_ISFIFO (st->stat.st_mode)) { type = FIFOTYPE; xattrs_acls_get (parentfd, name, st, 0, true); + xattrs_selinux_get (parentfd, name, st, 0); xattrs_xattrs_get (parentfd, name, st, 0); } else if (S_ISSOCK (st->stat.st_mode)) diff --git a/src/extract.c b/src/extract.c index 51a5ff0..dbcb4dc 100644 --- a/src/extract.c +++ b/src/extract.c @@ -100,6 +100,7 @@ struct delayed_set_stat int change_dir; /* extended attributes*/ + char *cntx_name; char *acls_a_ptr; size_t acls_a_len; char *acls_d_ptr; @@ -146,6 +147,9 @@ struct delayed_link hard-linked together. */ struct string_list *sources; + /* SELinux context */ + char *cntx_name; + /* ACLs */ char *acls_a_ptr; size_t acls_a_len; @@ -386,6 +390,7 @@ set_stat (char const *file_name, causes that linux capabilities becomes cleared. */ xattrs_xattrs_set (st, file_name, typeflag, 1); xattrs_acls_set (st, file_name, typeflag); + xattrs_selinux_set (st, file_name, typeflag); } /* For each entry H in the leading prefix of entries in HEAD that do @@ -457,6 +462,9 @@ delay_set_stat (char const *file_name, struct tar_stat_info const *st, data->atflag = atflag; data->after_links = 0; data->change_dir = chdir_current; + data->cntx_name = NULL; + if (st) + assign_string (&data->cntx_name, st->cntx_name); if (st && st->acls_a_ptr) { data->acls_a_ptr = xmemdup (st->acls_a_ptr, st->acls_a_len + 1); @@ -825,6 +833,7 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links) sb.stat.st_gid = data->gid; sb.atime = data->atime; sb.mtime = data->mtime; + sb.cntx_name = data->cntx_name; sb.acls_a_ptr = data->acls_a_ptr; sb.acls_a_len = data->acls_a_len; sb.acls_d_ptr = data->acls_d_ptr; @@ -838,6 +847,7 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links) delayed_set_stat_head = data->next; xheader_xattr_free (data->xattr_map, data->xattr_map_size); + free (data->cntx_name); free (data->acls_a_ptr); free (data->acls_d_ptr); free (data); @@ -1210,6 +1220,8 @@ create_placeholder_file (char *file_name, bool is_symlink, bool *interdir_made) + strlen (file_name) + 1); p->sources->next = 0; strcpy (p->sources->string, file_name); + p->cntx_name = NULL; + assign_string (&p->cntx_name, current_stat_info.cntx_name); p->acls_a_ptr = NULL; p->acls_a_len = 0; p->acls_d_ptr = NULL; @@ -1650,6 +1662,7 @@ apply_delayed_links (void) st1.stat.st_gid = ds->gid; st1.atime = ds->atime; st1.mtime = ds->mtime; + st1.cntx_name = ds->cntx_name; st1.acls_a_ptr = ds->acls_a_ptr; st1.acls_a_len = ds->acls_a_len; st1.acls_d_ptr = ds->acls_d_ptr; @@ -1671,6 +1684,7 @@ apply_delayed_links (void) } xheader_xattr_free (ds->xattr_map, ds->xattr_map_size); + free (ds->cntx_name); { struct delayed_link *next = ds->next; diff --git a/src/tar.c b/src/tar.c index e3f82f1..e8813de 100644 --- a/src/tar.c +++ b/src/tar.c @@ -304,6 +304,7 @@ enum NO_SAME_OWNER_OPTION, NO_SAME_PERMISSIONS_OPTION, NO_SEEK_OPTION, + NO_SELINUX_CONTEXT_OPTION, NO_UNQUOTE_OPTION, NO_WILDCARDS_MATCH_SLASH_OPTION, NO_WILDCARDS_OPTION, @@ -329,6 +330,7 @@ enum RMT_COMMAND_OPTION, RSH_COMMAND_OPTION, SAME_OWNER_OPTION, + SELINUX_CONTEXT_OPTION, SHOW_DEFAULTS_OPTION, SHOW_OMITTED_DIRS_OPTION, SHOW_TRANSFORMED_NAMES_OPTION, @@ -549,6 +551,10 @@ static struct argp_option options[] = { N_("specify the include pattern for xattr keys"), GRID+1 }, {"xattrs-exclude", XATTR_EXCLUDE, N_("MASK"), 0, N_("specify the exclude pattern for xattr keys"), GRID+1 }, + {"selinux", SELINUX_CONTEXT_OPTION, 0, 0, + N_("Enable the SELinux context support"), GRID+1 }, + {"no-selinux", NO_SELINUX_CONTEXT_OPTION, 0, 0, + N_("Disable the SELinux context support"), GRID+1 }, {"acls", ACLS_OPTION, 0, 0, N_("Enable the POSIX ACLs support"), GRID+1 }, {"no-acls", NO_ACLS_OPTION, 0, 0, @@ -2184,6 +2190,15 @@ parse_opt (int key, char *arg, struct argp_state *state) acls_option = -1; break; + case SELINUX_CONTEXT_OPTION: + set_archive_format ("posix"); + selinux_context_option = 1; + break; + + case NO_SELINUX_CONTEXT_OPTION: + selinux_context_option = -1; + break; + case XATTR_OPTION: set_archive_format ("posix"); xattrs_option = 1; @@ -2585,6 +2600,11 @@ decode_options (int argc, char **argv) && !READ_LIKE_SUBCOMMAND) USAGE_ERROR ((0, 0, _("--acls can be used only on POSIX archives"))); + if ((selinux_context_option > 0) + && archive_format != POSIX_FORMAT + && !READ_LIKE_SUBCOMMAND) + USAGE_ERROR ((0, 0, _("--selinux can be used only on POSIX archives"))); + if ((xattrs_option > 0) && archive_format != POSIX_FORMAT && !READ_LIKE_SUBCOMMAND) @@ -2849,6 +2869,7 @@ tar_stat_destroy (struct tar_stat_info *st) free (st->link_name); free (st->uname); free (st->gname); + free (st->cntx_name); free (st->acls_a_ptr); free (st->acls_d_ptr); free (st->sparse_map); diff --git a/src/tar.h b/src/tar.h index a9671df..209cae4 100644 --- a/src/tar.h +++ b/src/tar.h @@ -297,6 +297,8 @@ struct tar_stat_info char *uname; /* user name of owner */ char *gname; /* group name of owner */ + char *cntx_name; /* SELinux context for the current archive entry. */ + char *acls_a_ptr; /* Access ACLs for the current archive entry. */ size_t acls_a_len; /* Access ACLs for the current archive entry. */ diff --git a/src/xattrs.c b/src/xattrs.c index a3f0b5f..664a043 100644 --- a/src/xattrs.c +++ b/src/xattrs.c @@ -28,6 +28,7 @@ #include "common.h" #include "xattr-at.h" +#include "selinux-at.h" struct xattrs_mask_map { @@ -490,6 +491,64 @@ static void xattrs__fd_set (struct tar_stat_info const *st, } } +/* lgetfileconat is called against FILE_NAME iff the FD parameter is set to + zero, otherwise the fgetfileconat is used against correct file descriptor */ +void xattrs_selinux_get (int parentfd, char const *file_name, + struct tar_stat_info *st, int fd) +{ + if (selinux_context_option > 0) + { +#if HAVE_SELINUX_SELINUX_H != 1 + static int done = 0; + if (!done) + WARN ((0, 0, _("SELinux support is not available"))); + done = 1; +#else + int result = (fd ? fgetfilecon (fd, &st->cntx_name) + : lgetfileconat (parentfd, file_name, &st->cntx_name)); + + if (result == -1 && errno != ENODATA && errno != ENOTSUP) + call_arg_warn (fd ? "fgetfilecon" : "lgetfileconat", file_name); +#endif + } +} + +void xattrs_selinux_set (struct tar_stat_info const *st, + char const *file_name, char typeflag) +{ + if (selinux_context_option > 0) + { +#if HAVE_SELINUX_SELINUX_H != 1 + static int done = 0; + if (!done) + WARN ((0, 0, _("SELinux support is not available"))); + done = 1; +#else + const char *sysname = "setfilecon"; + int ret; + + if (!st->cntx_name) + return; + + if (typeflag != SYMTYPE) + { + ret = setfileconat (chdir_fd, file_name, st->cntx_name); + sysname = "setfileconat"; + } + else + { + ret = lsetfileconat (chdir_fd, file_name, st->cntx_name); + sysname = "lsetfileconat"; + } + + if (ret == -1) + WARNOPT (WARN_XATTR_WRITE, (0, errno, + _("%s: Cannot set SELinux context for file '%s'"), sysname, + file_name)); +#endif + } +} + static bool xattrs_matches_mask (const char *kw, struct xattrs_mask_map *mm) { int i; @@ -589,7 +648,7 @@ void xattrs_print_char (struct tar_stat_info const *st, char *output) return; } - if (xattrs_option > 0 || acls_option > 0) + if (xattrs_option > 0 || selinux_context_option > 0 || acls_option > 0) { /* placeholders */ *output = ' '; @@ -606,6 +665,9 @@ void xattrs_print_char (struct tar_stat_info const *st, char *output) break; } + if (selinux_context_option > 0 && st->cntx_name) + *output = '.'; + if (acls_option && (st->acls_a_len || st->acls_d_len)) *output = '+'; } @@ -615,6 +677,10 @@ void xattrs_print (struct tar_stat_info const *st) if (verbose_option < 3) return; + /* selinux */ + if (selinux_context_option && st->cntx_name) + fprintf (stdlis, " s: %s\n", st->cntx_name); + /* acls */ if (acls_option && (st->acls_a_len || st->acls_d_len)) { diff --git a/src/xheader.c b/src/xheader.c index 6141748..559e60a 100644 --- a/src/xheader.c +++ b/src/xheader.c @@ -469,6 +469,7 @@ void xheader_xattr_init (struct tar_stat_info *st) st->acls_a_len = 0; st->acls_d_ptr = NULL; st->acls_d_len = 0; + st->cntx_name = NULL; } void xheader_xattr_free (struct xattr_array *xattr_map, size_t xattr_map_size) @@ -1554,6 +1555,20 @@ volume_filename_decoder (struct tar_stat_info *st, decode_string (&continued_file_name, arg); } +static void +xattr_selinux_coder (struct tar_stat_info const *st, char const *keyword, + struct xheader *xhdr, void const *data) +{ + code_string (st->cntx_name, keyword, xhdr); +} + +static void +xattr_selinux_decoder (struct tar_stat_info *st, + char const *keyword, char const *arg, size_t size) +{ + decode_string (&st->cntx_name, arg); +} + static void xattr_acls_a_coder (struct tar_stat_info const *st , char const *keyword, struct xheader *xhdr, void const *data) @@ -1703,6 +1718,11 @@ struct xhdr_tab const xhdr_tab[] = { { "GNU.volume.offset", volume_offset_coder, volume_offset_decoder, XHDR_PROTECTED | XHDR_GLOBAL, false }, + /* We get the SELinux value from filecon, so add a namespace for SELinux + instead of storing it in SCHILY.xattr.* (which would be RAW). */ + { "RHT.security.selinux", + xattr_selinux_coder, xattr_selinux_decoder, 0, false }, + /* ACLs, use the star format... */ { "SCHILY.acl.access", xattr_acls_a_coder, xattr_acls_a_decoder, 0, false }, diff --git a/tests/Makefile.am b/tests/Makefile.am index e97c508..4a7970e 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -180,6 +180,8 @@ TESTSUITE_AT = \ xattr04.at\ acls01.at\ acls02.at\ + selnx01.at\ + selacl01.at\ capabs_raw01.at TESTSUITE = $(srcdir)/testsuite diff --git a/tests/selacl01.at b/tests/selacl01.at new file mode 100644 index 0000000..60f106b --- /dev/null +++ b/tests/selacl01.at @@ -0,0 +1,63 @@ +# Process this file with autom4te to create testsuite. -*- Autotest -*- +# +# Test suite for GNU tar. +# Copyright (C) 2011 Free Software Foundation, Inc. +# +# 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 +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Test description: +# +# This is basic test for support of extended attributes. + +AT_SETUP([acls/selinux: special files & fifos]) +AT_KEYWORDS([xattrs selinux acls selacls01]) + +AT_TAR_CHECK([ +AT_PRIVILEGED_PREREQ +AT_XATTRS_UTILS_PREREQ +AT_SELINUX_PREREQ +AT_ACLS_PREREQ + +mkdir dir +mkfifo dir/fifo +MAJOR=$( stat /dev/urandom --printf="%t" ) +MINOR=$( stat /dev/urandom --printf="%T" ) +mknod dir/chartype c $MAJOR $MINOR + +# setup attributes +chcon -h --user=system_u dir/fifo +chcon -h --user=system_u dir/chartype +setfacl -m u:$UID:--- dir/fifo +setfacl -m u:$UID:rwx dir/chartype + +getfacl dir/fifo >> before +getfattr -msecurity.selinux dir/chartype >> before + +tar --xattrs --selinux --acls -cf archive.tar dir + +mv dir olddir + +tar --xattrs --selinux --acls -xf archive.tar + +getfacl dir/fifo >> after +getfattr -msecurity.selinux dir/chartype >> after + +diff before after +echo separator +], +[0], +[separator +]) + +AT_CLEANUP diff --git a/tests/selnx01.at b/tests/selnx01.at new file mode 100644 index 0000000..13a208a --- /dev/null +++ b/tests/selnx01.at @@ -0,0 +1,95 @@ +# Process this file with autom4te to create testsuite. -*- Autotest -*- +# +# Test suite for GNU tar. +# Copyright (C) 2012 Free Software Foundation, Inc. +# +# 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 +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Test description: +# +# This is basic test for selinux support (store & restore). + +AT_SETUP([selinux: basic store/restore]) +AT_KEYWORDS([xattrs selinux selnx01]) + +AT_TAR_CHECK([ +AT_XATTRS_UTILS_PREREQ +AT_SELINUX_PREREQ + +mkdir dir +genfile --file dir/file +ln -s file dir/link + +getfattr -h -d -msecurity.selinux dir dir/file dir/link > start + +chcon -h --user=system_u dir +chcon -h --user=unconfined_u dir/file +chcon -h --user=system_u dir/link + +# archive whole directory including selinux contexts +tar --selinux -cf archive.tar dir + +# clear the directory +rm -rf dir + +# ================================================ +# check if selinux contexts are correctly restored + +tar --selinux -xf archive.tar + +# archive for later debugging +cp archive.tar archive_origin.tar + +# check if selinux contexts were restored +getfattr -h -d dir dir/file dir/link -msecurity.selinux | \ + grep -v -e '^#' -e ^$ | cut -d: -f1 + +# =========================================================================== +# check if selinux contexts are not restored when --selinux option is missing + +getfattr -h -d -msecurity.selinux dir dir/file dir/link > with_selinux +rm -rf dir +tar -xf archive.tar +getfattr -h -d -msecurity.selinux dir dir/file dir/link > without_selinux + +diff with_selinux without_selinux > diff_with_without +if test "$?" -eq "0"; then + echo "selinux contexts probably restored while --selinux is off" +fi + +# ================================================================= +# check if selinux is not archived when --selinux option is missing + +tar -cf archive.tar dir + +# clear the directory +rm -rf dir + +# restore (with --selinux) +tar --selinux -xf archive.tar dir + +getfattr -h -d -msecurity.selinux dir dir/file dir/link > final +diff start final > final_diff +if test "$?" -ne "0"; then + echo "bad result" +fi + +], +[0], +[security.selinux="system_u +security.selinux="unconfined_u +security.selinux="system_u +]) + +AT_CLEANUP diff --git a/tests/testsuite.at b/tests/testsuite.at index 2a65cd3..08266c9 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -130,6 +130,11 @@ m4_define([AT_XATTRS_UTILS_PREREQ],[ AT_CHECK_UTIL(setfattr -n user.test -v test $file,0) AT_CHECK_UTIL(getfattr $file,0) ]) +m4_define([AT_SELINUX_UTILS_PREREQ],[ + file=$( mktemp -p . ) + AT_CHECK_UTIL(chcon -h --user=unconfined_u $file,0) + rm -rf $file +]) m4_define([AT_ACLS_UTILS_PREREQ],[ file=$( mktemp -p . ) AT_CHECK_UTIL(setfacl -m u:$UID:rwx $file,0) @@ -152,6 +157,14 @@ m4_define([AT_XATTRS_PREREQ],[ AT_SKIP_TEST fi ]) +m4_define([AT_SELINUX_PREREQ],[ + AT_XATTRS_UTILS_PREREQ + file=$( mktemp -p . ) + err=$( tar --selinux -cf /dev/null $file 2>&1 >/dev/null | wc -l ) + if test "$err" != "0"; then + AT_SKIP_TEST + fi +]) m4_define([AT_ACLS_PREREQ],[ AT_XATTRS_UTILS_PREREQ file=$( mktemp -p . ) @@ -328,6 +341,9 @@ m4_include([xattr04.at]) m4_include([acls01.at]) m4_include([acls02.at]) +m4_include([selnx01.at]) +m4_include([selacl01.at]) + m4_include([capabs_raw01.at]) m4_include([star/gtarfail.at]) -- 2.45.2