This is an automated email from the git hooks/post-receive script.

guillem pushed a commit to branch master
in repository dpkg.

View the commit online:
https://git.dpkg.org/cgit/dpkg/dpkg.git/commit/?id=0118b3c7052327f657b9a05cd0b8988775b24a42

commit 0118b3c7052327f657b9a05cd0b8988775b24a42
Author: Guillem Jover <[email protected]>
AuthorDate: Mon Jan 30 03:04:07 2017 +0100

    libdpkg: Add proper tar error handling
    
    This makes the tar extractor track and report back parse errors, so that
    we can give more descriptive messages.
---
 debian/changelog          |  3 ++
 lib/dpkg/t/c-tarextract.c | 28 ++++++++++-------
 lib/dpkg/tarfn.c          | 80 ++++++++++++++++++++++++++---------------------
 lib/dpkg/tarfn.h          | 24 +++++++++++---
 src/archives.c            | 10 +++---
 src/archives.h            |  6 ++--
 src/unpack.c              | 18 ++++++-----
 7 files changed, 104 insertions(+), 65 deletions(-)

diff --git a/debian/changelog b/debian/changelog
index 71bf15826..0cb18d8ed 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -48,6 +48,9 @@ dpkg (1.19.3) UNRELEASED; urgency=medium
     end up trying to process triggers with yet unsatisifiable dependencies.
     Closes: #810724, #854478, #911620
   * dpkg: Fix --help output, to clarify which arguments are optional.
+  * libdpkg: Add proper tar error handling. This makes the tar extractor
+    track and report back parse errors, so that we can give more descriptive
+    messages.
   * Perl modules:
     - Dpkg::Changelog::Debian: Preserve modelines at EOF. Closes: #916056
       Thanks to Chris Lamb <[email protected]> for initial test cases.
diff --git a/lib/dpkg/t/c-tarextract.c b/lib/dpkg/t/c-tarextract.c
index 4781c0237..34f3d08fa 100644
--- a/lib/dpkg/t/c-tarextract.c
+++ b/lib/dpkg/t/c-tarextract.c
@@ -42,16 +42,17 @@ struct tar_context {
 };
 
 static int
-tar_read(void *ctx, char *buffer, int size)
+tar_read(struct tar_archive *tar, char *buffer, int size)
 {
-       struct tar_context *tc = ctx;
+       struct tar_context *tc = tar->ctx;
 
        return fd_read(tc->tar_fd, buffer, size);
 }
 
 static int
-tar_object_skip(struct tar_context *tc, struct tar_entry *te)
+tar_object_skip(struct tar_archive *tar, struct tar_entry *te)
 {
+       struct tar_context *tc = tar->ctx;
        off_t size;
 
        size = (te->size + TARBLKSZ - 1) / TARBLKSZ * TARBLKSZ;
@@ -62,7 +63,7 @@ tar_object_skip(struct tar_context *tc, struct tar_entry *te)
 }
 
 static int
