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.
CVE: CVE-2015-1191
CWE: CWE-22
Vulnerability description: 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.

Project: pigz
File: pigz.c

local void defaults(void)
{
    g.level = Z_DEFAULT_COMPRESSION;
    /* default zopfli options as set by ZopfliInitOptions():
        verbose = 0
        numiterations = 15
        blocksplitting = 1
        blocksplittinglast = 0
        blocksplittingmax = 15
     */
    ZopfliInitOptions(&g.zopts);
#ifdef NOTHREAD
    g.procs = 1;
#else
    g.procs = nprocs(8);
#endif
    g.block = 131072UL;             /* 128K */
    g.rsync = 0;                    /* don't do rsync blocking */
    g.setdict = 1;                  /* initialize dictionary each thread */
    g.verbosity = 1;                /* normal message level */
    g.headis = 3;                   /* store/restore name and timestamp */
    g.pipeout = 0;                  /* don't force output to stdout */
    g.sufx = ".gz";                 /* compressed file suffix */
    g.decode = 0;                   /* compress */
    g.list = 0;                     /* compress */
    g.keep = 0;                     /* delete input file once compressed */
    g.force = 0;                    /* don't overwrite, don't compress links */
    g.recurse = 0;                  /* don't go into directories */
    g.form = 0;                     /* use gzip format */
}

Project: pigz
File: pigz.c

local int get_header(int save)
{
    unsigned magic;             /* magic header */
    int method;                 /* compression method */
    int flags;                  /* header flags */
    unsigned fname, extra;      /* name and extra field lengths */
    unsigned tmp2;              /* for macro */
    unsigned long tmp4;         /* for macro */

    /* clear return information */
    if (save) {
        g.stamp = 0;
        RELEASE(g.hname);
    }

    /* see if it's a gzip, zlib, or lzw file */
    g.form = -1;
    g.magic1 = GET();
    if (g.in_eof)
        return -1;
    magic = g.magic1 << 8;
    magic += GET();
    if (g.in_eof)
        return -2;
    if (magic % 31 == 0) {          /* it's zlib */
        g.form = 1;
        return (int)((magic >> 8) & 0xf);
    }
    if (magic == 0x1f9d)            /* it's lzw */
        return 257;
    if (magic == 0x504b) {          /* it's zip */
        magic = GET2();             /* the rest of the signature */
        if (g.in_eof)
            return -3;
        if (magic == 0x0201 || magic == 0x0806)
            return -5;              /* central header or archive extra */
        if (magic != 0x0403)
            return -4;              /* not a local header */
        SKIP(2);
        flags = GET2();
        if (g.in_eof)
            return -3;
        if (flags & 0xfff0)
            return -4;
        method = GET();             /* return low byte of method or 256 */
        if (GET() != 0 || flags & 1)
            method = 256;           /* unknown or encrypted */
        if (g.in_eof)
            return -3;
        if (save)
            g.stamp = dos2time(GET4());
        else
            SKIP(4);
        g.zip_crc = GET4();
        g.zip_clen = GET4();
        g.zip_ulen = GET4();
        fname = GET2();
        extra = GET2();
        if (save) {
            char *next = g.hname = MALLOC(fname + 1);
            if (g.hname == NULL)
                bail("not enough memory", "");
            while (fname > g.in_left) {
                memcpy(next, g.in_next, g.in_left);
                fname -= g.in_left;
                next += g.in_left;
                if (load() == 0)
                    return -3;
            }
            memcpy(next, g.in_next, fname);
            g.in_left -= fname;
            g.in_next += fname;
            next += fname;
            *next = 0;
        }
        else
            SKIP(fname);
        read_extra(extra, save);
        g.form = 2 + ((flags & 8) >> 3);
        return g.in_eof ? -3 : method;
    }
    if (magic != 0x1f8b) {          /* not gzip */
        g.in_left++;    /* unget second magic byte */
        g.in_next--;
        return -2;
    }

    /* it's gzip -- get method and flags */
    method = GET();
    flags = GET();
    if (g.in_eof)
        return -1;
    if (flags & 0xe0)
        return -4;

    /* get time stamp */
    if (save)
        g.stamp = tolong(GET4());
    else
        SKIP(4);

    /* skip extra field and OS */
    SKIP(2);

    /* skip extra field, if present */
    if (flags & 4) {
        extra = GET2();
        if (g.in_eof)
            return -3;
        SKIP(extra);
    }

    /* read file name, if present, into allocated memory */
    if ((flags & 8) && save) {
        unsigned char *end;
        size_t copy, have, size = 128;
        g.hname = MALLOC(size);
        if (g.hname == NULL)
            bail("not enough memory", "");
        have = 0;
        do {
            if (g.in_left == 0 && load() == 0)
                return -3;
            end = memchr(g.in_next, 0, g.in_left);
            copy = end == NULL ? g.in_left : (size_t)(end - g.in_next) + 1;
            if (have + copy > size) {
                while (have + copy > (size <<= 1))
                    ;
                g.hname = REALLOC(g.hname, size);
                if (g.hname == NULL)
                    bail("not enough memory", "");
            }
            memcpy(g.hname + have, g.in_next, copy);
            have += copy;
            g.in_left -= copy;
            g.in_next += copy;
        } while (end == NULL);
    }
    else if (flags & 8)
        while (GET() != 0)
            if (g.in_eof)
                return -3;

    /* skip comment */
    if (flags & 16)
        while (GET() != 0)
            if (g.in_eof)
                return -3;

    /* skip header crc */
    if (flags & 2)
        SKIP(2);

    /* return gzip compression method */
    g.form = 0;
    return method;
}

