Vulnerability: 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.
Vulnerability ID: CVE-2015-1191
CWE: CWE-22
Severity score: 2.9
Project: https://github.com/madler/pigz
Published: 2015-01-21
File: pigz.c
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.
Commit author: Mark Adler

Vulnerable version (before fix):
 local void process(char *path)                  " (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);         }          /* replace .tgz with .tar when decoding */         repl = g.decode && strcmp(to + len, ".tgz") ? "" : ".tar";          /* create output file and open to write */         g.outf = MALLOC(len + (g.decode ? strlen(repl) : strlen(g.sufx)) + 1);         if (g.outf == NULL)             bail("not enough memory", "");         memcpy(g.outf, to, len);         strcpy(g.outf + len, g.decode ? repl : g.sufx);         g.outd = open(g.outf, O_CREAT | O_TRUNC | O_WRONLY |                              (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) {

Fixed version (after fix):
 local void process(char *path)                  " (use -f to force)");     }     else {         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;          /* create output file and open to write */         g.outf = MALLOC(pre + len + strlen(sufx) + 1);         if (g.outf == NULL)             bail("not enough memory", "");         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);          /* if exists and not -f, give user a chance to overwrite */         if (g.outd < 0 && errno == EEXIST && isatty(0) && g.verbosity) {

Patch:
patch: @@ -3520,26 +3520,35 @@ local void process(char *path)
                  " (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) {
path: pigz.c