libclamav/swf.c
44a3e21a
 /*
c442ca9c
  *  Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
4db9cda5
  *  Copyright (C) 2011-2013 Sourcefire, Inc.
44a3e21a
  *
  *  The code is based on Flasm, command line assembler & disassembler of Flash
  *  ActionScript bytecode Copyright (c) 2001 Opaque Industries, (c) 2002-2007
  *  Igor Kogan, (c) 2005 Wang Zhen. All rights reserved.
  *
  *  Redistribution and use in source and binary forms, with or without modification,
  *  are permitted provided that the following conditions are met:
  *
  *  - Redistributions of source code must retain the above copyright notice, this list
  *  of conditions and the following disclaimer.
  *  - Redistributions in binary form must reproduce the above copyright notice, this
  *  list of conditions and the following disclaimer in the documentation and/or other
  *  materials provided with the distribution.
  *  - Neither the name of the Opaque Industries nor the names of its contributors may
  *  be used to endorse or promote products derived from this software without specific
  *  prior written permission.
  *
  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
  *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
  *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
  *  SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
  *  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
  *  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 
  *  WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
 #include <stdio.h>
 #include <string.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <sys/stat.h>
7c05ec73
 #ifdef        HAVE_UNISTD_H
44a3e21a
 #include <unistd.h>
 #endif
 #include <time.h>
 #include <zlib.h>
 
 #include "swf.h"
 #include "clamav.h"
1fb9e80c
 #include "scanners.h"
d3530d45
 #include "lzma_iface.h"
44a3e21a
 
7c05ec73
 #define EC16(v)        le16_to_host(v)
 #define EC32(v)        le32_to_host(v)
 
 #define INITBITS                                                                \
 {                                                                               \
     if(fmap_readn(map, &get_c, offset, sizeof(get_c)) == sizeof(get_c)) {       \
         bitpos = 8;                                                             \
         bitbuf = (unsigned int) get_c;                                          \
         offset += sizeof(get_c);                                                \
     } else {                                                                    \
         cli_warnmsg("cli_scanswf: INITBITS: Can't read file or file truncated\n"); \
         return CL_EFORMAT;                                                      \
     }                                                                           \
44a3e21a
 }
 
7c05ec73
 #define GETBITS(v, n)                                                           \
 {                                                                               \
     getbits_n = n;                                                              \
     bits = 0;                                                                   \
     while(getbits_n > bitpos) {                                                 \
         getbits_n -= bitpos;                                                    \
         bits |= bitbuf << getbits_n;                                            \
         if(fmap_readn(map, &get_c, offset, sizeof(get_c)) == sizeof(get_c)) {   \
             bitbuf = (unsigned int) get_c;                                      \
             bitpos = 8;                                                         \
             offset += sizeof(get_c);                                            \
         } else {                                                                \
             cli_warnmsg("cli_scanswf: GETBITS: Can't read file or file truncated\n"); \
             return CL_EFORMAT;                                                  \
         }                                                                       \
     }                                                                           \
     bitpos -= getbits_n;                                                        \
     bits |= bitbuf >> bitpos;                                                   \
     bitbuf &= 0xff >> (8 - bitpos);                                             \
     v = bits & 0xffff;                                                          \
44a3e21a
 }
 
7c05ec73
 #define GETWORD(v)                                                              \
 {                                                                               \
     if(fmap_readn(map, &get_c, offset, sizeof(get_c)) == sizeof(get_c)) {       \
         getword_1 = (unsigned int) get_c;                                       \
         offset += sizeof(get_c);                                                \
     } else {                                                                    \
         cli_warnmsg("cli_scanswf: GETWORD: Can't read file or file truncated\n"); \
         return CL_EFORMAT;                                                      \
     }                                                                           \
     if(fmap_readn(map, &get_c, offset, sizeof(get_c)) == sizeof(get_c)) {       \
         getword_2 = (unsigned int) get_c;                                       \
         offset += sizeof(get_c);                                                \
     } else {                                                                    \
         cli_warnmsg("cli_scanswf: GETWORD: Can't read file or file truncated\n"); \
         return CL_EFORMAT;                                                      \
     }                                                                           \
     v = (uint16_t)(getword_1 & 0xff) | ((getword_2 & 0xff) << 8);               \
44a3e21a
 }
 
7c05ec73
 #define GETDWORD(v)                                                             \
 {                                                                               \
     GETWORD(getdword_1);                                                        \
     GETWORD(getdword_2);                                                        \
     v = (uint32_t)(getdword_1 | (getdword_2 << 16));                            \
44a3e21a
 }
 
 struct swf_file_hdr {
     char signature[3];
     uint8_t version;
     uint32_t filesize;
 };
 
d3530d45
 static int scanzws(cli_ctx *ctx, struct swf_file_hdr *hdr)
 {
7c05ec73
         struct CLI_LZMA lz;
         unsigned char inbuff[FILEBUFF], outbuff[FILEBUFF];
         fmap_t *map = *ctx->fmap;
         /* strip off header */
         off_t offset = 8;
         uint32_t d_insize;
         size_t outsize = 8;
         int ret, lret, count;
         char *tmpname;
         int fd;
