libclamav/unzip.c
50593e02
 /*
c442ca9c
  *  Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
9eff9418
  *  Copyright (C) 2007-2013 Sourcefire, Inc.
2023340a
  *
  *  Authors: Alberto Wu
50593e02
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2 as
  *  published by the Free Software Foundation.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
  */
 
 /* FIXME: get a clue about masked stuff */
 
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #ifdef HAVE_UNISTD_H
 #include <unistd.h>
 #endif
 #if HAVE_STRING_H
 #include <string.h>
 #endif
 #include <stdlib.h>
 #include <stdio.h>
 
 #include <zlib.h>
 #include "inflate64.h"
 #if HAVE_BZLIB_H
 #include <bzlib.h>
 #endif
 
f8be651b
 #include "explode.h"
50593e02
 #include "others.h"
 #include "clamav.h"
 #include "scanners.h"
 #include "matcher.h"
774898a7
 #include "fmap.h"
20b45621
 #include "json_api.h"
50593e02
 
 #define UNZIP_PRIVATE
 #include "unzip.h"
 
dcd26ea5
 #define ZIP_MAX_NUM_OVERLAPPING_FILES 5
 
a60ec799
 #define ZIP_CRC32(r,c,b,l)			\
     do {					\
 	r = crc32(~c,b,l);			\
 	r = ~r;					\
     } while(0)
 
 
50593e02
 static int wrap_inflateinit2(void *a, int b) {
   return inflateInit2(a, b);
 }
 
