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