libclamav/nsis/nulsft.c
faaf436a
 /*
2023340a
  *  Copyright (C) 2007-2008 Sourcefire Inc.
  *
  *  Authors: Alberto Wu
faaf436a
  *
  *  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.
  */
 
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <string.h>
 #ifdef HAVE_UNISTD_H
 #include <unistd.h>
 #endif
 
 #include "others.h"
 #include "cltypes.h"
 #include "nsis_bzlib.h"
84f4e2bb
 /* #include "zlib.h" */
 #include "nsis_zlib.h"
9f0d9b98
 #include "lzma_iface.h"
69c06112
 #include "matcher.h"
 #include "scanners.h"
a993c5ad
 #include "nulsft.h" /* SHUT UP GCC -Wextra */
5ef1fa42
 #include "fmap.h"
faaf436a
 
 #define EC32(x) le32_to_host(x)
 
 enum {
   COMP_NOT_DETECTED,
   COMP_BZIP2,
   COMP_LZMA,
   COMP_ZLIB,
   COMP_NOCOMP
 };
 
b8ad506b
 struct nsis_st {
5ef1fa42
   size_t curpos;
b8ad506b
   int ofd;
   off_t off;
1cd33a34
   off_t fullsz;
b8ad506b
   char *dir;
   uint32_t asz;
   uint32_t hsz;
   uint32_t fno;
1cd33a34
   uint8_t comp;
   uint8_t solid;
   uint8_t freecomp;
   uint8_t eof;
24555841
   struct stream_state nsis;
b8ad506b
   nsis_bzstream bz;
87787e76
   struct CLI_LZMA lz;
84f4e2bb
 /*   z_stream z; */
   nsis_z_stream z;
b8ad506b
   unsigned char *freeme;
49cc1e3c
   fmap_t *map;
b8ad506b
   char ofn[1024];
 };
 
 