c8c878f9
 static int unz(const uint8_t *src, uint32_t csize, uint32_t usize, uint16_t method, uint16_t flags, unsigned int *fu, cli_ctx *ctx, char *tmpd, zip_cb zcb) {
50593e02
   char name[1024], obuf[BUFSIZ];
   char *tempfile = name;
   int of, ret=CL_CLEAN;
   unsigned int res=1, written=0;
 
   if(tmpd) {
58481352
     snprintf(name, sizeof(name), "%s"PATHSEP"zip.%03u", tmpd, *fu);
50593e02
     name[sizeof(name)-1]='\0';
   } else {
33068e09
     if(!(tempfile = cli_gentemp(ctx->engine->tmpdir))) return CL_EMEM;
50593e02
   }
   if((of = open(tempfile, O_RDWR|O_CREAT|O_TRUNC|O_BINARY, S_IRUSR|S_IWUSR))==-1) {
     cli_warnmsg("cli_unzip: failed to create temporary file %s\n", tempfile);
     if(!tmpd) free(tempfile);
1b9b5f6d
     return CL_ETMPFILE;
50593e02
   }
   switch (method) {
   case ALG_STORED:
     if(csize<usize) {
       unsigned int fake = *fu + 1;
       cli_dbgmsg("cli_unzip: attempting to inflate stored file with inconsistent size\n");
c8c878f9
       if ((ret=unz(src, csize, usize, ALG_DEFLATE, 0, &fake, ctx, tmpd, zcb))==CL_CLEAN) {
50593e02
 	(*fu)++;
 	res=fake-(*fu);
       }
       else break;
     }
     if(res==1) {
724b2bf7
       if(ctx->engine->maxfilesize && csize > ctx->engine->maxfilesize) {
e78b5186
 	cli_dbgmsg("cli_unzip: trimming output size to maxfilesize (%lu)\n", (long unsigned int) ctx->engine->maxfilesize);
724b2bf7
 	csize = ctx->engine->maxfilesize;
50593e02
       }
871177cd
       if(cli_writen(of, src, csize)!=(int)csize) ret = CL_EWRITE;
50593e02
       else res=0;
     }
     break;
 
   case ALG_DEFLATE:
   case ALG_DEFLATE64: {
     union {
       z_stream64 strm64;
       z_stream strm;
     } strm;
     typedef int (*unz_init_) (void *, int);
     typedef int (*unz_unz_) (void *, int);
     typedef int (*unz_end_) (void *);
     unz_init_ unz_init;
     unz_unz_ unz_unz;
     unz_end_ unz_end;
     int wbits;
     void **next_in;
     void **next_out;
     unsigned int *avail_in;
     unsigned int *avail_out;
 
     if(method == ALG_DEFLATE64) {
       unz_init = (unz_init_)inflate64Init2;
       unz_unz = (unz_unz_)inflate64;
       unz_end = (unz_end_)inflate64End;
       next_in = (void *)&strm.strm64.next_in;
       next_out = (void *)&strm.strm64.next_out;
       avail_in = &strm.strm64.avail_in;
       avail_out = &strm.strm64.avail_out;
       wbits=MAX_WBITS64;
     } else {
       unz_init = (unz_init_)wrap_inflateinit2;
       unz_unz = (unz_unz_)inflate;
       unz_end = (unz_end_)inflateEnd;
       next_in = (void *)&strm.strm.next_in;
       next_out = (void *)&strm.strm.next_out;
       avail_in = &strm.strm.avail_in;
       avail_out = &strm.strm.avail_out;
       wbits=MAX_WBITS;
     }
 
     memset(&strm, 0, sizeof(strm));
 
f304dc68
     *next_in = (void*) src;
50593e02
     *next_out = obuf;
     *avail_in = csize;
     *avail_out = sizeof(obuf);
     if (unz_init(&strm, -wbits)!=Z_OK) {
       cli_dbgmsg("cli_unzip: zinit failed\n");
       break;
     }
     while(1) {
       while((res = unz_unz(&strm, Z_NO_FLUSH))==Z_OK) {};
       if(*avail_out!=sizeof(obuf)) {
 	written+=sizeof(obuf)-(*avail_out);
724b2bf7
 	if(ctx->engine->maxfilesize && written > ctx->engine->maxfilesize) {
e78b5186
 	  cli_dbgmsg("cli_unzip: trimming output size to maxfilesize (%lu)\n", (long unsigned int) ctx->engine->maxfilesize);
50593e02
 	  res = Z_STREAM_END;
 	  break;
 	}
 	if(cli_writen(of, obuf, sizeof(obuf)-(*avail_out)) != (int)(sizeof(obuf)-(*avail_out))) {
c8c878f9
             cli_warnmsg("cli_unzip: falied to write %lu inflated bytes\n", (unsigned long int)sizeof(obuf)-(*avail_out));
871177cd
 	  ret = CL_EWRITE;
b346e362
 	  res = 100;
 	  break;
50593e02
 	}
 	*next_out = obuf;
 	*avail_out = sizeof(obuf);
 	continue;
       }
       break;
     }
     unz_end(&strm);
f3fd2ac2
     if ((res == Z_STREAM_END) | (res == Z_BUF_ERROR)) res=0;
50593e02
     break;
   }
 
 
 #if HAVE_BZLIB_H
c09deba1
 #ifdef NOBZ2PREFIX
a622f5f3
 #define BZ2_bzDecompress bzDecompress
 #define BZ2_bzDecompressEnd bzDecompressEnd
 #define BZ2_bzDecompressInit bzDecompressInit
c09deba1
 #endif
 
50593e02
   case ALG_BZIP2: {
     bz_stream strm;
     memset(&strm, 0, sizeof(strm));
     strm.next_in = (char *)src;
     strm.next_out = obuf;
     strm.avail_in = csize;
     strm.avail_out = sizeof(obuf);
     if (BZ2_bzDecompressInit(&strm, 0, 0)!=BZ_OK) {
       cli_dbgmsg("cli_unzip: bzinit failed\n");
       break;
     }
     while((res = BZ2_bzDecompress(&strm))==BZ_OK || res==BZ_STREAM_END) {
       if(strm.avail_out!=sizeof(obuf)) {
 	written+=sizeof(obuf)-strm.avail_out;
724b2bf7
 	if(ctx->engine->maxfilesize && written > ctx->engine->maxfilesize) {
e78b5186
 	  cli_dbgmsg("cli_unzip: trimming output size to maxfilesize (%lu)\n", (unsigned long int) ctx->engine->maxfilesize);
f8be651b
 	  res = BZ_STREAM_END;
50593e02
 	  break;
 	}
 	if(cli_writen(of, obuf, sizeof(obuf)-strm.avail_out) != (int)(sizeof(obuf)-strm.avail_out)) {
c8c878f9
             cli_warnmsg("cli_unzip: falied to write %lu bunzipped bytes\n", (long unsigned int)sizeof(obuf)-strm.avail_out);
871177cd
 	  ret = CL_EWRITE;
b346e362
 	  res = 100;
 	  break;
50593e02
 	}
 	strm.next_out = obuf;
 	strm.avail_out = sizeof(obuf);
b346e362
 	if (res == BZ_OK) continue; /* after returning BZ_STREAM_END once, decompress returns an error */
50593e02
       }
       break;
     }
     BZ2_bzDecompressEnd(&strm);
b346e362
     if (res == BZ_STREAM_END) res=0;
50593e02
     break;
   }
 #endif /* HAVE_BZLIB_H */
 
f8be651b
 
   case ALG_IMPLODE: {
     struct xplstate strm;
f304dc68
     strm.next_in = (void*)src;
7e05c025
     strm.next_out = (uint8_t *)obuf;
f8be651b
     strm.avail_in = csize;
     strm.avail_out = sizeof(obuf);
     if (explode_init(&strm, flags)!=EXPLODE_OK) {
       cli_dbgmsg("cli_unzip: explode_init() failed\n");
       break;
     }
     while((res = explode(&strm))==EXPLODE_OK) {
       if(strm.avail_out!=sizeof(obuf)) {
 	written+=sizeof(obuf)-strm.avail_out;
724b2bf7
 	if(ctx->engine->maxfilesize && written > ctx->engine->maxfilesize) {
e78b5186
 	  cli_dbgmsg("cli_unzip: trimming output size to maxfilesize (%lu)\n", (unsigned long int) ctx->engine->maxfilesize);
f8be651b
 	  res = 0;
 	  break;
 	}
 	if(cli_writen(of, obuf, sizeof(obuf)-strm.avail_out) != (int)(sizeof(obuf)-strm.avail_out)) {
c8c878f9
             cli_warnmsg("cli_unzip: falied to write %lu exploded bytes\n", (unsigned long int) sizeof(obuf)-strm.avail_out);
871177cd
 	  ret = CL_EWRITE;
b346e362
 	  res = 100;
 	  break;
f8be651b
 	}
7e05c025
 	strm.next_out = (uint8_t *)obuf;
f8be651b
 	strm.avail_out = sizeof(obuf);
 	continue;
       }
       break;
     }
     break;
   }
 
 
50593e02
   case ALG_LZMA:
     /* easy but there's not a single sample in the zoo */
 
 #if !HAVE_BZLIB_H
   case ALG_BZIP2:
 #endif
   case ALG_SHRUNK:
   case ALG_REDUCE1:
   case ALG_REDUCE2:
   case ALG_REDUCE3:
   case ALG_REDUCE4:
   case ALG_TOKENZD:
   case ALG_OLDTERSE:
   case ALG_RSVD1:
   case ALG_RSVD2:
   case ALG_RSVD3:
   case ALG_RSVD4:
   case ALG_RSVD5:
   case ALG_NEWTERSE:
   case ALG_LZ77:
   case ALG_WAVPACK:
   case ALG_PPMD:
     cli_dbgmsg("cli_unzip: unsupported method (%d)\n", method);
     break;
   default:
     cli_dbgmsg("cli_unzip: unknown method (%d)\n", method);
     break;
   }
 
   if(!res) {
     (*fu)++;
     cli_dbgmsg("cli_unzip: extracted to %s\n", tempfile);
7e40bab9
     if (lseek(of, 0, SEEK_SET) == -1) {
         cli_dbgmsg("cli_unzip: call to lseek() failed\n");
         if (!(tmpd))
             free(tempfile);
9eff9418
         close(of);
7e40bab9
         return CL_ESEEK;
     }
d39cb658
     ret = zcb(of, tempfile, ctx);
50593e02
     close(of);
33068e09
     if(!ctx->engine->keeptmp)
871177cd
       if(cli_unlink(tempfile)) ret = CL_EUNLINK;
50593e02
     if(!tmpd) free(tempfile);
     return ret;
   }
 
   close(of);
33068e09
   if(!ctx->engine->keeptmp)
871177cd
     if(cli_unlink(tempfile)) ret = CL_EUNLINK;
50593e02
   if(!tmpd) free(tempfile);
   cli_dbgmsg("cli_unzip: extraction failed\n");
   return ret;
 }
 
