libclamav/nsis/nulsft.c
faaf436a
 /*
  *  Copyright (C) 2007 aCaB <acab@clamav.net>
  *
  *  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"
 #include "LZMADecode.h"
 #include "nsis_zlib.h"
69c06112
 #include "matcher.h"
 #include "scanners.h"
a993c5ad
 #include "nulsft.h" /* SHUT UP GCC -Wextra */
faaf436a
 
 #ifndef O_BINARY
 #define O_BINARY 0
 #endif
 
 #define EC32(x) le32_to_host(x)
 
 enum {
   COMP_NOT_DETECTED,
   COMP_BZIP2,
   COMP_LZMA,
   COMP_ZLIB,
   COMP_NOCOMP
 };
 
b8ad506b
 struct nsis_st {
   int ifd;
   int ofd;
   off_t off;
   char *dir;
   uint32_t asz;
   uint32_t hsz;
   uint32_t fno;
   struct {
     uint32_t avail_in;
     unsigned char *next_in;
     uint32_t avail_out;
     unsigned char *next_out;
   } nsis;
   nsis_bzstream bz;
   lzma_stream lz;
   nsis_z_stream z;
   unsigned char *freeme;
   uint8_t comp;
   uint8_t solid;
   uint8_t freecomp;
   uint8_t eof;
   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:
     if (nsis_BZ2_bzDecompressInit(&n->bz, 0, 0)!=BZ_OK)
       return CL_EBZIP;
     n->freecomp=1;
     break;
   case COMP_LZMA:
     lzmaInit(&n->lz);
     n->freecomp=1;
     break;
   case COMP_ZLIB:
     nsis_inflateInit(&n->z);
     n->freecomp=0;
   }
   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:
     lzmaShutdown(&n->lz);
   case COMP_ZLIB:
     break;
   }
 
   n->freecomp=0;
 }
 
 static int nsis_decomp(struct nsis_st *n) {
   int ret = CL_EFORMAT;
   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:
     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 (lzmaDecode(&n->lz)) {
     case LZMA_OK:
       ret = CL_SUCCESS;
       break;
     case LZMA_STREAM_END:
       ret = CL_BREAK;
     }
     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;
     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;
     switch (nsis_inflate(&n->z)) {
     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;
   int ret;
   unsigned char obuf[BUFSIZ];
 
   if (n->eof) {
     cli_dbgmsg("NSIS: extraction complete\n");
     return CL_BREAK;
   }
   if (ctx->limits && ctx->limits->maxfiles && n->fno >= ctx->limits->maxfiles) {
     cli_dbgmsg("NSIS: Files limit reached (max: %u)\n", ctx->limits->maxfiles);
     return CL_EMAXFILES;
   }
 
   if (n->fno)
     snprintf(n->ofn, 1023, "%s/content.%.3u", n->dir, n->fno);
   else
     snprintf(n->ofn, 1023, "%s/headers", n->dir);
 
   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);
     return CL_EIO;
   }
 
   if (!n->solid) {
     if (cli_readn(n->ifd, &size, 4)!=4) {
       cli_dbgmsg("NSIS: reached EOF - extraction complete\n");
       close(n->ofd);
       return CL_BREAK;
     }
     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;
 
     if (ctx->limits && ctx->limits->maxfilesize && size > ctx->limits->maxfilesize) {
       cli_dbgmsg("NSIS: Skipping file due to size limit (%u, max: %lu)\n", size, ctx->limits->maxfilesize);
       close(n->ofd);
       if (lseek(n->ifd, size, SEEK_CUR)==-1) return CL_EIO;
       return CL_EMAXSIZE;
     }
     if (!(ibuf= (unsigned char *) cli_malloc(size))) {
       	cli_dbgmsg("NSIS: out of memory"__AT__"\n");
       close(n->ofd);
       return CL_EMEM;
     }
     if (cli_readn(n->ifd, ibuf, size) != (ssize_t) size) {
       cli_dbgmsg("NSIS: cannot read %u bytes"__AT__"\n", size);
       free(ibuf);
       close(n->ofd);
       return CL_EIO;
     }
     if (loops==size) {
       if (cli_writen(n->ofd, ibuf, size) != (ssize_t) size) {
 	cli_dbgmsg("NSIS: cannot write output file"__AT__"\n");
 	free(ibuf);
 	close(n->ofd);
 	return CL_EIO;
       }
     } else {
       if ((ret=nsis_init(n))!=CL_SUCCESS) {
 	cli_dbgmsg("NSIS: decompressor init failed"__AT__"\n");
 	free(ibuf);
 	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)) {
 	  if (cli_writen(n->ofd, obuf, size) != (ssize_t) size) {
 	    cli_dbgmsg("NSIS: cannot write output file"__AT__"\n");
 	    free(ibuf);
 	    close(n->ofd);
 	    return CL_EIO;
 	  }
 	  n->nsis.next_out = obuf;
 	  n->nsis.avail_out = BUFSIZ;
 	  loops=0;
 	  if (ctx->limits && ctx->limits->maxfilesize && size > ctx->limits->maxfilesize) {
 	    cli_dbgmsg("NSIS: Skipping file due to size limit (%u, max: %lu)\n", size, ctx->limits->maxfilesize);
 	    free(ibuf);
 	    close(n->ofd);
 	    nsis_shutdown(n);
 	    return CL_EMAXSIZE;
 	  }
 	} else if (++loops > 10) {
 	  cli_dbgmsg("NSIS: xs looping, breaking out"__AT__"\n");
 	  ret = CL_BREAK;
 	  break;
 	}
       }
 
       if (ret != CL_BREAK) {
 	cli_dbgmsg("NSIS: bad stream"__AT__"\n");
 	free(ibuf);
 	close(n->ofd);
 	return CL_EFORMAT;
       }
 
       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");
 	free(ibuf);
 	close(n->ofd);
 	return CL_EIO;
       }
       nsis_shutdown(n);
     }
 
     free(ibuf);
     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;
       }
       if (!(n->freeme= (unsigned char *) cli_malloc(n->asz))) {
 	cli_dbgmsg("NSIS: out of memory\n");
 	close(n->ofd);
 	return CL_EMEM;
       }
       if (cli_readn(n->ifd, n->freeme, n->asz) != (ssize_t) n->asz) {
cd6d37c1
 	cli_dbgmsg("NSIS: cannot read %u bytes"__AT__"\n", n->asz);
faaf436a
 	close(n->ofd);
 	return CL_EIO;
       }
       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);
     if (ctx->limits && ctx->limits->maxfilesize && size > ctx->limits->maxfilesize) {
       cli_dbgmsg("NSIS: Breaking out due to filesize limit (%u, max: %lu) in solid archive\n", size, ctx->limits->maxfilesize);
       close(n->ofd);
       return CL_EFORMAT;
     }
 
     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)) {
 	if (cli_writen(n->ofd, obuf, wsz) != (ssize_t) wsz) {
 	  close(n->ofd);
 	  return CL_EIO;
 	}
 	size-=wsz;
 	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");
 	ret = CL_BREAK;
 	break;
       }
     }
 
     if (ret == CL_BREAK) {
       if (cli_writen(n->ofd, obuf, n->nsis.next_out - obuf) != n->nsis.next_out - obuf) {
 	close(n->ofd);
 	return CL_EIO;
       }
       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) {
   char buf[28];
   struct stat st;
   uint32_t pos;
   int i;
   uint8_t comps[] = {0, 0, 0, 0}, trunc = 0;
   
   if (fstat(n->ifd, &st)==-1 ||
       lseek(n->ifd, n->off, SEEK_SET)==-1 ||
       cli_readn(n->ifd, buf, 28) != 28)
     return CL_EIO;
 
   n->hsz = (uint32_t)cli_readint32(buf+0x14);
   n->asz = (uint32_t)cli_readint32(buf+0x18);
 
   cli_dbgmsg("NSIS: Header info - Flags=%x, Header size=%x, Archive size=%x\n", cli_readint32(buf), n->hsz, n->asz);
 
   if (st.st_size - n->off < (off_t) n->asz) {
     cli_dbgmsg("NSIS: Possibly truncated file\n");
     n->asz = st.st_size - n->off;
     trunc++;
   } else if (st.st_size - n->off != (off_t) n->asz) {
     cli_dbgmsg("NSIS: Overlays found\n");
   }
 
   n->asz -= 0x1c;
 
   /* Guess if solid */
   for (i=0, pos=0;pos < n->asz-4;i++) {
     int32_t nextsz;
     if (cli_readn(n->ifd, buf+4, 4)!=4) return CL_EIO;
     nextsz=cli_readint32(buf+4);
b8ad506b
     if (!i) n->comp = nsis_detcomp(buf+4);
faaf436a
     if (nextsz&0x80000000) {
       nextsz&=~0x80000000;
       if (cli_readn(n->ifd, buf+4, 4)!=4) return CL_EIO;
b8ad506b
       comps[nsis_detcomp(buf+4)]++;
faaf436a
       nextsz-=4;
       pos+=4;
     }
     if ((pos+=4+nextsz) > n->asz) {
       n->solid = 1;
       break;
     }
 
     if (lseek(n->ifd, nextsz, SEEK_CUR)==-1) return CL_EIO;
   }
   
   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);
   }
 
   if (lseek(n->ifd, n->off+0x1c, SEEK_SET)==-1) return CL_EIO;
 
   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);
 }
 