faaf436a
 #define LINESTR(x) #x
 #define LINESTR2(x) LINESTR(x)
 #define __AT__  " at "__FILE__":"LINESTR2(__LINE__)
 
 static int nsis_init(struct nsis_st *n) {
   switch(n->comp) {
   case COMP_BZIP2:
f377e052
     memset(&n->bz, 0, sizeof(nsis_bzstream));
faaf436a
     if (nsis_BZ2_bzDecompressInit(&n->bz, 0, 0)!=BZ_OK)
871177cd
       return CL_EUNPACK;
faaf436a
     n->freecomp=1;
     break;
   case COMP_LZMA:
5e1d2c28
     memset(&n->lz, 0, sizeof(struct CLI_LZMA));
     if(cli_LzmaInit(&n->lz, 0xffffffffffffffffULL)!=LZMA_RESULT_OK)
       return CL_EUNPACK;
faaf436a
     n->freecomp=1;
     break;
   case COMP_ZLIB:
cadf86d4
     memset(&n->z, 0, sizeof(z_stream));
84f4e2bb
 /*     inflateInit2(&n->z, -MAX_WBITS); */
 /*     n->freecomp=1; */
     nsis_inflateInit(&n->z);
     n->freecomp=0;
faaf436a
   }
   return CL_SUCCESS;
 }
 
 static void nsis_shutdown(struct nsis_st *n) {
   if(!n->freecomp)
     return;
 
   switch(n->comp) {
   case COMP_BZIP2:
     nsis_BZ2_bzDecompressEnd(&n->bz);
     break;
   case COMP_LZMA:
f377e052
     cli_LzmaShutdown(&n->lz);
19da6c08
     break;
faaf436a
   case COMP_ZLIB:
84f4e2bb
 /*     inflateEnd(&n->z); */
faaf436a
     break;
   }
 
   n->freecomp=0;
 }
 
 static int nsis_decomp(struct nsis_st *n) {
c9896758
   int ret = CL_EFORMAT;
faaf436a
   switch(n->comp) {
   case COMP_BZIP2:
     n->bz.avail_in = n->nsis.avail_in;
     n->bz.next_in = n->nsis.next_in;
     n->bz.avail_out = n->nsis.avail_out;
     n->bz.next_out = n->nsis.next_out;
     switch (nsis_BZ2_bzDecompress(&n->bz)) {
     case BZ_OK:
       ret = CL_SUCCESS;
       break;
     case BZ_STREAM_END:
       ret = CL_BREAK;
     }
     n->nsis.avail_in = n->bz.avail_in;
     n->nsis.next_in = n->bz.next_in;
     n->nsis.avail_out = n->bz.avail_out;
     n->nsis.next_out = n->bz.next_out;
     break;
   case COMP_LZMA:
87787e76
     n->lz.avail_in = n->nsis.avail_in;
     n->lz.next_in = n->nsis.next_in;
     n->lz.avail_out = n->nsis.avail_out;
     n->lz.next_out = n->nsis.next_out;
     switch (cli_LzmaDecode(&n->lz)) {
f377e052
     case LZMA_RESULT_OK:
faaf436a
       ret = CL_SUCCESS;
       break;
     case LZMA_STREAM_END:
       ret = CL_BREAK;
     }
87787e76
     n->nsis.avail_in = n->lz.avail_in;
     n->nsis.next_in = n->lz.next_in;
     n->nsis.avail_out = n->lz.avail_out;
     n->nsis.next_out = n->lz.next_out;
faaf436a
     break;
   case COMP_ZLIB:
     n->z.avail_in = n->nsis.avail_in;
     n->z.next_in = n->nsis.next_in;
     n->z.avail_out = n->nsis.avail_out;
     n->z.next_out = n->nsis.next_out;
84f4e2bb
 /*  switch (inflate(&n->z, Z_NO_FLUSH)) { */
     switch (nsis_inflate(&n->z)) {
faaf436a
     case Z_OK:
       ret = CL_SUCCESS;
       break;
     case Z_STREAM_END:
       ret = CL_BREAK;
     }
     n->nsis.avail_in = n->z.avail_in;
     n->nsis.next_in = n->z.next_in;
     n->nsis.avail_out = n->z.avail_out;
     n->nsis.next_out = n->z.next_out;
     break;
   }
   return ret;
 }
 
 static int nsis_unpack_next(struct nsis_st *n, cli_ctx *ctx) {
   unsigned char *ibuf;
   uint32_t size, loops;
a87b030d
   int ret, gotsome=0;
faaf436a
   unsigned char obuf[BUFSIZ];
 
   if (n->eof) {
     cli_dbgmsg("NSIS: extraction complete\n");
     return CL_BREAK;
   }
bbd6ca3f
   
5a0c148d
   if ((ret=cli_checklimits("NSIS", ctx, 0, 0, 0))!=CL_CLEAN)
bbd6ca3f
     return ret;
faaf436a
 
   if (n->fno)
58481352
     snprintf(n->ofn, 1023, "%s"PATHSEP"content.%.3u", n->dir, n->fno);
faaf436a
   else
58481352
     snprintf(n->ofn, 1023, "%s"PATHSEP"headers", n->dir);
faaf436a
 
   n->fno++;
 
   if ((n->ofd=open(n->ofn, O_RDWR|O_CREAT|O_TRUNC|O_BINARY, 0600))==-1) {
     cli_errmsg("NSIS: unable to create output file %s - aborting.", n->ofn);
871177cd
     return CL_ECREAT;
faaf436a
   }
 
   if (!n->solid) {
5ef1fa42
     if (fmap_readn(n->map, &size, n->curpos, 4)!=4) {
faaf436a
       cli_dbgmsg("NSIS: reached EOF - extraction complete\n");
       close(n->ofd);
       return CL_BREAK;
     }
5ef1fa42
     n->curpos += 4;
faaf436a
     if (n->asz==4) {
       cli_dbgmsg("NSIS: reached CRC - extraction complete\n");
       close(n->ofd);
       return CL_BREAK;
     }
     loops = EC32(size);
     if (!(size = (loops&~0x80000000))) {
       cli_dbgmsg("NSIS: empty file found\n");
       return CL_SUCCESS;
     }
     if (n->asz <4 || size > n->asz-4) {
       cli_dbgmsg("NSIS: next file is outside the archive\n");
       close(n->ofd);
       return CL_BREAK;
     }
 
     n->asz -= size+4;
 
5a0c148d
     if ((ret=cli_checklimits("NSIS", ctx, size, 0, 0))!=CL_CLEAN) {
faaf436a
       close(n->ofd);
5ef1fa42
       n->curpos += size;
bbd6ca3f
       return ret;
faaf436a
     }
5ef1fa42
     if (!(ibuf = fmap_need_off_once(n->map, n->curpos, size))) {
faaf436a
       cli_dbgmsg("NSIS: cannot read %u bytes"__AT__"\n", size);
       close(n->ofd);
871177cd
       return CL_EREAD;
faaf436a
     }
5ef1fa42
     n->curpos += size;
faaf436a
     if (loops==size) {
       if (cli_writen(n->ofd, ibuf, size) != (ssize_t) size) {
 	cli_dbgmsg("NSIS: cannot write output file"__AT__"\n");
 	close(n->ofd);
871177cd
 	return CL_EWRITE;
faaf436a
       }
     } else {
       if ((ret=nsis_init(n))!=CL_SUCCESS) {
 	cli_dbgmsg("NSIS: decompressor init failed"__AT__"\n");
 	close(n->ofd);
 	return ret;
       }
       
       n->nsis.avail_in = size;
       n->nsis.next_in = ibuf;
       n->nsis.next_out = obuf;
       n->nsis.avail_out = BUFSIZ;
       loops=0;
 
       while ((ret=nsis_decomp(n))==CL_SUCCESS) {
 	if ((size = n->nsis.next_out - obuf)) {
a87b030d
 	  gotsome=1;
faaf436a
 	  if (cli_writen(n->ofd, obuf, size) != (ssize_t) size) {
 	    cli_dbgmsg("NSIS: cannot write output file"__AT__"\n");
 	    close(n->ofd);
a87b030d
 	    nsis_shutdown(n);
871177cd
 	    return CL_EWRITE;
faaf436a
 	  }
 	  n->nsis.next_out = obuf;
 	  n->nsis.avail_out = BUFSIZ;
 	  loops=0;
bbd6ca3f
 	  if ((ret=cli_checklimits("NSIS", ctx, size, 0, 0))!=CL_CLEAN) {
faaf436a
 	    close(n->ofd);
 	    nsis_shutdown(n);
bbd6ca3f
 	    return ret;
faaf436a
 	  }
a87b030d
 	} else if (++loops > 20) {
faaf436a
 	  cli_dbgmsg("NSIS: xs looping, breaking out"__AT__"\n");
a87b030d
 	  ret = CL_EFORMAT;
faaf436a
 	  break;
 	}
       }
 
a87b030d
       nsis_shutdown(n);
 
       if (n->nsis.next_out - obuf) {
 	gotsome=1;
 	if (cli_writen(n->ofd, obuf, n->nsis.next_out - obuf) != n->nsis.next_out - obuf) {
 	  cli_dbgmsg("NSIS: cannot write output file"__AT__"\n");
 	  close(n->ofd);
871177cd
 	  return CL_EWRITE;
a87b030d
 	}
faaf436a
       }
 
a87b030d
       if (ret != CL_SUCCESS && ret != CL_BREAK) {
 	cli_dbgmsg("NSIS: bad stream"__AT__"\n");
 	if (gotsome) {
 	  ret = CL_SUCCESS;
 	} else {
04468f36
 	  ret = CL_EMAXSIZE;
a87b030d
 	  close(n->ofd);
 	}
 	return ret;
faaf436a
       }
a87b030d
 
faaf436a
     }
 
     return CL_SUCCESS;
 
   } else {
     if (!n->freeme) {
       if ((ret=nsis_init(n))!=CL_SUCCESS) {
 	cli_dbgmsg("NSIS: decompressor init failed\n");
 	close(n->ofd);
 	return ret;
       }
5ef1fa42
       if(!(n->freeme = fmap_need_off_once(n->map, n->curpos, n->asz))) {
cd6d37c1
 	cli_dbgmsg("NSIS: cannot read %u bytes"__AT__"\n", n->asz);
faaf436a
 	close(n->ofd);
871177cd
 	return CL_EREAD;
faaf436a
       }
       n->nsis.next_in = n->freeme;
       n->nsis.avail_in = n->asz;
     }
 
     if (n->nsis.avail_in<=4) {
       cli_dbgmsg("NSIS: extraction complete\n");
       close(n->ofd);
       return CL_BREAK;
     }
     n->nsis.next_out = obuf;
     n->nsis.avail_out = 4;
     loops = 0;
 
     while ((ret=nsis_decomp(n))==CL_SUCCESS) {
       if (n->nsis.next_out - obuf == 4) break;
       if (++loops > 20) {
 	cli_dbgmsg("NSIS: xs looping, breaking out"__AT__"\n");
 	ret = CL_BREAK;
 	break;
       }
     }
 
     if (ret != CL_SUCCESS) {
       cli_dbgmsg("NSIS: bad stream"__AT__"\n");
       close(n->ofd);
       return CL_EFORMAT;
     }
 
     size=cli_readint32(obuf);
bbd6ca3f
     if ((ret=cli_checklimits("NSIS", ctx, size, 0, 0))!=CL_CLEAN) {
faaf436a
       close(n->ofd);
bbd6ca3f
       return ret;
faaf436a
     }
 
     n->nsis.next_out = obuf;
     n->nsis.avail_out = MIN(BUFSIZ,size);
     loops = 0;
 
     while (size && (ret=nsis_decomp(n))==CL_SUCCESS) {
       unsigned int wsz;
       if ((wsz = n->nsis.next_out - obuf)) {
a87b030d
 	gotsome=1;
faaf436a
 	if (cli_writen(n->ofd, obuf, wsz) != (ssize_t) wsz) {
a87b030d
 	  cli_dbgmsg("NSIS: cannot write output file"__AT__"\n");
faaf436a
 	  close(n->ofd);
871177cd
 	  return CL_EWRITE;
faaf436a
 	}
 	size-=wsz;
a87b030d
 	loops=0;
faaf436a
 	n->nsis.next_out = obuf;
 	n->nsis.avail_out = MIN(size,BUFSIZ);
       } else if ( ++loops > 20 ) {
 	cli_dbgmsg("NSIS: xs looping, breaking out"__AT__"\n");
a87b030d
 	ret = CL_EFORMAT;
faaf436a
 	break;
       }
     }
 
a87b030d
     if (n->nsis.next_out - obuf) {
       gotsome=1;
faaf436a
       if (cli_writen(n->ofd, obuf, n->nsis.next_out - obuf) != n->nsis.next_out - obuf) {
a87b030d
 	cli_dbgmsg("NSIS: cannot write output file"__AT__"\n");
faaf436a
 	close(n->ofd);
871177cd
 	return CL_EWRITE;
faaf436a
       }
a87b030d
     }
 
     if (ret == CL_EFORMAT) {
       cli_dbgmsg("NSIS: bad stream"__AT__"\n");
       if (!gotsome) {
 	close(n->ofd);
 	return CL_EMAXSIZE;
       } 
     }
 
     if (ret == CL_EFORMAT || ret == CL_BREAK) {
faaf436a
       n->eof=1;
     } else if (ret != CL_SUCCESS) {
       cli_dbgmsg("NSIS: bad stream"__AT__"\n");
       close(n->ofd);
       return CL_EFORMAT;
     }
     return CL_SUCCESS;
   }
 
 }
 
