Commit message: Avoid memory leak from __zzip_parse_root_directory().
CVE: CVE-2018-16548
CWE: CWE-772
Vulnerability description: An issue was discovered in ZZIPlib through 0.13.69. There is a memory leak triggered in the function __zzip_parse_root_directory in zip.c, which will lead to a denial of service attack.

Project: zziplib
File: zip.c

zzip_dir_open(zzip_char_t * filename, zzip_error_t * e)
{
    return zzip_dir_open_ext_io(filename, e, 0, 0);
}

Project: zziplib
File: zip.c

zzip_dir_alloc_ext_io(zzip_strings_t * ext, const zzip_plugin_io_t io)
{
    ZZIP_DIR *dir;
    if ((dir = (ZZIP_DIR *) calloc(1, sizeof(*dir))) == NULL)
        return 0;

    /* dir->fileext is currently unused - so what, still initialize it */
    dir->fileext = ext ? ext : zzip_get_default_ext();
    dir->io = io ? io : zzip_get_default_io();
    return dir;
}

Project: zziplib
File: zip.c

__zzip_dir_parse(ZZIP_DIR * dir)
{
    zzip_error_t rv;
    zzip_off_t filesize;
    struct _disk_trailer trailer;
    /* if (! dir || dir->fd < 0)
     *     { rv = EINVAL; goto error; }
     */

    HINT2("------------------ fd=%i", (int) dir->fd);
    if ((filesize = dir->io->fd.filesize(dir->fd)) < 0)
        { rv = ZZIP_DIR_STAT; goto error; }

    HINT2("------------------ filesize=%ld", (long) filesize);
    if ((rv = __zzip_fetch_disk_trailer(dir->fd, filesize, &trailer,
                                        dir->io)) != 0)
        { goto error; }

    HINT5("directory = { entries= %ld/%ld, size= %ld, seek= %ld } ",
          (long) _disk_trailer_localentries(&trailer),
          (long) _disk_trailer_finalentries(&trailer),
          (long) _disk_trailer_rootsize(&trailer),
          (long) _disk_trailer_rootseek(&trailer));

    if ((rv = __zzip_parse_root_directory(dir->fd, &trailer, &dir->hdr0,
                                          dir->io, filesize)) != 0)
        { goto error; }
  error:
    return rv;
}

Project: zziplib
File: zip.c

__debug_dir_hdr(struct zzip_dir_hdr *hdr)
{
    if (sizeof(struct zzip_dir_hdr) > sizeof(struct zzip_disk_entry))
        { WARN1("internal sizeof-mismatch may break wreakage"); }
    /*  the internal directory structure is never bigger than the
     *  external zip central directory space had been beforehand
     *  (as long as the following assertion holds...)
     */

    if (((zzip_off_t) hdr) & 3)
    {
        NOTE1("this machine's malloc(3) returns sth. not u32-aligned");
    }
    /* we assume that if this machine's malloc has returned a non-aligned
     * memory block, then it is actually safe to access misaligned data, and
     * since it does only affect the first hdr it should not even bring about
     * too much of that cpu's speed penalty
     */
}

Project: zziplib
File: zip.c

zzip_dir_free(ZZIP_DIR * dir)
{
    if (dir->refcount)
        return (dir->refcount); /* still open files attached */

    if (dir->fd >= 0)
        dir->io->fd.close(dir->fd);
    if (dir->hdr0)
        free(dir->hdr0);
    if (dir->cache.fp)
        free(dir->cache.fp);
    if (dir->cache.buf32k)
        free(dir->cache.buf32k);
    if (dir->realname)
        free(dir->realname);
    free(dir);
    return 0;
}

Project: zziplib
File: zip.c

zzip_dir_fdopen(int fd, zzip_error_t * errcode_p)
{
    return zzip_dir_fdopen_ext_io(fd, errcode_p, 0, 0);
}

Project: zziplib
File: zip.c

