libclamav/tiff.c
059e90fc
 /*
e1cbc270
  *  Copyright (C) 2015-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
059e90fc
  *
  *  Authors: Kevin Lin <kevlin2@cisco.com>
  *
  *  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 "others.h"
 #include "tiff.h"
 
288057e9
 #define tiff32_to_host(be, x) (be ? be32_to_host(x) : le32_to_host(x))
 #define tiff16_to_host(be, x) (be ? be16_to_host(x) : le16_to_host(x))
059e90fc
 
 struct tiff_ifd {
     uint16_t tag;
     uint16_t type;
     uint32_t numval;
     uint32_t value;
 };
 
 int cli_parsetiff(cli_ctx *ctx)
 {
288057e9
     fmap_t *map = *ctx->fmap;
059e90fc
     unsigned char magic[4];
     int big_endian;
     uint32_t offset = 0, ifd_count = 0;
     uint16_t i, num_entries;
     struct tiff_ifd entry;
     size_t value_size;
 
     cli_dbgmsg("in cli_parsetiff()\n");
 
     /* check the magic */
288057e9
     if (fmap_readn(map, magic, offset, 4) != 4)
059e90fc
         return CL_SUCCESS;
     offset += 4;
 
288057e9
     if (!memcmp(magic, "\x4d\x4d\x00\x2a", 4))
059e90fc
         big_endian = 1;
288057e9
     else if (!memcmp(magic, "\x49\x49\x2a\x00", 4))
059e90fc
         big_endian = 0;
     else
         return CL_SUCCESS; /* Not a TIFF file */
 
     cli_dbgmsg("cli_parsetiff: %s-endian tiff file\n", big_endian ? "big" : "little");
 
     /* acquire offset of first IFD */
288057e9
     if (fmap_readn(map, &offset, offset, 4) != 4)
059e90fc
         return CL_EPARSE;
     offset = tiff32_to_host(big_endian, offset);
 
     cli_dbgmsg("cli_parsetiff: first IFD located @ offset %u\n", offset);
 
288057e9
     if (!offset) {
059e90fc
         cli_errmsg("cli_parsetiff: invalid offset for first IFD\n");
         return CL_EPARSE;
     }
 
     /* each IFD represents a subfile, though only the first one normally matters */
     do {
         /* acquire number of directory entries in current IFD */
288057e9
         if (fmap_readn(map, &num_entries, offset, 2) != 2)
059e90fc
             return CL_EPARSE;
         offset += 2;
         num_entries = tiff16_to_host(big_endian, num_entries);
 
         cli_dbgmsg("cli_parsetiff: IFD %u declared %u directory entries\n", ifd_count, num_entries);
 
7cd9337a
         /* transverse IFD entries */
288057e9
         for (i = 0; i < num_entries; i++) {
             if (fmap_readn(map, &entry, offset, sizeof(entry)) != sizeof(entry))
059e90fc
                 return CL_EPARSE;
             offset += sizeof(entry);
 
288057e9
             entry.tag    = tiff16_to_host(big_endian, entry.tag);
             entry.type   = tiff16_to_host(big_endian, entry.type);
059e90fc
             entry.numval = tiff32_to_host(big_endian, entry.numval);
288057e9
             entry.value  = tiff32_to_host(big_endian, entry.value);
059e90fc
 
             //cli_dbgmsg("%02u: %u %u %u %u\n", i, entry.tag, entry.type, entry.numval, entry.value);
 
             value_size = entry.numval;
288057e9
             switch (entry.type) {
                 case 1: /* BYTE */
                     value_size *= 1;
                     break;
                 case 2: /* ASCII */
                     value_size *= 1;
                     break;
                 case 3: /* SHORT */
                     value_size *= 2;
                     break;
                 case 4: /* LONG */
                     value_size *= 4;
                     break;
                 case 5: /* RATIONAL (LONG/LONG) */
                     value_size *= 8;
                     break;
 
                     /* TIFF 6.0 Types */
                 case 6: /* SBYTE */
                     value_size *= 1;
                     break;
                 case 7: /* UNDEFINED */
                     value_size *= 1;
                     break;
                 case 8: /* SSHORT */
                     value_size *= 2;
                     break;
                 case 9: /* SLONG */
                     value_size *= 4;
                     break;
                 case 10: /* SRATIONAL (SLONG/SLONG) */
                     value_size *= 8;
                     break;
                 case 11: /* FLOAT */
                     value_size *= 4;
                     break;
                 case 12: /* DOUBLE */
                     value_size *= 8;
                     break;
 
                 default: /* INVALID or NEW Type */
                     value_size *= 0;
                     break;
059e90fc
             }
 
288057e9
             if (value_size > sizeof(entry.value)) {
                 if (entry.value + value_size > map->len) {
059e90fc
                     cli_warnmsg("cli_parsetiff: TFD entry field %u exceeds bounds of TIFF file [%llu > %llu]\n",
                                 i, (long long unsigned)(entry.value + value_size), (long long unsigned)map->len);
048a88e6
                     return cli_append_virus(ctx, "Heuristics.TIFF.OutOfBoundsAccess");
059e90fc
                 }
             }
         }
 
         ifd_count++;
 
         /* acquire next IFD location, gets 0 if last IFD */
288057e9
         if (fmap_readn(map, &offset, offset, sizeof(offset)) != sizeof(offset))
059e90fc
             return CL_EPARSE;
         offset = tiff32_to_host(big_endian, offset);
288057e9
     } while (offset);
059e90fc
 
     cli_dbgmsg("cli_parsetiff: examined %u IFD(s)\n", ifd_count);
 
     return CL_SUCCESS;
 }