CVE-2015-1191: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: Multiple directory traversal vulnerabilities in pigz 2.3.1 allow remote attackers to write to arbitrary files via a (1) full pathname or (2) .. (dot dot) in an archive.
CWE description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
Published: 2015-01-21T18:59Z
Repository: https://github.com/madler/pigz
Language: C
Files changed: pigz.c
Commit date: 2015-01-11 20:29:08 -0800
Commit message: When decompressing with -N or -NT, strip any path from header name.

This uses the path of the compressed file combined with the name
from the header as the name of the decompressed output file.  Any
path information in the header name is stripped.  This avoids a
possible vulnerability where absolute or descending paths are put
in the gzip header.

Diff: diff --git a/pigz.c b/pigz.c
index ca307a5..38c0d4f 100644
--- a/pigz.c
+++ b/pigz.c
@@ -3518,30 +3518,39 @@ local void process(char *path)
         if (!g.decode && !g.force && isatty(g.outd))
             bail("trying to write compressed data to a terminal",
                  " (use -f to force)");
     }
     else {
-        char *to, *repl;
-
-        /* use header name for output when decompressing with -N */
-        to = g.inf;
-        if (g.decode && (g.headis & 1) != 0 && g.hname != NULL) {
-            to = g.hname;
-            len = strlen(g.hname);
+        char *to = g.inf, *sufx = "";
+        size_t pre = 0;
+
+        /* select parts of the output file name */
+        if (g.decode) {
+            /* for -dN or -dNT, use the path from the input file and the name
+               from the header, stripping any path in the header name */
+            if ((g.headis & 1) != 0 && g.hname != NULL) {
+                pre = justname(g.inf) - g.inf;
+                to = justname(g.hname);
+                len = strlen(to);
+            }
+            /* for -d or -dNn, replace abbreviated suffixes */
+            else if (strcmp(to + len, ".tgz") == 0)
+                sufx = ".tar";
         }
-
-        /* replace .tgz with .tar when decoding */
-        repl = g.decode && strcmp(to + len, ".tgz") ? "" : ".tar";
+        else
+            /* add appropriate suffix when compressing */
+            sufx = g.sufx;
 
         /* create output file and open to write */
-        g.outf = MALLOC(len + (g.decode ? strlen(repl) : strlen(g.sufx)) + 1);
+        g.outf = MALLOC(pre + len + strlen(sufx) + 1);
         if (g.outf == NULL)
             bail("not enough memory", "");
-        memcpy(g.outf, to, len);
-        strcpy(g.outf + len, g.decode ? repl : g.sufx);
+        memcpy(g.outf, g.inf, pre);
+        memcpy(g.outf + pre, to, len);
+        strcpy(g.outf + pre + len, sufx);
         g.outd = open(g.outf, O_CREAT | O_TRUNC | O_WRONLY |
-                             (g.force ? 0 : O_EXCL), 0600);
+                              (g.force ? 0 : O_EXCL), 0600);
 
         /* if exists and not -f, give user a chance to overwrite */
         if (g.outd < 0 && errno == EEXIST && isatty(0) && g.verbosity) {
             int ch, reply;
 

Vulnerable code:         char *to, *repl;

        /* use header name for output when decompressing with -N */
        to = g.inf;
        if (g.decode && (g.headis & 1) != 0 && g.hname != NULL) {
            to = g.hname;
            len = strlen(g.hname);

        /* replace .tgz with .tar when decoding */
        repl = g.decode && strcmp(to + len, ".tgz") ? "" : ".tar";
        g.outf = MALLOC(len + (g.decode ? strlen(repl) : strlen(g.sufx)) + 1);
        memcpy(g.outf, to, len);
        strcpy(g.outf + len, g.decode ? repl : g.sufx);
                             (g.force ? 0 : O_EXCL), 0600);
Fixed code:         char *to = g.inf, *sufx = "";
        size_t pre = 0;

        /* select parts of the output file name */
        if (g.decode) {
            /* for -dN or -dNT, use the path from the input file and the name
               from the header, stripping any path in the header name */
            if ((g.headis & 1) != 0 && g.hname != NULL) {
                pre = justname(g.inf) - g.inf;
                to = justname(g.hname);
                len = strlen(to);
            }
            /* for -d or -dNn, replace abbreviated suffixes */
            else if (strcmp(to + len, ".tgz") == 0)
                sufx = ".tar";
        else
            /* add appropriate suffix when compressing */
            sufx = g.sufx;
        g.outf = MALLOC(pre + len + strlen(sufx) + 1);
        memcpy(g.outf, g.inf, pre);
        memcpy(g.outf + pre, to, len);
        strcpy(g.outf + pre + len, sufx);
                              (g.force ? 0 : O_EXCL), 0600);