a993c5ad
 static void cli_nsis_free(struct nsis_st *n) {
faaf436a
   nsis_shutdown(n);
   if (n->solid && n->freeme) free(n->freeme);
 }
05eff05f
 
 int cli_scannulsft(int desc, cli_ctx *ctx, off_t offset) {
         int ret;
 	struct nsis_st nsist;
 
     cli_dbgmsg("in scannulsft()\n");
     if(ctx->limits && ctx->limits->maxreclevel && ctx->arec >= ctx->limits->maxreclevel) {
         cli_dbgmsg("Archive recursion limit exceeded (arec == %u).\n", ctx->arec+1);
 	return CL_EMAXREC;
     }
 
     memset(&nsist, 0, sizeof(struct nsis_st));
 
     nsist.ifd = desc;
     nsist.off = offset;
f5354453
     if (!(nsist.dir = cli_gentemp(NULL)))
         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;
     }
 
b8ad506b
     if(cli_leavetemps_flag) cli_dbgmsg("NSIS: Extracting files to %s\n", nsist.dir);
 
05eff05f
     ctx->arec++;
 
     do {
         ret = cli_nsis_unpack(&nsist, ctx);
 	if(ret != CL_SUCCESS) {
 	    if(ret == CL_EMAXSIZE) {
 	        if(BLOCKMAX) {
 		    *ctx->virname = "NSIS.ExceededFileSize";
 		    ret=CL_VIRUS;
 		} else {
 		    ret = nsist.solid ? CL_BREAK : CL_SUCCESS;
 		}
 	    }
 	} else {
 	    cli_dbgmsg("NSIS: Successully extracted file #%u\n", nsist.fno);
 	    lseek(nsist.ofd, 0, SEEK_SET);
 	    if(nsist.fno == 1)
 	        ret=cli_scandesc(nsist.ofd, ctx, 0, 0, 0, NULL);
 	    else
 	        ret=cli_magic_scandesc(nsist.ofd, ctx);
 	    close(nsist.ofd);
 	    if(!cli_leavetemps_flag)
 	        unlink(nsist.ofn);
 	}
     } while(ret == CL_SUCCESS);
 
     if(ret == CL_BREAK)
 	ret = CL_CLEAN;
 
     cli_nsis_free(&nsist);
 
     if(!cli_leavetemps_flag)
         cli_rmdirs(nsist.dir);
 
     free(nsist.dir);
 
     ctx->arec--;    
     return ret;
 }
69c06112