1ac97cf0
 /* zip update keys, taken from zip specification */
 static inline void zupdatekey(uint32_t key[3], unsigned char input)
 {
     unsigned char tmp[1];
     unsigned long crctmp;
 
     tmp[0] = input;
     ZIP_CRC32(key[0], key[0], tmp, 1);
 
     key[1] = key[1] + (key[0] & 0xff);
     key[1] = key[1] * 134775813 + 1;
 
     tmp[0] = key[1] >> 24;
     ZIP_CRC32(key[2], key[2], tmp, 1);
 }
 
 /* zip init keys */
038cb67a
 static inline void zinitkey(uint32_t key[3], struct cli_pwdb *password)
1ac97cf0
 {
     int i;
 
7cd9337a
     /* initialize keys, these are specified but the zip specification */
1ac97cf0
     key[0] = 305419896L;
     key[1] = 591751049L;
     key[2] = 878082192L;
 
     /* update keys with password  */
     for (i = 0; i < password->length; i++)
 	zupdatekey(key, password->passwd[i]);
 }
 
 /* zip decrypt byte */
 static inline unsigned char zdecryptbyte(uint32_t key[3])
 {
     unsigned short temp;
     temp = key[2] | 2;
     return ((temp * (temp ^ 1)) >> 8);
 }
 
 /* zip decrypt, CL_EPARSE = could not apply a password, csize includes the decryption header */
 /* TODO - search for strong encryption header (0x0017) and handle them */
 static inline int zdecrypt(const uint8_t *src, uint32_t csize, uint32_t usize, const uint8_t *lh, unsigned int *fu, cli_ctx *ctx, char *tmpd, zip_cb zcb)
 {
     int i, ret, v = 0;
     uint32_t key[3];
     uint8_t eh[12]; /* encryption header buffer */
038cb67a
     struct cli_pwdb *password, *pass_any, *pass_zip;
1ac97cf0
 
     if (!ctx || !ctx->engine)
 	return CL_ENULLARG;
 
f5f7b7a1
     /* dconf */
     if (ctx->dconf && !(ctx->dconf->archive & ARCH_CONF_PASSWD)) {
 	cli_dbgmsg("cli_unzip: decrypt - skipping encrypted file\n");
 	return CL_SUCCESS;
     }
1ac97cf0
 
038cb67a
     pass_any = ctx->engine->pwdbs[CLI_PWDB_ANY];
     pass_zip = ctx->engine->pwdbs[CLI_PWDB_ZIP];
1ac97cf0
 
038cb67a
     while (pass_any || pass_zip) {
 	password = pass_zip ? pass_zip : pass_any;
1ac97cf0
 
 	zinitkey(key, password);
 
 	/* decrypting the encryption header */
 	memcpy(eh, src, SIZEOF_EH);
 
 	for (i = 0; i < SIZEOF_EH; i++) {
 	    eh[i] ^= zdecryptbyte(key);
 	    zupdatekey(key, eh[i]);
 	}
 
 	/* verify that the password is correct */
 	if (LH_version > 20) { /* higher than 2.0 */
 	    uint16_t a = eh[SIZEOF_EH-1];
 
 	    if (LH_flags & F_USEDD) {
0b119e6f
 		cli_dbgmsg("cli_unzip: decrypt - (v%u) >> 0x%02x 0x%x (moddate)\n", LH_version, a, LH_mtime);
1ac97cf0
 		if (a == ((LH_mtime >> 8) & 0xff))
 		    v = 1;
 	    } else {
0b119e6f
 		cli_dbgmsg("cli_unzip: decrypt - (v%u) >> 0x%02x 0x%x (crc32)\n", LH_version, a, LH_crc32);
1ac97cf0
 		if (a == ((LH_crc32 >> 24) & 0xff))
 		    v = 1;
 	    }
 	} else {
 	    uint16_t a = eh[SIZEOF_EH-1], b = eh[SIZEOF_EH-2];
 
 	    if (LH_flags & F_USEDD) {
0b119e6f
 		cli_dbgmsg("cli_unzip: decrypt - (v%u) >> 0x0000%02x%02x 0x%x (moddate)\n", LH_version, a, b, LH_mtime);
1ac97cf0
 		if ((b | (a << 8)) == (LH_mtime & 0xffff))
 		    v = 1;
 	    } else {
0b119e6f
 		cli_dbgmsg("cli_unzip: decrypt - (v%u) >> 0x0000%02x%02x 0x%x (crc32)\n", LH_version, eh[SIZEOF_EH-1], eh[SIZEOF_EH-2], LH_crc32);
1ac97cf0
 		if ((b | (a << 8)) == ((LH_crc32 >> 16) & 0xffff))
 		    v = 1;
 	    }
 	}
 
 	if (v) {
 	    char name[1024], obuf[BUFSIZ];
 	    char *tempfile = name;
 	    unsigned int written = 0, total = 0;
 	    fmap_t *dcypt_map;
 	    const uint8_t *dcypt_zip;
 	    int of;
 
0b119e6f
 	    cli_dbgmsg("cli_unzip: decrypt - password [%s] matches\n", password->name);
1ac97cf0
 
 	    /* output decrypted data to tempfile */
 	    if(tmpd) {
 		snprintf(name, sizeof(name), "%s"PATHSEP"zip.decrypt.%03u", tmpd, *fu);
 		name[sizeof(name)-1]='\0';
 	    } else {
 		if(!(tempfile = cli_gentemp(ctx->engine->tmpdir))) return CL_EMEM;
 	    }
 	    if((of = open(tempfile, O_RDWR|O_CREAT|O_TRUNC|O_BINARY, S_IRUSR|S_IWUSR))==-1) {
0b119e6f
 		cli_warnmsg("cli_unzip: decrypt - failed to create temporary file %s\n", tempfile);
1ac97cf0
 		if(!tmpd) free(tempfile);
1b9b5f6d
 		return CL_ETMPFILE;
1ac97cf0
 	    }
 
 	    for (i = 12; i < csize; i++) {
 		obuf[written] = src[i] ^ zdecryptbyte(key);
 		zupdatekey(key, obuf[written]);
 
 		written++;
 		if (written >= BUFSIZ) {
 		    if (cli_writen(of, obuf, written)!=(int)written) {
 			ret = CL_EWRITE;
 			goto zd_clean;
 		    }
 		    total += written;
 		    written = 0;
 		}
 	    }
 	    if (written) {
 		if (cli_writen(of, obuf, written)!=(int)written) {
 		    ret = CL_EWRITE;
 		    goto zd_clean;
 		}
 		total += written;
 		written = 0;
 	    }
 
0b119e6f
 	    cli_dbgmsg("cli_unzip: decrypt - decrypted %u bytes to %s\n", total, tempfile);
1ac97cf0
 
 	    /* decrypt data to new fmap -> buffer */
 	    if (!(dcypt_map = fmap(of, 0, total))) {
0b119e6f
 		cli_warnmsg("cli_unzip: decrypt - failed to create fmap on decrypted file %s\n", tempfile);
1ac97cf0
 		ret = CL_EMAP;
 		goto zd_clean;
 	    }
 
 	    if (!(dcypt_zip = fmap_need_off_once(dcypt_map, 0, total))) {
0b119e6f
 		cli_warnmsg("cli_unzip: decrypt - failed to acquire buffer on decrypted file %s\n", tempfile);
1ac97cf0
 		funmap(dcypt_map);
 		ret = CL_EREAD;
 		goto zd_clean;
 	    }
 
 	    /* call unz on decrypted output */
 	    ret = unz(dcypt_zip, csize - SIZEOF_EH, usize, LH_method, LH_flags, fu, ctx, tmpd, zcb);
 
 	    /* clean-up and return */
 	    funmap(dcypt_map);
 	zd_clean:
 	    close(of);
 	    if (!ctx->engine->keeptmp)
202a5dae
                 if (cli_unlink(tempfile)) {
 		    if (!tmpd) free(tempfile);
 		    return CL_EUNLINK;
 		}
1ac97cf0
             if (!tmpd) free(tempfile);
 	    return ret;
 	}
 
038cb67a
 	if (pass_zip)
 	    pass_zip = pass_zip->next;
 	else
dcd26ea5
 	    pass_any = pass_any->next;
1ac97cf0
     }
 
0b119e6f
     cli_dbgmsg("cli_unzip: decrypt - skipping encrypted file, no valid passwords\n");
1ac97cf0
     return CL_SUCCESS;
 }
 