Project: pigz
File: pigz.c

local struct space *get_space(struct pool *pool)
{
    struct space *space;

    /* if can't create any more, wait for a space to show up */
    possess(pool->have);
    if (pool->limit == 0)
        wait_for(pool->have, NOT_TO_BE, 0);

    /* if a space is available, pull it from the list and return it */
    if (pool->head != NULL) {
        space = pool->head;
        possess(space->use);
        pool->head = space->next;
        twist(pool->have, BY, -1);      /* one less in pool */
        twist(space->use, TO, 1);       /* initially one user */
        space->len = 0;
        return space;
    }

    /* nothing available, don't want to wait, make a new space */
    assert(pool->limit != 0);
    if (pool->limit > 0)
        pool->limit--;
    pool->made++;
    release(pool->have);
    space = MALLOC(sizeof(struct space));
    if (space == NULL)
        bail("not enough memory", "");
    space->use = new_lock(1);           /* initially one user */
    space->buf = MALLOC(pool->size);
    if (space->buf == NULL)
        bail("not enough memory", "");
    space->size = pool->size;
    space->len = 0;
    space->pool = pool;                 /* remember the pool this belongs to */
    return space;
}

Project: pigz
File: pigz.c

local char *justname(char *path)
{
    char *p;

    p = strrchr(path, '/');
    return p == NULL ? path : p + 1;
}

Project: pigz
File: pigz.c

local int log_show(void)
{
    struct log *me;
    struct timeval diff;

    if (log_tail == NULL)
        return 0;
#ifndef NOTHREAD
    possess(log_lock);
#endif
    me = log_head;
    if (me == NULL) {
#ifndef NOTHREAD
        release(log_lock);
#endif
        return 0;
    }
    log_head = me->next;
    if (me->next == NULL)
        log_tail = &log_head;
#ifndef NOTHREAD
    twist(log_lock, BY, -1);
#endif
    diff.tv_usec = me->when.tv_usec - start.tv_usec;
    diff.tv_sec = me->when.tv_sec - start.tv_sec;
    if (diff.tv_usec < 0) {
        diff.tv_usec += 1000000L;
        diff.tv_sec--;
    }
    fprintf(stderr, "trace %ld.%06ld %s\n",
            (long)diff.tv_sec, (long)diff.tv_usec, me->msg);
    fflush(stderr);
    FREE(me->msg);
    FREE(me);
    return 1;
}

Project: pigz
File: pigz.c

