libclamav/jpeg.c
b44fb658
 /*
e1cbc270
  *  Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
  *  Copyright (C) 2011-2013 Sourcefire, Inc.
b44fb658
  *
  *  Authors: Tomasz Kojm <tkojm@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 <string.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <sys/stat.h>
288057e9
 #ifdef HAVE_UNISTD_H
b44fb658
 #include <unistd.h>
 #endif
 #include <time.h>
 
 #include "jpeg.h"
 #include "clamav.h"
 
ac47dac7
 #define EC16(x) le16_to_host(x)
 
288057e9
 #define GETBYTE(v)                                                         \
     if (fmap_readn(map, &v, offset, sizeof(v)) == sizeof(v)) {             \
         offset += sizeof(v);                                               \
     } else {                                                               \
         cli_errmsg("cli_parse(jpeg|gif): Can't read file (corrupted?)\n"); \
         return CL_EPARSE;                                                  \
b44fb658
     }
 
 int cli_parsejpeg(cli_ctx *ctx)
 {
288057e9
     fmap_t *map = *ctx->fmap;
     unsigned char marker, prev_marker, prev_segment = 0, v1, v2, buff[8];
     unsigned int offset = 0, i, len, comment = 0, segment = 0, app = 0;
b44fb658
 
     cli_dbgmsg("in cli_parsejpeg()\n");
 
288057e9
     if (fmap_readn(map, buff, offset, 4) != 4)
         return CL_SUCCESS; /* Ignore */
1fb9e80c
 
288057e9
     if (!memcmp(buff, "\xff\xd8\xff", 3))
         offset = 2;
     else if (!memcmp(buff, "\xff\xd9\xff\xd8", 4))
         offset = 4;
1fb9e80c
     else
288057e9
         return CL_SUCCESS; /* Not a JPEG file */
 
     while (1) {
         segment++;
         prev_marker = 0;
         for (i = 0; offset < map->len && i < 16; i++) {
             GETBYTE(marker);
             if (prev_marker == 0xff && marker != 0xff)
                 break;
             prev_marker = marker;
         }
         if (i == 16) {
             cli_warnmsg("cli_parsejpeg: Spurious bytes before segment %u\n", segment);
             return CL_EPARSE;
         }
         if (offset == map->len) {
             cli_warnmsg("cli_parsejpeg: Error looking for marker\n");
             return CL_EPARSE;
         }
         GETBYTE(v1);
         GETBYTE(v2);
         len = (unsigned int)(v1 << 8) | v2;
         cli_dbgmsg("JPEG: Marker %02x, length %u\n", marker, len);
         if (len < 2) {
             cli_warnmsg("cli_parsejpeg: Invalid segment size\n");
             return CL_EPARSE;
         }
         if (len >= map->len - offset + 2) {
             cli_warnmsg("cli_parsejpeg: Segment data out of file\n");
             return CL_EPARSE;
         }
         offset += len - 2;
 
         switch (marker) {
b44fb658
             case 0xe0: /* JFIF */
288057e9
                 if (app) {
                     cli_warnmsg("cli_parsejpeg: Duplicate Application Marker\n");
                     return CL_EPARSE;
                 }
                 if (segment != 1 && (segment != 2 || !comment)) {
                     cli_warnmsg("cli_parsejpeg: JFIF marker at wrong position\n");
                     return CL_EPARSE;
                 }
                 if (fmap_readn(map, buff, offset - len + 2, 5) != 5 || memcmp(buff, "JFIF\0", 5)) {
                     cli_warnmsg("cli_parsejpeg: No JFIF marker\n");
                     return CL_EPARSE;
b44fb658
                 }
288057e9
                 if (len < 16) {
                     cli_warnmsg("cli_parsejpeg: JFIF header too short\n");
                     return CL_EPARSE;
b44fb658
                 }
288057e9
                 app = 0xe0;
b44fb658
                 break;
 
             case 0xe1: /* EXIF */
288057e9
                 if (fmap_readn(map, buff, offset - len + 2, 7) != 7) {
                     cli_warnmsg("cli_parsejpeg: Can't read Exif header\n");
                     return CL_EPARSE;
                 }
                 if (!memcmp(buff, "Exif\0\0", 6)) {
                     if (app && app != 0xe0) {
                         cli_warnmsg("cli_parsejpeg: Duplicate Application Marker\n");
                         return CL_EPARSE;
                     }
                     if (segment > 3 && !comment && app != 0xe0) {
                         cli_warnmsg("cli_parsejpeg: Exif marker at wrong position\n");
                         return CL_EPARSE;
                     }
                 } else if (!memcmp(buff, "http://", 7)) {
                     cli_dbgmsg("JPEG: XMP data in segment %u\n", segment);
b44fb658
                 } else {
288057e9
                     cli_warnmsg("cli_parsejpeg: Invalid Exif header\n");
                     return CL_EPARSE;
                 }
                 if (len < 16) {
                     cli_warnmsg("cli_parsejpeg: Exif header too short\n");
                     return CL_EPARSE;
b44fb658
                 }
288057e9
                 app = 0xe1;
b44fb658
                 break;
 
             case 0xe8: /* SPIFF */
288057e9
                 if (app) {
                     cli_warnmsg("cli_parsejpeg: Duplicate Application Marker\n");
                     return CL_EPARSE;
b44fb658
                 }
288057e9
                 if (segment != 1 && (segment != 2 || !comment)) {
                     cli_warnmsg("cli_parsejpeg: SPIFF marker at wrong position\n");
                     return CL_EPARSE;
b44fb658
                 }
288057e9
                 if (fmap_readn(map, buff, offset - len + 2, 6) != 6 || memcmp(buff, "SPIFF\0", 6)) {
                     cli_warnmsg("cli_parsejpeg: No SPIFF marker\n");
                     return CL_EPARSE;
                 }
                 if (len < 16) {
                     cli_warnmsg("cli_parsejpeg: SPIFF header too short\n");
                     return CL_EPARSE;
                 }
                 app = 0xe8;
b44fb658
                 break;
 
288057e9
             case 0xf7: /* JPG7 */
                 if (app) {
                     cli_warnmsg("cli_parsejpeg: Application Marker before JPG7\n");
                     return CL_EPARSE;
b44fb658
                 }
288057e9
                 return CL_SUCCESS;
b44fb658
 
288057e9
             case 0xda: /* SOS */
                 if (!app) {
                     cli_warnmsg("cli_parsejpeg: Invalid file structure\n");
                     return CL_EPARSE;
b44fb658
                 }
288057e9
                 return CL_SUCCESS;
b44fb658
 
             case 0xd9: /* EOI */
                 cli_warnmsg("cli_parsejpeg: No image in jpeg\n");
                 return CL_EPARSE;
 
             case 0xfe: /* COM */
288057e9
                 comment = 1;
b44fb658
                 break;
 
             case 0xed: /* IPTC */
288057e9
                 comment = 1;
b44fb658
                 break;
 
288057e9
             case 0xf2: /* DTT */
                 if (prev_segment != 0xf1) {
                     cli_warnmsg("cli_parsejpeg: No DTI segment before DTT\n");
                     return CL_EPARSE;
                 }
                 break;
b44fb658
 
             default:
                 break;
         }
288057e9
         prev_segment = marker;
b44fb658
     }
     return CL_SUCCESS;
 }