b8ad506b
 static uint8_t nsis_detcomp(const char *b) {
faaf436a
   if (*b=='1') return COMP_BZIP2;
   if ((cli_readint32(b)&~0x80000000)==0x5d) return COMP_LZMA;
   return COMP_ZLIB;
 }
 
 static int nsis_headers(struct nsis_st *n, cli_ctx *ctx) {
5ef1fa42
   const char *buf;
faaf436a
   uint32_t pos;
   int i;
   uint8_t comps[] = {0, 0, 0, 0}, trunc = 0;
   
5ef1fa42
   if (!(buf = fmap_need_off_once(n->map, n->off, 0x1c)))
871177cd
     return CL_EREAD;
faaf436a
 
   n->hsz = (uint32_t)cli_readint32(buf+0x14);
   n->asz = (uint32_t)cli_readint32(buf+0x18);
5ef1fa42
   n->fullsz = n->map->len;
faaf436a
 
   cli_dbgmsg("NSIS: Header info - Flags=%x, Header size=%x, Archive size=%x\n", cli_readint32(buf), n->hsz, n->asz);
 
5ef1fa42
   if (n->fullsz - n->off < (off_t) n->asz) {
faaf436a
     cli_dbgmsg("NSIS: Possibly truncated file\n");
5ef1fa42
     n->asz = n->fullsz - n->off;
faaf436a
     trunc++;
5ef1fa42
   } else if (n->fullsz - n->off != (off_t) n->asz) {
faaf436a
     cli_dbgmsg("NSIS: Overlays found\n");
   }
 
   n->asz -= 0x1c;
5ef1fa42
   buf += 0x1c;
faaf436a
 
   /* Guess if solid */
   for (i=0, pos=0;pos < n->asz-4;i++) {
     int32_t nextsz;
49cc1e3c
     if (!(buf = fmap_need_ptr_once(n->map, (void *)buf, 4))) return CL_EREAD;
5ef1fa42
     nextsz=cli_readint32(buf);
     if (!i) n->comp = nsis_detcomp(buf);
     buf += 4;
faaf436a
     if (nextsz&0x80000000) {
       nextsz&=~0x80000000;
49cc1e3c
       if (!(buf = fmap_need_ptr_once(n->map, (void *)buf, 4))) return CL_EREAD;
5ef1fa42
       comps[nsis_detcomp(buf)]++;
faaf436a
       nextsz-=4;
       pos+=4;
5ef1fa42
       buf+=4;
faaf436a
     }
     if ((pos+=4+nextsz) > n->asz) {
       n->solid = 1;
       break;
     }
 
5ef1fa42
     buf += nextsz;
faaf436a
   }
   
   if (trunc && i>=2) n->solid=0;
 
   cli_dbgmsg("NSIS: solid compression%s detected\n", (n->solid)?"":" not");
 
   /* Guess the compression method */
   if (!n->solid) {
     cli_dbgmsg("NSIS: bzip2 %u - lzma %u - zlib %u\n", comps[1], comps[2], comps[3]);
     n->comp = (comps[1]<comps[2]) ? (comps[2]<comps[3] ? COMP_ZLIB : COMP_LZMA) : (comps[1]<comps[3] ? COMP_ZLIB : COMP_BZIP2);
   }
 
5ef1fa42
   n->curpos = n->off+0x1c;
faaf436a
   return nsis_unpack_next(n, ctx);
 }
 
 
 
