426 lines
17 KiB
C
426 lines
17 KiB
C
|
/*
|
||
|
* Copyright (C) 2013-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
|
||
|
* Copyright (C) 2011-2013 Sourcefire, Inc.
|
||
|
*
|
||
|
* Authors: Tomasz Kojm <tkojm@clamav.net>, Aldo Mazzeo, Micah Snyder
|
||
|
*
|
||
|
* 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.
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* GIF Format
|
||
|
* ----------
|
||
|
*
|
||
|
* 1. Signature: 3 bytes ("GIF")
|
||
|
*
|
||
|
* 2. Version: 3 bytes ("87a" or "89a")
|
||
|
*
|
||
|
* 3. Logical Screen Descriptor: 7 bytes (see `struct gif_screen_descriptor`)
|
||
|
* (Opt.) Global Color Table: n bytes (defined in the Logical Screen Descriptor flags)
|
||
|
*
|
||
|
* 4. All subsequent blocks are precededed by the following 1-byte labels...
|
||
|
*
|
||
|
* 0x21: Extension Introducer
|
||
|
* 0x01: Opt. (0+) Plain Text Extension
|
||
|
* 0xF9: Opt. (0+) Graphic Control Extension
|
||
|
* 0xFE: Opt. (0+) Comment Extension
|
||
|
* 0xFF: Opt. (0+) Application Extension
|
||
|
*
|
||
|
* Note: Each extension has a size field followed by some data. After the
|
||
|
* data may be a series of sub-blocks, each with a block size.
|
||
|
* If there are no more sub-blocks, the size will be 0x00, meaning
|
||
|
* there's no more blocks.
|
||
|
* The Graphic Control Extension never has any sub-blocks.
|
||
|
*
|
||
|
* 0x2C: Image Descriptor (1 per image, unlimited images)
|
||
|
* (Opt.) Local Color Table: n bytes (defined in the Image Descriptor flags)
|
||
|
* (Req.) Table-based Image Data Block*
|
||
|
*
|
||
|
* Note: Each image a series of data blocks of size 0-255 bytes each where
|
||
|
* the first byte is the size of the data-block.
|
||
|
* If there are no more data-blocks, the size will be 0x00, meaning
|
||
|
* there's no more data.
|
||
|
*
|
||
|
* 0x3B: Trailer (1 located at end of data stream)
|
||
|
*
|
||
|
* Reference https://www.w3.org/Graphics/GIF/spec-gif89a.txt for the GIF spec.
|
||
|
*/
|
||
|
|
||
|
#if HAVE_CONFIG_H
|
||
|
#include "clamav-config.h"
|
||
|
#endif
|
||
|
|
||
|
#include <math.h>
|
||
|
#include <stdbool.h>
|
||
|
|
||
|
#include "gif.h"
|
||
|
#include "scanners.h"
|
||
|
#include "clamav.h"
|
||
|
|
||
|
/* clang-format off */
|
||
|
#ifndef HAVE_ATTRIB_PACKED
|
||
|
#define __attribute__(x)
|
||
|
#endif
|
||
|
#ifdef HAVE_PRAGMA_PACK
|
||
|
#pragma pack(1)
|
||
|
#endif
|
||
|
#ifdef HAVE_PRAGMA_PACK_HPPA
|
||
|
#pragma pack 1
|
||
|
#endif
|
||
|
|
||
|
/**
|
||
|
* @brief Logical Screen Descriptor
|
||
|
*
|
||
|
* This block immediately follows the "GIF89a" magic bytes
|
||
|
|
||
|
* Flags contains packed fields which are as follows:
|
||
|
* Global Color Table Flag - 1 Bit
|
||
|
* Color Resolution - 3 Bits
|
||
|
* Sort Flag - 1 Bit
|
||
|
* Size of Global Color Table - 3 Bits
|
||
|
*/
|
||
|
struct gif_screen_descriptor {
|
||
|
uint16_t width;
|
||
|
uint16_t height;
|
||
|
uint8_t flags;
|
||
|
uint8_t bg_color_idx;
|
||
|
uint8_t pixel_aspect_ratio;
|
||
|
} __attribute__((packed));
|
||
|
|
||
|
#define GIF_SCREEN_DESC_FLAGS_MASK_HAVE_GLOBAL_COLOR_TABLE 0x80 /* If set, a Global Color Table will follow the Logical Screen Descriptor */
|
||
|
#define GIF_SCREEN_DESC_FLAGS_MASK_COLOR_RESOLUTION 0x70 /* Number of bits per primary color available to the original image, minus 1. */
|
||
|
#define GIF_SCREEN_DESC_FLAGS_MASK_SORT_FLAG 0x08 /* Indicates whether the Global Color Table is sorted. */
|
||
|
#define GIF_SCREEN_DESC_FLAGS_MASK_SIZE_OF_GLOBAL_COLOR_TABLE 0x07 /* If exists, the size = 3 * pow(2, this_field + 1), or: 3 * (1 << (this_field + 1)) */
|
||
|
|
||
|
/**
|
||
|
* @brief Graphic Control Extension
|
||
|
*
|
||
|
*/
|
||
|
struct gif_graphic_control_extension {
|
||
|
uint8_t block_size;
|
||
|
uint8_t flags;
|
||
|
uint16_t delaytime;
|
||
|
uint8_t transparent_color_idx;
|
||
|
uint8_t block_terminator;
|
||
|
} __attribute__((packed));
|
||
|
|
||
|
/**
|
||
|
* @brief Image Descriptor
|
||
|
*
|
||
|
* Flags contains packed fields which are as follows:
|
||
|
* Local Color Table Flag - 1 Bit
|
||
|
* Interlace Flag - 1 Bit
|
||
|
* Sort Flag - 1 Bit
|
||
|
* Reserved - 2 Bits
|
||
|
* Size of Local Color Table - 3 Bits
|
||
|
*/
|
||
|
struct gif_image_descriptor {
|
||
|
uint16_t leftpos;
|
||
|
uint16_t toppos;
|
||
|
uint16_t width;
|
||
|
uint16_t height;
|
||
|
uint8_t flags;
|
||
|
} __attribute__((packed));
|
||
|
|
||
|
#define GIF_IMAGE_DESC_FLAGS_MASK_HAVE_LOCAL_COLOR_TABLE 0x80 /* If set, a Global Color Table will follow the Logical Screen Descriptor */
|
||
|
#define GIF_IMAGE_DESC_FLAGS_MASK_IS_INTERLACED 0x40 /* Indicates if the image is interlaced */
|
||
|
#define GIF_IMAGE_DESC_FLAGS_MASK_SORT_FLAG 0x20 /* Indicates whether the Local Color Table is sorted. */
|
||
|
#define GIF_IMAGE_DESC_FLAGS_MASK_SIZE_OF_LOCAL_COLOR_TABLE 0x07 /* If exists, the size = 3 * pow(2, this_field + 1), or: 3 * (1 << (this_field + 1)) */
|
||
|
|
||
|
/* Main labels */
|
||
|
#define GIF_LABEL_EXTENSION_INTRODUCER 0x21
|
||
|
#define GIF_LABEL_GRAPHIC_IMAGE_DESCRIPTOR 0x2C
|
||
|
#define GIF_LABEL_SPECIAL_TRAILER 0x3B
|
||
|
|
||
|
/* Extension labels (found after the Extension Introducer) */
|
||
|
#define GIF_LABEL_GRAPHIC_PLAIN_TEXT_EXTENSION 0x01
|
||
|
#define GIF_LABEL_CONTROL_GRAPHIC_CONTROL_EXTENSION 0xF9
|
||
|
#define GIF_LABEL_SPECIAL_COMMENT_EXTENSION 0xFE
|
||
|
#define GIF_LABEL_SPECIAL_APP_EXTENSION 0xFF
|
||
|
|
||
|
#define GIF_BLOCK_TERMINATOR 0x00 /* Used to indicate end of image data and also for end of extension sub-blocks */
|
||
|
|
||
|
#ifdef HAVE_PRAGMA_PACK
|
||
|
#pragma pack()
|
||
|
#endif
|
||
|
#ifdef HAVE_PRAGMA_PACK_HPPA
|
||
|
#pragma pack
|
||
|
#endif
|
||
|
/* clang-format on */
|
||
|
|
||
|
cl_error_t cli_parsegif(cli_ctx *ctx)
|
||
|
{
|
||
|
cl_error_t status = CL_SUCCESS;
|
||
|
|
||
|
fmap_t *map = NULL;
|
||
|
size_t offset = 0;
|
||
|
|
||
|
const char *signature = NULL;
|
||
|
char version[4];
|
||
|
struct gif_screen_descriptor screen_desc;
|
||
|
size_t global_color_table_size = 0;
|
||
|
bool have_image_data = false;
|
||
|
|
||
|
cli_dbgmsg("in cli_parsegif()\n");
|
||
|
|
||
|
if (NULL == ctx) {
|
||
|
cli_dbgmsg("GIF: passed context was NULL\n");
|
||
|
status = CL_EARG;
|
||
|
goto done;
|
||
|
}
|
||
|
map = ctx->fmap;
|
||
|
|
||
|
/*
|
||
|
* Skip the "GIF" Signature and "87a" or "89a" Version.
|
||
|
*/
|
||
|
if (NULL == (signature = fmap_need_off(map, offset, strlen("GIF")))) {
|
||
|
cli_dbgmsg("GIF: Can't read GIF magic bytes, not a GIF\n");
|
||
|
goto done;
|
||
|
}
|
||
|
offset += strlen("GIF");
|
||
|
|
||
|
if (0 != strncmp("GIF", signature, 3)) {
|
||
|
cli_dbgmsg("GIF: First 3 bytes not 'GIF', not a GIF\n");
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
if (3 != fmap_readn(map, &version, offset, strlen("89a"))) {
|
||
|
cli_dbgmsg("GIF: Can't read GIF format version, not a GIF\n");
|
||
|
goto done;
|
||
|
}
|
||
|
offset += strlen("89a");
|
||
|
|
||
|
version[3] = '\0';
|
||
|
cli_dbgmsg("GIF: Version: %s\n", version);
|
||
|
|
||
|
/*
|
||
|
* Read the Logical Screen Descriptor
|
||
|
*/
|
||
|
if (fmap_readn(map, &screen_desc, offset, sizeof(screen_desc)) != sizeof(screen_desc)) {
|
||
|
cli_errmsg("GIF: Can't read logical screen description, file truncated?\n");
|
||
|
cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedScreenDescriptor");
|
||
|
status = CL_EPARSE;
|
||
|
goto scan_overlay;
|
||
|
}
|
||
|
offset += sizeof(screen_desc);
|
||
|
|
||
|
cli_dbgmsg("GIF: Screen Size: %u width x %u height.\n",
|
||
|
le16_to_host(screen_desc.width),
|
||
|
le16_to_host(screen_desc.height));
|
||
|
|
||
|
if (screen_desc.flags & GIF_SCREEN_DESC_FLAGS_MASK_HAVE_GLOBAL_COLOR_TABLE) {
|
||
|
global_color_table_size = 3 * (1 << ((screen_desc.flags & GIF_SCREEN_DESC_FLAGS_MASK_SIZE_OF_GLOBAL_COLOR_TABLE) + 1));
|
||
|
cli_dbgmsg("GIF: Global Color Table size: %zu\n", global_color_table_size);
|
||
|
|
||
|
if (offset + (size_t)global_color_table_size > map->len) {
|
||
|
cli_errmsg("GIF: EOF in the middle of the global color table, file truncated?\n");
|
||
|
cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedGlobalColorTable");
|
||
|
status = CL_EPARSE;
|
||
|
goto scan_overlay;
|
||
|
}
|
||
|
offset += global_color_table_size;
|
||
|
} else {
|
||
|
cli_dbgmsg("GIF: No Global Color Table.\n");
|
||
|
}
|
||
|
|
||
|
while (1) {
|
||
|
uint8_t block_label = 0;
|
||
|
|
||
|
/*
|
||
|
* Get the block label
|
||
|
*/
|
||
|
if (fmap_readn(map, &block_label, offset, sizeof(block_label)) != sizeof(block_label)) {
|
||
|
if (have_image_data) {
|
||
|
/* Users have identified that GIF's lacking the image trailer are surprisingly common,
|
||
|
can be rendered, and should be allowed. */
|
||
|
cli_dbgmsg("GIF: Missing GIF trailer, slightly (but acceptably) malformed.\n");
|
||
|
} else {
|
||
|
cli_errmsg("GIF: Can't read block label, EOF before image data. File truncated?\n");
|
||
|
cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.MissingImageData");
|
||
|
}
|
||
|
status = CL_EPARSE;
|
||
|
goto scan_overlay;
|
||
|
}
|
||
|
offset += sizeof(block_label);
|
||
|
|
||
|
if (block_label == GIF_LABEL_SPECIAL_TRAILER) {
|
||
|
/*
|
||
|
* Trailer (end of data stream)
|
||
|
*/
|
||
|
cli_dbgmsg("GIF: Trailer (End of stream)\n");
|
||
|
goto scan_overlay;
|
||
|
}
|
||
|
|
||
|
switch (block_label) {
|
||
|
case GIF_LABEL_EXTENSION_INTRODUCER: {
|
||
|
uint8_t extension_label = 0;
|
||
|
cli_dbgmsg("GIF: Extension introducer:\n");
|
||
|
|
||
|
if (fmap_readn(map, &extension_label, offset, sizeof(extension_label)) != sizeof(extension_label)) {
|
||
|
cli_errmsg("GIF: Failed to read the extension block label, file truncated?\n");
|
||
|
cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedExtension");
|
||
|
status = CL_EPARSE;
|
||
|
goto scan_overlay;
|
||
|
}
|
||
|
offset += sizeof(extension_label);
|
||
|
|
||
|
if (extension_label == GIF_LABEL_CONTROL_GRAPHIC_CONTROL_EXTENSION) {
|
||
|
cli_dbgmsg("GIF: Graphic control extension!\n");
|
||
|
|
||
|
/* The size of a graphic control extension block is fixed, we can skip it quickly */
|
||
|
offset += sizeof(struct gif_graphic_control_extension);
|
||
|
} else {
|
||
|
switch (extension_label) {
|
||
|
case GIF_LABEL_GRAPHIC_PLAIN_TEXT_EXTENSION:
|
||
|
cli_dbgmsg("GIF: Plain text extension\n");
|
||
|
break;
|
||
|
case GIF_LABEL_SPECIAL_COMMENT_EXTENSION:
|
||
|
cli_dbgmsg("GIF: Special comment extension\n");
|
||
|
break;
|
||
|
case GIF_LABEL_SPECIAL_APP_EXTENSION:
|
||
|
cli_dbgmsg("GIF: Special app extension\n");
|
||
|
break;
|
||
|
default:
|
||
|
cli_dbgmsg("GIF: Unfamiliar extension, label: 0x%x\n", extension_label);
|
||
|
}
|
||
|
|
||
|
while (1) {
|
||
|
/*
|
||
|
* Skip over the extension and any sub-blocks,
|
||
|
* Try to read the block size for each sub-block to skip them.
|
||
|
*/
|
||
|
uint8_t extension_block_size = 0;
|
||
|
if (fmap_readn(map, &extension_block_size, offset, sizeof(extension_block_size)) != sizeof(extension_block_size)) {
|
||
|
cli_errmsg("GIF: EOF while attempting to read the block size for an extension, file truncated?\n");
|
||
|
cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedExtension");
|
||
|
status = CL_EPARSE;
|
||
|
goto scan_overlay;
|
||
|
} else {
|
||
|
offset += sizeof(extension_block_size);
|
||
|
}
|
||
|
if (extension_block_size == GIF_BLOCK_TERMINATOR) {
|
||
|
cli_dbgmsg("GIF: No more sub-blocks for this extension.\n");
|
||
|
break;
|
||
|
} else {
|
||
|
cli_dbgmsg("GIF: Found sub-block of size %d\n", extension_block_size);
|
||
|
}
|
||
|
|
||
|
if (offset + (size_t)extension_block_size > map->len) {
|
||
|
cli_errmsg("GIF: EOF in the middle of a graphic control extension sub-block, file truncated?\n");
|
||
|
cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedExtensionSubBlock");
|
||
|
status = CL_EPARSE;
|
||
|
goto scan_overlay;
|
||
|
}
|
||
|
offset += extension_block_size;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case GIF_LABEL_GRAPHIC_IMAGE_DESCRIPTOR: {
|
||
|
struct gif_image_descriptor image_desc;
|
||
|
size_t local_color_table_size = 0;
|
||
|
|
||
|
cli_dbgmsg("GIF: Found an image descriptor.\n");
|
||
|
if (fmap_readn(map, &image_desc, offset, sizeof(image_desc)) != sizeof(image_desc)) {
|
||
|
cli_errmsg("GIF: Can't read image descriptor, file truncated?\n");
|
||
|
cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedImageDescriptor");
|
||
|
status = CL_EPARSE;
|
||
|
goto scan_overlay;
|
||
|
} else {
|
||
|
offset += sizeof(image_desc);
|
||
|
}
|
||
|
cli_dbgmsg("GIF: Image size: %u width x %u height, left pos: %u, top pos: %u\n",
|
||
|
le16_to_host(image_desc.width),
|
||
|
le16_to_host(image_desc.height),
|
||
|
le16_to_host(image_desc.leftpos),
|
||
|
le16_to_host(image_desc.toppos));
|
||
|
|
||
|
if (image_desc.flags & GIF_IMAGE_DESC_FLAGS_MASK_HAVE_LOCAL_COLOR_TABLE) {
|
||
|
local_color_table_size = 3 * (1 << ((image_desc.flags & GIF_IMAGE_DESC_FLAGS_MASK_SIZE_OF_LOCAL_COLOR_TABLE) + 1));
|
||
|
cli_dbgmsg("GIF: Found a Local Color Table (size: %zu)\n", local_color_table_size);
|
||
|
offset += local_color_table_size;
|
||
|
} else {
|
||
|
cli_dbgmsg("GIF: No Local Color Table.\n");
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Parse the image data.
|
||
|
*/
|
||
|
offset++; /* Skip over the LZW Minimum Code Size uint8_t */
|
||
|
|
||
|
while (1) {
|
||
|
/*
|
||
|
* Skip over the image data block(s).
|
||
|
* Try to read the block size for each image data sub-block to skip them.
|
||
|
*/
|
||
|
uint8_t image_data_block_size = 0;
|
||
|
if (fmap_readn(map, &image_data_block_size, offset, sizeof(image_data_block_size)) != sizeof(image_data_block_size)) {
|
||
|
cli_errmsg("GIF: EOF while attempting to read the block size for an image data block, file truncated?\n");
|
||
|
cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedImageDataBlock");
|
||
|
status = CL_EPARSE;
|
||
|
goto scan_overlay;
|
||
|
} else {
|
||
|
offset += sizeof(image_data_block_size);
|
||
|
}
|
||
|
if (image_data_block_size == GIF_BLOCK_TERMINATOR) {
|
||
|
cli_dbgmsg("GIF: No more data sub-blocks for this image.\n");
|
||
|
break;
|
||
|
} else {
|
||
|
cli_dbgmsg("GIF: Found a sub-block of size %d\n", image_data_block_size);
|
||
|
}
|
||
|
|
||
|
if (offset + (size_t)image_data_block_size > map->len) {
|
||
|
cli_errmsg("GIF: EOF in the middle of an image data sub-block, file truncated?\n");
|
||
|
cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedImageDataBlock");
|
||
|
status = CL_EPARSE;
|
||
|
goto scan_overlay;
|
||
|
}
|
||
|
offset += image_data_block_size;
|
||
|
}
|
||
|
have_image_data = true;
|
||
|
break;
|
||
|
}
|
||
|
default: {
|
||
|
// An unknown code: break.
|
||
|
cli_errmsg("GIF: Found an unfamiliar block label: 0x%x\n", block_label);
|
||
|
cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.UnknownBlockLabel");
|
||
|
status = CL_EPARSE;
|
||
|
goto scan_overlay;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
scan_overlay:
|
||
|
if (status == CL_EPARSE) {
|
||
|
/* We added with cli_append_possibly_unwanted so it will alert at the end if nothing else matches. */
|
||
|
status = CL_CLEAN;
|
||
|
|
||
|
// Some recovery (I saw some "GIF89a;" or things like this)
|
||
|
if (offset == (strlen("GIF89a") + sizeof(screen_desc) + 1)) {
|
||
|
offset = strlen("GIF89a");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Is there an overlay?
|
||
|
if (offset < map->len) {
|
||
|
cli_dbgmsg("GIF: Found extra data after the end of the GIF data stream: %zu bytes, we'll scan it!\n", map->len - offset);
|
||
|
cl_error_t nested_scan_result = cli_magic_scan_nested_fmap_type(map, offset, map->len - offset, ctx, CL_TYPE_ANY, NULL);
|
||
|
status = nested_scan_result != CL_SUCCESS ? nested_scan_result : status;
|
||
|
}
|
||
|
|
||
|
done:
|
||
|
return status;
|
||
|
}
|