-tar_object(void *ctx, struct tar_entry *te)
+tar_object(struct tar_archive *tar, struct tar_entry *te)
 {
        printf("%s mode=%o time=%ld.%.9d uid=%d gid=%d", te->name,
               te->stat.mode, te->mtime, 0, te->stat.uid, te->stat.gid);
@@ -74,7 +75,7 @@ tar_object(void *ctx, struct tar_entry *te)
        switch (te->type) {
        case TAR_FILETYPE_FILE0:
        case TAR_FILETYPE_FILE:
-               tar_object_skip(ctx, te);
+               tar_object_skip(tar, te);
                printf(" type=file size=%jd", (intmax_t)te->size);
                break;
        case TAR_FILETYPE_HARDLINK:
@@ -116,7 +117,8 @@ struct tar_operations tar_ops = {
 int
 main(int argc, char **argv)
 {
-       struct tar_context ctx;
+       struct tar_archive tar;
+       struct tar_context tar_ctx;
        const char *tar_name = argv[1];
 
        setvbuf(stdout, NULL, _IOLBF, 0);
@@ -124,18 +126,22 @@ main(int argc, char **argv)
        push_error_context();
 
        if (tar_name) {
-               ctx.tar_fd = open(tar_name, O_RDONLY);
-               if (ctx.tar_fd < 0)
+               tar_ctx.tar_fd = open(tar_name, O_RDONLY);
+               if (tar_ctx.tar_fd < 0)
                        ohshite("cannot open file '%s'", tar_name);
        } else {
-               ctx.tar_fd = STDIN_FILENO;
+               tar_ctx.tar_fd = STDIN_FILENO;
        }
 
-       if (tar_extractor(&ctx, &tar_ops))
+       tar.err = DPKG_ERROR_OBJECT;
+       tar.ctx = &tar_ctx;
+       tar.ops = &tar_ops;
+
+       if (tar_extractor(&tar))
                ohshite("extracting tar");
 
        if (tar_name)
-               close(ctx.tar_fd);
+               close(tar_ctx.tar_fd);
 
        pop_error_context(ehflag_normaltidy);
 
diff --git a/lib/dpkg/tarfn.c b/lib/dpkg/tarfn.c
index 16005e6f8..8e3b6e545 100644
--- a/lib/dpkg/tarfn.c
+++ b/lib/dpkg/tarfn.c
@@ -38,6 +38,8 @@
 
 #include <dpkg/macros.h>
 #include <dpkg/dpkg.h>
+#include <dpkg/i18n.h>
+#include <dpkg/error.h>
 #include <dpkg/tarfn.h>
 
 #define TAR_MAGIC_USTAR "ustar\0" "00"
@@ -121,7 +123,7 @@ tar_atol8(const char *s, size_t size)
        if (s < end)
                return tar_ret_errno(EINVAL, 0);
 
-       return n;
+       return tar_ret_errno(0, n);
 }
 
 /**
@@ -162,7 +164,7 @@ tar_atol256(const char *s, size_t size, intmax_t min, 
uintmax_t max)
                c = *s++;
        }
 
-       return n;
+       return tar_ret_errno(0, n);
 }
 
 static uintmax_t
@@ -272,7 +274,7 @@ tar_header_checksum(struct tar_header *h)
 }
 
 static int
-tar_header_decode(struct tar_header *h, struct tar_entry *d)
+tar_header_decode(struct tar_header *h, struct tar_entry *d, struct dpkg_error 
*err)
 {
        long checksum;
 
@@ -299,7 +301,11 @@ tar_header_decode(struct tar_header *h, struct tar_entry 
*d)
        /* Even though off_t is signed, we use an unsigned parser here because
         * negative offsets are not allowed. */
        d->size = TAR_ATOUL(h->size, off_t);
+       if (errno)
+               return dpkg_put_errno(err, _("invalid tar header size field"));
        d->mtime = TAR_ATOSL(h->mtime, time_t);
+       if (errno)
+               return dpkg_put_errno(err, _("invalid tar header mtime field"));
 
        if (d->type == TAR_FILETYPE_CHARDEV || d->type == TAR_FILETYPE_BLOCKDEV)
                d->dev = makedev(TAR_ATOUL(h->devmajor, dev_t),
@@ -312,19 +318,25 @@ tar_header_decode(struct tar_header *h, struct tar_entry 
*d)
        else
                d->stat.uname = NULL;
        d->stat.uid = TAR_ATOUL(h->uid, uid_t);
+       if (errno)
+               return dpkg_put_errno(err, _("invalid tar header uid field"));
 
        if (*h->group)
                d->stat.gname = m_strndup(h->group, sizeof(h->group));
        else
                d->stat.gname = NULL;
        d->stat.gid = TAR_ATOUL(h->gid, gid_t);
+       if (errno)
+               return dpkg_put_errno(err, _("invalid tar header gid field"));
 
        checksum = tar_atol8(h->checksum, sizeof(h->checksum));
-
-       /* Check for parse errors. */
        if (errno)
-               return 0;
-       return tar_header_checksum(h) == checksum;
+               return dpkg_put_errno(err, _("invalid tar header checksum 
field"));
+
+       if (tar_header_checksum(h) != checksum)
+               return dpkg_put_error(err, _("invalid tar header checksum"));
+
+       return 0;
 }
 
 /**
@@ -339,8 +351,7 @@ tar_header_decode(struct tar_header *h, struct tar_entry *d)
  *   bogus name or link.
  */
 static int
-tar_gnu_long(void *ctx, const struct tar_operations *ops, struct tar_entry *te,
-             char **longp)
+tar_gnu_long(struct tar_archive *tar, struct tar_entry *te, char **longp)
 {
        char buf[TARBLKSZ];
        char *bp;
@@ -353,14 +364,15 @@ tar_gnu_long(void *ctx, const struct tar_operations *ops, 
struct tar_entry *te,
        for (long_read = te->size; long_read > 0; long_read -= TARBLKSZ) {
                int copysize;
 
-               status = ops->read(ctx, buf, TARBLKSZ);
+               status = tar->ops->read(tar, buf, TARBLKSZ);
                if (status == TARBLKSZ)
                        status = 0;
                else {
                        /* Read partial header record? */
                        if (status > 0) {
                                errno = 0;
-                               status = -1;
+                               status = dpkg_put_error(&tar->err,
+                                                       _("partially read tar 
header"));
                        }
 
                        /* If we didn't get TARBLKSZ bytes read, punt. */
@@ -427,7 +439,7 @@ tar_entry_update_from_system(struct tar_entry *te)
 }
 
 int
-tar_extractor(void *ctx, const struct tar_operations *ops)
+tar_extractor(struct tar_archive *tar)
 {
        int status;
        char buffer[TARBLKSZ];
@@ -445,18 +457,13 @@ tar_extractor(void *ctx, const struct tar_operations *ops)
        h.stat.uname = NULL;
        h.stat.gname = NULL;
 
-       while ((status = ops->read(ctx, buffer, TARBLKSZ)) == TARBLKSZ) {
+       while ((status = tar->ops->read(tar, buffer, TARBLKSZ)) == TARBLKSZ) {
                int name_len;
 
-               if (!tar_header_decode((struct tar_header *)buffer, &h)) {
+               if (tar_header_decode((struct tar_header *)buffer, &h, 
&tar->err) < 0) {
                        if (h.name[0] == '\0') {
-                               /* End of tape. */
+                               /* End Of Tape. */
                                status = 0;
-                       } else {
-                               /* Indicates broken tarfile:
-                                * “Header checksum error”. */
-                               errno = 0;
-                               status = -1;
                        }
                        tar_entry_destroy(&h);
                        break;
@@ -474,9 +481,9 @@ tar_extractor(void *ctx, const struct tar_operations *ops)
                }
 
                if (h.name[0] == '\0') {
-                       /* Indicates broken tarfile: “Bad header data”. */
+                       status = dpkg_put_error(&tar->err,
+                                               _("invalid tar header with 
empty name field"));
                        errno = 0;
-                       status = -1;
                        tar_entry_destroy(&h);
                        break;
                }
@@ -487,7 +494,7 @@ tar_extractor(void *ctx, const struct tar_operations *ops)
                case TAR_FILETYPE_FILE:
                        /* Compatibility with pre-ANSI ustar. */
                        if (h.name[name_len - 1] != '/') {
-                               status = ops->extract_file(ctx, &h);
+                               status = tar->ops->extract_file(tar, &h);
                                break;
                        }
                        /* Else, fall through. */
@@ -495,10 +502,10 @@ tar_extractor(void *ctx, const struct tar_operations *ops)
                        if (h.name[name_len - 1] == '/') {
                                h.name[name_len - 1] = '\0';
                        }
-                       status = ops->mkdir(ctx, &h);
+                       status = tar->ops->mkdir(tar, &h);
                        break;
                case TAR_FILETYPE_HARDLINK:
-                       status = ops->link(ctx, &h);
+                       status = tar->ops->link(tar, &h);
                        break;
                case TAR_FILETYPE_SYMLINK:
                        symlink_node = m_malloc(sizeof(*symlink_node));
@@ -515,18 +522,19 @@ tar_extractor(void *ctx, const struct tar_operations *ops)
                case TAR_FILETYPE_CHARDEV:
                case TAR_FILETYPE_BLOCKDEV:
                case TAR_FILETYPE_FIFO:
-                       status = ops->mknod(ctx, &h);
+                       status = tar->ops->mknod(tar, &h);
                        break;
                case TAR_FILETYPE_GNU_LONGLINK:
-                       status = tar_gnu_long(ctx, ops, &h, &next_long_link);
+                       status = tar_gnu_long(tar, &h, &next_long_link);
                        break;
                case TAR_FILETYPE_GNU_LONGNAME:
-                       status = tar_gnu_long(ctx, ops, &h, &next_long_name);
+                       status = tar_gnu_long(tar, &h, &next_long_name);
                        break;
                default:
-                       /* Indicates broken tarfile: “Bad header field”. */
+                       status = dpkg_put_error(&tar->err,
+                                               _("unknown tar header type 
'%c'"),
+                                               h.type);
                        errno = 0;
-                       status = -1;
                }
                tar_entry_destroy(&h);
                if (status != 0)
@@ -537,7 +545,7 @@ tar_extractor(void *ctx, const struct tar_operations *ops)
        while (symlink_head) {
                symlink_node = symlink_head->next;
                if (status == 0)
-                       status = ops->symlink(ctx, &symlink_head->h);
+                       status = tar->ops->symlink(tar, &symlink_head->h);
                tar_entry_destroy(&symlink_head->h);
                free(symlink_head);
                symlink_head = symlink_node;
@@ -548,11 +556,11 @@ tar_extractor(void *ctx, const struct tar_operations *ops)
        free(next_long_link);
 
        if (status > 0) {
-               /* Indicates broken tarfile: “Read partial header record”. */
+               status = dpkg_put_error(&tar->err,
+                                       _("partially read tar header"));
                errno = 0;
-               return -1;
-       } else {
-               /* Return whatever I/O function returned. */
-               return status;
        }
+
+       /* Return whatever I/O function returned. */
+       return status;
 }
diff --git a/lib/dpkg/tarfn.h b/lib/dpkg/tarfn.h
index 37269de02..06685a720 100644
--- a/lib/dpkg/tarfn.h
+++ b/lib/dpkg/tarfn.h
@@ -26,6 +26,7 @@
 
 #include <stdint.h>
 
+#include <dpkg/error.h>
 #include <dpkg/file.h>
 
 /**
@@ -58,7 +59,7 @@ enum tar_filetype {
 };
 
 struct tar_entry {
-       /** Tar archive format. */
+       /** Tar entry format. */
        enum tar_format format;
        /** File type. */
        enum tar_filetype type;
@@ -76,8 +77,10 @@ struct tar_entry {
        struct file_stat stat;
 };
 
-typedef int tar_read_func(void *ctx, char *buffer, int length);
-typedef int tar_make_func(void *ctx, struct tar_entry *h);
+struct tar_archive;
+
+typedef int tar_read_func(struct tar_archive *tar, char *buffer, int length);
+typedef int tar_make_func(struct tar_archive *tar, struct tar_entry *h);
 
 struct tar_operations {
        tar_read_func *read;
@@ -89,6 +92,18 @@ struct tar_operations {
        tar_make_func *mknod;
 };
 
+struct tar_archive {
+       /* Global tar archive error. */
+       struct dpkg_error err;
+
+       /** Tar archive format. */
+       enum tar_format format;
+
+       /* Operation functions and context. */
+       const struct tar_operations *ops;
+       void *ctx;
+};
+
 uintmax_t
 tar_atoul(const char *s, size_t size, uintmax_t max);
 intmax_t
@@ -97,7 +112,8 @@ tar_atosl(const char *s, size_t size, intmax_t min, intmax_t 
max);
 void
 tar_entry_update_from_system(struct tar_entry *te);
 
-int tar_extractor(void *ctx, const struct tar_operations *ops);
+int
+tar_extractor(struct tar_archive *tar);
 
 /** @} */
 
diff --git a/src/archives.c b/src/archives.c
index 5a6fc0838..ab93551b8 100644
--- a/src/archives.c
+++ b/src/archives.c
@@ -264,8 +264,10 @@ void cu_pathname(int argc, void **argv) {
   path_remove_tree((char*)(argv[0]));
 }
 
-int tarfileread(void *ud, char *buf, int len) {
-  struct tarcontext *tc= (struct tarcontext*)ud;
+int
+tarfileread(struct tar_archive *tar, char *buf, int len)
+{
+  struct tarcontext *tc = (struct tarcontext *)tar->ctx;
   int r;
 
   r = fd_read(tc->backendpipe, buf, len);
@@ -661,14 +663,14 @@ linktosameexistingdir(const struct tar_entry *ti, const 
char *fname,
 }
 
 int
-tarobject(void *ctx, struct tar_entry *ti)
+tarobject(struct tar_archive *tar, struct tar_entry *ti)
 {
   static struct varbuf conffderefn, symlinkfn;
   const char *usename;
   struct fsys_namenode *usenode;
 
   struct conffile *conff;
-  struct tarcontext *tc = ctx;
+  struct tarcontext *tc = tar->ctx;
   bool existingdir, keepexisting;
   bool refcounting;
   char oldhash[MD5HASHLEN + 1];
diff --git a/src/archives.h b/src/archives.h
index 19c450c11..2ceab9b1b 100644
--- a/src/archives.h
+++ b/src/archives.h
@@ -70,8 +70,10 @@ void ok_prermdeconfigure(int argc, void **argv);
 
 void setupfnamevbs(const char *filename);
 
-int tarobject(void *ctx, struct tar_entry *ti);
-int tarfileread(void *ud, char *buf, int len);
+int
+tarobject(struct tar_archive *tar, struct tar_entry *ti);
+int
+tarfileread(struct tar_archive *tar, char *buf, int len);
 void
 tar_deferred_extract(struct fsys_namenode_list *files, struct pkginfo *pkg);
 
diff --git a/src/unpack.c b/src/unpack.c
index e8b06d289..ad1e05f56 100644
--- a/src/unpack.c
+++ b/src/unpack.c
@@ -1077,6 +1077,7 @@ void process_archive(const char *filename) {
   static enum pkgstatus oldversionstatus;
   static struct tarcontext tc;
 
+  struct tar_archive tar;
   struct dpkg_error err;
   enum parsedbflags parsedb_flags;
   int rc;
@@ -1431,14 +1432,15 @@ void process_archive(const char *filename) {
   tc.backendpipe= p1[0];
   tc.pkgset_getting_in_sync = pkgset_getting_in_sync(pkg);
 
-  rc = tar_extractor(&tc, &tf);
-  if (rc) {
-    if (errno) {
-      ohshite(_("error reading dpkg-deb tar output"));
-    } else {
-      ohshit(_("corrupted filesystem tarfile - corrupted package archive"));
-    }
-  }
+  /* Setup the tar archive. */
+  tar.err = DPKG_ERROR_OBJECT;
+  tar.ctx = &tc;
+  tar.ops = &tf;
+
+  rc = tar_extractor(&tar);
+  if (rc)
+    dpkg_error_print(&tar.err,
+                     _("corrupted filesystem tarfile in package archive"));
   if (fd_skip(p1[0], -1, &err) < 0)
     ohshit(_("cannot zap possible trailing zeros from dpkg-deb: %s"), err.str);
   close(p1[0]);

-- 
Dpkg.Org's dpkg

Reply via email to