a993c5ad
 static int cli_nsis_unpack(struct nsis_st *n, cli_ctx *ctx) {
faaf436a
   return (n->fno) ? nsis_unpack_next(n, ctx) : nsis_headers(n, ctx);
 }
 
05eff05f
 
 int cli_scannulsft(int desc, cli_ctx *ctx, off_t offset) {
         int ret;
 	struct nsis_st nsist;
 
     cli_dbgmsg("in scannulsft()\n");
 
     memset(&nsist, 0, sizeof(struct nsis_st));
 
     nsist.off = offset;
33068e09
     if (!(nsist.dir = cli_gentemp(ctx->engine->tmpdir)))
f5354453
         return CL_ETMPDIR;
05eff05f
     if(mkdir(nsist.dir, 0700)) {
 	cli_dbgmsg("NSIS: Can't create temporary directory %s\n", nsist.dir);
 	free(nsist.dir);
 	return CL_ETMPDIR;
     }
5ef1fa42
     
     if(!(nsist.map = fmap(desc, 0, 0))) {
 	cli_errmsg("scannulsft: fmap failed\n");
 	return CL_EMEM;
     }
05eff05f
 
33068e09
     if(ctx->engine->keeptmp) cli_dbgmsg("NSIS: Extracting files to %s\n", nsist.dir);
b8ad506b
 
05eff05f
     do {
         ret = cli_nsis_unpack(&nsist, ctx);
c17ed38c
 	if (ret == CL_SUCCESS) {
 	  cli_dbgmsg("NSIS: Successully extracted file #%u\n", nsist.fno);
 	  lseek(nsist.ofd, 0, SEEK_SET);
 	  if(nsist.fno == 1)
ffa9b060
 	    ret=cli_scandesc(nsist.ofd, ctx, 0, 0, NULL, AC_SCAN_VIR, NULL);
c17ed38c
 	  else
 	    ret=cli_magic_scandesc(nsist.ofd, ctx);
 	  close(nsist.ofd);
33068e09
 	  if(!ctx->engine->keeptmp)
871177cd
 	    if(cli_unlink(nsist.ofn)) ret = CL_EUNLINK;
c17ed38c
 	} else if(ret == CL_EMAXSIZE) {
 	    ret = nsist.solid ? CL_BREAK : CL_SUCCESS;
05eff05f
 	}
     } while(ret == CL_SUCCESS);
 
bbd6ca3f
     if(ret == CL_BREAK || ret == CL_EMAXFILES)
05eff05f
 	ret = CL_CLEAN;
 
5ef1fa42
     nsis_shutdown(&nsist);
49cc1e3c
     funmap(nsist.map);
05eff05f
 
33068e09
     if(!ctx->engine->keeptmp)
05eff05f
         cli_rmdirs(nsist.dir);
 
     free(nsist.dir);
 
     return ret;
 }
69c06112