ac47dac7
 
 /* GIF */
 
 struct gif_screen_desc {
     uint16_t width;
     uint16_t height;
     uint8_t flags;
     uint8_t bgcolor;
     uint8_t aspect;
 };
 
 struct gif_graphic_control_ext {
     uint8_t blksize;
     uint8_t flags;
     uint16_t delaytime;
     uint8_t tcoloridx;
     uint8_t blkterm;
 };
 
 struct gif_image_desc {
     uint16_t leftpos;
     uint16_t toppos;
     uint16_t width;
     uint16_t height;
     uint8_t flags;
 };
 
 int cli_parsegif(cli_ctx *ctx)
 {
288057e9
     fmap_t *map = *ctx->fmap;
     unsigned char magic[6], v;
     unsigned int offset = 0, have_gce = 0;
     struct gif_screen_desc screen_desc;
     struct gif_graphic_control_ext graphic_control_ext;
     struct gif_image_desc image_desc;
ac47dac7
 
     cli_dbgmsg("in cli_parsegif()\n");
 
288057e9
     if (fmap_readn(map, magic, offset, 6) != 6)
         return CL_SUCCESS; /* Ignore */
ac47dac7
 
288057e9
     if (!memcmp(magic, "GIF87a", 6) || !memcmp(magic, "GIF89a", 6))
         offset = 6;
ac47dac7
     else
288057e9
         return CL_SUCCESS; /* Not a GIF file */
ac47dac7
 
288057e9
     if (fmap_readn(map, &screen_desc, offset, sizeof(screen_desc)) != sizeof(screen_desc)) {
         cli_warnmsg("cli_parsegif: Can't read Logical Screen Descriptor block\n");
         return CL_EPARSE;
ac47dac7
     }
     offset += 7;
 
     cli_dbgmsg("GIF: Screen size %ux%u, gctsize: %u\n", EC16(screen_desc.width), EC16(screen_desc.height), screen_desc.flags & 0x7);
288057e9
     if (screen_desc.flags & 0x80)
         offset += 3 * (1 << ((screen_desc.flags & 0x7) + 1));
 
     while (1) {
         GETBYTE(v);
         if (v == 0x21) {
             GETBYTE(v);
             if (v == 0xf9) {
                 if (fmap_readn(map, &graphic_control_ext, offset, sizeof(graphic_control_ext)) != sizeof(graphic_control_ext)) {
                     cli_warnmsg("cli_parsegif: Can't read Graphic Control Extension block\n");
                     return CL_EPARSE;
                 }
                 if (have_gce) {
                     cli_warnmsg("cli_parsegif: Multiple Graphic Control Extension blocks not allowed\n");
                     return CL_EPARSE;
                 }
                 have_gce = 1;
                 offset += sizeof(graphic_control_ext);
                 if (graphic_control_ext.blksize != 4 || graphic_control_ext.blkterm) {
                     cli_warnmsg("cli_parsegif: Invalid Graphic Control Extension block\n");
                     return CL_EPARSE;
                 }
             } else {
                 while (1) {
                     GETBYTE(v);
                     if (!v)
                         break;
                     offset += v;
                 }
             }
         } else if (v == 0x2c) {
             if (fmap_readn(map, &image_desc, offset, sizeof(image_desc)) != sizeof(image_desc)) {
                 cli_warnmsg("cli_parsegif: Can't read Image Descriptor block\n");
                 return CL_EPARSE;
             }
             offset += 9;
             cli_dbgmsg("GIF: Image size %ux%u, left pos: %u, top pos: %u\n", EC16(image_desc.width), EC16(image_desc.height), EC16(image_desc.leftpos), EC16(image_desc.toppos));
             break;
         }
ac47dac7
     }
 
     return CL_SUCCESS;
 }