dcd26ea5
 static unsigned int lhdr(fmap_t *map, uint32_t loff,uint32_t zsize, unsigned int *fu, unsigned int fc, const uint8_t *ch, int *ret, cli_ctx *ctx, char *tmpd, int detect_encrypted, zip_cb zcb, uint32_t *file_local_header_size, uint32_t* file_local_data_size) {
f304dc68
   const uint8_t *lh, *zip;
50593e02
   char name[256];
e5083cb5
   uint32_t csize, usize;
7a307529
   int virus_found = 0;
50593e02
 
774898a7
   if(!(lh = fmap_need_off(map, loff, SIZEOF_LH))) {
       cli_dbgmsg("cli_unzip: lh - out of file\n");
       return 0;
50593e02
   }
   if(LH_magic != 0x04034b50) {
     if (!ch) cli_dbgmsg("cli_unzip: lh - wrkcomplete\n");
     else cli_dbgmsg("cli_unzip: lh - bad magic\n");
774898a7
     fmap_unneed_off(map, loff, SIZEOF_LH);
50593e02
     return 0;
   }
 
774898a7
   zip = lh + SIZEOF_LH;
50593e02
   zsize-=SIZEOF_LH;
 
   if(zsize<=LH_flen) {
     cli_dbgmsg("cli_unzip: lh - fname out of file\n");
b183aa78
     fmap_unneed_off(map, loff, SIZEOF_LH);
50593e02
     return 0;
   }
15f413d1
   if(ctx->engine->cdb || cli_debug_flag) {
774898a7
       uint32_t nsize = (LH_flen>=sizeof(name))?sizeof(name)-1:LH_flen;
f304dc68
       const char *src;
774898a7
       if(nsize && (src = fmap_need_ptr_once(map, zip, nsize))) {
 	  memcpy(name, zip, nsize);
 	  name[nsize]='\0';
       } else
 	  name[0] = '\0';
50593e02
   }
   zip+=LH_flen;
   zsize-=LH_flen;
 
3145cde0
   cli_dbgmsg("cli_unzip: lh - ZMDNAME:%d:%s:%u:%u:%x:%u:%u:%u\n", ((LH_flags & F_ENCR)!=0), name, LH_usize, LH_csize, LH_crc32, LH_method, fc, ctx->recursion);
50593e02
   /* ZMDfmt virname:encrypted(0-1):filename(exact|*):usize(exact|*):csize(exact|*):crc32(exact|*):method(exact|*):fileno(exact|*):maxdepth(exact|*) */
 
570b1d00
   if(cli_matchmeta(ctx, name, LH_csize, LH_usize, (LH_flags & F_ENCR)!=0, fc, LH_crc32, NULL) == CL_VIRUS) {
7a307529
       *ret = CL_VIRUS;
d7979d4f
       if (!SCAN_ALLMATCHES)
7a307529
           return 0;
       virus_found = 1;
50593e02
   }
 
   if(LH_flags & F_MSKED) {
     cli_dbgmsg("cli_unzip: lh - header has got unusable masked data\n");
     /* FIXME: need to find/craft a sample */
b183aa78
     fmap_unneed_off(map, loff, SIZEOF_LH);
50593e02
     return 0;
   }
 
f61e92da
   if(detect_encrypted && (LH_flags & F_ENCR) && SCAN_HEURISTIC_ENCRYPTED_ARCHIVE) {
8201d79d
     cli_dbgmsg("cli_unzip: Encrypted files found in archive.\n");
cbf5017a
     *ret = cli_append_virus(ctx, "Heuristics.Encrypted.Zip");
d7979d4f
     if ((*ret == CL_VIRUS && !SCAN_ALLMATCHES) || *ret != CL_CLEAN) {
7a307529
         fmap_unneed_off(map, loff, SIZEOF_LH);
         return 0;
     }
     virus_found = 1;
8201d79d
   }
dcd26ea5
 
50593e02
   if(LH_flags & F_USEDD) {
     cli_dbgmsg("cli_unzip: lh - has data desc\n");
774898a7
     if(!ch) {
b183aa78
 	fmap_unneed_off(map, loff, SIZEOF_LH);
774898a7
 	return 0;
     }
e5083cb5
     else { usize = CH_usize; csize = CH_csize; }
   } else { usize = LH_usize; csize = LH_csize; }
50593e02
 
   if(zsize<=LH_elen) {
     cli_dbgmsg("cli_unzip: lh - extra out of file\n");
b183aa78
     fmap_unneed_off(map, loff, SIZEOF_LH);
50593e02
     return 0;
   }
   zip+=LH_elen;
   zsize-=LH_elen;
 
dcd26ea5
   if (NULL != file_local_header_size)
       *file_local_header_size = zip - lh;
   if (NULL != file_local_data_size)
       *file_local_data_size = csize;
 
50593e02
   if (!csize) { /* FIXME: what's used for method0 files? csize or usize? Nothing in the specs, needs testing */
774898a7
       cli_dbgmsg("cli_unzip: lh - skipping empty file\n");
50593e02
   } else {
774898a7
       if(zsize<csize) {
 	  cli_dbgmsg("cli_unzip: lh - stream out of file\n");
b183aa78
 	  fmap_unneed_off(map, loff, SIZEOF_LH);
774898a7
 	  return 0;
       }
dcd26ea5
 
774898a7
       if(LH_flags & F_ENCR) {
a60ec799
 	  if(fmap_need_ptr_once(map, zip, csize))
 	      *ret = zdecrypt(zip, csize, usize, lh, fu, ctx, tmpd, zcb);
774898a7
       } else {
 	  if(fmap_need_ptr_once(map, zip, csize))
c8c878f9
 	      *ret = unz(zip, csize, usize, LH_method, LH_flags, fu, ctx, tmpd, zcb);
774898a7
       }
       zip+=csize;
       zsize-=csize;
50593e02
   }
 
7a307529
   if (virus_found != 0)
       *ret = CL_VIRUS;
 
b183aa78
   fmap_unneed_off(map, loff, SIZEOF_LH); /* unneed now. block is guaranteed to exists till the next need */
50593e02
   if(LH_flags & F_USEDD) {
774898a7
       if(zsize<12) {
 	  cli_dbgmsg("cli_unzip: lh - data desc out of file\n");
 	  return 0;
50593e02
       }
774898a7
       zsize-=12;
       if(fmap_need_ptr_once(map, zip, 4)) {
 	  if(cli_readint32(zip)==0x08074b50) {
 	      if(zsize<4) {
 		  cli_dbgmsg("cli_unzip: lh - data desc out of file\n");
 		  return 0;
 	      }
 	      zip+=4;
 	  }
       }
       zip+=12;
50593e02
   }
   return zip-lh;
 }
 