d3530d45
 
     if((ret = cli_gentempfd(ctx->engine->tmpdir, &tmpname, &fd)) != CL_SUCCESS) {
7c05ec73
         cli_errmsg("scanzws: Can't generate temporary file\n");
         return ret;
d3530d45
     }
 
     hdr->signature[0] = 'F';
     if(cli_writen(fd, hdr, sizeof(struct swf_file_hdr)) != sizeof(struct swf_file_hdr)) {
7c05ec73
         cli_errmsg("scanzws: Can't write to file %s\n", tmpname);
d3530d45
         close(fd);
7c05ec73
         if(cli_unlink(tmpname)) {
             free(tmpname);
             return CL_EUNLINK;
         }
         free(tmpname);
         return CL_EWRITE;
d3530d45
     }
 
207cf18e
     /* read 4 bytes (for compressed 32-bit filesize) [not used for LZMA] */
     if (fmap_readn(map, &d_insize, offset, sizeof(d_insize)) != sizeof(d_insize)) {
7c05ec73
         cli_errmsg("scanzws: Error reading SWF file\n");
207cf18e
         close(fd);
         if (cli_unlink(tmpname)) {
             free(tmpname);
             return CL_EUNLINK;
         }
         free(tmpname);
         return CL_EREAD;
     }
     offset += sizeof(d_insize);
 
     /* check if declared input size matches actual output size */
     /* map->len = header (8 bytes) + d_insize (4 bytes) + flags (5 bytes) + compressed stream */
     if (d_insize != (map->len - 17)) {
         cli_warnmsg("SWF: declared input length != compressed stream size, %u != %llu\n",
                     d_insize, (long long unsigned)(map->len - 17));
     } else {
         cli_dbgmsg("SWF: declared input length == compressed stream size, %u == %llu\n",
7c05ec73
                     d_insize, (long long unsigned)(map->len - 17));
207cf18e
     }
 
     /* first buffer required for initializing LZMA */
     ret = fmap_readn(map, inbuff, offset, FILEBUFF);
     if (ret < 0) {
7c05ec73
         cli_errmsg("scanzws: Error reading SWF file\n");
         close(fd);
         if (cli_unlink(tmpname)) {
             free(tmpname);
             return CL_EUNLINK;
         }
         free(tmpname);
         return CL_EUNPACK;
207cf18e
     }
015d05bd
     /* nothing written, likely truncated */
     if (!ret) {
         cli_errmsg("scanzws: possibly truncated file\n");
         close(fd);
         if (cli_unlink(tmpname)) {
             free(tmpname);
             return CL_EUNLINK;
         }
         free(tmpname);
         return CL_EFORMAT;
     }
207cf18e
     offset += ret;
 
     memset(&lz, 0, sizeof(lz));
d3530d45
     lz.next_in = inbuff;
     lz.next_out = outbuff;
207cf18e
     lz.avail_in = ret;
     lz.avail_out = FILEBUFF;
d3530d45
 
207cf18e
     lret = cli_LzmaInit(&lz, hdr->filesize);
d3530d45
     if (lret != LZMA_RESULT_OK) {
7c05ec73
         cli_errmsg("scanzws: LzmaInit() failed\n");
         close(fd);
         if (cli_unlink(tmpname)) {
             free(tmpname);
             return CL_EUNLINK;
         }
         free(tmpname);
         return CL_EUNPACK;
d3530d45
     }
 
