denyhosts/clamscan/libclamav/gif.c
2022-10-22 18:41:00 +08:00

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;
}