From 20384c878ea24e8090aa0b15726753fc44168bd4 Mon Sep 17 00:00:00 2001 From: Tomasz Kojm Date: Sat, 7 Jan 2006 03:30:07 +0000 Subject: [PATCH] extract and scan SIS packages git-svn: trunk@1805 --- clamav-devel/ChangeLog | 4 + clamav-devel/libclamav/scanners.c | 3 +- clamav-devel/libclamav/scanners.h | 2 + clamav-devel/libclamav/sis.c | 529 ++++++++++++++++++++++++++---- 4 files changed, 465 insertions(+), 73 deletions(-) diff --git a/clamav-devel/ChangeLog b/clamav-devel/ChangeLog index e15caf7db..ab7c81161 100644 --- a/clamav-devel/ChangeLog +++ b/clamav-devel/ChangeLog @@ -1,3 +1,7 @@ +Sat Jan 7 04:27:05 CET 2006 (tk) +--------------------------------- + * libclamav/sis.c: extract and scan SIS packages + Mon Jan 2 18:02:20 GMT 2006 (njh) ---------------------------------- * libclamav/mbox.c: Bug fix to the patch of 28/12 for versions of curl diff --git a/clamav-devel/libclamav/scanners.c b/clamav-devel/libclamav/scanners.c index f89eeb158..607705703 100644 --- a/clamav-devel/libclamav/scanners.c +++ b/clamav-devel/libclamav/scanners.c @@ -105,7 +105,6 @@ extern int cli_mbox(const char *dir, int desc, unsigned int options); /* FIXME * static int cli_scanfile(const char *filename, const char **virname, unsigned long int *scanned, const struct cl_engine *engine, const struct cl_limits *limits, unsigned int options, unsigned int arec, unsigned int mrec); -static int cli_scandir(const char *dirname, const char **virname, unsigned long int *scanned, const struct cl_engine *engine, const struct cl_limits *limits, unsigned int options, unsigned int arec, unsigned int mrec); /* #ifdef CL_THREAD_SAFE @@ -842,7 +841,7 @@ static int cli_scanmscab(int desc, const char **virname, unsigned long int *scan return ret; } -static int cli_scandir(const char *dirname, const char **virname, unsigned long int *scanned, const struct cl_engine *engine, const struct cl_limits *limits, unsigned int options, unsigned int arec, unsigned int mrec) +int cli_scandir(const char *dirname, const char **virname, unsigned long int *scanned, const struct cl_engine *engine, const struct cl_limits *limits, unsigned int options, unsigned int arec, unsigned int mrec) { DIR *dd; struct dirent *dent; diff --git a/clamav-devel/libclamav/scanners.h b/clamav-devel/libclamav/scanners.h index 3da50a81b..26ca31740 100644 --- a/clamav-devel/libclamav/scanners.h +++ b/clamav-devel/libclamav/scanners.h @@ -23,4 +23,6 @@ int cli_magic_scandesc(int desc, const char **virname, unsigned long int *scanned, const struct cl_engine *engine, const struct cl_limits *limits, unsigned int options, unsigned int arec, unsigned int mrec); +int cli_scandir(const char *dirname, const char **virname, unsigned long int *scanned, const struct cl_engine *engine, const struct cl_limits *limits, unsigned int options, unsigned int arec, unsigned int mrec); + #endif diff --git a/clamav-devel/libclamav/sis.c b/clamav-devel/libclamav/sis.c index a7c02d7bb..db284ac88 100644 --- a/clamav-devel/libclamav/sis.c +++ b/clamav-devel/libclamav/sis.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005 Tomasz Kojm + * Copyright (C) 2005 - 2006 Tomasz Kojm * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,6 +20,8 @@ #include "clamav-config.h" #endif +#if HAVE_MMAP + #include #include #include @@ -28,20 +30,21 @@ #include #include #include +#include -#if HAVE_MMAP #if HAVE_SYS_MMAN_H #include #else /* HAVE_SYS_MMAN_H */ #undef HAVE_MMAP #endif -#endif #include "cltypes.h" #include "clamav.h" #include "others.h" #include "sis.h" +#define BLOCKMAX (options & CL_SCAN_BLOCKMAX) + #if WORDS_BIGENDIAN == 0 #define EC16(v) (v) #define EC32(v) (v) @@ -71,15 +74,268 @@ static char *langcodes[] = { "BO", "TI", "CT", "TK", "UK", "UR", "", "VI", "CY", "ZU" }; +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +#define SIS_MAX_NAME 512 +#define SIS_MAX_SIZE 134217728 + +static char *sis_utf16_decode(const char *str, uint32_t length) +{ + char *decoded; + int i, j; + + + if(!length || length % 2) { + cli_warnmsg("SIS: sis_utf16_decode: Broken filename (length == %d)\n", length); + return NULL; + } + + if(!(decoded = cli_calloc(length / 2 + 1, sizeof(char)))) + return NULL; + + for(i = 0, j = 0; i < length; i += 2, j++) { + decoded[j] = str[i + 1] << 4; + decoded[j] += str[i]; + if(decoded[j] == '%') + decoded[j] = '_'; + } + + return decoded; +} + +static int sis_extract_simple(int fd, char *mfile, uint32_t length, uint32_t offset, uint16_t nlangs, uint8_t compressed, const char *dir, const char **virname, const struct cl_limits *limits, unsigned int options) +{ + const char *typedir = NULL; + char *sname = NULL, *dname = NULL, *subdir, *fname, *buff; + int desc, i; + uint8_t get_dname = 1; + uint32_t namelen, nameoff, filelen, fileoff; + struct stat sb; + uLong osize = 0; + uLongf csize = 0; + + + if(offset + 24 + 8 * nlangs >= length) { + cli_errmsg("SIS: sis_extract_simple: Broken file record\n"); + return CL_EFORMAT; + } + + switch(cli_readint32(mfile + offset)) { + case 0x00: + cli_dbgmsg("SIS: File type: Standard file\n"); + typedir = "standard"; + break; + case 0x01: + cli_dbgmsg("SIS: File type: Text file\n"); + typedir = "text"; + get_dname = 0; + break; + case 0x02: + cli_dbgmsg("SIS: File type: Component file\n"); + typedir = "component"; + break; + case 0x03: + cli_dbgmsg("SIS: File type: Run file\n"); + typedir = "run"; + switch(cli_readint32(mfile + offset + 4)) { + case 0x0000: + cli_dbgmsg("SIS: * During installation only\n"); + break; + case 0x0001: + cli_dbgmsg("SIS: * During removal only\n"); + break; + case 0x0002: + cli_dbgmsg("SIS: * During installation and removal\n"); + break; + case 0x0100: + cli_dbgmsg("SIS: * Ends when installation finished\n"); + break; + case 0x0200: + cli_dbgmsg("SIS: * Waits until closed before continuing\n"); + break; + default: + cli_warnmsg("SIS: sis_extract_simple: Unknown value in file details\n"); + } + break; + case 0x04: + cli_dbgmsg("SIS: File type: Null file\n"); + break; + case 0x05: + cli_dbgmsg("SIS: File type: MIME file\n"); + break; + default: + cli_warnmsg("SIS: Unknown file type in file record\n"); + } + + /* Source name */ + namelen = (uint32_t) cli_readint32(mfile + offset + 8); + if(namelen > SIS_MAX_NAME) { + cli_warnmsg("SIS: sis_extract_simple: Source name too long and will not be decoded\n"); + } else { + nameoff = cli_readint32(mfile + offset + 12); + if(nameoff >= length || nameoff + namelen >= length) { + cli_errmsg("SIS: sis_extract_simple: Broken source name data\n"); + return CL_EFORMAT; + } + + if((sname = sis_utf16_decode(mfile + nameoff, namelen))) + cli_dbgmsg("SIS: Source name: %s\n", sname); + else + cli_warnmsg("SIS: Source name not decoded\n"); + } + + /* Destination name */ + if(get_dname) { + namelen = (uint32_t) cli_readint32(mfile + offset + 16); + if(namelen > SIS_MAX_NAME) { + cli_warnmsg("SIS: sis_extract_simple: Destination name too long and will not be decoded\n"); + } else { + nameoff = cli_readint32(mfile + offset + 20); + if(nameoff >= length || nameoff + namelen >= length) { + cli_errmsg("SIS: sis_extract_simple: Broken destination name data\n"); + if(sname) + free(sname); + return CL_EFORMAT; + } + + if((dname = sis_utf16_decode(mfile + nameoff, namelen))) + cli_dbgmsg("SIS: Destination name: %s\n", dname); + else + cli_warnmsg("SIS: Destination name not decoded\n"); + } + } + + if(!cli_leavetemps_flag) { + if(sname) + free(sname); + if(dname) + free(dname); + } + + /* Files */ + if(typedir) { + if(!(subdir = cli_malloc(strlen(dir) + strlen(typedir) + 2))) + return CL_EMEM; + sprintf(subdir, "%s/%s", dir, typedir); + } else { + if(!(subdir = strdup(dir))) + return CL_EMEM; + } + + if(stat(subdir, &sb) == -1) { + if(mkdir(subdir, 0700) == -1) { + free(subdir); + return CL_EIO; + } + } + + for(i = 0; i < nlangs; i++) { + filelen = cli_readint32(mfile + offset + 24 + 4 * i); + fileoff = cli_readint32(mfile + offset + 24 + 4 * (i + 1)); + + if(filelen >= length || fileoff >= length || filelen + fileoff > length) { + cli_errmsg("SIS: sis_extract_simple: Broken file data (filelen, fileoff)\n"); + free(subdir); + return CL_EFORMAT; + } + + if(!(fname = cli_gentemp(subdir))) { + free(subdir); + return CL_EMEM; + } + + if(compressed) { + csize = (uLong) filelen; + filelen = cli_readint32(mfile + offset + 24 + 8 * (i + 1)); + osize = (uLongf) filelen; + + if(limits && limits->maxfilesize && osize > limits->maxfilesize) { + cli_dbgmsg("SIS: Size exceeded (%d, max: %ld)\n", osize, limits->maxfilesize); + if(BLOCKMAX) { + *virname = "SIS.ExceededFileSize"; + free(subdir); + free(fname); + return CL_VIRUS; + } + free(subdir); + free(fname); + return CL_EFORMAT; + } + + if(!(buff = cli_malloc((size_t) osize))) { + cli_errmsg("SIS: sis_extract_simple: Can't allocate decompression buffer\n"); + free(subdir); + free(fname); + return CL_EIO; + } + + if(uncompress((Bytef *) buff, &osize , (Bytef *) mfile + fileoff, csize) != Z_OK) { + cli_errmsg("SIS: sis_extract_simple: File decompression failed\n"); + free(buff); + free(subdir); + free(fname); + return CL_EIO; + } + + } else { + buff = mfile + fileoff; + } + + if((desc = open(fname, O_CREAT|O_WRONLY|O_TRUNC|O_BINARY, S_IRUSR|S_IWUSR)) == -1) { + cli_errmsg("SIS: sis_extract_simple: Can't create new file %s\n", fname); + free(subdir); + free(fname); + if(compressed) + free(buff); + return CL_EIO; + } + + if(cli_writen(desc, buff, filelen) != filelen) { + cli_errmsg("SIS: sis_extract_simple: Can't write %d bytes to %s\n", filelen, fname); + free(subdir); + free(fname); + if(compressed) + free(buff); + return CL_EIO; + } else { + if(compressed) + cli_dbgmsg("SIS: File decompressed into %s\n", fname); + else + cli_dbgmsg("SIS: File saved into %s\n", fname); + } + + if(close(desc) == -1) { + cli_errmsg("SIS: sis_extract_simple: Can't close descriptor %d\n", filelen, fname); + free(subdir); + free(fname); + if(compressed) + free(buff); + return CL_EIO; + } + + free(fname); + + if(compressed) + free(buff); + } + + free(subdir); + return 0; +} + int cli_scansis(int desc, const char **virname, long int *scanned, const struct cl_engine *engine, const struct cl_limits *limits, unsigned int options, unsigned int arec, unsigned int mrec) { struct sis_file_hdr file_hdr; struct sis_file_hdr6 file_hdr6; - uint8_t release = 0; - uint16_t opts, nlangs, *langrecs; - char *mfile = NULL, *langs; + uint8_t release = 0, compressed; + uint16_t opts, nlangs, *langrecs, nfiles; + uint32_t recp, frecord, n; + size_t length; + char *mfile = NULL, *langs, *dir; struct stat sb; - int i; + int i, ret; if(fstat(desc, &sb) == -1) { @@ -92,31 +348,25 @@ int cli_scansis(int desc, const char **virname, long int *scanned, const struct return CL_CLEAN; } -#if HAVE_MMAP - if(sb.st_size < 33554432) { - mfile = (char *) mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, desc, 0); + length = sb.st_size; + + if(length <= SIS_MAX_SIZE) { + mfile = (char *) mmap(NULL, length, PROT_READ, MAP_PRIVATE, desc, 0); if(mfile == MAP_FAILED) { - mfile = NULL; + cli_errmsg("SIS: mmap() failed\n"); + return CL_EMEM; } else { cli_dbgmsg("SIS: mmap'ed file\n"); memcpy(&file_hdr, mfile, sizeof(struct sis_file_hdr)); } - } -#endif - - if(!mfile) { - if(read(desc, &file_hdr, sizeof(struct sis_file_hdr)) != sizeof(struct sis_file_hdr)) { - cli_dbgmsg("SIS: Can't read file header\n"); /* Not a SIS file? */ - return CL_CLEAN; - } + } else { + cli_warnmsg("SIS: File too large (> %d)\n", SIS_MAX_SIZE); + return CL_CLEAN; } if(EC32(file_hdr.uid3) != 0x10000419) { cli_dbgmsg("SIS: Not a SIS file\n"); -#if HAVE_MMAP - if(mfile) - munmap(mfile, sb.st_size); -#endif + munmap(mfile, length); return CL_CLEAN; } @@ -130,7 +380,9 @@ int cli_scansis(int desc, const char **virname, long int *scanned, const struct release = 6; break; default: - cli_warnmsg("SIS: Unknown value of UID 2 (EPOC release)\n"); + cli_warnmsg("SIS: Unknown value of UID 2 (EPOC release) -> not a real SIS file??\n"); + munmap(mfile, length); + return CL_CLEAN; } /* TODO: Verify checksums (uid4 and checksum) */ @@ -138,38 +390,28 @@ int cli_scansis(int desc, const char **virname, long int *scanned, const struct /* Languages */ nlangs = EC16(file_hdr.nlangs); cli_dbgmsg("SIS: Number of languages: %d\n", nlangs); - cli_dbgmsg("SIS: Offset of languages records: %d\n", EC32(file_hdr.plangs)); if(nlangs && nlangs < 100) { - if(EC32(file_hdr.plangs) + nlangs * 2 >= sb.st_size) { + if(EC32(file_hdr.plangs) >= length || EC32(file_hdr.plangs) + nlangs * 2 >= sb.st_size) { cli_errmsg("SIS: Broken file structure (language records)\n"); -#if HAVE_MMAP - if(mfile) - munmap(mfile, sb.st_size); -#endif + munmap(mfile, length); return CL_EFORMAT; } - langrecs = (uint16_t *) cli_malloc(nlangs * 2); + if(!(langrecs = (uint16_t *) cli_malloc(nlangs * 2))) { + munmap(mfile, length); + return CL_EMEM; + } - if(!mfile) { - if(lseek(desc, EC32(file_hdr.plangs), SEEK_SET) < 0) { - cli_errmsg("SIS: No language records\n"); - free(langrecs); - return CL_EFORMAT; - } + memcpy(langrecs, mfile + EC32(file_hdr.plangs), nlangs * 2); - if(read(desc, langrecs, nlangs * 2) != nlangs * 2) { - cli_errmsg("SIS: Can't read language records\n"); - free(langrecs); - return CL_EFORMAT; - } - } else { - memcpy(langrecs, mfile + EC32(file_hdr.plangs), nlangs * 2); + if(!(langs = (char *) cli_calloc(nlangs * 3 + 1, sizeof(char)))) { + munmap(mfile, length); + free(langrecs); + return CL_EMEM; } - langs = (char *) cli_calloc(nlangs * 3 + 1, sizeof(char)); for(i = 0; i < nlangs; i++) { strncat(langs, langcodes[EC16(langrecs[i]) % 98], 2); if(i != nlangs - 1) @@ -178,21 +420,22 @@ int cli_scansis(int desc, const char **virname, long int *scanned, const struct cli_dbgmsg("SIS: Supported languages: %s\n", langs); free(langrecs); free(langs); + + } else { + cli_errmsg("SIS: Incorrect number of languages (%d)\n", nlangs); + munmap(mfile, length); + return CL_EFORMAT; } + cli_dbgmsg("SIS: Offset of languages records: %d\n", EC32(file_hdr.plangs)); + if(EC16(file_hdr.ilang)) cli_dbgmsg("SIS: Installation language: %d\n", EC16(file_hdr.ilang)); - /* Files */ - cli_dbgmsg("SIS: Number of files: %d\n", EC16(file_hdr.nfiles)); - cli_dbgmsg("SIS: Offset of files records: %d\n", EC32(file_hdr.pfiles)); - - /* Requisites */ cli_dbgmsg("SIS: Number of requisites: %d\n", EC16(file_hdr.nreqs)); cli_dbgmsg("SIS: Offset of requisites records: %d\n", EC32(file_hdr.preqs)); - /* Options flags */ opts = EC16(file_hdr.options); cli_dbgmsg("SIS: Options:\n"); @@ -200,10 +443,13 @@ int cli_scansis(int desc, const char **virname, long int *scanned, const struct cli_dbgmsg("SIS: * File is in Unicode format\n"); if(opts & 0x0002) cli_dbgmsg("SIS: * File is distributable\n"); - if(opts & 0x0008) + if(opts & 0x0008) { cli_dbgmsg("SIS: * Packed files are not compressed\n"); - else + compressed = 0; + } else { cli_dbgmsg("SIS: * Packed files are compressed\n"); + compressed = 1; + } if(opts & 0x0010) cli_dbgmsg("SIS: * File installation shuts down all applications\n"); @@ -236,33 +482,174 @@ int cli_scansis(int desc, const char **virname, long int *scanned, const struct if(release == 6) { - if(sizeof(struct sis_file_hdr) + sizeof(struct sis_file_hdr6) >= sb.st_size) { + if(sizeof(struct sis_file_hdr) + sizeof(struct sis_file_hdr6) >= length) { cli_errmsg("SIS: Broken file structure (language records)\n"); -#if HAVE_MMAP - if(mfile) - munmap(mfile, sb.st_size); -#endif + munmap(mfile, length); return CL_EFORMAT; } + memcpy(&file_hdr6, mfile + sizeof(struct sis_file_hdr), sizeof(struct sis_file_hdr6)); + cli_dbgmsg("SIS: Maximum space required: %d\n", EC32(file_hdr6.maxispace)); + } + + /* Files */ + nfiles = EC16(file_hdr.nfiles); + + if(limits && limits->maxfiles && nfiles > limits->maxfiles) { + cli_dbgmsg("SIS: Files limit reached (max: %d)\n", limits->maxfiles); + if(BLOCKMAX) { + *virname = "SIS.ExceededFilesLimit"; + munmap(mfile, length); + return CL_VIRUS; + } + return CL_CLEAN; + } + + cli_dbgmsg("SIS: Number of files: %d\n", nfiles); + cli_dbgmsg("SIS: Offset of files records: %d\n", EC32(file_hdr.pfiles)); - if(!mfile) { - lseek(desc, sizeof(struct sis_file_hdr), SEEK_SET); + if(!(dir = cli_gentempdir(NULL))) { + cli_errmsg("SIS: Can't generate temporary directory\n"); + munmap(mfile, length); + return CL_ETMPDIR; + } - if(read(desc, &file_hdr6, sizeof(struct sis_file_hdr6)) != sizeof(struct sis_file_hdr6)) { - cli_dbgmsg("SIS: Can't read additional data of EPOC 6 file header\n"); /* Not a SIS file? */ - return CL_EFORMAT; - } - } else { - memcpy(&file_hdr6, mfile + sizeof(struct sis_file_hdr), sizeof(struct sis_file_hdr6)); + if((frecord = EC32(file_hdr.pfiles)) >= length) { + cli_errmsg("SIS: Broken file structure (frecord)\n"); + munmap(mfile, length); + free(dir); + return CL_EFORMAT; + } + + for(i = 0; i < nfiles; i++) { + + cli_dbgmsg("SIS: -----\n"); + + if(frecord + 4 >= length) { + cli_errmsg("SIS: Broken file structure (frecord)\n"); + munmap(mfile, length); + if(!cli_leavetemps_flag) + cli_rmdirs(dir); + free(dir); + return CL_EFORMAT; + } + + switch(cli_readint32(mfile + frecord)) { + case 0x00000000: + cli_dbgmsg("SIS: Simple file record\n"); + if((ret = sis_extract_simple(desc, mfile, sb.st_size, frecord + 4, nlangs, compressed, dir, virname, limits, options))) { + munmap(mfile, length); + if(!cli_leavetemps_flag) + cli_rmdirs(dir); + free(dir); + return ret; + } + + if(release == 6) + frecord += 32 + 12 * nlangs + 4; + else + frecord += 28 + 4 * nlangs + 4; + + break; + case 0x00000001: + cli_dbgmsg("SIS: Multiple languages file record\n"); + /* TODO: Pass language strings into sis_extract */ + if((ret = sis_extract_simple(desc, mfile, sb.st_size, frecord + 4, nlangs, compressed, dir, virname, limits, options))) { + munmap(mfile, length); + if(!cli_leavetemps_flag) + cli_rmdirs(dir); + free(dir); + return ret; + } + + if(release == 6) + frecord += 32 + 12 * nlangs + 4; + else + frecord += 28 + 4 * nlangs + 4; + + break; + case 0x00000002: + cli_dbgmsg("SIS: Options record\n"); + if(frecord + 8 >= length) { + munmap(mfile, length); + if(!cli_leavetemps_flag) + cli_rmdirs(dir); + free(dir); + return CL_EFORMAT; + } + + n = cli_readint32(mfile + frecord + 4); + cli_dbgmsg("SIS: Number of options: %d\n", n); + + if(n > 128 || frecord + 8 * n * nlangs >= length) { + cli_errmsg("SIS: Incorrect number of options\n"); + munmap(mfile, length); + if(!cli_leavetemps_flag) + cli_rmdirs(dir); + free(dir); + return CL_EFORMAT; + } + + frecord += 8 + 8 * n * nlangs + 16; + + break; + case 0x00000003: + case 0x00000004: + cli_dbgmsg("SIS: If/ElseIf record\n"); + if(frecord + 8 >= length) { + munmap(mfile, length); + if(!cli_leavetemps_flag) + cli_rmdirs(dir); + free(dir); + return CL_EFORMAT; + } + + n = cli_readint32(mfile + frecord + 4); + cli_dbgmsg("SIS: Size of conditional expression: %d\n", n); + + if(n >= length) { + cli_errmsg("SIS: Incorrect size of conditional expression\n"); + munmap(mfile, length); + if(!cli_leavetemps_flag) + cli_rmdirs(dir); + free(dir); + return CL_EFORMAT; + } + + frecord += 8 + n; + break; + case 0x00000005: + cli_dbgmsg("SIS: Else record\n"); + frecord += 4; + break; + case 0x00000006: + cli_dbgmsg("SIS: EndIf record\n"); + frecord += 4; + break; + default: + cli_warnmsg("SIS: Unknown file record type\n"); } - cli_dbgmsg("SIS: Maximum space required: %d\n", EC32(file_hdr6.maxispace)); } -#if HAVE_MMAP - if(mfile) - munmap(mfile, sb.st_size); -#endif + /* scan extracted files */ + cli_dbgmsg("SIS: ****** Scanning extracted files ******\n"); + ret = cli_scandir(dir, virname, scanned, engine, limits, options, arec, mrec); + + if(!cli_leavetemps_flag) + cli_rmdirs(dir); + free(dir); + munmap(mfile, length); + + return CL_CLEAN; +} + +#else /* HAVE_MMAP */ + +int cli_scansis(int desc, const char **virname, long int *scanned, const struct cl_engine *engine, const struct cl_limits *limits, unsigned int options, unsigned int arec, unsigned int mrec) +{ + cli_warnmsg("Support for SIS files not compiled in!\n"); return CL_CLEAN; } + +#endif /* HAVE_MMAP */