207cf18e
     while (lret == LZMA_RESULT_OK) {
7c05ec73
         if (lz.avail_in == 0) {
             lz.next_in = inbuff;
 
             ret = fmap_readn(map, inbuff, offset, FILEBUFF);
             if (ret < 0) {
                 cli_errmsg("scanzws: Error reading SWF file\n");
                 cli_LzmaShutdown(&lz);
                 close(fd);
                 if (cli_unlink(tmpname)) {
                     free(tmpname);
                     return CL_EUNLINK;
                 }
                 free(tmpname);
                 return CL_EUNPACK;
             }
             if (!ret)
                 break;
             lz.avail_in = ret;
             offset += ret;
         }
         lret = cli_LzmaDecode(&lz);
         count = FILEBUFF - lz.avail_out;
         if (count) {
             if (cli_checklimits("SWF", ctx, outsize + count, 0, 0) != CL_SUCCESS)
                 break;
             if (cli_writen(fd, outbuff, count) != count) {
                 cli_errmsg("scanzws: Can't write to file %s\n", tmpname);
                 cli_LzmaShutdown(&lz);
                 close(fd);
                 if (cli_unlink(tmpname)) {
                     free(tmpname);
                     return CL_EUNLINK;
                 }
                 free(tmpname);
                 return CL_EWRITE;
             }
             outsize += count;
         }
         lz.next_out = outbuff;
         lz.avail_out = FILEBUFF;
207cf18e
     }
d3530d45
 
     cli_LzmaShutdown(&lz);
 
207cf18e
     if (lret != LZMA_STREAM_END && lret != LZMA_RESULT_OK) {
7c05ec73
         /* outsize starts at 8, therefore, if its still 8, nothing was decompressed */
         if (outsize == 8) {
             cli_infomsg(ctx, "scanzws: Error decompressing SWF file. No data decompressed.\n");
             close(fd);
             if (cli_unlink(tmpname)) {
                 free(tmpname);
                 return CL_EUNLINK;
             }
             free(tmpname);
             return CL_EUNPACK;
         }
         cli_infomsg(ctx, "scanzws: Error decompressing SWF file. Scanning what was decompressed.\n");
d3530d45
     }
2f9c1bd2
     cli_dbgmsg("SWF: Decompressed[LZMA] to %s, size %llu\n", tmpname, (long long unsigned)outsize);
d3530d45
 
207cf18e
     /* check if declared output size matches actual output size */
     if (hdr->filesize != outsize) {
         cli_warnmsg("SWF: declared output length != inflated stream size, %u != %llu\n",
                     hdr->filesize, (long long unsigned)outsize);
     } else {
         cli_dbgmsg("SWF: declared output length == inflated stream size, %u == %llu\n",
                    hdr->filesize, (long long unsigned)outsize);
     }
 
d39cb658
     ret = cli_magic_scandesc(fd, tmpname, ctx);
d3530d45
 
     close(fd);
     if (!(ctx->engine->keeptmp)) {
7c05ec73
         if (cli_unlink(tmpname)) {
             free(tmpname);
             return CL_EUNLINK;
         }
d3530d45
     }
     free(tmpname);
     return ret;
 }
 