dcd26ea5
 static unsigned int chdr(fmap_t *map, uint32_t coff, uint32_t zsize, unsigned int *fu, unsigned int fc, int *ret, cli_ctx *ctx, char *tmpd, struct zip_requests *requests, uint32_t *file_local_offset, uint32_t *file_local_header_size, uint32_t *file_local_data_size) {
50593e02
   char name[256];
   int last = 0;
f304dc68
   const uint8_t *ch;
6146fae1
   int virus_found = 0;
50593e02
 
dcd26ea5
   if (NULL != file_local_offset)
       *file_local_offset = 0;
   if (NULL != file_local_header_size)
       *file_local_header_size = 0;
   if (NULL != file_local_data_size)
       *file_local_data_size = 0;
 
774898a7
   if(!(ch = fmap_need_off(map, coff, SIZEOF_CH)) || CH_magic != 0x02014b50) {
       if(ch) fmap_unneed_ptr(map, ch, SIZEOF_CH);
       cli_dbgmsg("cli_unzip: ch - wrkcomplete\n");
       return 0;
50593e02
   }
   coff+=SIZEOF_CH;
 
   cli_dbgmsg("cli_unzip: ch - flags %x - method %x - csize %x - usize %x - flen %x - elen %x - clen %x - disk %x - off %x\n", CH_flags, CH_method, CH_csize, CH_usize, CH_flen, CH_elen, CH_clen, CH_dsk, CH_off);
 
   if(zsize-coff<=CH_flen) {
     cli_dbgmsg("cli_unzip: ch - fname out of file\n");
     last=1;
   }
c8c878f9
 
   name[0]='\0';
6146fae1
   if(!last) {
774898a7
       unsigned int size = (CH_flen>=sizeof(name))?sizeof(name)-1:CH_flen;
f304dc68
       const char *src = fmap_need_off_once(map, coff, size);
774898a7
       if(src) {
 	  memcpy(name, src, size);
 	  name[size]='\0';
 	  cli_dbgmsg("cli_unzip: ch - fname: %s\n", name);
       }
50593e02
   }
   coff+=CH_flen;
 
51b8cc32
   /* requests do not supply a ctx; also prevent multiple scans */
   if(ctx && cli_matchmeta(ctx, name, CH_csize, CH_usize, (CH_flags & F_ENCR)!=0, fc, CH_crc32, NULL) == CL_VIRUS)
6146fae1
     virus_found = 1;
 
50593e02
   if(zsize-coff<=CH_elen && !last) {
     cli_dbgmsg("cli_unzip: ch - extra out of file\n");
     last=1;
   }
   coff+=CH_elen;
 
   if(zsize-coff<CH_clen && !last) {
     cli_dbgmsg("cli_unzip: ch - comment out of file\n");
     last = 1;
   }
   coff+=CH_clen;
 
c8c80ddf
   if (!requests) {
c8c878f9
       if(CH_off<zsize-SIZEOF_LH) {
dcd26ea5
           if (NULL != file_local_offset)
               *file_local_offset = CH_off;
           lhdr(map, CH_off, zsize-CH_off, fu, fc, ch, ret, ctx, tmpd, 1, zip_scan_cb, file_local_header_size, file_local_data_size);
c8c878f9
       } else cli_dbgmsg("cli_unzip: ch - local hdr out of file\n");
   }
   else {
c8c80ddf
       int i;
       size_t len;
 
       if (!last) {
           for (i = 0; i < requests->namecnt; ++i) {
               cli_dbgmsg("checking for %i: %s\n", i, requests->names[i]);
 
dcd26ea5
               len = MIN(sizeof(name)-1, requests->namelens[i]);
c8c80ddf
               if (!strncmp(requests->names[i], name, len)) {
                   requests->match = 1;
                   requests->found = i;
                   requests->loff = CH_off;
               }
           }
c8c878f9
       }
   }
 
6146fae1
   if (virus_found == 1)
       *ret = CL_VIRUS;
774898a7
   fmap_unneed_ptr(map, ch, SIZEOF_CH);
871b862e
   return (last?0:coff);
50593e02
 }
 