int main(int argc, char **argv)
{
    int n;                          /* general index */
    int noop;                       /* true to suppress option decoding */
    unsigned long done;             /* number of named files processed */
    char *opts, *p;                 /* environment default options, marker */

    /* initialize globals */
    g.outf = NULL;
    g.first = 1;
    g.hname = NULL;

    /* save pointer to program name for error messages */
    p = strrchr(argv[0], '/');
    p = p == NULL ? argv[0] : p + 1;
    g.prog = *p ? p : "pigz";

    /* prepare for interrupts and logging */
    signal(SIGINT, cut_short);
#ifndef NOTHREAD
    yarn_prefix = g.prog;           /* prefix for yarn error messages */
    yarn_abort = cut_short;         /* call on thread error */
#endif
#ifdef DEBUG
    gettimeofday(&start, NULL);     /* starting time for log entries */
    log_init();                     /* initialize logging */
#endif

    /* set all options to defaults */
    defaults();

    /* process user environment variable defaults in GZIP */
    opts = getenv("GZIP");
    if (opts != NULL) {
        while (*opts) {
            while (*opts == ' ' || *opts == '\t')
                opts++;
            p = opts;
            while (*p && *p != ' ' && *p != '\t')
                p++;
            n = *p;
            *p = 0;
            if (option(opts))
                bail("cannot provide files in GZIP environment variable", "");
            opts = p + (n ? 1 : 0);
        }
        option(NULL);
    }

    /* process user environment variable defaults in PIGZ as well */
    opts = getenv("PIGZ");
    if (opts != NULL) {
        while (*opts) {
            while (*opts == ' ' || *opts == '\t')
                opts++;
            p = opts;
            while (*p && *p != ' ' && *p != '\t')
                p++;
            n = *p;
            *p = 0;
            if (option(opts))
                bail("cannot provide files in PIGZ environment variable", "");
            opts = p + (n ? 1 : 0);
        }
        option(NULL);
    }

    /* decompress if named "unpigz" or "gunzip", to stdout if "*cat" */
    if (strcmp(g.prog, "unpigz") == 0 || strcmp(g.prog, "gunzip") == 0) {
        if (!g.decode)
            g.headis >>= 2;
        g.decode = 1;
    }
    if ((n = strlen(g.prog)) > 2 && strcmp(g.prog + n - 3, "cat") == 0) {
        if (!g.decode)
            g.headis >>= 2;
        g.decode = 1;
        g.pipeout = 1;
    }

    /* if no arguments and compressed data to or from a terminal, show help */
    if (argc < 2 && isatty(g.decode ? 0 : 1))
        help();

    /* process command-line arguments, no options after "--" */
    done = noop = 0;
    for (n = 1; n < argc; n++)
        if (noop == 0 && strcmp(argv[n], "--") == 0) {
            noop = 1;
            option(NULL);
        }
        else if (noop || option(argv[n])) { /* true if file name, process it */
            if (done == 1 && g.pipeout && !g.decode && !g.list && g.form > 1)
                complain("warning: output will be concatenated zip files -- "
                         "will not be able to extract");
            process(strcmp(argv[n], "-") ? argv[n] : NULL);
            done++;
        }
    option(NULL);

    /* list stdin or compress stdin to stdout if no file names provided */
    if (done == 0)
        process(NULL);

    /* done -- release resources, show log */
    new_opts();
    log_dump();
    return 0;
}

Project: pigz
File: pigz.c

local void new_opts(void)
{
    single_compress(1);
#ifndef NOTHREAD
    finish_jobs();
#endif
}

Project: pigz
File: pigz.c

local void outb_check(void *dummy)
{
    size_t len;

    (void)dummy;

    Trace(("-- launched decompress check thread"));
    do {
        possess(outb_check_more);
        wait_for(outb_check_more, TO_BE, 1);
        len = out_len;
        g.out_check = CHECK(g.out_check, out_copy, len);
        Trace(("-- decompress checked %lu bytes", len));
        twist(outb_check_more, TO, 0);
    } while (len);
    Trace(("-- exited decompress check thread"));
}

Project: pigz
File: pigz.c

local void touch(char *path, time_t t)
{
    struct timeval times[2];

    times[0].tv_sec = t;
    times[0].tv_usec = 0;
    times[1].tv_sec = t;
    times[1].tv_usec = 0;
    (void)utimes(path, times);
}

Project: pigz
File: pigz.c

local void write_thread(void *dummy)
{
    long seq;                       /* next sequence number looking for */
    struct job *job;                /* job pulled and working on */
    size_t len;                     /* input length */
    int more;                       /* true if more chunks to write */
    unsigned long head;             /* header length */
    unsigned long ulen;             /* total uncompressed size (overflow ok) */
    unsigned long clen;             /* total compressed size (overflow ok) */
    unsigned long check;            /* check value of uncompressed data */

    (void)dummy;

    /* build and write header */
    Trace(("-- write thread running"));
    head = put_header();

    /* process output of compress threads until end of input */
    ulen = clen = 0;
    check = CHECK(0L, Z_NULL, 0);
    seq = 0;
    do {
        /* get next write job in order */
        possess(write_first);
        wait_for(write_first, TO_BE, seq);
        job = write_head;
        write_head = job->next;
        twist(write_first, TO, write_head == NULL ? -1 : write_head->seq);

        /* update lengths, save uncompressed length for COMB */
        more = job->more;
        len = job->in->len;
        drop_space(job->in);
        ulen += (unsigned long)len;
        clen += (unsigned long)(job->out->len);

        /* write the compressed data and drop the output buffer */
        Trace(("-- writing #%ld", seq));
        writen(g.outd, job->out->buf, job->out->len);
        drop_space(job->out);
        Trace(("-- wrote #%ld%s", seq, more ? "" : " (last)"));

        /* wait for check calculation to complete, then combine, once
           the compress thread is done with the input, release it */
        possess(job->calc);
        wait_for(job->calc, TO_BE, 1);
        release(job->calc);
        check = COMB(check, job->check, len);

        /* free the job */
        free_lock(job->calc);
        FREE(job);

        /* get the next buffer in sequence */
        seq++;
    } while (more);

    /* write trailer */
    put_trailer(ulen, clen, check, head);

    /* verify no more jobs, prepare for next use */
    possess(compress_have);
    assert(compress_head == NULL && peek_lock(compress_have) == 0);
    release(compress_have);
    possess(write_first);
    assert(write_head == NULL);
    twist(write_first, TO, -1);
}