44a3e21a
 static int scancws(cli_ctx *ctx, struct swf_file_hdr *hdr)
 {
7c05ec73
         z_stream stream;
         char inbuff[FILEBUFF], outbuff[FILEBUFF];
         fmap_t *map = *ctx->fmap;
         int offset = 8, ret, zret, outsize = 8, count, zend;
         char *tmpname;
         int fd;
44a3e21a
 
     if((ret = cli_gentempfd(ctx->engine->tmpdir, &tmpname, &fd)) != CL_SUCCESS) {
7c05ec73
         cli_errmsg("scancws: Can't generate temporary file\n");
         return ret;
44a3e21a
     }
 
     hdr->signature[0] = 'F';
     if(cli_writen(fd, hdr, sizeof(struct swf_file_hdr)) != sizeof(struct swf_file_hdr)) {
7c05ec73
         cli_errmsg("scancws: Can't write to file %s\n", tmpname);
db2138d8
         close(fd);
7c05ec73
         if(cli_unlink(tmpname)) {
             free(tmpname);
             return CL_EUNLINK;
         }
         free(tmpname);
         return CL_EWRITE;
44a3e21a
     }
 
     stream.avail_in = 0;
cd94be7a
     stream.next_in = (Bytef *)inbuff;
     stream.next_out = (Bytef *)outbuff;
44a3e21a
     stream.zalloc = (alloc_func) NULL;
     stream.zfree = (free_func) NULL;
     stream.opaque = (voidpf) 0;
     stream.avail_out = FILEBUFF;
 
     zret = inflateInit(&stream);
     if(zret != Z_OK) {
7c05ec73
         cli_errmsg("scancws: inflateInit() failed\n");
44a3e21a
         close(fd);
7c05ec73
         if(cli_unlink(tmpname)) {
             free(tmpname);
             return CL_EUNLINK;
         }
         free(tmpname);
         return CL_EUNPACK;
44a3e21a
     }
 
     do {
7c05ec73
         if(stream.avail_in == 0) {
             stream.next_in = (Bytef *)inbuff;
             ret = fmap_readn(map, inbuff, offset, FILEBUFF);
             if(ret < 0) {
                 cli_errmsg("scancws: Error reading SWF file\n");
                 close(fd);
                 inflateEnd(&stream);
                 if(cli_unlink(tmpname)) {
                     free(tmpname);
                     return CL_EUNLINK;
                 }
                 free(tmpname);
                 return CL_EUNPACK;
             }
             if(!ret)
                 break;
             stream.avail_in = ret;
             offset += ret;
         }
         zret = inflate(&stream, Z_SYNC_FLUSH);
         count = FILEBUFF - stream.avail_out;
         if(count) {
             if(cli_checklimits("SWF", ctx, outsize + count, 0, 0) != CL_SUCCESS)
                 break;
             if(cli_writen(fd, outbuff, count) != count) {
                 cli_errmsg("scancws: Can't write to file %s\n", tmpname);
                 inflateEnd(&stream);
                 close(fd);
                 if(cli_unlink(tmpname)) {
                     free(tmpname);
                     return CL_EUNLINK;
                 }
                 free(tmpname);
                 return CL_EWRITE;
             }
             outsize += count;
         }
         stream.next_out = (Bytef *)outbuff;
         stream.avail_out = FILEBUFF;
44a3e21a
     } while(zret == Z_OK);
 
cf3138e1
     zend = inflateEnd(&stream);
 
     if((zret != Z_STREAM_END && zret != Z_OK) || zend != Z_OK) {
126fd1ee
         /*
          * outsize is initialized to 8, it being 8 here means that we couldn't even read a single byte.
          * If outsize > 8, then we have data. Let's scan what we have.
          */
         if (outsize == 8) {
             cli_infomsg(ctx, "scancws: Error decompressing SWF file. No data decompressed.\n");
             close(fd);
             if(cli_unlink(tmpname)) {
                 free(tmpname);
                 return CL_EUNLINK;
             }
             free(tmpname);
             return CL_EUNPACK;
         }
         cli_infomsg(ctx, "scancws: Error decompressing SWF file. Scanning what was decompressed.\n");
44a3e21a
     }
d3530d45
     cli_dbgmsg("SWF: Decompressed[zlib] to %s, size %d\n", tmpname, outsize);
44a3e21a
 
207cf18e
     /* check if declared output size matches actual output size */
     if (hdr->filesize != outsize) {
         cli_warnmsg("SWF: declared output length != inflated stream size, %u != %llu\n",
                     hdr->filesize, (long long unsigned)outsize);
     } else {
         cli_dbgmsg("SWF: declared output length == inflated stream size, %u == %llu\n",
                    hdr->filesize, (long long unsigned)outsize);
     }
 
d39cb658
     ret = cli_magic_scandesc(fd, tmpname, ctx);
44a3e21a
 
     close(fd);
     if(!ctx->engine->keeptmp) {
7c05ec73
         if(cli_unlink(tmpname)) {
             free(tmpname);
             return CL_EUNLINK;
         }
44a3e21a
     }
     free(tmpname);
     return ret;
 }
 
 static const char *tagname(tag_id id)
 {
7c05ec73
         unsigned int i;
44a3e21a
 
     for(i = 0; tag_names[i].name; i++)
7c05ec73
         if(tag_names[i].id == id)
             return tag_names[i].name;
44a3e21a
     return NULL;
 }
 
 int cli_scanswf(cli_ctx *ctx)
 {
4db9cda5
     struct swf_file_hdr file_hdr;
     fmap_t *map = *ctx->fmap;
     unsigned int bitpos, bitbuf, getbits_n, nbits, getword_1, getword_2, getdword_1, getdword_2;
     const char *pt;
f98726bd
     unsigned char get_c;
60e36cd6
     size_t offset = 0;
     unsigned int val, foo, tag_hdr, tag_type, tag_len;
4db9cda5
     unsigned long int bits;
44a3e21a
 
     cli_dbgmsg("in cli_scanswf()\n");
 
     if(fmap_readn(map, &file_hdr, offset, sizeof(file_hdr)) != sizeof(file_hdr)) {
7c05ec73
         cli_dbgmsg("SWF: Can't read file header\n");
         return CL_CLEAN;
44a3e21a
     }
     offset += sizeof(file_hdr);
1f858111
     /*
     **  SWF stores the integer bytes with the least significate byte first
     */
     
     file_hdr.filesize = le32_to_host (file_hdr.filesize); 
 
     cli_dbgmsg("SWF: Version: %u\n", file_hdr.version);
     cli_dbgmsg("SWF: File size: %u\n", file_hdr.filesize);
44a3e21a
 
     if(!strncmp(file_hdr.signature, "CWS", 3)) {
7c05ec73
         cli_dbgmsg("SWF: zlib compressed file\n");
         return scancws(ctx, &file_hdr);
d3530d45
     } else if(!strncmp(file_hdr.signature, "ZWS", 3)) {
7c05ec73
         cli_dbgmsg("SWF: LZMA compressed file\n");
         return scanzws(ctx, &file_hdr);
44a3e21a
     } else if(!strncmp(file_hdr.signature, "FWS", 3)) {
7c05ec73
         cli_dbgmsg("SWF: Uncompressed file\n");
44a3e21a
     } else {
7c05ec73
         cli_dbgmsg("SWF: Not a SWF file\n");
         return CL_CLEAN;
44a3e21a
     }
 
     INITBITS;
 
     GETBITS(nbits, 5);
f98726bd
     cli_dbgmsg("SWF: FrameSize RECT size bits: %u\n", nbits);
     {
         uint32_t xMin = 0, xMax = 0, yMin = 0, yMax = 0;
         GETBITS(xMin, nbits); /* Should be zero */
         GETBITS(xMax, nbits);
         GETBITS(yMin, nbits); /* Should be zero */
         GETBITS(yMax, nbits);
         cli_dbgmsg("SWF: FrameSize xMin %u xMax %u yMin %u yMax %u\n", xMin, xMax, yMin, yMax);
     }
44a3e21a
 
     GETWORD(foo);
35b242b9
     GETWORD(val);
     cli_dbgmsg("SWF: Frames total: %d\n", val);
44a3e21a
 
d260468a
     /* Skip Flash tag walk unless debug mode */
     if(!cli_debug_flag) {
         return CL_CLEAN;
     }
 
44a3e21a
     while(offset < map->len) {
7c05ec73
         GETWORD(tag_hdr);
         tag_type = tag_hdr >> 6;
         if(tag_type == 0)
             break;
         tag_len = tag_hdr & 0x3f;
         if(tag_len == 0x3f)
             GETDWORD(tag_len);
 
         pt = tagname(tag_type);
         cli_dbgmsg("SWF: %s\n", pt ? pt : "UNKNOWN TAG");
         cli_dbgmsg("SWF: Tag length: %u\n", tag_len);
         if (tag_len > map->len) {
             cli_dbgmsg("SWF: Invalid tag length.\n");
             return CL_EFORMAT;
         }
         if ((offset + tag_len) < offset) {
             cli_warnmsg("SWF: Tag length too large.\n");
             break;
         }
         if(!pt) {
             offset += tag_len;
             continue;
         }
 
         switch(tag_type) {
             case TAG_SCRIPTLIMITS: {
                 unsigned int recursion, timeout;
                 GETWORD(recursion);
                 GETWORD(timeout);
                 cli_dbgmsg("SWF: scriptLimits recursion %u timeout %u\n", recursion, timeout);
                 break;
             }
 
             case TAG_FILEATTRIBUTES:
                 GETDWORD(val);
                 cli_dbgmsg("SWF: File attributes:\n");
                 if(val & SWF_ATTR_USENETWORK)
                     cli_dbgmsg("    * Use network\n");
                 if(val & SWF_ATTR_RELATIVEURLS)
                     cli_dbgmsg("    * Relative URLs\n");
                 if(val & SWF_ATTR_SUPPRESSCROSSDOMAINCACHE)
                     cli_dbgmsg("    * Suppress cross domain cache\n");
                 if(val & SWF_ATTR_ACTIONSCRIPT3)
                     cli_dbgmsg("    * ActionScript 3.0\n");
                 if(val & SWF_ATTR_HASMETADATA)
                     cli_dbgmsg("    * Has metadata\n");
                 if(val & SWF_ATTR_USEDIRECTBLIT)
                     cli_dbgmsg("    * Use hardware acceleration\n");
                 if(val & SWF_ATTR_USEGPU)
                     cli_dbgmsg("    * Use GPU\n");
                 break;
 
             default:
                 offset += tag_len;
                 continue;
         }
44a3e21a
     }
 
     return CL_CLEAN;
 }