bd7f7684
 int cli_unzip(cli_ctx *ctx) {
50593e02
   unsigned int fc=0, fu=0;
   int ret=CL_CLEAN;
   uint32_t fsize, lhoff = 0, coff = 0;
49cc1e3c
   fmap_t *map = *ctx->fmap;
f304dc68
   char *tmpd;
   const char *ptr;
92e0ae15
   int virus_found = 0;
20b45621
 #if HAVE_JSON
   int toval = 0;
 #endif
dcd26ea5
   int bZipBombDetected                 = 0;
   uint32_t cur_file_local_offset       = 0;
   uint32_t cur_file_local_header_size  = 0;
   uint32_t cur_file_local_data_size    = 0;
   uint32_t prev_file_local_offset      = 0;
   uint32_t prev_file_local_header_size = 0;
   uint32_t prev_file_local_data_size   = 0;
50593e02
 
   cli_dbgmsg("in cli_unzip\n");
bd7f7684
   fsize = (uint32_t)map->len;
cd94be7a
   if(sizeof(off_t)!=sizeof(uint32_t) && (size_t)fsize!=map->len) {
50593e02
     cli_dbgmsg("cli_unzip: file too big\n");
     return CL_CLEAN;
   }
   if (fsize < SIZEOF_CH) {
     cli_dbgmsg("cli_unzip: file too short\n");
     return CL_CLEAN;
   }
33068e09
   if (!(tmpd = cli_gentemp(ctx->engine->tmpdir))) {
50593e02
     return CL_ETMPDIR;
0b8d9622
   }
50593e02
   if (mkdir(tmpd, 0700)) {
     cli_dbgmsg("cli_unzip: Can't create temporary directory %s\n", tmpd);
     free(tmpd);
     return CL_ETMPDIR;
   }
 
   for(coff=fsize-22 ; coff>0 ; coff--) { /* sizeof(EOC)==22 */
774898a7
       if(!(ptr = fmap_need_off_once(map, coff, 20)))
 	  continue;
       if(cli_readint32(ptr)==0x06054b50) {
 	  uint32_t chptr = cli_readint32(&ptr[16]);
 	  if(!CLI_ISCONTAINED(0, fsize, chptr, SIZEOF_CH)) continue;
 	  coff=chptr;
 	  break;
       }
50593e02
   }
 
   if(coff) {
dcd26ea5
       uint32_t nOverlappingFiles = 0;
 
774898a7
       cli_dbgmsg("cli_unzip: central @%x\n", coff);
dcd26ea5
       while((coff=chdr(map, coff, fsize, &fu, fc+1, &ret, ctx, tmpd, NULL, &cur_file_local_offset, &cur_file_local_header_size, &cur_file_local_data_size))) {
774898a7
 	  fc++;
 	  if (ctx->engine->maxfiles && fu>=ctx->engine->maxfiles) {
 	      cli_dbgmsg("cli_unzip: Files limit reached (max: %u)\n", ctx->engine->maxfiles);
 	      ret=CL_EMAXFILES;
 	  }
dcd26ea5
     /*
      * Detect overlapping files and zip bombs.
      */
     if ((((cur_file_local_offset > prev_file_local_offset) && (cur_file_local_offset < prev_file_local_offset + prev_file_local_header_size + prev_file_local_data_size)) ||
          ((prev_file_local_offset > cur_file_local_offset) && (prev_file_local_offset < cur_file_local_offset + cur_file_local_header_size + cur_file_local_data_size))) &&
         (cur_file_local_header_size + cur_file_local_data_size > 0)) {
         /* Overlapping file detected */
         nOverlappingFiles++;
 
         cli_dbgmsg("cli_unzip: Overlapping files detected.\n");
         cli_dbgmsg("    previous file end:  %u\n", prev_file_local_offset + prev_file_local_header_size + prev_file_local_data_size);
         cli_dbgmsg("    current file start: %u\n", cur_file_local_offset);
         if (ZIP_MAX_NUM_OVERLAPPING_FILES < nOverlappingFiles) {
           if (SCAN_HEURISTICS) {
               ret         = cli_append_virus(ctx, "Heuristics.Zip.OverlappingFiles");
               virus_found = 1;
           } else {
               ret = CL_EFORMAT;
           }
           bZipBombDetected = 1;
         }
     }
     prev_file_local_offset      = cur_file_local_offset;
     prev_file_local_header_size = cur_file_local_header_size;
     prev_file_local_data_size   = cur_file_local_data_size;
 
20b45621
 #if HAVE_JSON
           if (cli_json_timeout_cycle_check(ctx, &toval) != CL_SUCCESS) {
93a9a942
               ret=CL_ETIMEOUT;
20b45621
           }
 #endif
9276cd1f
           if (ret != CL_CLEAN) {
dcd26ea5
               if (ret == CL_VIRUS && SCAN_ALLMATCHES && !bZipBombDetected) {
9276cd1f
                   ret = CL_CLEAN;
                   virus_found = 1;
               } else
                   break;
           }
50593e02
       }
   } else cli_dbgmsg("cli_unzip: central not found, using localhdrs\n");
9276cd1f
   if (virus_found == 1)
       ret = CL_VIRUS;
50593e02
   if(fu<=(fc/4)) { /* FIXME: make up a sane ratio or remove the whole logic */
     fc = 0;
dcd26ea5
     while (ret==CL_CLEAN && lhoff<fsize && (coff=lhdr(map, lhoff, fsize-lhoff, &fu, fc+1, NULL, &ret, ctx, tmpd, 1, zip_scan_cb, NULL, NULL))) {
50593e02
       fc++;
       lhoff+=coff;
d7979d4f
       if (SCAN_ALLMATCHES && ret == CL_VIRUS) {
92e0ae15
           ret = CL_CLEAN;
           virus_found = 1;
       }
724b2bf7
       if (ctx->engine->maxfiles && fu>=ctx->engine->maxfiles) {
 	cli_dbgmsg("cli_unzip: Files limit reached (max: %u)\n", ctx->engine->maxfiles);
50593e02
 	ret=CL_EMAXFILES;
       }
20b45621
 #if HAVE_JSON
       if (cli_json_timeout_cycle_check(ctx, &toval) != CL_SUCCESS) {
93a9a942
           ret=CL_ETIMEOUT;
20b45621
       }
 #endif
 
50593e02
     }
   }
 
33068e09
   if (!ctx->engine->keeptmp) cli_rmdirs(tmpd);
50593e02
   free(tmpd);
 
92e0ae15
   if (ret == CL_CLEAN && virus_found)
     ret = CL_VIRUS;
 
50593e02
   return ret;
 }
 
