libtar: backup and restore posix capabilities
This patch also allows libtar to combine data from multiple extended
tar headers into a single header.
Change-Id: I82d13e89a3622ea665b60062b1904ddbedfa41b3
diff --git a/libtar/append.c b/libtar/append.c
index 4388297..7c679f6 100644
--- a/libtar/append.c
+++ b/libtar/append.c
@@ -22,6 +22,10 @@
#include <sys/types.h>
#include <stdbool.h>
+#include <sys/capability.h>
+#include <sys/xattr.h>
+#include <linux/xattr.h>
+
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <string.h>
@@ -154,6 +158,24 @@
}
#endif
+ /* get posix file capabilities */
+ if (TH_ISREG(t) && t->options & TAR_STORE_POSIX_CAP)
+ {
+ if (t->th_buf.has_cap_data)
+ {
+ memset(&t->th_buf.cap_data, 0, sizeof(struct vfs_cap_data));
+ t->th_buf.has_cap_data = 0;
+ }
+
+ if (getxattr(realname, XATTR_NAME_CAPS, &t->th_buf.cap_data, sizeof(struct vfs_cap_data)) >= 0)
+ {
+ t->th_buf.has_cap_data = 1;
+#if 1 //def DEBUG
+ print_caps(&t->th_buf.cap_data);
+#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 2fd61bb..a117857 100644
--- a/libtar/block.c
+++ b/libtar/block.c
@@ -29,6 +29,10 @@
#define E4CRYPT_TAG "TWRP.security.e4crypt="
#define E4CRYPT_TAG_LEN 22
+// Used to identify Posix capabilities in extended ('x')
+#define CAPABILITIES_TAG "SCHILY.xattr.security.capability="
+#define CAPABILITIES_TAG_LEN 33
+
/* read a header block */
/* FIXME: the return value of this function should match the return value
of tar_block_read(), which is a macro which references a prototype
@@ -128,6 +132,11 @@
free(t->th_buf.e4crypt_policy);
}
#endif
+ if (t->th_buf.has_cap_data)
+ {
+ memset(&t->th_buf.cap_data, 0, sizeof(struct vfs_cap_data));
+ t->th_buf.has_cap_data = 0;
+ }
memset(&(t->th_buf), 0, sizeof(struct tar_header));
@@ -241,8 +250,8 @@
}
}
-#ifdef HAVE_SELINUX
- if(TH_ISEXTHEADER(t))
+ // Extended headers (selinux contexts, posix file capabilities, ext4 encryption policies)
+ while(TH_ISEXTHEADER(t) || TH_ISPOLHEADER(t))
{
sz = th_get_size(t);
@@ -267,7 +276,19 @@
buf[T_BLOCKSIZE-1] = 0;
int len = strlen(buf);
- char *start = strstr(buf, SELINUX_TAG);
+ // posix capabilities
+ char *start = strstr(buf, CAPABILITIES_TAG);
+ if(start && start+CAPABILITIES_TAG_LEN < buf+len)
+ {
+ start += CAPABILITIES_TAG_LEN;
+ memcpy(&t->th_buf.cap_data, start, sizeof(struct vfs_cap_data));
+ t->th_buf.has_cap_data = 1;
+#ifdef DEBUG
+ printf(" th_read(): Posix capabilities detected\n");
+#endif
+ } // end posix capabilities
+#ifdef HAVE_SELINUX // selinux contexts
+ start = strstr(buf, SELINUX_TAG);
if(start && start+SELINUX_TAG_LEN < buf+len)
{
start += SELINUX_TAG_LEN;
@@ -280,45 +301,9 @@
#endif
}
}
- }
-
- i = th_read_internal(t);
- if (i != T_BLOCKSIZE)
- {
- if (i != -1)
- errno = EINVAL;
- return -1;
- }
- }
-#endif
-
+#endif // HAVE_SELINUX
#ifdef HAVE_EXT4_CRYPT
- if(TH_ISPOLHEADER(t))
- {
- sz = th_get_size(t);
-
- if(sz >= T_BLOCKSIZE) // Not supported
- {
-#ifdef DEBUG
- printf(" th_read(): Policy 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, E4CRYPT_TAG);
+ start = strstr(buf, E4CRYPT_TAG);
if(start && start+E4CRYPT_TAG_LEN < buf+len)
{
start += E4CRYPT_TAG_LEN;
@@ -331,6 +316,7 @@
#endif
}
}
+#endif // HAVE_EXT4_CRYPT
}
i = th_read_internal(t);
@@ -341,11 +327,55 @@
return -1;
}
}
-#endif
return 0;
}
+/* write an extended block */
+static int
+th_write_extended(TAR *t, char* buf, uint64_t sz)
+{
+ char type2;
+ uint64_t sz2;
+ int i;
+
+ /* 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;
+
+ 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;
+ }
+
+ 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);
+ memset(buf, 0, T_BLOCKSIZE);
+ return 0;
+}
/* write a header block */
int
@@ -353,7 +383,7 @@
{
int i, j;
char type2;
- uint64_t sz, sz2;
+ uint64_t sz, sz2, total_sz = 0;
char *ptr;
char buf[T_BLOCKSIZE];
@@ -464,6 +494,8 @@
th_set_size(t, sz2);
}
+ memset(buf, 0, T_BLOCKSIZE);
+ ptr = buf;
#ifdef HAVE_SELINUX
if((t->options & TAR_STORE_SELINUX) && t->th_buf.selinux_context != NULL)
{
@@ -471,13 +503,6 @@
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;
@@ -485,35 +510,9 @@
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", (int)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);
+ total_sz += sz;
+ snprintf(ptr, T_BLOCKSIZE, "%d "SELINUX_TAG"%s\n", (int)sz, t->th_buf.selinux_context);
+ ptr += sz;
}
#endif
@@ -524,13 +523,6 @@
printf("th_write(): using e4crypt_policy %s\n",
t->th_buf.e4crypt_policy);
#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_POL_TYPE;
-
/* setup size - EXT header has format "*size of this whole tag as ascii numbers* *space* *content* *newline* */
// size newline
sz = E4CRYPT_TAG_LEN + EXT4_KEY_DESCRIPTOR_HEX + 3 + 1;
@@ -538,38 +530,52 @@
if(sz >= 100) // another ascci digit for size
++sz;
- if(sz >= T_BLOCKSIZE) // impossible
+ if (total_sz + sz >= T_BLOCKSIZE)
{
- errno = EINVAL;
- return -1;
+ if (th_write_extended(t, &buf, total_sz))
+ return -1;
+ ptr = buf;
+ total_sz = sz;
}
+ else
+ total_sz += sz;
- 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 "E4CRYPT_TAG"%s\n", (int)sz, t->th_buf.e4crypt_policy);
- 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);
+ snprintf(ptr, T_BLOCKSIZE, "%d "E4CRYPT_TAG"%s", (int)sz, t->th_buf.e4crypt_policy);
+ char *nlptr = ptr + sz - 1;
+ *nlptr = '\n';
+ ptr += sz;
}
#endif
+ if((t->options & TAR_STORE_POSIX_CAP) && t->th_buf.has_cap_data)
+ {
+#ifdef DEBUG
+ printf("th_write(): has a posix capability\n");
+#endif
+ sz = CAPABILITIES_TAG_LEN + sizeof(struct vfs_cap_data) + 3 + 1;
+
+ if(sz >= 100) // another ascci digit for size
+ ++sz;
+
+ if (total_sz + sz >= T_BLOCKSIZE)
+ {
+ if (th_write_extended(t, &buf, total_sz))
+ return -1;
+ ptr = buf;
+ total_sz = sz;
+ }
+ else
+ total_sz += sz;
+
+ snprintf(ptr, T_BLOCKSIZE, "%d "CAPABILITIES_TAG, (int)sz);
+ memcpy(ptr + CAPABILITIES_TAG_LEN + 3, &t->th_buf.cap_data, sizeof(struct vfs_cap_data));
+ char *nlptr = ptr + sz - 1;
+ *nlptr = '\n';
+ ptr += sz;
+ }
+ if (total_sz > 0 && th_write_extended(t, &buf, total_sz)) // write any outstanding tar extended header
+ return -1;
+
th_finish(t);
#ifdef DEBUG
diff --git a/libtar/extract.c b/libtar/extract.c
index ba29a77..9e24e8e 100644
--- a/libtar/extract.c
+++ b/libtar/extract.c
@@ -20,6 +20,10 @@
#include <errno.h>
#include <utime.h>
+#include <sys/capability.h>
+#include <sys/xattr.h>
+#include <linux/xattr.h>
+
#ifdef STDC_HEADERS
# include <stdlib.h>
#endif
@@ -166,6 +170,16 @@
}
#endif
+ if((t->options & TAR_STORE_POSIX_CAP) && t->th_buf.has_cap_data)
+ {
+#if 1 //def DEBUG
+ printf("tar_extract_file(): restoring posix capabilities to file %s\n", realname);
+ print_caps(&t->th_buf.cap_data);
+#endif
+ if (setxattr(realname, XATTR_NAME_CAPS, &t->th_buf.cap_data, sizeof(struct vfs_cap_data), 0) < 0)
+ fprintf(stderr, "tar_extract_file(): failed to restore posix capabilities to file %s !!!\n", realname);
+ }
+
#ifdef LIBTAR_FILE_HASH
pn = th_get_pathname(t);
pathname_len = strlen(pn) + 1;
diff --git a/libtar/libtar.h b/libtar/libtar.h
index ab5a3be..8c42028 100644
--- a/libtar/libtar.h
+++ b/libtar/libtar.h
@@ -15,6 +15,7 @@
#include <sys/types.h>
#include <sys/stat.h>
+#include <linux/capability.h>
#include "tar.h"
#include "libtar_listhash.h"
@@ -43,7 +44,7 @@
/* extended metadata for next file - used to store selinux_context */
#define TH_EXT_TYPE 'x'
-#define TH_POL_TYPE 'p'
+#define TH_POL_TYPE_DO_NOT_USE 'p'
/* our version of the tar header structure */
struct tar_header
@@ -73,6 +74,8 @@
#ifdef HAVE_EXT4_CRYPT
char *e4crypt_policy;
#endif
+ int has_cap_data;
+ struct vfs_cap_data cap_data;
};
@@ -118,6 +121,7 @@
#define TAR_STORE_SELINUX 128 /* store selinux context */
#define TAR_USE_NUMERIC_ID 256 /* favor numeric owner over names */
#define TAR_STORE_EXT4_POL 512 /* store ext4 crypto policy */
+#define TAR_STORE_POSIX_CAP 1024 /* store posix file capabilities */
/* this is obsolete - it's here for backwards-compatibility only */
#define TAR_IGNORE_MAGIC 0
@@ -214,7 +218,7 @@
#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)
-#define TH_ISPOLHEADER(t) ((t)->th_buf.typeflag == TH_POL_TYPE)
+#define TH_ISPOLHEADER(t) ((t)->th_buf.typeflag == TH_POL_TYPE_DO_NOT_USE)
/* decode tar header info */
#define th_get_crc(t) oct_to_int((t)->th_buf.chksum, sizeof((t)->th_buf.chksum))
@@ -323,6 +327,9 @@
/* integer to string-octal conversion, or binary as necessary */
void int_to_oct_ex(int64_t num, char *oct, size_t octlen);
+/* prints posix file capabilities */
+void print_caps(struct vfs_cap_data *cap_data);
+
/***** wrapper.c **********************************************************/
diff --git a/libtar/util.c b/libtar/util.c
index f472f38..7fb3f51 100644
--- a/libtar/util.c
+++ b/libtar/util.c
@@ -15,6 +15,7 @@
#include <stdio.h>
#include <sys/param.h>
#include <errno.h>
+#include <linux/capability.h>
#ifdef STDC_HEADERS
# include <string.h>
@@ -210,3 +211,11 @@
}
int_to_oct(num, oct, octlen);
}
+
+void print_caps(struct vfs_cap_data *cap_data) {
+ printf(" magic_etc=%u \n", cap_data->magic_etc);
+ printf(" data[0].permitted=%u \n", cap_data->data[0].permitted);
+ printf(" data[0].inheritable=%u \n", cap_data->data[0].inheritable);
+ printf(" data[1].permitted=%u \n", cap_data->data[1].permitted);
+ printf(" data[1].inheritable=%u \n", cap_data->data[1].inheritable);
+}
diff --git a/twrpTar.cpp b/twrpTar.cpp
index 4f9b633..27c7c7b 100644
--- a/twrpTar.cpp
+++ b/twrpTar.cpp
@@ -55,9 +55,9 @@
#ifdef TW_INCLUDE_FBE
#include "crypto/ext4crypt/ext4crypt_tar.h"
-#define TWTAR_FLAGS TAR_GNU | TAR_STORE_SELINUX | TAR_STORE_EXT4_POL
+#define TWTAR_FLAGS TAR_GNU | TAR_STORE_SELINUX | TAR_STORE_POSIX_CAP |TAR_STORE_EXT4_POL
#else
-#define TWTAR_FLAGS TAR_GNU | TAR_STORE_SELINUX
+#define TWTAR_FLAGS TAR_GNU | TAR_STORE_SELINUX | TAR_STORE_POSIX_CAP
#endif
using namespace std;