zzip_dir_fdopen_ext_io(int fd, zzip_error_t * errcode_p,
                       zzip_strings_t * ext, const zzip_plugin_io_t io)
{
    zzip_error_t rv;
    ZZIP_DIR *dir;

    if ((dir = zzip_dir_alloc_ext_io(ext, io)) == NULL)
        { rv = ZZIP_OUTOFMEM; goto error; }

    dir->fd = fd;
    if ((rv = __zzip_dir_parse(dir)))
        goto error;

    dir->hdr = dir->hdr0;
    dir->refcount |= 0x10000000;

    if (errcode_p)
        *errcode_p = rv;
    return dir;
  error:
    if (dir)
        zzip_dir_free(dir);
    if (errcode_p)
        *errcode_p = rv;
    return NULL;
}

Project: zziplib
File: zip.c

__zzip_fetch_disk_trailer(int fd, zzip_off_t filesize,
                          struct _disk_trailer *_zzip_restrict trailer,
                          zzip_plugin_io_t io)
{
#ifdef DEBUG
#define return(val) { e=val; HINT2("%s", zzip_strerror(e)); goto cleanup; }
#else
#define return(val) { e=val; goto cleanup; }
#endif
    register int e;

#ifndef _LOWSTK
    auto char buffer[2 * ZZIP_BUFSIZ];
    char *buf = buffer;
#else
    char *buf = malloc(2 * ZZIP_BUFSIZ);
#endif
    zzip_off_t offset = 0;
    zzip_ssize_t maplen = 0;    /* mmap(),read(),getpagesize() use size_t !! */
    char *fd_map = 0;

    if (! trailer)
        { return(EINVAL); }

    if (filesize < __sizeof(struct zzip_disk_trailer))
          { return(ZZIP_DIR_TOO_SHORT); }

    if (! buf)
        { return(ZZIP_OUTOFMEM); }

    offset = filesize;          /* a.k.a. old offset */
    while (1)                   /* outer loop */
    {
        register unsigned char *mapped;

        if (offset <= 0)
            { return(ZZIP_DIR_EDH_MISSING); }

        /* trailer cannot be farther away than 64K from fileend */
        if (filesize - offset > 64 * 1024)
            { return(ZZIP_DIR_EDH_MISSING); }

        /* the new offset shall overlap with the area after the old offset! */
        if (USE_MMAP && io->fd.sys)
        {
            zzip_off_t mapoff = offset;
            {
                zzip_ssize_t pagesize = _zzip_getpagesize(io->fd.sys);
                if (pagesize < ZZIP_BUFSIZ)
                    goto non_mmap;      /* an error? */
                if (mapoff == filesize && filesize > pagesize)
                    mapoff -= pagesize;
                if (mapoff < pagesize)
                {
                    maplen = (zzip_ssize_t) mapoff + pagesize;
                    mapoff = 0;
                } else
                {
                    mapoff -= pagesize;
                    maplen = 2 * pagesize;
                    if ((zzip_ssize_t) mapoff & (pagesize - 1))
                    {           /*only 1. run */
                        pagesize -= (zzip_ssize_t) mapoff & (pagesize - 1);
                        mapoff += pagesize;
                        maplen -= pagesize;
                    }
                }
                if (mapoff + maplen > filesize)
                    maplen = filesize - mapoff;
            }

            fd_map = _zzip_mmap(io->fd.sys, fd, mapoff, (zzip_size_t) maplen);
            if (fd_map == MAP_FAILED)
                goto non_mmap;
            mapped = (unsigned char *) fd_map;
            offset = mapoff;    /* success */
            HINT3("mapped *%p len=%li", fd_map, (long) maplen);
        } else
        {
          non_mmap:
            fd_map = 0;         /* have no mmap */
            {
                zzip_off_t pagesize = ZZIP_BUFSIZ;
                if (offset == filesize && filesize > pagesize)
                    offset -= pagesize;
                if (offset < pagesize)
                {
                    maplen = (zzip_ssize_t) offset + pagesize;
                    offset = 0;
                } else
                {
                    offset -= pagesize;
                    maplen = 2 * pagesize;
                    if ((zzip_ssize_t) offset & (pagesize - 1))
                    {           /*on 1st run */
                        pagesize -= (zzip_ssize_t) offset & (pagesize - 1);
                        offset += pagesize;
                        maplen -= pagesize;
                    }
                }
                if (offset + maplen > filesize)
                    maplen = filesize - offset;
            }

            if (io->fd.seeks(fd, offset, SEEK_SET) < 0)
                { return(ZZIP_DIR_SEEK); }
            if (io->fd.read(fd, buf, (zzip_size_t) maplen) < maplen)
                { return(ZZIP_DIR_READ); }
            mapped = (unsigned char *) buf;     /* success */
            HINT5("offs=$%lx len=%li filesize=%li pagesize=%i",
                  (long) offset, (long) maplen, (long) filesize, ZZIP_BUFSIZ);
        }

        {                       /* now, check for the trailer-magic, hopefully near the end of file */
            register unsigned char *end = mapped + maplen;
            register unsigned char *tail;
            for (tail = end - 1; (tail >= mapped); tail--)
            {
                if ((*tail == 'P') &&   /* quick pre-check for trailer magic */
                    end - tail >= __sizeof(struct zzip_disk_trailer) - 2 &&
                    zzip_disk_trailer_check_magic(tail))
                {
#                  ifndef ZZIP_DISK64_TRAILER
                    /* if the file-comment is not present, it happens
                       that the z_comment field often isn't either */
                    if (end - tail >= __sizeof(*trailer))
                    {
                        memcpy(trailer, tail, sizeof(*trailer));
                    } else
                    {
                        memcpy(trailer, tail, sizeof(*trailer) - 2);
                        trailer->z_comment[0] = 0;
                        trailer->z_comment[1] = 0;
                    }
#                  else
                    struct zzip_disk_trailer *orig =
                        (struct zzip_disk_trailer *) tail;
                    trailer->zz_tail = tail;
                    trailer->zz_entries = zzip_disk_trailer_localentries(orig);
                    trailer->zz_finalentries =
                        zzip_disk_trailer_finalentries(orig);
                    trailer->zz_rootseek = zzip_disk_trailer_rootseek(orig);
                    trailer->zz_rootsize = zzip_disk_trailer_rootsize(orig);
#                  endif
                    if (trailer->zz_rootseek < 0 || trailer->zz_rootsize < 0)
                       return(ZZIP_CORRUPTED); // forged value

                    __fixup_rootseek(offset + tail - mapped, trailer);
		    /*
		     * "extract data from files archived in a single zip file."
		     * So the file offsets must be within the current ZIP archive!
		     */
		    if (trailer->zz_rootseek >= filesize || (trailer->zz_rootseek + trailer->zz_rootsize) >= filesize)
		        return(ZZIP_CORRUPTED);
                    { return(0); }
                } else if ((*tail == 'P') &&
                           end - tail >=
                           __sizeof(struct zzip_disk64_trailer) - 2
                           && zzip_disk64_trailer_check_magic(tail))
                {
#                  ifndef ZZIP_DISK64_TRAILER
                    return (ZZIP_DIR_LARGEFILE);
#                  else
                    struct zzip_disk64_trailer *orig =
                        (struct zzip_disk64_trailer *) tail;
                    trailer->zz_tail = tail;
                    trailer->zz_entries =
                        zzip_disk64_trailer_localentries(orig);
                    trailer->zz_finalentries =
                        zzip_disk64_trailer_finalentries(orig);
                    trailer->zz_rootseek = zzip_disk64_trailer_rootseek(orig);
                    trailer->zz_rootsize = zzip_disk64_trailer_rootsize(orig);
                    if (trailer->zz_rootseek < 0 || trailer->zz_rootsize < 0)
                       return(ZZIP_CORRUPTED); // forged value
		    /*
		     * "extract data from files archived in a single zip file."
		     * So the file offsets must be within the current ZIP archive!
		     */
		    if (trailer->zz_rootseek >= filesize || (trailer->zz_rootseek + trailer->zz_rootsize) >= filesize)
		        return(ZZIP_CORRUPTED);
                    { return(0); }
#                  endif
                }
            }
        }

        if (USE_MMAP && fd_map)
        {
            HINT3("unmap *%p len=%li", fd_map, (long) maplen);
            _zzip_munmap(io->fd.sys, fd_map, (zzip_size_t) maplen);
            fd_map = 0;
        }
    }                           /*outer loop */

  cleanup:
    if (USE_MMAP && fd_map)
    {
        HINT3("unmap *%p len=%li", fd_map, (long) maplen);
        _zzip_munmap(io->fd.sys, fd_map, (zzip_size_t) maplen);
    }
#   ifdef _LOWSTK
    free(buf);
#   endif
#   undef return
    return e;
}

