mirror of https://github.com/Cisco-Talos/clamav
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
550 lines
14 KiB
550 lines
14 KiB
/*
|
|
* 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 "zlib.h"
|
|
#include "lzma_iface.h"
|
|
#include "matcher.h"
|
|
#include "scanners.h"
|
|
#include "nulsft.h" /* SHUT UP GCC -Wextra */
|
|
|
|
#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
|
|
};
|
|
|
|
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;
|
|
CLI_LZMA lz;
|
|
z_stream z;
|
|
unsigned char *freeme;
|
|
uint8_t comp;
|
|
uint8_t solid;
|
|
uint8_t freecomp;
|
|
uint8_t eof;
|
|
char ofn[1024];
|
|
};
|
|
|
|
|
|
#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:
|
|
memset(&n->bz, 0, sizeof(nsis_bzstream));
|
|
if (nsis_BZ2_bzDecompressInit(&n->bz, 0, 0)!=BZ_OK)
|
|
return CL_EBZIP;
|
|
n->freecomp=1;
|
|
break;
|
|
case COMP_LZMA:
|
|
memset(&n->lz, 0, sizeof(CLI_LZMA));
|
|
cli_LzmaInit(&n->lz, 0xffffffffffffffff);
|
|
n->freecomp=1;
|
|
break;
|
|
case COMP_ZLIB:
|
|
memset(&n->bz, 0, sizeof(z_stream));
|
|
inflateInit2(&n->z, -MAX_WBITS);
|
|
n->freecomp=1;
|
|
}
|
|
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:
|
|
cli_LzmaShutdown(&n->lz);
|
|
break;
|
|
case COMP_ZLIB:
|
|
inflateEnd(&n->z);
|
|
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 (cli_LzmaDecode(&n->lz)) {
|
|
case LZMA_RESULT_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 (inflate(&n->z, Z_NO_FLUSH)) {
|
|
case Z_OK:
|
|
ret = CL_SUCCESS;
|
|
break;
|
|
case Z_STREAM_END:
|
|
ret = CL_BREAK;
|
|
/* FIXME: regression needed */
|
|
}
|
|
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) {
|
|
cli_dbgmsg("NSIS: cannot read %u bytes"__AT__"\n", n->asz);
|
|
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;
|
|
}
|
|
|
|
}
|
|
|
|
static uint8_t nsis_detcomp(const char *b) {
|
|
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);
|
|
if (!i) n->comp = nsis_detcomp(buf+4);
|
|
if (nextsz&0x80000000) {
|
|
nextsz&=~0x80000000;
|
|
if (cli_readn(n->ifd, buf+4, 4)!=4) return CL_EIO;
|
|
comps[nsis_detcomp(buf+4)]++;
|
|
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);
|
|
}
|
|
|
|
|
|
|
|
static int cli_nsis_unpack(struct nsis_st *n, cli_ctx *ctx) {
|
|
return (n->fno) ? nsis_unpack_next(n, ctx) : nsis_headers(n, ctx);
|
|
}
|
|
|
|
static void cli_nsis_free(struct nsis_st *n) {
|
|
nsis_shutdown(n);
|
|
if (n->solid && n->freeme) free(n->freeme);
|
|
}
|
|
|
|
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;
|
|
if (!(nsist.dir = cli_gentemp(NULL)))
|
|
return CL_ETMPDIR;
|
|
if(mkdir(nsist.dir, 0700)) {
|
|
cli_dbgmsg("NSIS: Can't create temporary directory %s\n", nsist.dir);
|
|
free(nsist.dir);
|
|
return CL_ETMPDIR;
|
|
}
|
|
|
|
if(cli_leavetemps_flag) cli_dbgmsg("NSIS: Extracting files to %s\n", nsist.dir);
|
|
|
|
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;
|
|
}
|
|
|
|
|