c8c878f9
 int unzip_single_internal(cli_ctx *ctx, off_t lhoffl, zip_cb zcb)
 {
50593e02
   int ret=CL_CLEAN;
   unsigned int fu=0;
   uint32_t fsize;
49cc1e3c
   fmap_t *map = *ctx->fmap;
50593e02
 
   cli_dbgmsg("in cli_unzip_single\n");
2d5dbc37
   fsize = (uint32_t)(map->len - lhoffl);
cd94be7a
   if (lhoffl<0 || (size_t)lhoffl>map->len || (sizeof(off_t)!=sizeof(uint32_t) && (size_t)fsize!=map->len - lhoffl)) {
50593e02
     cli_dbgmsg("cli_unzip: bad offset\n");
     return CL_CLEAN;
   }
   if (fsize < SIZEOF_LH) {
     cli_dbgmsg("cli_unzip: file too short\n");
     return CL_CLEAN;
   }
 
dcd26ea5
   lhdr(map, lhoffl, fsize, &fu, 0, NULL, &ret, ctx, NULL, 0, zcb, NULL, NULL);
50593e02
 
   return ret;
 }
c8c878f9
 
 int cli_unzip_single(cli_ctx *ctx, off_t lhoffl) {
     return unzip_single_internal(ctx, lhoffl, zip_scan_cb);
 }
 