Project: zziplib
File: zip.c

__zzip_try_open(zzip_char_t * filename, int filemode,
                zzip_strings_t * ext, zzip_plugin_io_t io)
{
    auto char file[PATH_MAX];
    int fd;
    zzip_size_t len = strlen(filename);

    if (len + 4 >= PATH_MAX)
        { errno = ENAMETOOLONG; return -1; }
    memcpy(file, filename, len + 1);

    if (! io)
        io = zzip_get_default_io();
    if (! ext)
        ext = zzip_get_default_ext();

    for (; *ext; ++ext)
    {
        strcpy(file + len, *ext);
        fd = (io->fd.open)(file, filemode);
        if (fd != -1)
            return fd;
    }
    return -1;
}

Project: zziplib
File: zip.c

zzip_get_default_ext(void)
{
    static zzip_strings_t ext[] = {
	/* *INDENT-OFF* */
       ".zip", ".ZIP", /* common extension */
#     ifdef ZZIP_USE_ZIPLIKES
       ".pk3", ".PK3", /* ID Software's Quake3 zipfiles */
       ".jar", ".JAR", /* Java zipfiles */
#      endif
	/* *INDENT-ON* */
        0
    };

    return ext;
}

Project: zziplib
File: zip.c

zzip_dir_close(ZZIP_DIR * dir)
{
    dir->refcount &= ~0x10000000;       /* explicit dir close */
    return zzip_dir_free(dir);
}

