Implement support for the pax extended header found in tar files. From all the collected pax header entries we set the security.ima extended attribute associate with the extracted file.
Signed-off-by: Stefan Berger <stef...@linux.vnet.ibm.com> --- dpkg-deb/extract.c | 5 +++ lib/dpkg/tarfn.c | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/dpkg/tarfn.h | 13 +++++++ src/archives.c | 20 ++++++++++ 4 files changed, 148 insertions(+) diff --git a/dpkg-deb/extract.c b/dpkg-deb/extract.c index 6c63498..0646341 100644 --- a/dpkg-deb/extract.c +++ b/dpkg-deb/extract.c @@ -329,6 +329,11 @@ extracthalf(const char *debar, const char *dir, else internerr("unknown or missing tar action '%d'", taroption); + if (taroption & DPKG_TAR_EXTRACT) { + command_add_arg(&cmd, "--xattrs"); + command_add_arg(&cmd, "--xattrs-include=security.ima"); + } + if (taroption & DPKG_TAR_PERMS) command_add_arg(&cmd, "-p"); if (taroption & DPKG_TAR_NOMTIME) diff --git a/lib/dpkg/tarfn.c b/lib/dpkg/tarfn.c index 6082053..2f32e7f 100644 --- a/lib/dpkg/tarfn.c +++ b/lib/dpkg/tarfn.c @@ -36,6 +36,7 @@ #include <dpkg/macros.h> #include <dpkg/dpkg.h> #include <dpkg/tarfn.h> +#include <dpkg/i18n.h> #define TAR_MAGIC_USTAR "ustar\0" "00" #define TAR_MAGIC_GNU "ustar " " \0" @@ -244,6 +245,104 @@ tar_gnu_long(void *ctx, const struct tar_operations *ops, struct tar_entry *te, } static void +pax_data_destroy(struct pax_data *pd) +{ + size_t i; + + for (i = 0; i < pd->num_entries; i++) { + free(pd->entries[i].key); + free(pd->entries[i].value); + } + free(pd->entries); + pd->entries = NULL; + pd->num_entries = 0; +} + +static void +pax_data_add(struct pax_data *pd, const char *key, + const unsigned char *value, size_t value_len) +{ + size_t idx = pd->num_entries++; + + pd->entries = m_realloc(pd->entries, (idx + 1) * sizeof(struct pax_entry)); + pd->entries[idx].key = m_strdup(key); + pd->entries[idx].value = m_malloc(value_len); + memcpy(pd->entries[idx].value, value, value_len); + pd->entries[idx].value_len = value_len; +} + +static int +tar_pax_header(void *ctx, const struct tar_operations *ops, + struct tar_entry *te, struct pax_data *pd) +{ + int status, n; + unsigned int len; + char *key_start, *key_end, *buf, *current, space; + unsigned char *value; + size_t value_len, buf_len; + + pax_data_destroy(pd); + + /* read whole blocks: round up to the next block size */ + buf_len = (te->size + TARBLKSZ - 1) & ~(TARBLKSZ - 1); + buf = m_malloc(buf_len + 1); + + /* avoid parsing beyond the buffer */ + buf[buf_len] = 0; + + status = ops->read(ctx, buf, buf_len); + if (status == (int)buf_len) + status = 0; + else { + free(buf); + return -1; + } + + current = buf; + + while (current - buf < te->size) { + /* parse 'untrusted' len */ + n = sscanf(current, "%u%c", &len, &space); + if (n != 2 || space != ' ' || len == 0) + break; + + key_start = index(current, ' '); + if (!key_start) + break; + + key_start++; + key_end = index(key_start, '='); + if (!key_end) + break; + + *key_end = '\0'; + + /* check 'untrusted' len: beyond buffer ? */ + current += len; + if (current > buf + te->size || current[-1] != '\n') + break; + + /* value starts after the '='; value can be binary data */ + value = (unsigned char *)key_end + 1; + value_len = current - 1 - (char *)value; + /* overwrite terminating '\n' */ + value[value_len] = 0; + + pax_data_add(pd, key_start, value, value_len); + } + + free(buf); + + if (current - buf != te->size) + status = -1; + + if (status) + ohshite(_("Malformed PAX header")); + + return status; +} + +static void tar_entry_copy(struct tar_entry *dst, struct tar_entry *src) { memcpy(dst, src, sizeof(struct tar_entry)); @@ -264,6 +363,7 @@ tar_entry_destroy(struct tar_entry *te) free(te->linkname); free(te->stat.uname); free(te->stat.gname); + te->pd = NULL; } struct tar_symlink_entry { @@ -303,6 +403,9 @@ tar_extractor(void *ctx, const struct tar_operations *ops) char *next_long_name, *next_long_link; struct tar_symlink_entry *symlink_head, *symlink_tail, *symlink_node; + struct pax_data pd; + + memset(&pd, 0, sizeof(pd)); next_long_name = NULL; next_long_link = NULL; @@ -355,7 +458,9 @@ tar_extractor(void *ctx, const struct tar_operations *ops) case TAR_FILETYPE_FILE: /* Compatibility with pre-ANSI ustar. */ if (h.name[name_len - 1] != '/') { + h.pd = &pd; status = ops->extract_file(ctx, &h); + pax_data_destroy(&pd); break; } /* Else, fall through. */ @@ -391,6 +496,9 @@ tar_extractor(void *ctx, const struct tar_operations *ops) case TAR_FILETYPE_GNU_LONGNAME: status = tar_gnu_long(ctx, ops, &h, &next_long_name); break; + case TAR_FILETYPE_PAX_EXTENDED_HEADER: + status = tar_pax_header(ctx, ops, &h, &pd); + break; default: /* Indicates broken tarfile: “Bad header field”. */ errno = 0; @@ -402,6 +510,8 @@ tar_extractor(void *ctx, const struct tar_operations *ops) break; } + pax_data_destroy(&pd); + while (symlink_head) { symlink_node = symlink_head->next; if (status == 0) diff --git a/lib/dpkg/tarfn.h b/lib/dpkg/tarfn.h index ce69424..0cf301b 100644 --- a/lib/dpkg/tarfn.h +++ b/lib/dpkg/tarfn.h @@ -53,6 +53,18 @@ enum tar_filetype { TAR_FILETYPE_FIFO = '6', TAR_FILETYPE_GNU_LONGLINK = 'K', TAR_FILETYPE_GNU_LONGNAME = 'L', + TAR_FILETYPE_PAX_EXTENDED_HEADER = 'x', +}; + +struct pax_entry { + char *key; + unsigned char *value; + size_t value_len; +}; + +struct pax_data { + size_t num_entries; + struct pax_entry *entries; }; struct tar_entry { @@ -72,6 +84,7 @@ struct tar_entry { dev_t dev; struct file_stat stat; + struct pax_data *pd; }; typedef int tar_read_func(void *ctx, char *buffer, int length); diff --git a/src/archives.c b/src/archives.c index fa64d0c..ba96f39 100644 --- a/src/archives.c +++ b/src/archives.c @@ -28,6 +28,7 @@ #include <sys/types.h> #include <sys/time.h> #include <sys/stat.h> +#include <sys/xattr.h> #include <assert.h> #include <errno.h> @@ -523,6 +524,24 @@ tarobject_set_se_context(const char *matchpath, const char *path, mode_t mode) } static void +tarobject_set_pax_data(const char *path, struct pax_data *pd) +{ + size_t i; + const char *key; + + if (!pd) + return; + + for (i = 0; i < pd->num_entries; i++) { + key = pd->entries[i].key; + if (!strcmp(key, "SCHILY.xattr.security.ima")) { + lsetxattr(path, &key[13], + pd->entries[i].value, pd->entries[i].value_len, 0); + } + } +} + +static void tarobject_matches(struct tarcontext *tc, const char *fn_old, struct stat *stab, char *oldhash, const char *fn_new, struct tar_entry *te, @@ -993,6 +1012,7 @@ tarobject(void *ctx, struct tar_entry *ti) tarobject_set_perms(ti, fnamenewvb.buf, &nodestat); tarobject_set_mtime(ti, fnamenewvb.buf); tarobject_set_se_context(fnamevb.buf, fnamenewvb.buf, nodestat.mode); + tarobject_set_pax_data(fnamenewvb.buf, ti->pd); /* * CLEANUP: Now we have extracted the new object in .dpkg-new (or, -- 1.9.1