c8c80ddf
 int unzip_search_add(struct zip_requests *requests, const char *name, size_t nlen)
 {
     cli_dbgmsg("in unzip_search_add\n");
 
     if (requests->namecnt >= MAX_ZIP_REQUESTS) {
         cli_dbgmsg("DEBUGGING MESSAGE GOES HERE!\n");
         return CL_BREAK;
     }
 
     cli_dbgmsg("unzip_search_add: adding %s (len %llu)\n", name, (long long unsigned)nlen);
 
     requests->names[requests->namecnt] = name;
     requests->namelens[requests->namecnt] = nlen;
     requests->namecnt++;
 
     return CL_SUCCESS;
 }
 
 int unzip_search(cli_ctx *ctx, fmap_t *map, struct zip_requests *requests)
c8c878f9
 {
     unsigned int fc = 0;
c8c80ddf
     fmap_t *zmap = map;
c8c878f9
     size_t fsize;
     uint32_t coff = 0;
     const char *ptr;
     int ret = CL_CLEAN;
20b45621
 #if HAVE_JSON
     uint32_t toval = 0;
 #endif
c8c878f9
     cli_dbgmsg("in unzip_search\n");
c8c80ddf
 
     if ((!ctx && !map) || !requests) {
c8c878f9
         return CL_ENULLARG;
     }
 
c8c80ddf
     /* get priority to given map over *ctx->fmap */
     if (ctx && !map)
         zmap = *ctx->fmap;
     fsize = zmap->len;
     if(sizeof(off_t)!=sizeof(uint32_t) && fsize!=zmap->len) {
c8c878f9
         cli_dbgmsg("unzip_search: file too big\n");
         return CL_CLEAN;
     }
     if (fsize < SIZEOF_CH) {
         cli_dbgmsg("unzip_search: file too short\n");
         return CL_CLEAN;
     }
 
     for(coff=fsize-22 ; coff>0 ; coff--) { /* sizeof(EOC)==22 */
c8c80ddf
         if(!(ptr = fmap_need_off_once(zmap, coff, 20)))
c8c878f9
             continue;
         if(cli_readint32(ptr)==0x06054b50) {
             uint32_t chptr = cli_readint32(&ptr[16]);
             if(!CLI_ISCONTAINED(0, fsize, chptr, SIZEOF_CH)) continue;
             coff=chptr;
             break;
         }
     }
 
     if(coff) {
         cli_dbgmsg("unzip_search: central @%x\n", coff);
dcd26ea5
         while(ret==CL_CLEAN && (coff=chdr(zmap, coff, fsize, NULL, fc+1, &ret, ctx, NULL, requests, NULL, NULL, NULL))) {
c8c80ddf
             if (requests->match) {
93a9a942
                 ret=CL_VIRUS;
c8c878f9
             }
 
             fc++;
c8c80ddf
             if (ctx && ctx->engine->maxfiles && fc >= ctx->engine->maxfiles) {
c8c878f9
                 cli_dbgmsg("cli_unzip: Files limit reached (max: %u)\n", ctx->engine->maxfiles);
                 ret=CL_EMAXFILES;
             }
20b45621
 #if HAVE_JSON
c8c80ddf
             if (ctx && cli_json_timeout_cycle_check(ctx, (int *)(&toval)) != CL_SUCCESS) {
93a9a942
                 ret=CL_ETIMEOUT;
20b45621
             }
 #endif
c8c878f9
         }
     } else {
         cli_dbgmsg("unzip_search: cannot locate central directory\n");
     }
 
     return ret;
 }
 
c8c80ddf
 int unzip_search_single(cli_ctx *ctx, const char *name, size_t nlen, uint32_t *loff)
 {
     struct zip_requests requests;
     int ret;
 
     cli_dbgmsg("in unzip_search_single\n");
     if (!ctx) {
         return CL_ENULLARG;
     }
 
     memset(&requests, 0, sizeof(struct zip_requests));
 
     if ((ret = unzip_search_add(&requests, name, nlen)) != CL_SUCCESS) {
         return ret;
     }
 
     if ((ret = unzip_search(ctx, NULL, &requests)) == CL_VIRUS) {
         *loff = requests.loff;
     }
 
     return ret;
 }