1 /*
2 * Copyright (C) 2013-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
3 * Copyright (C) 2011-2013 Sourcefire, Inc.
4 *
5 * Authors: Tomasz Kojm <tkojm@clamav.net>, Aldo Mazzeo, Micah Snyder
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21
22 /*
23 * GIF Format
24 * ----------
25 *
26 * 1. Signature: 3 bytes ("GIF")
27 *
28 * 2. Version: 3 bytes ("87a" or "89a")
29 *
30 * 3. Logical Screen Descriptor: 7 bytes (see `struct gif_screen_descriptor`)
31 * (Opt.) Global Color Table: n bytes (defined in the Logical Screen Descriptor flags)
32 *
33 * 4. All subsequent blocks are precededed by the following 1-byte labels...
34 *
35 * 0x21: Extension Introducer
36 * 0x01: Opt. (0+) Plain Text Extension
37 * 0xF9: Opt. (0+) Graphic Control Extension
38 * 0xFE: Opt. (0+) Comment Extension
39 * 0xFF: Opt. (0+) Application Extension
40 *
41 * Note: Each extension has a size field followed by some data. After the
42 * data may be a series of sub-blocks, each with a block size.
43 * If there are no more sub-blocks, the size will be 0x00, meaning
44 * there's no more blocks.
45 * The Graphic Control Extension never has any sub-blocks.
46 *
47 * 0x2C: Image Descriptor (1 per image, unlimited images)
48 * (Opt.) Local Color Table: n bytes (defined in the Image Descriptor flags)
49 * (Req.) Table-based Image Data Block*
50 *
51 * Note: Each image a series of data blocks of size 0-255 bytes each where
52 * the first byte is the size of the data-block.
53 * If there are no more data-blocks, the size will be 0x00, meaning
54 * there's no more data.
55 *
56 * 0x3B: Trailer (1 located at end of data stream)
57 *
58 * Reference https://www.w3.org/Graphics/GIF/spec-gif89a.txt for the GIF spec.
59 */
60
61 #if HAVE_CONFIG_H
62 #include "clamav-config.h"
63 #endif
64
65 #include <math.h>
66 #include <stdbool.h>
67
68 #include "gif.h"
69 #include "scanners.h"
70 #include "clamav.h"
71
72 /* clang-format off */
73 #ifndef HAVE_ATTRIB_PACKED
74 #define __attribute__(x)
75 #endif
76 #ifdef HAVE_PRAGMA_PACK
77 #pragma pack(1)
78 #endif
79 #ifdef HAVE_PRAGMA_PACK_HPPA
80 #pragma pack 1
81 #endif
82
83 /**
84 * @brief Logical Screen Descriptor
85 *
86 * This block immediately follows the "GIF89a" magic bytes
87
88 * Flags contains packed fields which are as follows:
89 * Global Color Table Flag - 1 Bit
90 * Color Resolution - 3 Bits
91 * Sort Flag - 1 Bit
92 * Size of Global Color Table - 3 Bits
93 */
94 struct gif_screen_descriptor {
95 uint16_t width;
96 uint16_t height;
97 uint8_t flags;
98 uint8_t bg_color_idx;
99 uint8_t pixel_aspect_ratio;
100 } __attribute__((packed));
101
102 #define GIF_SCREEN_DESC_FLAGS_MASK_HAVE_GLOBAL_COLOR_TABLE 0x80 /* If set, a Global Color Table will follow the Logical Screen Descriptor */
103 #define GIF_SCREEN_DESC_FLAGS_MASK_COLOR_RESOLUTION 0x70 /* Number of bits per primary color available to the original image, minus 1. */
104 #define GIF_SCREEN_DESC_FLAGS_MASK_SORT_FLAG 0x08 /* Indicates whether the Global Color Table is sorted. */
105 #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)) */
106
107 /**
108 * @brief Graphic Control Extension
109 *
110 */
111 struct gif_graphic_control_extension {
112 uint8_t block_size;
113 uint8_t flags;
114 uint16_t delaytime;
115 uint8_t transparent_color_idx;
116 uint8_t block_terminator;
117 } __attribute__((packed));
118
119 /**
120 * @brief Image Descriptor
121 *
122 * Flags contains packed fields which are as follows:
123 * Local Color Table Flag - 1 Bit
124 * Interlace Flag - 1 Bit
125 * Sort Flag - 1 Bit
126 * Reserved - 2 Bits
127 * Size of Local Color Table - 3 Bits
128 */
129 struct gif_image_descriptor {
130 uint16_t leftpos;
131 uint16_t toppos;
132 uint16_t width;
133 uint16_t height;
134 uint8_t flags;
135 } __attribute__((packed));
136
137 #define GIF_IMAGE_DESC_FLAGS_MASK_HAVE_LOCAL_COLOR_TABLE 0x80 /* If set, a Global Color Table will follow the Logical Screen Descriptor */
138 #define GIF_IMAGE_DESC_FLAGS_MASK_IS_INTERLACED 0x40 /* Indicates if the image is interlaced */
139 #define GIF_IMAGE_DESC_FLAGS_MASK_SORT_FLAG 0x20 /* Indicates whether the Local Color Table is sorted. */
140 #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)) */
141
142 /* Main labels */
143 #define GIF_LABEL_EXTENSION_INTRODUCER 0x21
144 #define GIF_LABEL_GRAPHIC_IMAGE_DESCRIPTOR 0x2C
145 #define GIF_LABEL_SPECIAL_TRAILER 0x3B
146
147 /* Extension labels (found after the Extension Introducer) */
148 #define GIF_LABEL_GRAPHIC_PLAIN_TEXT_EXTENSION 0x01
149 #define GIF_LABEL_CONTROL_GRAPHIC_CONTROL_EXTENSION 0xF9
150 #define GIF_LABEL_SPECIAL_COMMENT_EXTENSION 0xFE
151 #define GIF_LABEL_SPECIAL_APP_EXTENSION 0xFF
152
153 #define GIF_BLOCK_TERMINATOR 0x00 /* Used to indicate end of image data and also for end of extension sub-blocks */
154
155 #ifdef HAVE_PRAGMA_PACK
156 #pragma pack()
157 #endif
158 #ifdef HAVE_PRAGMA_PACK_HPPA
159 #pragma pack
160 #endif
161 /* clang-format on */
162
cli_parsegif(cli_ctx * ctx)163 cl_error_t cli_parsegif(cli_ctx *ctx)
164 {
165 cl_error_t status = CL_SUCCESS;
166
167 fmap_t *map = NULL;
168 size_t offset = 0;
169
170 const char *signature = NULL;
171 char version[4];
172 struct gif_screen_descriptor screen_desc;
173 size_t global_color_table_size = 0;
174 bool have_image_data = false;
175
176 cli_dbgmsg("in cli_parsegif()\n");
177
178 if (NULL == ctx) {
179 cli_dbgmsg("GIF: passed context was NULL\n");
180 status = CL_EARG;
181 goto done;
182 }
183 map = ctx->fmap;
184
185 /*
186 * Skip the "GIF" Signature and "87a" or "89a" Version.
187 */
188 if (NULL == (signature = fmap_need_off(map, offset, strlen("GIF")))) {
189 cli_dbgmsg("GIF: Can't read GIF magic bytes, not a GIF\n");
190 goto done;
191 }
192 offset += strlen("GIF");
193
194 if (0 != strncmp("GIF", signature, 3)) {
195 cli_dbgmsg("GIF: First 3 bytes not 'GIF', not a GIF\n");
196 goto done;
197 }
198
199 if (3 != fmap_readn(map, &version, offset, strlen("89a"))) {
200 cli_dbgmsg("GIF: Can't read GIF format version, not a GIF\n");
201 goto done;
202 }
203 offset += strlen("89a");
204
205 version[3] = '\0';
206 cli_dbgmsg("GIF: Version: %s\n", version);
207
208 /*
209 * Read the Logical Screen Descriptor
210 */
211 if (fmap_readn(map, &screen_desc, offset, sizeof(screen_desc)) != sizeof(screen_desc)) {
212 cli_errmsg("GIF: Can't read logical screen description, file truncated?\n");
213 cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedScreenDescriptor");
214 status = CL_EPARSE;
215 goto scan_overlay;
216 }
217 offset += sizeof(screen_desc);
218
219 cli_dbgmsg("GIF: Screen Size: %u width x %u height.\n",
220 le16_to_host(screen_desc.width),
221 le16_to_host(screen_desc.height));
222
223 if (screen_desc.flags & GIF_SCREEN_DESC_FLAGS_MASK_HAVE_GLOBAL_COLOR_TABLE) {
224 global_color_table_size = 3 * (1 << ((screen_desc.flags & GIF_SCREEN_DESC_FLAGS_MASK_SIZE_OF_GLOBAL_COLOR_TABLE) + 1));
225 cli_dbgmsg("GIF: Global Color Table size: %zu\n", global_color_table_size);
226
227 if (offset + (size_t)global_color_table_size > map->len) {
228 cli_errmsg("GIF: EOF in the middle of the global color table, file truncated?\n");
229 cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedGlobalColorTable");
230 status = CL_EPARSE;
231 goto scan_overlay;
232 }
233 offset += global_color_table_size;
234 } else {
235 cli_dbgmsg("GIF: No Global Color Table.\n");
236 }
237
238 while (1) {
239 uint8_t block_label = 0;
240
241 /*
242 * Get the block label
243 */
244 if (fmap_readn(map, &block_label, offset, sizeof(block_label)) != sizeof(block_label)) {
245 if (have_image_data) {
246 /* Users have identified that GIF's lacking the image trailer are surprisingly common,
247 can be rendered, and should be allowed. */
248 cli_dbgmsg("GIF: Missing GIF trailer, slightly (but acceptably) malformed.\n");
249 } else {
250 cli_errmsg("GIF: Can't read block label, EOF before image data. File truncated?\n");
251 cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.MissingImageData");
252 }
253 status = CL_EPARSE;
254 goto scan_overlay;
255 }
256 offset += sizeof(block_label);
257
258 if (block_label == GIF_LABEL_SPECIAL_TRAILER) {
259 /*
260 * Trailer (end of data stream)
261 */
262 cli_dbgmsg("GIF: Trailer (End of stream)\n");
263 goto scan_overlay;
264 }
265
266 switch (block_label) {
267 case GIF_LABEL_EXTENSION_INTRODUCER: {
268 uint8_t extension_label = 0;
269 cli_dbgmsg("GIF: Extension introducer:\n");
270
271 if (fmap_readn(map, &extension_label, offset, sizeof(extension_label)) != sizeof(extension_label)) {
272 cli_errmsg("GIF: Failed to read the extension block label, file truncated?\n");
273 cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedExtension");
274 status = CL_EPARSE;
275 goto scan_overlay;
276 }
277 offset += sizeof(extension_label);
278
279 if (extension_label == GIF_LABEL_CONTROL_GRAPHIC_CONTROL_EXTENSION) {
280 cli_dbgmsg("GIF: Graphic control extension!\n");
281
282 /* The size of a graphic control extension block is fixed, we can skip it quickly */
283 offset += sizeof(struct gif_graphic_control_extension);
284 } else {
285 switch (extension_label) {
286 case GIF_LABEL_GRAPHIC_PLAIN_TEXT_EXTENSION:
287 cli_dbgmsg("GIF: Plain text extension\n");
288 break;
289 case GIF_LABEL_SPECIAL_COMMENT_EXTENSION:
290 cli_dbgmsg("GIF: Special comment extension\n");
291 break;
292 case GIF_LABEL_SPECIAL_APP_EXTENSION:
293 cli_dbgmsg("GIF: Special app extension\n");
294 break;
295 default:
296 cli_dbgmsg("GIF: Unfamiliar extension, label: 0x%x\n", extension_label);
297 }
298
299 while (1) {
300 /*
301 * Skip over the extension and any sub-blocks,
302 * Try to read the block size for each sub-block to skip them.
303 */
304 uint8_t extension_block_size = 0;
305 if (fmap_readn(map, &extension_block_size, offset, sizeof(extension_block_size)) != sizeof(extension_block_size)) {
306 cli_errmsg("GIF: EOF while attempting to read the block size for an extension, file truncated?\n");
307 cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedExtension");
308 status = CL_EPARSE;
309 goto scan_overlay;
310 } else {
311 offset += sizeof(extension_block_size);
312 }
313 if (extension_block_size == GIF_BLOCK_TERMINATOR) {
314 cli_dbgmsg("GIF: No more sub-blocks for this extension.\n");
315 break;
316 } else {
317 cli_dbgmsg("GIF: Found sub-block of size %d\n", extension_block_size);
318 }
319
320 if (offset + (size_t)extension_block_size > map->len) {
321 cli_errmsg("GIF: EOF in the middle of a graphic control extension sub-block, file truncated?\n");
322 cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedExtensionSubBlock");
323 status = CL_EPARSE;
324 goto scan_overlay;
325 }
326 offset += extension_block_size;
327 }
328 }
329 break;
330 }
331 case GIF_LABEL_GRAPHIC_IMAGE_DESCRIPTOR: {
332 struct gif_image_descriptor image_desc;
333 size_t local_color_table_size = 0;
334
335 cli_dbgmsg("GIF: Found an image descriptor.\n");
336 if (fmap_readn(map, &image_desc, offset, sizeof(image_desc)) != sizeof(image_desc)) {
337 cli_errmsg("GIF: Can't read image descriptor, file truncated?\n");
338 cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedImageDescriptor");
339 status = CL_EPARSE;
340 goto scan_overlay;
341 } else {
342 offset += sizeof(image_desc);
343 }
344 cli_dbgmsg("GIF: Image size: %u width x %u height, left pos: %u, top pos: %u\n",
345 le16_to_host(image_desc.width),
346 le16_to_host(image_desc.height),
347 le16_to_host(image_desc.leftpos),
348 le16_to_host(image_desc.toppos));
349
350 if (image_desc.flags & GIF_IMAGE_DESC_FLAGS_MASK_HAVE_LOCAL_COLOR_TABLE) {
351 local_color_table_size = 3 * (1 << ((image_desc.flags & GIF_IMAGE_DESC_FLAGS_MASK_SIZE_OF_LOCAL_COLOR_TABLE) + 1));
352 cli_dbgmsg("GIF: Found a Local Color Table (size: %zu)\n", local_color_table_size);
353 offset += local_color_table_size;
354 } else {
355 cli_dbgmsg("GIF: No Local Color Table.\n");
356 }
357
358 /*
359 * Parse the image data.
360 */
361 offset++; /* Skip over the LZW Minimum Code Size uint8_t */
362
363 while (1) {
364 /*
365 * Skip over the image data block(s).
366 * Try to read the block size for each image data sub-block to skip them.
367 */
368 uint8_t image_data_block_size = 0;
369 if (fmap_readn(map, &image_data_block_size, offset, sizeof(image_data_block_size)) != sizeof(image_data_block_size)) {
370 cli_errmsg("GIF: EOF while attempting to read the block size for an image data block, file truncated?\n");
371 cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedImageDataBlock");
372 status = CL_EPARSE;
373 goto scan_overlay;
374 } else {
375 offset += sizeof(image_data_block_size);
376 }
377 if (image_data_block_size == GIF_BLOCK_TERMINATOR) {
378 cli_dbgmsg("GIF: No more data sub-blocks for this image.\n");
379 break;
380 } else {
381 cli_dbgmsg("GIF: Found a sub-block of size %d\n", image_data_block_size);
382 }
383
384 if (offset + (size_t)image_data_block_size > map->len) {
385 cli_errmsg("GIF: EOF in the middle of an image data sub-block, file truncated?\n");
386 cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedImageDataBlock");
387 status = CL_EPARSE;
388 goto scan_overlay;
389 }
390 offset += image_data_block_size;
391 }
392 have_image_data = true;
393 break;
394 }
395 default: {
396 // An unknown code: break.
397 cli_errmsg("GIF: Found an unfamiliar block label: 0x%x\n", block_label);
398 cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.UnknownBlockLabel");
399 status = CL_EPARSE;
400 goto scan_overlay;
401 }
402 }
403 }
404
405 scan_overlay:
406 if (status == CL_EPARSE) {
407 /* We added with cli_append_possibly_unwanted so it will alert at the end if nothing else matches. */
408 status = CL_CLEAN;
409
410 // Some recovery (I saw some "GIF89a;" or things like this)
411 if (offset == (strlen("GIF89a") + sizeof(screen_desc) + 1)) {
412 offset = strlen("GIF89a");
413 }
414 }
415
416 // Is there an overlay?
417 if (offset < map->len) {
418 cli_dbgmsg("GIF: Found extra data after the end of the GIF data stream: %zu bytes, we'll scan it!\n", map->len - offset);
419 cl_error_t nested_scan_result = cli_magic_scan_nested_fmap_type(map, offset, map->len - offset, ctx, CL_TYPE_ANY, NULL);
420 status = nested_scan_result != CL_SUCCESS ? nested_scan_result : status;
421 }
422
423 done:
424 return status;
425 }
426