From 25fd68d0989254bdf921bcd118c4bae4d8afc419 Mon Sep 17 00:00:00 2001 From: Vojtech Bocek Date: Tue, 27 Aug 2013 03:10:10 +0200 Subject: libtar: store SELinux file context in tar archives * Using RedHat's "RHT.security.selinux=" tag in extended tar header (type 'x') Signed-off-by: Vojtech Bocek Change-Id: I102e492e4fa5b8a08fae4d34eb36386862509b4d --- libtar/Android.mk | 6 +++ libtar/append.c | 25 ++++++++++++ libtar/block.c | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ libtar/compat.h | 4 ++ libtar/extract.c | 12 ++++++ libtar/libtar.h | 8 ++++ 6 files changed, 168 insertions(+) (limited to 'libtar') diff --git a/libtar/Android.mk b/libtar/Android.mk index b73a41f3f..7e0fafb51 100644 --- a/libtar/Android.mk +++ b/libtar/Android.mk @@ -11,5 +11,11 @@ LOCAL_C_INCLUDES += $(LOCAL_PATH) \ external/zlib LOCAL_SHARED_LIBRARIES += libz libc +ifneq ($(wildcard external/libselinux/Android.mk),) + LOCAL_C_INCLUDES += external/libselinux/include + LOCAL_SHARED_LIBRARIES += libselinux + LOCAL_CFLAGS += -DHAVE_SELINUX +endif + include $(BUILD_SHARED_LIBRARY) diff --git a/libtar/append.c b/libtar/append.c index 05024b926..3a8bfc690 100644 --- a/libtar/append.c +++ b/libtar/append.c @@ -90,6 +90,31 @@ tar_append_file(TAR *t, char *realname, char *savename) #endif th_set_path(t, (savename ? savename : realname)); +#ifdef HAVE_SELINUX + /* get selinux context */ + if(t->options & TAR_STORE_SELINUX) + { + if(t->th_buf.selinux_context != NULL) + { + free(t->th_buf.selinux_context); + t->th_buf.selinux_context = NULL; + } + + security_context_t selinux_context = NULL; + if(getfilecon(realname, &selinux_context) >= 0) + { + t->th_buf.selinux_context = strdup(selinux_context); + freecon(selinux_context); + } + else + { +#ifdef DEBUG + perror("Failed to get selinux context"); +#endif + } + } +#endif + /* check if it's a hardlink */ #ifdef DEBUG puts(" tar_append_file(): checking inode cache for hardlink..."); diff --git a/libtar/block.c b/libtar/block.c index 89e5e3d70..1cfc0e418 100644 --- a/libtar/block.c +++ b/libtar/block.c @@ -21,6 +21,10 @@ #define BIT_ISSET(bitmask, bit) ((bitmask) & (bit)) +// Used to identify selinux_context in extended ('x') +// metadata. From RedHat implementation. +#define SELINUX_TAG "RHT.security.selinux=" +#define SELINUX_TAG_LEN 21 /* read a header block */ int @@ -101,6 +105,11 @@ th_read(TAR *t) free(t->th_buf.gnu_longname); if (t->th_buf.gnu_longlink != NULL) free(t->th_buf.gnu_longlink); +#ifdef HAVE_SELINUX + if (t->th_buf.selinux_context != NULL) + free(t->th_buf.selinux_context); +#endif + memset(&(t->th_buf), 0, sizeof(struct tar_header)); i = th_read_internal(t); @@ -203,6 +212,57 @@ th_read(TAR *t) } } +#ifdef HAVE_SELINUX + if(TH_ISEXTHEADER(t)) + { + sz = th_get_size(t); + + if(sz >= T_BLOCKSIZE) // Not supported + { +#ifdef DEBUG + printf(" th_read(): Extended header is too long!\n"); +#endif + } + else + { + char buf[T_BLOCKSIZE]; + i = tar_block_read(t, buf); + if (i != T_BLOCKSIZE) + { + if (i != -1) + errno = EINVAL; + return -1; + } + + // To be sure + buf[T_BLOCKSIZE-1] = 0; + + int len = strlen(buf); + char *start = strstr(buf, SELINUX_TAG); + if(start && start+SELINUX_TAG_LEN < buf+len) + { + start += SELINUX_TAG_LEN; + char *end = strchr(start, '\n'); + if(end) + { + t->th_buf.selinux_context = strndup(start, end-start); +#ifdef DEBUG + printf(" th_read(): SELinux context xattr detected: %s\n", t->th_buf.selinux_context); +#endif + } + } + } + + i = th_read_internal(t); + if (i != T_BLOCKSIZE) + { + if (i != -1) + errno = EINVAL; + return -1; + } + } +#endif + #if 0 /* ** work-around for old archive files with broken typeflag fields @@ -359,6 +419,59 @@ th_write(TAR *t) th_set_size(t, sz2); } +#ifdef HAVE_SELINUX + if((t->options & TAR_STORE_SELINUX) && t->th_buf.selinux_context != NULL) + { +#ifdef DEBUG + printf("th_write(): using selinux_context (\"%s\")\n", + t->th_buf.selinux_context); +#endif + /* save old size and type */ + type2 = t->th_buf.typeflag; + sz2 = th_get_size(t); + + /* write out initial header block with fake size and type */ + t->th_buf.typeflag = TH_EXT_TYPE; + + /* setup size - EXT header has format "*size of this whole tag as ascii numbers* *space* *content* *newline* */ + // size newline + sz = SELINUX_TAG_LEN + strlen(t->th_buf.selinux_context) + 3 + 1; + + if(sz >= 100) // another ascci digit for size + ++sz; + + if(sz >= T_BLOCKSIZE) // impossible + { + errno = EINVAL; + return -1; + } + + th_set_size(t, sz); + th_finish(t); + i = tar_block_write(t, &(t->th_buf)); + if (i != T_BLOCKSIZE) + { + if (i != -1) + errno = EINVAL; + return -1; + } + + memset(buf, 0, T_BLOCKSIZE); + snprintf(buf, T_BLOCKSIZE, "%d "SELINUX_TAG"%s\n", sz, t->th_buf.selinux_context); + i = tar_block_write(t, &buf); + if (i != T_BLOCKSIZE) + { + if (i != -1) + errno = EINVAL; + return -1; + } + + /* reset type and size to original values */ + t->th_buf.typeflag = type2; + th_set_size(t, sz2); + } +#endif + th_finish(t); #ifdef DEBUG diff --git a/libtar/compat.h b/libtar/compat.h index 70ac2f435..d0862943b 100644 --- a/libtar/compat.h +++ b/libtar/compat.h @@ -12,6 +12,10 @@ # include #endif +#ifdef HAVE_SELINUX +#include "selinux/selinux.h" +#endif + #if defined(NEED_BASENAME) && !defined(HAVE_BASENAME) diff --git a/libtar/extract.c b/libtar/extract.c index 613e29fff..d19ba859d 100644 --- a/libtar/extract.c +++ b/libtar/extract.c @@ -154,6 +154,18 @@ tar_extract_file(TAR *t, char *realname, char *prefix) printf("FAILED SETTING PERMS: %d\n", i); return i; } + +#ifdef HAVE_SELINUX + if((t->options & TAR_STORE_SELINUX) && t->th_buf.selinux_context != NULL) + { +#ifdef DEBUG + printf(" Restoring SELinux context %s to file %s\n", t->th_buf.selinux_context, realname); +#endif + if(setfilecon(realname, t->th_buf.selinux_context) < 0) + fprintf(stderr, "Failed to restore SELinux context %s!\n", strerror(errno)); + } +#endif + /* pathname_len = strlen(th_get_pathname(t)) + 1; realname_len = strlen(realname) + 1; diff --git a/libtar/libtar.h b/libtar/libtar.h index e7a355a85..91523d043 100644 --- a/libtar/libtar.h +++ b/libtar/libtar.h @@ -35,6 +35,9 @@ extern "C" #define GNU_LONGNAME_TYPE 'L' #define GNU_LONGLINK_TYPE 'K' +/* extended metadata for next file - used to store selinux_context */ +#define TH_EXT_TYPE 'x' + /* our version of the tar header structure */ struct tar_header { @@ -57,6 +60,9 @@ struct tar_header char padding[12]; char *gnu_longname; char *gnu_longlink; +#ifdef HAVE_SELINUX + char *selinux_context; +#endif }; @@ -96,6 +102,7 @@ TAR; #define TAR_CHECK_MAGIC 16 /* check magic in file header */ #define TAR_CHECK_VERSION 32 /* check version in file header */ #define TAR_IGNORE_CRC 64 /* ignore CRC in file header */ +#define TAR_STORE_SELINUX 128 /* store selinux context */ /* this is obsolete - it's here for backwards-compatibility only */ #define TAR_IGNORE_MAGIC 0 @@ -177,6 +184,7 @@ int th_write(TAR *t); || S_ISFIFO((mode_t)oct_to_int((t)->th_buf.mode))) #define TH_ISLONGNAME(t) ((t)->th_buf.typeflag == GNU_LONGNAME_TYPE) #define TH_ISLONGLINK(t) ((t)->th_buf.typeflag == GNU_LONGLINK_TYPE) +#define TH_ISEXTHEADER(t) ((t)->th_buf.typeflag == TH_EXT_TYPE) /* decode tar header info */ #define th_get_crc(t) oct_to_int((t)->th_buf.chksum) -- cgit v1.2.3