Project: zziplib
File: zip.c

zzip_dir_open_ext_io(zzip_char_t * filename, zzip_error_t * e,
                     zzip_strings_t * ext, zzip_plugin_io_t io)
{
    int fd;

    if (! io)
        io = zzip_get_default_io();
    if (! ext)
        ext = zzip_get_default_ext();

    fd = (io->fd.open)(filename, O_RDONLY | O_BINARY);
    if (fd != -1)
    {
        return zzip_dir_fdopen_ext_io(fd, e, ext, io);
    } else
    {
        fd = __zzip_try_open(filename, O_RDONLY | O_BINARY, ext, io);
        if (fd != -1)
        {
            return zzip_dir_fdopen_ext_io(fd, e, ext, io);
        } else
        {
            if (e)
                { *e = ZZIP_DIR_OPEN; }
            return 0;
        }
    }
}

Project: zziplib
File: zip.c

zzip_dir_read(ZZIP_DIR * dir, ZZIP_DIRENT * d)
{
    if (! dir || ! dir->hdr || ! d)
        return 0;

    d->d_compr = dir->hdr->d_compr;
    d->d_csize = dir->hdr->d_csize;
    d->st_size = dir->hdr->d_usize;
    d->d_name = dir->hdr->d_name;

    if (! dir->hdr->d_reclen)
    {
        dir->hdr = 0;
    } else
    {
        dir->hdr = (struct zzip_dir_hdr *)
            ((char *) dir->hdr + dir->hdr->d_reclen);
    }

    return 1;
}

Project: zziplib
File: zip.c

zzip_dir_alloc(zzip_strings_t * fileext)
{
    return zzip_dir_alloc_ext_io(fileext, 0);
}

Project: zziplib
File: zip.c

__fixup_rootseek(zzip_off_t offset_of_trailer, struct _disk_trailer *trailer)
{
    if (_disk_trailer_rootseek(trailer) >
        offset_of_trailer - _disk_trailer_rootsize(trailer) &&
        offset_of_trailer > _disk_trailer_rootsize(trailer))
    {
        register zzip_off_t offset;
        offset = offset_of_trailer - _disk_trailer_rootsize(trailer);
        _disk_trailer_set_rootseek(trailer, offset);
        HINT2("new rootseek=%li", (long) _disk_trailer_rootseek(trailer));
    }
}

Project: zziplib
File: zip.c

__zzip_aligned4(char *p)
{
#define aligned4   __zzip_aligned4
    p += ((intptr_t) p) & 1;
    p += ((intptr_t) p) & 2;
    return p;
}