Browse code

GIF, PNG bugfixes; Add AlertBrokenMedia option

Added a new scan option to alert on broken media (graphics) file
formats. This feature mitigates the risk of malformed media files
intended to exploit vulnerabilities in other software. At present
media validation exists for JPEG, TIFF, PNG, and GIF files.

To enable this feature, set `AlertBrokenMedia yes` in clamd.conf, or
use the `--alert-broken-media` option when using `clamscan`.
These options are disabled by default for now.

Application developers may enable this scan option by enabling
`CL_SCAN_HEURISTIC_BROKEN_MEDIA` for the `heuristic` scan option bit
field.

Fixed PNG parser logic bugs that caused an excess of parsing errors
and fixed a stack exhaustion issue affecting some systems when
scanning PNG files. PNG file type detection was disabled via
signature database update for 0.103.0 to mitigate effects from these
bugs.

Fixed an issue where PNG and GIF files no longer work with Target:5
(graphics) signatures if detected as CL_TYPE_PNG/GIF rather than as
CL_TYPE_GRAPHICS. Target types now support up to 10 possible file
types to make way for additional graphics types in future releases.

Scanning JPEG, TIFF, PNG, and GIF files will no longer return "parse"
errors when file format validation fails. Instead, the scan will alert
with the "Heuristics.Broken.Media" signature prefix and a descriptive
suffix to indicate the issue, provided that the "alert broken media"
feature is enabled.

GIF format validation will no longer fail if the GIF image is missing
the trailer byte, as this appears to be a relatively common issue in
otherwise functional GIF files.

Added a TIFF dynamic configuration (DCONF) option, which was missing.
This will allow us to disable TIFF format validation via signature
database update in the event that it proves to be problematic.
This feature already exists for many other file types.

Added CL_TYPE_JPEG and CL_TYPE_TIFF types.

Micah Snyder authored on 2020/11/05 08:49:43
Showing 22 changed files
... ...
@@ -5,15 +5,53 @@ Note: This file refers to the source tarball. Things described here may differ
5 5
 
6 6
 ## 0.103.1
7 7
 
8
-ClamAV 0.103.1 is a bug patch release to address the following issues.
8
+ClamAV 0.103.1 is a patch release with the following fixes and improvements.
9 9
 
10
-- Inter-process file descriptor passing for clamonacc was non-functional in previous
11
-  versions due to a bug introduced by the switch to curl for communicating with clamd.
12
-  On Linux, passing file descriptors from one process to another is handled by the kernel,
13
-  so we reverted clamonacc to use standard system calls for socket communication when
14
-  fd passing is enabled.
10
+### Notable changes
11
+
12
+- Added a new scan option to alert on broken media (graphics) file formats.
13
+  This feature mitigates the risk of malformed media files intended to exploit
14
+  vulnerabilities in other software.
15
+  At present media validation exists for JPEG, TIFF, PNG, and GIF files.
16
+  To enable this feature, set `AlertBrokenMedia yes` in clamd.conf, or use
17
+  the `--alert-broken-media` option when using `clamscan`.
18
+  These options are disabled by default in this patch release, but may be
19
+  enabled in a subsequent release.
20
+  Application developers may enable this scan option by enabling
21
+  `CL_SCAN_HEURISTIC_BROKEN_MEDIA` for the `heuristic` scan option bit field.
22
+
23
+- Added CL_TYPE_TIFF, CL_TYPE_JPEG types to match GIF, PNG typing behavior.
24
+  BMP and JPEG 2000 files will continue to detect as CL_TYPE_GRAPHICS because
25
+  ClamAV does not yet have BMP or JPEG 2000 format checking capabilities.
26
+
27
+### Bug fixes
28
+
29
+- Fixed PNG parser logic bugs that caused an excess of parsing errors and fixed
30
+  a stack exhaustion issue affecting some systems when scanning PNG files.
31
+  PNG file type detection was disabled via signature database update for
32
+  ClamAV version 0.103.0 to mitigate the effects from these bugs.
33
+
34
+- Fixed an issue where PNG and GIF files no longer work with Target:5 graphics
35
+  signatures if detected as CL_TYPE_PNG/GIF rather than as CL_TYPE_GRAPHICS.
36
+  Target types now support up to 10 possible file types to make way for
37
+  additional graphics types in future releases.
38
+
39
+- Fixed clamonacc's `--fdpass` option.
15 40
 
16
-- Fix clamonacc stack corruption issue on some systems when using an older
41
+  File descriptor passing (or "fd-passing") is a mechanism by which clamonacc
42
+  and clamdscan may transfer an open file to clamd to scan, even if clamd is
43
+  running as a non-privileged user and wouldn't otherwise have read-access to
44
+  the file. This enables clamd to scan all files without having to run clamd as
45
+  root. If possible, clamd should never be run as root so as to mitigate the
46
+  risk in case clamd is somehow compromised while scanning malware.
47
+
48
+  Interprocess file descriptor passing for clamonacc was broken since version
49
+  0.102.0 due to a bug introduced by the switch to curl for communicating with
50
+  clamd. On Linux, passing file descriptors from one process to another is
51
+  handled by the kernel, so we reverted clamonacc to use standard system calls
52
+  for socket communication when fd passing is enabled.
53
+
54
+- Fixed a clamonacc stack corruption issue on some systems when using an older
17 55
   version of libcurl. Patch courtesy of Emilio Pozuelo Monfort.
18 56
 
19 57
 - Allow clamscan and clamdscan scans to proceed even if the realpath lookup
... ...
@@ -33,6 +71,22 @@ ClamAV 0.103.1 is a bug patch release to address the following issues.
33 33
 - Fixed an issue where freshclam database validation didn't work correctly when
34 34
   run in daemon mode on Linux/Unix.
35 35
 
36
+### Other improvements
37
+
38
+- Scanning JPEG, TIFF, PNG, and GIF files will no longer return "parse" errors
39
+  when file format validation fails. Instead, the scan will alert with the
40
+  "Heuristics.Broken.Media" signature prefix and a descriptive suffix to
41
+  indicate the issue, provided that the "alert broken media" feature is enabled.
42
+
43
+- GIF format validation will no longer fail if the GIF image is missing the
44
+  trailer byte, as this appears to be a relatively common issue in otherwise
45
+  functional GIF files.
46
+
47
+- Added a TIFF dynamic configuration (DCONF) option, which was missing.
48
+  This will allow us to disable TIFF format validation via signature database
49
+  update in the event that it proves to be problematic.
50
+  This feature already exists for many other file types.
51
+
36 52
 ### Acknowledgements
37 53
 
38 54
 The ClamAV team thanks the following individuals for their code submissions:
... ...
@@ -1203,6 +1203,11 @@ int recvloop(int *socketds, unsigned nsockets, struct cl_engine *engine, unsigne
1203 1203
         }
1204 1204
     }
1205 1205
 
1206
+    if (optget(opts, "AlertBrokenMedia")->enabled) {
1207
+        options.heuristic |= CL_SCAN_HEURISTIC_BROKEN_MEDIA;
1208
+        logg("Media (Graphics) Format Validatation enabled\n");
1209
+    }
1210
+
1206 1211
     if (optget(opts, "ScanMail")->enabled) {
1207 1212
         logg("Mail files support enabled.\n");
1208 1213
         options.parse |= CL_SCAN_PARSE_MAIL;
... ...
@@ -78,7 +78,7 @@ int main(int argc, char **argv)
78 78
         exit(2);
79 79
 
80 80
 #if !defined(_WIN32)
81
-    if(!setlocale(LC_CTYPE, "")) {
81
+    if (!setlocale(LC_CTYPE, "")) {
82 82
         mprintf("^Failed to set locale\n");
83 83
     }
84 84
 #if !defined(C_BEOS)
... ...
@@ -299,6 +299,7 @@ void help(void)
299 299
     mprintf("    --scan-hwp3[=yes(*)/no]              Scan HWP3 files\n");
300 300
     mprintf("    --scan-archive[=yes(*)/no]           Scan archive files (supported by libclamav)\n");
301 301
     mprintf("    --alert-broken[=yes/no(*)]           Alert on broken executable files (PE & ELF)\n");
302
+    mprintf("    --alert-broken-media[=yes/no(*)]     Alert on broken graphics files (JPEG, TIFF, PNG, GIF)\n");
302 303
     mprintf("    --alert-encrypted[=yes/no(*)]        Alert on encrypted archives and documents\n");
303 304
     mprintf("    --alert-encrypted-archive[=yes/no(*)] Alert on encrypted archives\n");
304 305
     mprintf("    --alert-encrypted-doc[=yes/no(*)]    Alert on encrypted documents\n");
... ...
@@ -1070,6 +1070,10 @@ int scanmanager(const struct optstruct *opts)
1070 1070
         options.heuristic |= CL_SCAN_HEURISTIC_BROKEN;
1071 1071
     }
1072 1072
 
1073
+    if (optget(opts, "alert-broken-media")->enabled) {
1074
+        options.heuristic |= CL_SCAN_HEURISTIC_BROKEN_MEDIA;
1075
+    }
1076
+
1073 1077
     /* TODO: Remove deprecated option in a future feature release */
1074 1078
     if ((optget(opts, "block-encrypted")->enabled) ||
1075 1079
         (optget(opts, "alert-encrypted")->enabled)) {
... ...
@@ -477,6 +477,11 @@ Alert on broken executable files (PE & ELF).
477 477
 .br
478 478
 Default: no
479 479
 .TP
480
+\fBAlertBrokenMedia BOOL\fR
481
+Alert on broken graphics files (JPEG, TIFF, PNG, GIF).
482
+.br
483
+Default: no
484
+.TP
480 485
 \fBAlertEncrypted BOOL\fR
481 486
 Alert on encrypted archives and documents (encrypted .zip, .7zip, .rar, .pdf).
482 487
 .br
... ...
@@ -301,6 +301,11 @@ Example
301 301
 # Default: no
302 302
 #AlertBrokenExecutables yes
303 303
 
304
+# With this option clamav will try to detect broken media file (JPEG,
305
+# TIFF, PNG, GIF) and alert on them with a Broken.Media heuristic signature.
306
+# Default: no
307
+#AlertBrokenMedia yes
308
+
304 309
 # Alert on encrypted archives _and_ documents with heuristic signature
305 310
 # (encrypted .zip, .7zip, .rar, .pdf).
306 311
 # Default: no
... ...
@@ -191,8 +191,8 @@ struct cl_scan_options {
191 191
 #define CL_SCAN_HEURISTIC_STRUCTURED                0x200  /* data loss prevention options, i.e. alert when detecting personal information */
192 192
 #define CL_SCAN_HEURISTIC_STRUCTURED_SSN_NORMAL     0x400  /* alert when detecting social security numbers */
193 193
 #define CL_SCAN_HEURISTIC_STRUCTURED_SSN_STRIPPED   0x800  /* alert when detecting stripped social security numbers */
194
-
195 194
 #define CL_SCAN_HEURISTIC_STRUCTURED_CC             0x1000 /* alert when detecting credit card numbers */
195
+#define CL_SCAN_HEURISTIC_BROKEN_MEDIA              0x2000 /* alert if a file does not match the identified file format, works with JPEG, TIFF, GIF, PNG */
196 196
 
197 197
 /* mail scanning options */
198 198
 #define CL_SCAN_MAIL_PARTIAL_MESSAGE                0x1
... ...
@@ -134,6 +134,7 @@ static struct dconf_module modules[] = {
134 134
     {"OTHER", "LZW", OTHER_CONF_LZW, 1},
135 135
     {"OTHER", "GIF", OTHER_CONF_GIF, 1},
136 136
     {"OTHER", "PNG", OTHER_CONF_PNG, 1},
137
+    {"OTHER", "TIFF", OTHER_CONF_TIFF, 1},
137 138
 
138 139
     {"PHISHING", "ENGINE", PHISHING_CONF_ENGINE, 1},
139 140
     {"PHISHING", "ENTCONV", PHISHING_CONF_ENTCONV, 1},
... ...
@@ -169,27 +170,21 @@ struct cli_dconf *cli_dconf_init(void)
169 169
         if (!strcmp(modules[i].mname, "PE")) {
170 170
             if (modules[i].state)
171 171
                 dconf->pe |= modules[i].bflag;
172
-
173 172
         } else if (!strcmp(modules[i].mname, "ELF")) {
174 173
             if (modules[i].state)
175 174
                 dconf->elf |= modules[i].bflag;
176
-
177 175
         } else if (!strcmp(modules[i].mname, "MACHO")) {
178 176
             if (modules[i].state)
179 177
                 dconf->macho |= modules[i].bflag;
180
-
181 178
         } else if (!strcmp(modules[i].mname, "ARCHIVE")) {
182 179
             if (modules[i].state)
183 180
                 dconf->archive |= modules[i].bflag;
184
-
185 181
         } else if (!strcmp(modules[i].mname, "DOCUMENT")) {
186 182
             if (modules[i].state)
187 183
                 dconf->doc |= modules[i].bflag;
188
-
189 184
         } else if (!strcmp(modules[i].mname, "MAIL")) {
190 185
             if (modules[i].state)
191 186
                 dconf->mail |= modules[i].bflag;
192
-
193 187
         } else if (!strcmp(modules[i].mname, "OTHER")) {
194 188
             if (modules[i].state)
195 189
                 dconf->other |= modules[i].bflag;
... ...
@@ -127,6 +127,7 @@ struct cli_dconf {
127 127
 #define OTHER_CONF_LZW          0x400
128 128
 #define OTHER_CONF_PNG          0x800
129 129
 #define OTHER_CONF_GIF          0x1000
130
+#define OTHER_CONF_TIFF         0x2000
130 131
 
131 132
 /* Phishing flags */
132 133
 #define PHISHING_CONF_ENGINE  0x1
... ...
@@ -86,6 +86,8 @@ static const struct ftmap_s {
86 86
     { "CL_TYPE_GRAPHICS",     CL_TYPE_GRAPHICS     },
87 87
     { "CL_TYPE_GIF",          CL_TYPE_GIF          },
88 88
     { "CL_TYPE_PNG",          CL_TYPE_PNG          },
89
+    { "CL_TYPE_JPEG",         CL_TYPE_JPEG         },
90
+    { "CL_TYPE_TIFF",         CL_TYPE_TIFF         },
89 91
     { "CL_TYPE_RIFF",         CL_TYPE_RIFF         },
90 92
     { "CL_TYPE_BINHEX",       CL_TYPE_BINHEX       },
91 93
     { "CL_TYPE_TNEF",         CL_TYPE_TNEF         },
... ...
@@ -68,6 +68,8 @@ typedef enum cli_file {
68 68
     CL_TYPE_GRAPHICS,
69 69
     CL_TYPE_GIF,
70 70
     CL_TYPE_PNG,
71
+    CL_TYPE_JPEG,
72
+    CL_TYPE_TIFF,
71 73
     CL_TYPE_RIFF,
72 74
     CL_TYPE_BINHEX,
73 75
     CL_TYPE_TNEF,
... ...
@@ -36,7 +36,7 @@ print "  NULL\n};\n"
36 36
 */
37 37
 
38 38
 static const char *ftypes_int[] = {
39
-    "0:0:0000000c6a5020200d0a870a:JPEG2000:CL_TYPE_ANY:CL_TYPE_GRAPHICS",
39
+    "0:0:0000000c6a5020200d0a870a:JPEG2000:CL_TYPE_ANY:CL_TYPE_JPEG",
40 40
     "0:0:000001b3:MPEG video stream:CL_TYPE_ANY:CL_TYPE_IGNORED",
41 41
     "0:0:000001ba:MPEG sys stream:CL_TYPE_ANY:CL_TYPE_IGNORED",
42 42
     "0:0:1f8b:GZip:CL_TYPE_ANY:CL_TYPE_GZ",
... ...
@@ -90,7 +90,7 @@ static const char *ftypes_int[] = {
90 90
     "0:0:89504e47:PNG:CL_TYPE_ANY:CL_TYPE_PNG",
91 91
     "0:0:b6b9acaefeffffff:CryptFF:CL_TYPE_ANY:CL_TYPE_CRYPTFF",
92 92
     "0:0:d0cf11e0a1b11ae1:OLE2 container:CL_TYPE_ANY:CL_TYPE_MSOLE2",
93
-    "0:0:ffd8ff:JPEG:CL_TYPE_ANY:CL_TYPE_GRAPHICS",
93
+    "0:0:ffd8ff:JPEG:CL_TYPE_ANY:CL_TYPE_JPEG",
94 94
     "0:0:fffb90:MP3:CL_TYPE_ANY:CL_TYPE_IGNORED",
95 95
     "1:*:3c4120*(68|48)(72|52)4546:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
96 96
     "1:*:3c4120*(68|48)(72|52)6566:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
... ...
@@ -189,8 +189,8 @@ static const char *ftypes_int[] = {
189 189
     "1:0:3c3f786d6c2076657273696f6e3d22312e3022*70726f6769643d22576f72642e446f63756d656e74223f3e:Microsoft Word 2003 XML Document:CL_TYPE_ANY:CL_TYPE_XML_WORD:80",
190 190
     "1:0:3c3f786d6c2076657273696f6e3d22312e3022*3c576f726b626f6f6b:Microsoft Excel 2003 XML Document:CL_TYPE_ANY:CL_TYPE_XML_XL:80",
191 191
     "1:0:3c3f786d6c2076657273696f6e3d22312e3022*3c??3a576f726b626f6f6b:Microsoft Excel 2003 XML Document:CL_TYPE_ANY:CL_TYPE_XML_XL:80",
192
-    "0:0:49492a00:TIFF Little Endian:CL_TYPE_ANY:CL_TYPE_GRAPHICS:81",
193
-    "0:0:4d4d:TIFF Big Endian:CL_TYPE_ANY:CL_TYPE_GRAPHICS:81",
192
+    "0:0:49492a00:TIFF Little Endian:CL_TYPE_ANY:CL_TYPE_TIFF:81",
193
+    "0:0:4d4d:TIFF Big Endian:CL_TYPE_ANY:CL_TYPE_TIFF:81",
194 194
     "0:4:d0cf11e0a1b11ae1:HWP embedded OLE2:CL_TYPE_ANY:CL_TYPE_HWPOLE2",
195 195
     "0:0:48575020446f63756d656e742046696c652056332e3030201a0102030405:HWP 3.x Document:CL_TYPE_ANY:CL_TYPE_HWP3:82",
196 196
     "1:0:efbbbf3c3f786d6c2076657273696f6e3d22312e3022*3c4857504d4c:HWPML Document:CL_TYPE_ANY:CL_TYPE_XML_HWP:82",
... ...
@@ -187,7 +187,7 @@ static inline void fmap_unneed_ptr(fmap_t *m, const void *ptr, size_t len)
187 187
 }
188 188
 
189 189
 /**
190
- * @brief Read bytes from fmap at offset into destinatio buffer.
190
+ * @brief Read bytes from fmap at offset into destination buffer.
191 191
  *
192 192
  * @param m         fmap
193 193
  * @param dst       destination buffer
... ...
@@ -1,32 +1,75 @@
1 1
 /*
2
-*  Copyright (C) 2013-2019 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>
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
-*/
2
+ *  Copyright (C) 2013-2019 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
+ */
21 60
 
22 61
 #if HAVE_CONFIG_H
23 62
 #include "clamav-config.h"
24 63
 #endif
25 64
 
65
+#include <math.h>
66
+#include <stdbool.h>
67
+
26 68
 #include "gif.h"
27 69
 #include "scanners.h"
28 70
 #include "clamav.h"
29 71
 
72
+/* clang-format off */
30 73
 #ifndef HAVE_ATTRIB_PACKED
31 74
 #define __attribute__(x)
32 75
 #endif
... ...
@@ -37,23 +80,53 @@
37 37
 #pragma pack 1
38 38
 #endif
39 39
 
40
-struct gif_screen_desc {
40
+/**
41
+ * @brief Logical Screen Descriptor
42
+ *
43
+ * This block immediately follows the  "GIF89a" magic bytes
44
+
45
+ * Flags contains packed fields which are as follows:
46
+ *  Global Color Table Flag     - 1 Bit
47
+ *  Color Resolution            - 3 Bits
48
+ *  Sort Flag                   - 1 Bit
49
+ *  Size of Global Color Table  - 3 Bits
50
+ */
51
+struct gif_screen_descriptor {
41 52
     uint16_t width;
42 53
     uint16_t height;
43 54
     uint8_t flags;
44
-    uint8_t bgcolor;
45
-    uint8_t aspect;
55
+    uint8_t bg_color_idx;
56
+    uint8_t pixel_aspect_ratio;
46 57
 } __attribute__((packed));
47 58
 
48
-struct gif_graphic_control_ext {
49
-    uint8_t blksize;
59
+#define GIF_SCREEN_DESC_FLAGS_MASK_HAVE_GLOBAL_COLOR_TABLE    0x80 /* If set, a Global Color Table will follow the Logical Screen Descriptor */
60
+#define GIF_SCREEN_DESC_FLAGS_MASK_COLOR_RESOLUTION           0x70 /* Number of bits per primary color available to the original image, minus 1. */
61
+#define GIF_SCREEN_DESC_FLAGS_MASK_SORT_FLAG                  0x08 /* Indicates whether the Global Color Table is sorted. */
62
+#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)) */
63
+
64
+/**
65
+ * @brief Graphic Control Extension
66
+ *
67
+ */
68
+struct gif_graphic_control_extension {
69
+    uint8_t block_size;
50 70
     uint8_t flags;
51 71
     uint16_t delaytime;
52
-    uint8_t tcoloridx;
53
-    uint8_t blkterm;
72
+    uint8_t transparent_color_idx;
73
+    uint8_t block_terminator;
54 74
 } __attribute__((packed));
55 75
 
56
-struct gif_image_desc {
76
+/**
77
+ * @brief Image Descriptor
78
+ *
79
+ * Flags contains packed fields which are as follows:
80
+ *  Local Color Table Flag      - 1 Bit
81
+ *  Interlace Flag              - 1 Bit
82
+ *  Sort Flag                   - 1 Bit
83
+ *  Reserved                    - 2 Bits
84
+ *  Size of Local Color Table   - 3 Bits
85
+ */
86
+struct gif_image_descriptor {
57 87
     uint16_t leftpos;
58 88
     uint16_t toppos;
59 89
     uint16_t width;
... ...
@@ -61,99 +134,292 @@ struct gif_image_desc {
61 61
     uint8_t flags;
62 62
 } __attribute__((packed));
63 63
 
64
+#define GIF_IMAGE_DESC_FLAGS_MASK_HAVE_LOCAL_COLOR_TABLE    0x80 /* If set, a Global Color Table will follow the Logical Screen Descriptor */
65
+#define GIF_IMAGE_DESC_FLAGS_MASK_IS_INTERLACED             0x40 /* Indicates if the image is interlaced */
66
+#define GIF_IMAGE_DESC_FLAGS_MASK_SORT_FLAG                 0x20 /* Indicates whether the Local Color Table is sorted. */
67
+#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)) */
68
+
69
+/* Main labels */
70
+#define GIF_LABEL_EXTENSION_INTRODUCER                  0x21
71
+#define GIF_LABEL_GRAPHIC_IMAGE_DESCRIPTOR              0x2C
72
+#define GIF_LABEL_SPECIAL_TRAILER                       0x3B
73
+
74
+/* Extension labels (found after the Extension Introducer) */
75
+#define GIF_LABEL_GRAPHIC_PLAIN_TEXT_EXTENSION          0x01
76
+#define GIF_LABEL_CONTROL_GRAPHIC_CONTROL_EXTENSION     0xF9
77
+#define GIF_LABEL_SPECIAL_COMMENT_EXTENSION             0xFE
78
+#define GIF_LABEL_SPECIAL_APP_EXTENSION                 0xFF
79
+
80
+#define GIF_BLOCK_TERMINATOR 0x00 /* Used to indicate end of image data and also for end of extension sub-blocks */
81
+
64 82
 #ifdef HAVE_PRAGMA_PACK
65 83
 #pragma pack()
66 84
 #endif
67 85
 #ifdef HAVE_PRAGMA_PACK_HPPA
68 86
 #pragma pack
69 87
 #endif
70
-
71
-#define EC16(x) le16_to_host(x)
72
-
73
-#define GETDATA(v)                                                      \
74
-    {                                                                   \
75
-        if (fmap_readn(map, &v, offset, sizeof(v)) == sizeof(v)) {      \
76
-            offset += sizeof(v);                                        \
77
-        } else {                                                        \
78
-            cli_errmsg("cli_parsegif: Can't read file (truncated?)\n"); \
79
-            return CL_EPARSE;                                           \
80
-        }                                                               \
81
-    }
88
+/* clang-format on */
82 89
 
83 90
 cl_error_t cli_parsegif(cli_ctx *ctx)
84 91
 {
85
-    fmap_t *map         = *ctx->fmap;
86
-    unsigned char v     = 0;
87
-    unsigned int offset = 6;
88
-    struct gif_screen_desc screen_desc;
89
-    struct gif_image_desc image_desc;
90
-    cl_error_t retVal = CL_SUCCESS;
92
+    cl_error_t status = CL_SUCCESS;
93
+
94
+    fmap_t *map   = NULL;
95
+    size_t offset = 0;
96
+
97
+    const char *signature = NULL;
98
+    char version[4];
99
+    struct gif_screen_descriptor screen_desc;
100
+    size_t global_color_table_size = 0;
101
+    bool have_image_data           = false;
91 102
 
92 103
     cli_dbgmsg("in cli_parsegif()\n");
93 104
 
94
-    GETDATA(screen_desc);
95
-    cli_dbgmsg("GIF: Screen size %ux%u, gctsize: %u\n", EC16(screen_desc.width), EC16(screen_desc.height), screen_desc.flags & 0x7);
96
-    if (screen_desc.flags & 0x80)
97
-        offset += 3 * (1 << ((screen_desc.flags & 0x7) + 1));
105
+    if (NULL == ctx) {
106
+        cli_dbgmsg("GIF: passed context was NULL\n");
107
+        status = CL_EARG;
108
+        goto done;
109
+    }
110
+    map = *ctx->fmap;
111
+
112
+    /*
113
+     * Skip the "GIF" Signature and "87a" or "89a" Version.
114
+     */
115
+    if (NULL == (signature = fmap_need_off(map, offset, strlen("GIF")))) {
116
+        cli_dbgmsg("GIF: Can't read GIF magic bytes, not a GIF\n");
117
+        goto done;
118
+    }
119
+    offset += strlen("GIF");
120
+
121
+    if (0 != strncmp("GIF", signature, 3)) {
122
+        cli_dbgmsg("GIF: First 3 bytes not 'GIF', not a GIF\n");
123
+        goto done;
124
+    }
125
+
126
+    if (3 != fmap_readn(map, &version, offset, strlen("89a"))) {
127
+        cli_dbgmsg("GIF: Can't read GIF format version, not a GIF\n");
128
+        goto done;
129
+    }
130
+    offset += strlen("89a");
131
+
132
+    version[3] = '\0';
133
+    cli_dbgmsg("GIF: Version: %s\n", version);
134
+
135
+    /*
136
+     * Read the Logical Screen Descriptor
137
+     */
138
+    if (fmap_readn(map, &screen_desc, offset, sizeof(screen_desc)) != sizeof(screen_desc)) {
139
+        cli_errmsg("GIF: Can't read logical screen description, file truncated?\n");
140
+        cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedScreenDescriptor");
141
+        status = CL_EPARSE;
142
+        goto scan_overlay;
143
+    }
144
+    offset += sizeof(screen_desc);
145
+
146
+    cli_dbgmsg("GIF: Screen Size: %u width x %u height.\n",
147
+               le16_to_host(screen_desc.width),
148
+               le16_to_host(screen_desc.height));
149
+
150
+    if (screen_desc.flags & GIF_SCREEN_DESC_FLAGS_MASK_HAVE_GLOBAL_COLOR_TABLE) {
151
+        global_color_table_size = 3 * (1 << ((screen_desc.flags & GIF_SCREEN_DESC_FLAGS_MASK_SIZE_OF_GLOBAL_COLOR_TABLE) + 1));
152
+        cli_dbgmsg("GIF: Global Color Table size: %u\n", global_color_table_size);
153
+
154
+        if (offset + (size_t)global_color_table_size > map->len) {
155
+            cli_errmsg("GIF: EOF in the middle of the global color table, file truncated?\n");
156
+            cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedGlobalColorTable");
157
+            status = CL_EPARSE;
158
+            goto scan_overlay;
159
+        }
160
+        offset += global_color_table_size;
161
+    } else {
162
+        cli_dbgmsg("GIF: No Global Color Table.\n");
163
+    }
98 164
 
99 165
     while (1) {
100
-        GETDATA(v);
101
-        if (v == 0x21) {
102
-            GETDATA(v);
103
-            if (v == 0xf9) {
104
-                offset += sizeof(struct gif_graphic_control_ext);
166
+        uint8_t block_label = 0;
167
+
168
+        /*
169
+         * Get the block label
170
+         */
171
+        if (fmap_readn(map, &block_label, offset, sizeof(block_label)) != sizeof(block_label)) {
172
+            if (have_image_data) {
173
+                /* Users have identified that GIF's lacking the image trailer are surprisingly common,
174
+                   can be rendered, and should be allowed. */
175
+                cli_dbgmsg("GIF: Missing GIF trailer, slightly (but acceptably) malformed.\n");
105 176
             } else {
106
-                while (1) {
107
-                    GETDATA(v);
108
-                    if (!v)
109
-                        break;
177
+                cli_errmsg("GIF: Can't read block label, EOF before image data. File truncated?\n");
178
+                cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.MissingImageData");
179
+            }
180
+            status = CL_EPARSE;
181
+            goto scan_overlay;
182
+        }
183
+        offset += sizeof(block_label);
110 184
 
111
-                    if (offset + v > map->len) {
112
-                        retVal = CL_EPARSE;
113
-                        goto scan_overlay;
185
+        if (block_label == GIF_LABEL_SPECIAL_TRAILER) {
186
+            /*
187
+             * Trailer (end of data stream)
188
+             */
189
+            cli_dbgmsg("GIF: Trailer (End of stream)\n");
190
+            goto scan_overlay;
191
+        }
192
+
193
+        switch (block_label) {
194
+            case GIF_LABEL_EXTENSION_INTRODUCER: {
195
+                uint8_t extension_label = 0;
196
+                cli_dbgmsg("GIF: Extension introducer:\n");
197
+
198
+                if (fmap_readn(map, &extension_label, offset, sizeof(extension_label)) != sizeof(extension_label)) {
199
+                    cli_errmsg("GIF: Failed to read the extension block label, file truncated?\n");
200
+                    cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedExtension");
201
+                    status = CL_EPARSE;
202
+                    goto scan_overlay;
203
+                }
204
+                offset += sizeof(extension_label);
205
+
206
+                if (extension_label == GIF_LABEL_CONTROL_GRAPHIC_CONTROL_EXTENSION) {
207
+                    cli_dbgmsg("GIF:   Graphic control extension!\n");
208
+
209
+                    /* The size of a graphic control extension block is fixed, we can skip it quickly */
210
+                    offset += sizeof(struct gif_graphic_control_extension);
211
+                } else {
212
+                    switch (extension_label) {
213
+                        case GIF_LABEL_GRAPHIC_PLAIN_TEXT_EXTENSION:
214
+                            cli_dbgmsg("GIF:   Plain text extension\n");
215
+                            break;
216
+                        case GIF_LABEL_SPECIAL_COMMENT_EXTENSION:
217
+                            cli_dbgmsg("GIF:   Special comment extension\n");
218
+                            break;
219
+                        case GIF_LABEL_SPECIAL_APP_EXTENSION:
220
+                            cli_dbgmsg("GIF:   Special app extension\n");
221
+                            break;
222
+                        default:
223
+                            cli_dbgmsg("GIF:   Unfamiliar extension, label: 0x%x\n", extension_label);
224
+                    }
225
+
226
+                    while (1) {
227
+                        /*
228
+                         * Skip over the extension and any sub-blocks,
229
+                         * Try to read the block size for each sub-block to skip them.
230
+                         */
231
+                        uint8_t extension_block_size = 0;
232
+                        if (fmap_readn(map, &extension_block_size, offset, sizeof(extension_block_size)) != sizeof(extension_block_size)) {
233
+                            cli_errmsg("GIF: EOF while attempting to read the block size for an extension, file truncated?\n");
234
+                            cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedExtension");
235
+                            status = CL_EPARSE;
236
+                            goto scan_overlay;
237
+                        } else {
238
+                            offset += sizeof(extension_block_size);
239
+                        }
240
+                        if (extension_block_size == GIF_BLOCK_TERMINATOR) {
241
+                            cli_dbgmsg("GIF:     No more sub-blocks for this extension.\n");
242
+                            break;
243
+                        } else {
244
+                            cli_dbgmsg("GIF:     Found sub-block of size %d\n", extension_block_size);
245
+                        }
246
+
247
+                        if (offset + (size_t)extension_block_size > map->len) {
248
+                            cli_errmsg("GIF: EOF in the middle of a graphic control extension sub-block, file truncated?\n");
249
+                            cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedExtensionSubBlock");
250
+                            status = CL_EPARSE;
251
+                            goto scan_overlay;
252
+                        }
253
+                        offset += extension_block_size;
114 254
                     }
115
-                    offset += v;
116 255
                 }
256
+                break;
117 257
             }
118
-        } else if (v == 0x2c) {
119
-            GETDATA(image_desc);
120
-            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));
258
+            case GIF_LABEL_GRAPHIC_IMAGE_DESCRIPTOR: {
259
+                struct gif_image_descriptor image_desc;
260
+                size_t local_color_table_size = 0;
121 261
 
122
-            offset++;
123
-            if (image_desc.flags & 0x80)
124
-                offset += 3 * (1 << ((image_desc.flags & 0x7) + 1));
262
+                cli_dbgmsg("GIF: Found an image descriptor.\n");
263
+                if (fmap_readn(map, &image_desc, offset, sizeof(image_desc)) != sizeof(image_desc)) {
264
+                    cli_errmsg("GIF: Can't read image descriptor, file truncated?\n");
265
+                    cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedImageDescriptor");
266
+                    status = CL_EPARSE;
267
+                    goto scan_overlay;
268
+                } else {
269
+                    offset += sizeof(image_desc);
270
+                }
271
+                cli_dbgmsg("GIF:   Image size: %u width x %u height, left pos: %u, top pos: %u\n",
272
+                           le16_to_host(image_desc.width),
273
+                           le16_to_host(image_desc.height),
274
+                           le16_to_host(image_desc.leftpos),
275
+                           le16_to_host(image_desc.toppos));
125 276
 
126
-            while (1) {
127
-                GETDATA(v);
128
-                if (!v)
129
-                    break;
277
+                if (image_desc.flags & GIF_IMAGE_DESC_FLAGS_MASK_HAVE_LOCAL_COLOR_TABLE) {
278
+                    local_color_table_size = 3 * (1 << ((image_desc.flags & GIF_IMAGE_DESC_FLAGS_MASK_SIZE_OF_LOCAL_COLOR_TABLE) + 1));
279
+                    cli_dbgmsg("GIF:     Found a Local Color Table (size: %u)\n", local_color_table_size);
280
+                    offset += local_color_table_size;
281
+                } else {
282
+                    cli_dbgmsg("GIF:     No Local Color Table.\n");
283
+                }
130 284
 
131
-                if (offset + v > map->len) {
132
-                    retVal = CL_EPARSE;
133
-                    goto scan_overlay;
285
+                /*
286
+                 * Parse the image data.
287
+                 */
288
+                offset++; /* Skip over the LZW Minimum Code Size uint8_t */
289
+
290
+                while (1) {
291
+                    /*
292
+                     * Skip over the image data block(s).
293
+                     * Try to read the block size for each image data sub-block to skip them.
294
+                     */
295
+                    uint8_t image_data_block_size = 0;
296
+                    if (fmap_readn(map, &image_data_block_size, offset, sizeof(image_data_block_size)) != sizeof(image_data_block_size)) {
297
+                        cli_errmsg("GIF: EOF while attempting to read the block size for an image data block, file truncated?\n");
298
+                        cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedImageDataBlock");
299
+                        status = CL_EPARSE;
300
+                        goto scan_overlay;
301
+                    } else {
302
+                        offset += sizeof(image_data_block_size);
303
+                    }
304
+                    if (image_data_block_size == GIF_BLOCK_TERMINATOR) {
305
+                        cli_dbgmsg("GIF:     No more data sub-blocks for this image.\n");
306
+                        break;
307
+                    } else {
308
+                        cli_dbgmsg("GIF:     Found a sub-block of size %d\n", image_data_block_size);
309
+                    }
310
+
311
+                    if (offset + (size_t)image_data_block_size > map->len) {
312
+                        cli_errmsg("GIF: EOF in the middle of an image data sub-block, file truncated?\n");
313
+                        cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.TruncatedImageDataBlock");
314
+                        status = CL_EPARSE;
315
+                        goto scan_overlay;
316
+                    }
317
+                    offset += image_data_block_size;
134 318
                 }
135
-                offset += v;
319
+                have_image_data = true;
320
+                break;
321
+            }
322
+            default: {
323
+                // An unknown code: break.
324
+                cli_errmsg("GIF: Found an unfamiliar block label: 0x%x\n", block_label);
325
+                cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.GIF.UnknownBlockLabel");
326
+                status = CL_EPARSE;
327
+                goto scan_overlay;
136 328
             }
137
-        } else if (v == 0x3b) {
138
-            break;
139
-        } else {
140
-            // An unknown code: break.
141
-            retVal = CL_EPARSE;
142
-            goto scan_overlay;
143 329
         }
144 330
     }
145 331
 
146 332
 scan_overlay:
147
-    // Some recovery (I saw some "GIF89a;" or things like this)
148
-    if (retVal == CL_EPARSE &&
149
-        offset == (6 + sizeof(screen_desc) + 1))
150
-        offset = 6;
333
+    if (status == CL_EPARSE) {
334
+        /* We added with cli_append_possibly_unwanted so it will alert at the end if nothing else matches. */
335
+        status = CL_CLEAN;
336
+
337
+        // Some recovery (I saw some "GIF89a;" or things like this)
338
+        if (offset == (strlen("GIF89a") + sizeof(screen_desc) + 1)) {
339
+            offset = strlen("GIF89a");
340
+        }
341
+    }
151 342
 
152 343
     // Is there an overlay?
153 344
     if (offset < map->len) {
154
-        cl_error_t recRetVal = cli_magic_scan_nested_fmap_type(map, offset, map->len - offset, ctx, CL_TYPE_ANY, NULL);
155
-        retVal               = recRetVal != CL_SUCCESS ? recRetVal : retVal;
345
+        cli_dbgmsg("GIF: Found extra data after the end of the GIF data stream: %zu bytes, we'll scan it!\n", map->len - offset);
346
+        cl_error_t nested_scan_result = cli_magic_scan_nested_fmap_type(map, offset, map->len - offset, ctx, CL_TYPE_ANY, NULL);
347
+        status                        = nested_scan_result != CL_SUCCESS ? nested_scan_result : status;
156 348
     }
157 349
 
158
-    return retVal;
350
+done:
351
+    return status;
159 352
 }
... ...
@@ -37,148 +37,196 @@
37 37
 #include "jpeg.h"
38 38
 #include "clamav.h"
39 39
 
40
-#define GETBYTE(v)                                                   \
41
-    if (fmap_readn(map, &v, offset, sizeof(v)) == sizeof(v)) {       \
42
-        offset += sizeof(v);                                         \
43
-    } else {                                                         \
44
-        cli_errmsg("cli_parsejpeg: Can't read file (corrupted?)\n"); \
45
-        return CL_EPARSE;                                            \
46
-    }
47
-
48 40
 cl_error_t cli_parsejpeg(cli_ctx *ctx)
49 41
 {
50
-    fmap_t *map = *ctx->fmap;
51
-    unsigned char marker, prev_marker, prev_segment = 0, v1, v2, buff[8];
42
+    cl_error_t status = CL_CLEAN;
43
+
44
+    fmap_t *map = NULL;
45
+    unsigned char marker, prev_marker, prev_segment = 0, buff[8];
46
+    uint16_t len_u16;
52 47
     unsigned int offset = 0, i, len, comment = 0, segment = 0, app = 0;
53 48
 
54 49
     cli_dbgmsg("in cli_parsejpeg()\n");
55 50
 
51
+    if (NULL == ctx) {
52
+        cli_dbgmsg("JPEG: passed context was NULL\n");
53
+        status = CL_EARG;
54
+        goto done;
55
+    }
56
+    map = *ctx->fmap;
57
+
56 58
     if (fmap_readn(map, buff, offset, 4) != 4)
57
-        return CL_SUCCESS; /* Ignore */
59
+        goto done; /* Ignore */
58 60
 
59 61
     if (!memcmp(buff, "\xff\xd8\xff", 3))
60 62
         offset = 2;
61 63
     else if (!memcmp(buff, "\xff\xd9\xff\xd8", 4))
62 64
         offset = 4;
63 65
     else
64
-        return CL_SUCCESS; /* Not a JPEG file */
66
+        goto done; /* Not a JPEG file */
65 67
 
66 68
     while (1) {
67 69
         segment++;
68 70
         prev_marker = 0;
69 71
         for (i = 0; offset < map->len && i < 16; i++) {
70
-            GETBYTE(marker);
72
+            if (fmap_readn(map, &marker, offset, sizeof(marker)) == sizeof(marker)) {
73
+                offset += sizeof(marker);
74
+            } else {
75
+                cli_errmsg("JPEG: Failed to read marker, file corrupted?\n");
76
+                cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.CantReadMarker");
77
+                goto done;
78
+            }
79
+
71 80
             if (prev_marker == 0xff && marker != 0xff)
72 81
                 break;
73 82
             prev_marker = marker;
74 83
         }
75 84
         if (i == 16) {
76
-            cli_warnmsg("cli_parsejpeg: Spurious bytes before segment %u\n", segment);
77
-            return CL_EPARSE;
85
+            cli_warnmsg("JPEG: Spurious bytes before segment %u\n", segment);
86
+            cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.SpuriousBytesBeforeSegment");
87
+            status = CL_EPARSE;
88
+            goto done;
78 89
         }
79
-        if (offset == map->len) {
80
-            cli_warnmsg("cli_parsejpeg: Error looking for marker\n");
81
-            return CL_EPARSE;
90
+
91
+        if (fmap_readn(map, &len_u16, offset, sizeof(len_u16)) != sizeof(len_u16)) {
92
+            cli_errmsg("JPEG: Failed to read the segment size, file corrupted?\n");
93
+            cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.CantReadSegmentSize");
94
+            goto done;
82 95
         }
83
-        GETBYTE(v1);
84
-        GETBYTE(v2);
85
-        len = (unsigned int)(v1 << 8) | v2;
96
+        len = (unsigned int)be16_to_host(len_u16);
86 97
         cli_dbgmsg("JPEG: Marker %02x, length %u\n", marker, len);
98
+
87 99
         if (len < 2) {
88
-            cli_warnmsg("cli_parsejpeg: Invalid segment size\n");
89
-            return CL_EPARSE;
100
+            cli_warnmsg("JPEG: Invalid segment size\n");
101
+            cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.InvalidSegmentSize");
102
+            status = CL_EPARSE;
103
+            goto done;
90 104
         }
91
-        if (len >= map->len - offset + 2) {
92
-            cli_warnmsg("cli_parsejpeg: Segment data out of file\n");
93
-            return CL_EPARSE;
105
+        if (len >= map->len - offset + sizeof(len_u16)) {
106
+            cli_warnmsg("JPEG: Segment data out of file\n");
107
+            cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.SegmentDataOutOfFile");
108
+            status = CL_EPARSE;
109
+            goto done;
94 110
         }
95
-        offset += len - 2;
111
+        offset += len;
96 112
 
97 113
         switch (marker) {
98 114
             case 0xe0: /* JFIF */
99 115
                 if (app) {
100
-                    cli_warnmsg("cli_parsejpeg: Duplicate Application Marker\n");
101
-                    return CL_EPARSE;
116
+                    cli_warnmsg("JPEG: Duplicate Application Marker\n");
117
+                    cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.JFIFdupAppMarker");
118
+                    status = CL_EPARSE;
119
+                    goto done;
102 120
                 }
103 121
                 if (segment != 1 && (segment != 2 || !comment)) {
104
-                    cli_warnmsg("cli_parsejpeg: JFIF marker at wrong position\n");
105
-                    return CL_EPARSE;
122
+                    cli_warnmsg("JPEG: JFIF marker at wrong position\n");
123
+                    cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.JFIFmarkerBadPosition");
124
+                    status = CL_EPARSE;
125
+                    goto done;
106 126
                 }
107
-                if (fmap_readn(map, buff, offset - len + 2, 5) != 5 || memcmp(buff, "JFIF\0", 5)) {
108
-                    cli_warnmsg("cli_parsejpeg: No JFIF marker\n");
109
-                    return CL_EPARSE;
127
+                if (fmap_readn(map, buff, offset - len + sizeof(len_u16), 5) != 5 || memcmp(buff, "JFIF\0", 5)) {
128
+                    cli_warnmsg("JPEG: No JFIF marker\n");
129
+                    cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.NoJFIFmarker");
130
+                    status = CL_EPARSE;
131
+                    goto done;
110 132
                 }
111 133
                 if (len < 16) {
112
-                    cli_warnmsg("cli_parsejpeg: JFIF header too short\n");
113
-                    return CL_EPARSE;
134
+                    cli_warnmsg("JPEG: JFIF header too short\n");
135
+                    cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.JFIFheaderTooShort");
136
+                    status = CL_EPARSE;
137
+                    goto done;
114 138
                 }
115 139
                 app = 0xe0;
116 140
                 break;
117 141
 
118 142
             case 0xe1: /* EXIF */
119
-                if (fmap_readn(map, buff, offset - len + 2, 7) != 7) {
120
-                    cli_warnmsg("cli_parsejpeg: Can't read Exif header\n");
121
-                    return CL_EPARSE;
143
+                if (fmap_readn(map, buff, offset - len + sizeof(len_u16), 7) != 7) {
144
+                    cli_warnmsg("JPEG: Can't read Exif header\n");
145
+                    cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.CantReadExifHeader");
146
+                    status = CL_EPARSE;
147
+                    goto done;
122 148
                 }
123 149
                 if (!memcmp(buff, "Exif\0\0", 6)) {
124 150
                     if (app && app != 0xe0) {
125
-                        cli_warnmsg("cli_parsejpeg: Duplicate Application Marker\n");
126
-                        return CL_EPARSE;
151
+                        cli_warnmsg("JPEG: Duplicate Application Marker\n");
152
+                        cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.ExifDupAppMarker");
153
+                        status = CL_EPARSE;
154
+                        goto done;
127 155
                     }
128 156
                     if (segment > 3 && !comment && app != 0xe0) {
129
-                        cli_warnmsg("cli_parsejpeg: Exif marker at wrong position\n");
130
-                        return CL_EPARSE;
157
+                        cli_warnmsg("JPEG: Exif marker at wrong position\n");
158
+                        cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.ExifHeaderBadPosition");
159
+                        status = CL_EPARSE;
160
+                        goto done;
131 161
                     }
132 162
                 } else if (!memcmp(buff, "http://", 7)) {
133 163
                     cli_dbgmsg("JPEG: XMP data in segment %u\n", segment);
134 164
                 } else {
135
-                    cli_warnmsg("cli_parsejpeg: Invalid Exif header\n");
136
-                    return CL_EPARSE;
165
+                    cli_warnmsg("JPEG: Invalid Exif header\n");
166
+                    cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.InvalidExifHeader");
167
+                    status = CL_EPARSE;
168
+                    goto done;
137 169
                 }
138 170
                 if (len < 16) {
139
-                    cli_warnmsg("cli_parsejpeg: Exif header too short\n");
140
-                    return CL_EPARSE;
171
+                    cli_warnmsg("JPEG: Exif header too short\n");
172
+                    cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.ExifHeaderTooShort");
173
+                    status = CL_EPARSE;
174
+                    goto done;
141 175
                 }
142 176
                 app = 0xe1;
143 177
                 break;
144 178
 
145 179
             case 0xe8: /* SPIFF */
146 180
                 if (app) {
147
-                    cli_warnmsg("cli_parsejpeg: Duplicate Application Marker\n");
148
-                    return CL_EPARSE;
181
+                    cli_warnmsg("JPEG: Duplicate Application Marker\n");
182
+                    cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.SPIFFdupAppMarker");
183
+                    status = CL_EPARSE;
184
+                    goto done;
149 185
                 }
150 186
                 if (segment != 1 && (segment != 2 || !comment)) {
151
-                    cli_warnmsg("cli_parsejpeg: SPIFF marker at wrong position\n");
152
-                    return CL_EPARSE;
187
+                    cli_warnmsg("JPEG: SPIFF marker at wrong position\n");
188
+                    cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.SPIFFmarkerBadPosition");
189
+                    status = CL_EPARSE;
190
+                    goto done;
153 191
                 }
154
-                if (fmap_readn(map, buff, offset - len + 2, 6) != 6 || memcmp(buff, "SPIFF\0", 6)) {
155
-                    cli_warnmsg("cli_parsejpeg: No SPIFF marker\n");
156
-                    return CL_EPARSE;
192
+                if (fmap_readn(map, buff, offset - len + sizeof(len_u16), 6) != 6 || memcmp(buff, "SPIFF\0", 6)) {
193
+                    cli_warnmsg("JPEG: No SPIFF marker\n");
194
+                    cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.NoSPIFFmarker");
195
+                    status = CL_EPARSE;
196
+                    goto done;
157 197
                 }
158 198
                 if (len < 16) {
159
-                    cli_warnmsg("cli_parsejpeg: SPIFF header too short\n");
160
-                    return CL_EPARSE;
199
+                    cli_warnmsg("JPEG: SPIFF header too short\n");
200
+                    cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.SPIFFheaderTooShort");
201
+                    status = CL_EPARSE;
202
+                    goto done;
161 203
                 }
162 204
                 app = 0xe8;
163 205
                 break;
164 206
 
165 207
             case 0xf7: /* JPG7 */
166 208
                 if (app) {
167
-                    cli_warnmsg("cli_parsejpeg: Application Marker before JPG7\n");
168
-                    return CL_EPARSE;
209
+                    cli_warnmsg("JPEG: Application Marker before JPG7\n");
210
+                    cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.AppMarkerBeforeJPG7");
211
+                    status = CL_EPARSE;
212
+                    goto done;
169 213
                 }
170
-                return CL_SUCCESS;
214
+                goto done;
171 215
 
172 216
             case 0xda: /* SOS */
173 217
                 if (!app) {
174
-                    cli_warnmsg("cli_parsejpeg: Invalid file structure\n");
175
-                    return CL_EPARSE;
218
+                    cli_warnmsg("JPEG: Invalid file structure\n");
219
+                    cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.InvalidStructure");
220
+                    status = CL_EPARSE;
221
+                    goto done;
176 222
                 }
177
-                return CL_SUCCESS;
223
+                goto done;
178 224
 
179 225
             case 0xd9: /* EOI */
180
-                cli_warnmsg("cli_parsejpeg: No image in jpeg\n");
181
-                return CL_EPARSE;
226
+                cli_warnmsg("JPEG: No image in jpeg\n");
227
+                cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.NoImages");
228
+                status = CL_EPARSE;
229
+                goto done;
182 230
 
183 231
             case 0xfe: /* COM */
184 232
                 comment = 1;
... ...
@@ -190,8 +238,10 @@ cl_error_t cli_parsejpeg(cli_ctx *ctx)
190 190
 
191 191
             case 0xf2: /* DTT */
192 192
                 if (prev_segment != 0xf1) {
193
-                    cli_warnmsg("cli_parsejpeg: No DTI segment before DTT\n");
194
-                    return CL_EPARSE;
193
+                    cli_warnmsg("JPEG: No DTI segment before DTT\n");
194
+                    cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.JPEG.DTTMissingDTISegment");
195
+                    status = CL_EPARSE;
196
+                    goto done;
195 197
                 }
196 198
                 break;
197 199
 
... ...
@@ -200,5 +250,12 @@ cl_error_t cli_parsejpeg(cli_ctx *ctx)
200 200
         }
201 201
         prev_segment = marker;
202 202
     }
203
-    return CL_SUCCESS;
203
+
204
+done:
205
+    if (status == CL_EPARSE) {
206
+        /* We added with cli_append_possibly_unwanted so it will alert at the end if nothing else matches. */
207
+        status = CL_CLEAN;
208
+    }
209
+
210
+    return status;
204 211
 }
... ...
@@ -163,8 +163,8 @@ struct cli_cdb {
163 163
     cli_file_t ctype;        /* container type */
164 164
     regex_t name;            /* filename regex */
165 165
     size_t csize[2];         /* container size (min, max); if csize[0] != csize[1]
166
-			                     * then value of 0 makes the field ignored
167
-			                     */
166
+                              * then value of 0 makes the field ignored
167
+                              */
168 168
     size_t fsizec[2];        /* file size in container */
169 169
     size_t fsizer[2];        /* real file size */
170 170
     int encrypted;           /* file is encrypted; 2 == ignore */
... ...
@@ -175,7 +175,7 @@ struct cli_cdb {
175 175
     struct cli_cdb *next;
176 176
 };
177 177
 
178
-#define CLI_MAX_TARGETS 2 /* maximum filetypes for a specific target */
178
+#define CLI_MAX_TARGETS 10 /* maximum filetypes for a specific target */
179 179
 struct cli_mtarget {
180 180
     cli_file_t target[CLI_MAX_TARGETS];
181 181
     const char *name;
... ...
@@ -185,26 +185,26 @@ struct cli_mtarget {
185 185
     uint8_t target_count; /* must be synced with non-zero values in the target array */
186 186
 };
187 187
 
188
-// clang-format off
189
-
190 188
 #define CLI_MTARGETS 15
191
-static const struct cli_mtarget cli_mtargets[CLI_MTARGETS] =  {
192
-    { {0, 0},                                   "GENERIC",      0,  0, 1, 1 },
193
-    { {CL_TYPE_MSEXE, 0},                       "PE",           1,  0, 1, 1 },
194
-    { {CL_TYPE_MSOLE2, 0},                      "OLE2",         2,  1, 0, 1 },
195
-    { {CL_TYPE_HTML, 0},                        "HTML",         3,  1, 0, 1 },
196
-    { {CL_TYPE_MAIL, 0},                        "MAIL",         4,  1, 1, 1 },
197
-    { {CL_TYPE_GRAPHICS, 0},                    "GRAPHICS",     5,  1, 0, 1 },
198
-    { {CL_TYPE_ELF, 0},                         "ELF",          6,  1, 0, 1 },
199
-    { {CL_TYPE_TEXT_ASCII, 0},                  "ASCII",        7,  1, 1, 1 },
200
-    { {CL_TYPE_ERROR, 0},                       "NOT USED",     8,  1, 0, 1 },
201
-    { {CL_TYPE_MACHO, CL_TYPE_MACHO_UNIBIN},    "MACH-O",       9,  1, 0, 2 },
202
-    { {CL_TYPE_PDF, 0},                         "PDF",         10,  1, 0, 1 },
203
-    { {CL_TYPE_SWF, 0},                         "FLASH",       11,  1, 0, 1 },
204
-    { {CL_TYPE_JAVA, 0},                        "JAVA",        12,  1, 0, 1 },
205
-    { {CL_TYPE_INTERNAL, 0},                    "INTERNAL",    13,  1, 0, 1 },
206
-    { {CL_TYPE_OTHER, 0},                       "OTHER",       14,  1, 0, 1 }
207
-};
189
+static const struct cli_mtarget cli_mtargets[CLI_MTARGETS] = {
190
+    /* All types for target, name, idx, ac_only, pre-filtering?, # of types */
191
+    {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "GENERIC", 0, 0, 1, 1},
192
+    {{CL_TYPE_MSEXE, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "PE", 1, 0, 1, 1},
193
+    {{CL_TYPE_MSOLE2, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "OLE2", 2, 1, 0, 1},
194
+    {{CL_TYPE_HTML, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "HTML", 3, 1, 0, 1},
195
+    {{CL_TYPE_MAIL, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "MAIL", 4, 1, 1, 1},
196
+    {{CL_TYPE_GRAPHICS, CL_TYPE_GIF, CL_TYPE_PNG, CL_TYPE_JPEG, CL_TYPE_TIFF, 0, 0, 0, 0, 0}, "GRAPHICS", 5, 1, 0, 5},
197
+    {{CL_TYPE_ELF, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "ELF", 6, 1, 0, 1},
198
+    {{CL_TYPE_TEXT_ASCII, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "ASCII", 7, 1, 1, 1},
199
+    {{CL_TYPE_ERROR, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "NOT USED", 8, 1, 0, 1},
200
+    {{CL_TYPE_MACHO, CL_TYPE_MACHO_UNIBIN, 0, 0, 0, 0, 0, 0, 0, 0}, "MACH-O", 9, 1, 0, 2},
201
+    {{CL_TYPE_PDF, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "PDF", 10, 1, 0, 1},
202
+    {{CL_TYPE_SWF, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "FLASH", 11, 1, 0, 1},
203
+    {{CL_TYPE_JAVA, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "JAVA", 12, 1, 0, 1},
204
+    {{CL_TYPE_INTERNAL, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "INTERNAL", 13, 1, 0, 1},
205
+    {{CL_TYPE_OTHER, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "OTHER", 14, 1, 0, 1}};
206
+
207
+// clang-format off
208 208
 
209 209
 #define CLI_OFF_ANY         0xffffffff
210 210
 #define CLI_OFF_NONE        0xfffffffe
... ...
@@ -509,6 +509,7 @@ extern LIBCLAMAV_EXPORT int have_rar;
509 509
 #define SCAN_PARSE_PE (ctx->options->parse & CL_SCAN_PARSE_PE)
510 510
 
511 511
 #define SCAN_HEURISTIC_BROKEN (ctx->options->heuristic & CL_SCAN_HEURISTIC_BROKEN)
512
+#define SCAN_HEURISTIC_BROKEN_MEDIA (ctx->options->heuristic & CL_SCAN_HEURISTIC_BROKEN_MEDIA)
512 513
 #define SCAN_HEURISTIC_EXCEEDS_MAX (ctx->options->heuristic & CL_SCAN_HEURISTIC_EXCEEDS_MAX)
513 514
 #define SCAN_HEURISTIC_PHISHING_SSL_MISMATCH (ctx->options->heuristic & CL_SCAN_HEURISTIC_PHISHING_SSL_MISMATCH)
514 515
 #define SCAN_HEURISTIC_PHISHING_CLOAK (ctx->options->heuristic & CL_SCAN_HEURISTIC_PHISHING_CLOAK)
... ...
@@ -6,7 +6,9 @@
6 6
  *                              Glenn Randers-Pehrson <randeg@alum.rpi.edu>,
7 7
  *                              Greg Roelofs <newt@pobox.com>,
8 8
  *                              John Bowler <jbowler@acm.org>,
9
- *                              Tom Lane <tgl@sss.pgh.pa.us>\
9
+ *                              Tom Lane <tgl@sss.pgh.pa.us>
10
+ *
11
+ *   Initial work derived from pngcheck: http://www.libpng.org/pub/png/apps/pngcheck.html
10 12
  *
11 13
  *   Permission to use, copy, modify, and distribute this software and its
12 14
  *   documentation for any purpose and without fee is hereby granted, provided
... ...
@@ -26,6 +28,7 @@
26 26
 #include <ctype.h>
27 27
 #include <string.h>
28 28
 #include <fcntl.h>
29
+#include <stdbool.h>
29 30
 #include <sys/types.h>
30 31
 #include <sys/stat.h>
31 32
 #ifdef HAVE_UNISTD_H
... ...
@@ -38,288 +41,407 @@
38 38
 #include "png.h"
39 39
 #include "scanners.h"
40 40
 
41
+#define PNG_CHUNK_LENGTH_SIZE (4)
42
+#define PNG_CHUNK_TYPE_SIZE (4)
43
+#define PNG_CHUNK_CRC_SIZE (4)
44
+/* Header Size does not include chunk data size */
45
+#define PNG_CHUNK_HEADER_SIZE (PNG_CHUNK_LENGTH_SIZE + \
46
+                               PNG_CHUNK_TYPE_SIZE +   \
47
+                               PNG_CHUNK_CRC_SIZE)
48
+
49
+#ifndef HAVE_ATTRIB_PACKED
50
+#define __attribute__(x)
51
+#endif
52
+
53
+#ifdef HAVE_PRAGMA_PACK
54
+#pragma pack(1)
55
+#endif
56
+
57
+#ifdef HAVE_PRAGMA_PACK_HPPA
58
+#pragma pack 1
59
+#endif
60
+
61
+typedef struct __attribute__((packed)) {
62
+    uint8_t red;
63
+    uint8_t green;
64
+    uint8_t blue;
65
+} png_palette_entry;
66
+
67
+#ifdef HAVE_PRAGMA_PACK
68
+#pragma pack()
69
+#endif
70
+
71
+#ifdef HAVE_PRAGMA_PACK_HPPA
72
+#pragma pack
73
+#endif
74
+
41 75
 #define BUFFER_SIZE 128000 /* size of read block  */
42 76
 
77
+typedef enum {
78
+    PNG_IDAT_NOT_FOUND_YET,
79
+    PNG_IDAT_DECOMPRESSION_IN_PROGRESS,
80
+    PNG_IDAT_DECOMPRESSION_COMPLETE,
81
+    PNG_IDAT_DECOMPRESSION_FAILED,
82
+} png_idat_state;
83
+
43 84
 cl_error_t cli_parsepng(cli_ctx *ctx)
44 85
 {
45
-    uint64_t sz     = 0;
46
-    char chunkid[5] = {'\0', '\0', '\0', '\0', '\0'};
47
-    size_t toread = 0, toread_check = 0;
48
-    int32_t c         = 0;
49
-    int32_t have_IEND = 0, have_PLTE = 0;
50
-    uint64_t zhead     = 1; /* 0x10000 indicates both zlib header bytes read */
51
-    int64_t num_chunks = 0L;
52
-    int64_t w = 0L, h = 0L;
53
-    int32_t bitdepth = 0, sampledepth = 0, lace = 0;
54
-    uint64_t nplte = 0;
55
-    uint32_t ityp  = 1;
56
-    uint32_t buffer[BUFFER_SIZE];
57
-    uint64_t offset = 8;
58
-    fmap_t *map     = NULL;
59
-
60
-    int64_t cur_xoff, cur_xskip;
61
-    uint64_t cur_width, cur_linebytes, cur_imagesize;
62
-    int32_t err      = Z_OK;
63
-    uint32_t *outbuf = NULL;
86
+    cl_error_t status = CL_ERROR;
87
+
88
+    uint64_t chunk_data_length = 0;
89
+    char chunk_type[5]         = {'\0', '\0', '\0', '\0', '\0'};
90
+    uint32_t chunk_crc;
91
+    uint32_t chunk_data_length_u32 = 0;
92
+    bool have_IEND                 = false;
93
+    bool have_PLTE                 = false;
94
+    uint64_t zhead                 = 1; /* 0x10000 indicates both zlib header bytes read */
95
+
96
+    int64_t num_chunks = 0;
97
+    uint64_t width     = 0;
98
+    uint64_t height    = 0;
99
+
100
+    uint32_t sample_depth = 0, bit_depth = 0, interlace_method = 0;
101
+    uint64_t num_palette_entries = 0;
102
+    uint32_t color_type          = 1;
103
+    uint32_t compression_method  = 0;
104
+    uint32_t filter_method       = 0;
105
+    uint8_t *ptr                 = NULL;
106
+    uint64_t offset              = 8;
107
+    fmap_t *map                  = NULL;
108
+
109
+    uint64_t image_size = 0;
110
+
111
+    int err                              = Z_OK;
112
+    uint8_t *decompressed_data           = NULL;
113
+    size_t decompressed_data_buffer_size = 0;
114
+
64 115
     z_stream zstrm;
65
-    uint64_t offadjust    = 0;
66
-    size_t left_comp_read = 0, uncomp_data = 0;
116
+    size_t decompressed_data_len = 0;
117
+
118
+    png_idat_state idat_state = PNG_IDAT_NOT_FOUND_YET;
67 119
 
68 120
     cli_dbgmsg("in cli_parsepng()\n");
69 121
 
70 122
     if (NULL == ctx) {
71 123
         cli_dbgmsg("PNG: passed context was NULL\n");
72
-        return CL_EARG;
124
+        status = CL_EARG;
125
+        goto done;
73 126
     }
74 127
     map = *ctx->fmap;
75 128
 
76
-    while (fmap_readn(map, &c, offset, sizeof(c)) == sizeof(c)) {
129
+    while (fmap_readn(map, (void *)&chunk_data_length_u32, offset, PNG_CHUNK_LENGTH_SIZE) == PNG_CHUNK_LENGTH_SIZE) {
130
+        chunk_data_length = be32_to_host(chunk_data_length_u32);
131
+        offset += PNG_CHUNK_LENGTH_SIZE;
77 132
 
78
-        int j = 0;
79
-        for (j = 0; j < 4; ++j) {
80
-            unsigned char c;
81
-            if (fmap_readn(map, &c, offset, sizeof(c)) != sizeof(c)) {
82
-                cli_dbgmsg("PNG: EOF(?) while reading %s\n", "chunk length");
83
-                return CL_CLEAN;
133
+        if (chunk_data_length > (uint64_t)0x7fffffff) {
134
+            cli_dbgmsg("PNG: invalid chunk length (too large): 0x" STDx64 "\n", chunk_data_length);
135
+            if (SCAN_HEURISTIC_BROKEN_MEDIA) {
136
+                cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.PNG.InvalidChunkLength");
137
+                status = CL_EPARSE;
84 138
             }
85
-            offset++;
86
-            sz <<= 8;
87
-            sz |= c & 0xff;
88
-        }
89
-
90
-        if (sz > 0x7fffffff) {
91
-            cli_dbgmsg("PNG: invalid chunk length (too large)\n");
92
-            return CL_EPARSE;
139
+            goto scan_overlay;
93 140
         }
94 141
 
95
-        if (fmap_readn(map, chunkid, offset, 4) != 4) {
142
+        if (fmap_readn(map, chunk_type, offset, PNG_CHUNK_TYPE_SIZE) != PNG_CHUNK_TYPE_SIZE) {
96 143
             cli_dbgmsg("PNG: EOF while reading chunk type\n");
97
-            return CL_EPARSE;
144
+            if (SCAN_HEURISTIC_BROKEN_MEDIA) {
145
+                cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.PNG.EOFReadingChunkType");
146
+                status = CL_EPARSE;
147
+            }
148
+            goto scan_overlay;
98 149
         }
99
-        offset += 4;
150
+        offset += PNG_CHUNK_TYPE_SIZE;
100 151
 
101
-        /* GRR:  add 4-character EBCDIC conversion here (chunkid) */
152
+        /* GRR:  add 4-character EBCDIC conversion here (chunk_type) */
102 153
 
103
-        chunkid[4] = '\0';
154
+        chunk_type[4] = '\0';
104 155
         ++num_chunks;
105 156
 
106
-        toread       = (sz > BUFFER_SIZE) ? BUFFER_SIZE : sz;
107
-        toread_check = fmap_readn(map, buffer, offset, toread);
108
-        if ((size_t)-1 == toread_check) {
109
-            cli_dbgmsg("PNG: Failed to read from map.\n");
110
-            return CL_EPARSE;
111
-        }
112
-        if (toread > toread_check) {
113
-            cli_dbgmsg("PNG: EOF while reading data\n");
114
-            return CL_EPARSE;
115
-        }
116
-        toread = toread_check;
157
+        cli_dbgmsg("Chunk Type: %s, Data Length: " STDu64 " bytes\n", chunk_type, chunk_data_length);
117 158
 
118
-        offset += toread;
159
+        if (chunk_data_length > 0) {
160
+            ptr = (uint8_t *)fmap_need_off_once(map, offset, chunk_data_length);
161
+            if (NULL == ptr) {
162
+                cli_warnmsg("PNG: Unexpected early end-of-file.\n");
163
+                if (SCAN_HEURISTIC_BROKEN_MEDIA) {
164
+                    cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.PNG.EOFReadingChunk");
165
+                    status = CL_EPARSE;
166
+                }
167
+                goto scan_overlay;
168
+            }
169
+            offset += chunk_data_length;
170
+        }
119 171
 
120
-        /*------*
121
-         | IHDR |
122
-         *------*/
123
-        if (strcmp(chunkid, "IHDR") == 0) {
124
-            if (sz != 13) {
125
-                cli_dbgmsg("PNG: invalid IHDR length\n");
172
+        if (strcmp(chunk_type, "IHDR") == 0) {
173
+            /*------*
174
+             | IHDR |
175
+             *------*/
176
+            if (chunk_data_length != 13) {
177
+                cli_dbgmsg("PNG: invalid IHDR length: " STDu64 "\n", chunk_data_length);
126 178
                 break;
127 179
             } else {
128
-                w = be32_to_host(*buffer);
129
-                h = be32_to_host(*(buffer + 4));
130
-                if (w <= 0 || h <= 0 || w > 2147483647 || h > 2147483647) {
131
-                    cli_dbgmsg("PNG: invalid image dimensions\n");
180
+                width  = be32_to_host(*(uint32_t *)ptr);
181
+                height = be32_to_host(*(uint32_t *)(ptr + 4));
182
+                if (width == 0 || height == 0 || width > (uint64_t)0x7fffffff || height > (uint64_t)0x7fffffff) {
183
+                    cli_dbgmsg("PNG: invalid image dimensions: width = " STDu64 ", height = " STDu64 "\n", width, height);
132 184
                     break;
133 185
                 }
134
-                bitdepth = sampledepth = (uint32_t)buffer[8];
135
-                ityp                   = (uint32_t)buffer[9];
136
-                lace                   = (uint32_t)buffer[12];
137
-                switch (sampledepth) {
186
+                sample_depth = bit_depth = (uint32_t)ptr[8];
187
+                color_type               = (uint32_t)ptr[9];
188
+                compression_method       = (uint32_t)ptr[10];
189
+                filter_method            = (uint32_t)ptr[11];
190
+                interlace_method         = (uint32_t)ptr[12];
191
+
192
+                if (compression_method != 0) {
193
+                    cli_dbgmsg("PNG: invalid compression method (%u)\n", compression_method);
194
+                }
195
+                if (filter_method != 0) {
196
+                    cli_dbgmsg("PNG: invalid filter method (%u)\n", filter_method);
197
+                }
198
+                switch (bit_depth) {
138 199
                     case 1:
139 200
                     case 2:
140 201
                     case 4:
141
-                        if (ityp == 2 || ityp == 4 || ityp == 6) { /* RGB or GA or RGBA */
142
-                            cli_dbgmsg("PNG: invalid sample depth (%d)\n", sampledepth);
202
+                        if (color_type == 2 || color_type == 4 || color_type == 6) { /* RGB or GA or RGBA */
203
+                            cli_dbgmsg("PNG: invalid sample depth (%u)\n", bit_depth);
143 204
                             break;
144 205
                         }
145 206
                         break;
146 207
                     case 8:
147 208
                         break;
148 209
                     case 16:
149
-                        if (ityp == 3) { /* palette */
150
-                            cli_dbgmsg("PNG: invalid sample depth (%d)\n", sampledepth);
210
+                        if (color_type == 3) { /* palette */
211
+                            cli_dbgmsg("PNG: invalid sample depth (%u)\n", bit_depth);
151 212
                             break;
152 213
                         }
153 214
                         break;
154 215
                     default:
155
-                        cli_dbgmsg("PNG: invalid sample depth (%d)\n", sampledepth);
216
+                        cli_dbgmsg("PNG: invalid sample depth (%u)\n", bit_depth);
156 217
                         break;
157 218
                 }
158
-                switch (ityp) {
219
+                switch (color_type) {
159 220
                     case 2:
160
-                        bitdepth = sampledepth * 3; /* RGB */
221
+                        sample_depth = bit_depth * 3; /* RGB */
161 222
                         break;
162 223
                     case 4:
163
-                        bitdepth = sampledepth * 2; /* gray+alpha */
224
+                        sample_depth = bit_depth * 2; /* gray+alpha */
164 225
                         break;
165 226
                     case 6:
166
-                        bitdepth = sampledepth * 4; /* RGBA */
227
+                        sample_depth = bit_depth * 4; /* RGBA */
167 228
                         break;
168 229
                 }
230
+                cli_dbgmsg("  Width:                 " STDu64 "\n", width);
231
+                cli_dbgmsg("  Height:                " STDu64 "\n", height);
232
+                cli_dbgmsg("  Bit Depth:             " STDu32 " (Sample Depth: " STDu32 ")\n", bit_depth, sample_depth);
233
+                cli_dbgmsg("  Color Type:            " STDu32 "\n", color_type);
234
+                cli_dbgmsg("  Compression Method:    " STDu32 "\n", compression_method);
235
+                cli_dbgmsg("  Filter Method:         " STDu32 "\n", filter_method);
236
+                cli_dbgmsg("  Interlace Method:      " STDu32 "\n", interlace_method);
237
+            }
238
+        } else if (strcmp(chunk_type, "PLTE") == 0) {
239
+            /*------*
240
+             | PLTE |
241
+             *------*/
242
+            if (have_PLTE) {
243
+                cli_dbgmsg("PNG: More than one PTLE chunk found in a PNG file, which is not valid\n", color_type);
169 244
             }
170 245
 
171
-            /* GRR 20000304:  data dump not yet compatible with interlaced images: */
172
-            /*================================================*
173
-            * PNG chunks (with the exception of IHDR, above) *
174
-            *================================================*/
246
+            if (!(chunk_data_length > sizeof(png_palette_entry) * 256 || chunk_data_length % 3 != 0)) {
247
+                num_palette_entries = chunk_data_length / 3;
248
+            }
249
+            if (color_type == 1) /* for MNG and tRNS */ {
250
+                color_type = 3;
251
+            }
175 252
 
176
-        }
177
-        /*------*
178
-         | PLTE |
179
-         *------*/
180
-        else if (strcmp(chunkid, "PLTE") == 0) {
181
-            if (!(sz > 768 || sz % 3 != 0)) {
182
-                nplte = sz / 3;
253
+            if (color_type == 0 || color_type == 4) {
254
+                cli_dbgmsg("PNG: PTLE chunk found in a PNG file with color type set to (%u), which is not valid\n", color_type);
183 255
             }
184
-            if (ityp == 1) /* for MNG and tRNS */
185
-                ityp = 3;
186
-            have_PLTE = 1;
256
+            have_PLTE = true;
257
+
258
+            cli_dbgmsg("  # palette entries: " STDu64 "\n", num_palette_entries);
259
+        } else if (interlace_method == 0 && strcmp(chunk_type, "IDAT") == 0) {
260
+            /*------*
261
+             | IDAT |
262
+             *------*/
263
+
264
+            /*
265
+             * Note from pngcheck:
266
+             *   GRR 20000304:  data dump not yet compatible with interlaced images.
267
+             */
268
+
269
+            if (idat_state == PNG_IDAT_NOT_FOUND_YET) {
270
+                unsigned zlib_windowbits = 15;
271
+
272
+                /* Dump the zlib header from the first two bytes. */
273
+                if (zhead < 0x10000 && chunk_data_length > 0) {
274
+                    zhead = (zhead << 8) + ptr[0];
275
+                    if (chunk_data_length > 1 && zhead < 0x10000)
276
+                        zhead = (zhead << 8) + ptr[1];
277
+                    if (zhead >= 0x10000) {
278
+                        unsigned int CINFO = (zhead & 0xf000) >> 12;
279
+                        zlib_windowbits    = CINFO + 8;
280
+                    }
281
+                }
187 282
 
188
-        }
189
-        /*------*
190
-         | IDAT |
191
-         *------*/
192
-        else if (lace == 0 && strcmp(chunkid, "IDAT") == 0) {
193
-            unsigned zlib_windowbits = 15;
194
-
195
-            /* Dump the zlib header from the first two bytes. */
196
-            if (zhead < 0x10000 && sz > 0) {
197
-                zhead = (zhead << 8) + buffer[0];
198
-                if (sz > 1 && zhead < 0x10000)
199
-                    zhead = (zhead << 8) + buffer[1];
200
-                if (zhead >= 0x10000) {
201
-                    unsigned int CINFO = (zhead & 0xf000) >> 12;
202
-                    zlib_windowbits    = CINFO + 8;
283
+                decompressed_data_buffer_size = BUFFER_SIZE;
284
+                decompressed_data             = (uint8_t *)malloc(decompressed_data_buffer_size);
285
+                if (NULL == decompressed_data) {
286
+                    cli_errmsg("Failed to allocation memory for decompressed PNG data.\n");
287
+                    goto done;
203 288
                 }
204
-            }
205 289
 
206
-            outbuf         = (uint32_t *)malloc(BUFFER_SIZE);
207
-            offadjust      = offset + sz - 8;
208
-            left_comp_read = MIN(map->len - offset + sz - 8, sz);
209
-
210
-            zstrm.next_in  = (uint8_t *)buffer;
211
-            zstrm.avail_in = MIN(toread, left_comp_read);
212
-            left_comp_read -= zstrm.avail_in;
213
-
214
-            /* initialize zlib and bit/byte/line variables if not already done */
215
-            zstrm.zalloc = (alloc_func)Z_NULL;
216
-            zstrm.zfree  = (free_func)Z_NULL;
217
-            zstrm.opaque = (voidpf)Z_NULL;
218
-            if ((err = inflateInit2(&zstrm, zlib_windowbits)) != Z_OK) {
219
-                cli_dbgmsg("PNG: zlib: can't initialize (error = %d)\n", err);
220
-                if (outbuf) {
221
-                    free(outbuf);
222
-                    outbuf = NULL;
290
+                /* initialize zlib and bit/byte/line variables if not already done */
291
+                zstrm.zalloc = (alloc_func)Z_NULL;
292
+                zstrm.zfree  = (free_func)Z_NULL;
293
+                zstrm.opaque = (voidpf)Z_NULL;
294
+                if ((err = inflateInit2(&zstrm, zlib_windowbits)) != Z_OK) {
295
+                    cli_dbgmsg("PNG: zlib: can't initialize (error = %d)\n", err);
296
+
297
+                    idat_state = PNG_IDAT_DECOMPRESSION_FAILED;
298
+                } else {
299
+                    uint64_t cur_width, cur_linebytes;
300
+                    int64_t cur_xoff  = 0;
301
+                    int64_t cur_xskip = interlace_method ? 8 : 1;
302
+                    cur_width         = (width - cur_xoff + cur_xskip - 1) / cur_xskip; /* round up */
303
+                    cur_linebytes     = ((cur_width * sample_depth + 7) >> 3) + 1;      /* round, fltr */
304
+                    image_size        = cur_linebytes * height;
305
+                    cli_dbgmsg("  Image Size:            " STDu64 "\n", image_size);
306
+
307
+                    idat_state = PNG_IDAT_DECOMPRESSION_IN_PROGRESS;
223 308
                 }
224
-            } else {
225
-                cur_xoff      = 0;
226
-                cur_xskip     = lace ? 8 : 1;
227
-                cur_width     = (w - cur_xoff + cur_xskip - 1) / cur_xskip; /* round up */
228
-                cur_linebytes = ((cur_width * bitdepth + 7) >> 3) + 1;      /* round, fltr */
229
-                cur_imagesize = cur_linebytes * h;
309
+            }
310
+
311
+            if (idat_state == PNG_IDAT_DECOMPRESSION_IN_PROGRESS) {
312
+                zstrm.next_in  = ptr;
313
+                zstrm.avail_in = chunk_data_length;
314
+
315
+                zstrm.next_out  = decompressed_data + decompressed_data_len;
316
+                zstrm.avail_out = decompressed_data_buffer_size - decompressed_data_len;
230 317
 
231 318
                 while (err != Z_STREAM_END) {
232 319
                     if (zstrm.avail_in == 0) {
233
-                        // The zlib stream is over. Quit the while loop
234
-                        if (left_comp_read == 0)
235
-                            break;
320
+                        // Ran out of data before zstream ended... Additional IDAT chunks expected.
321
+                        idat_state = PNG_IDAT_DECOMPRESSION_IN_PROGRESS;
322
+                        break;
323
+                    }
236 324
 
237
-                        toread       = MIN(sizeof(buffer), left_comp_read);
238
-                        toread_check = fmap_readn(map, buffer, offset, toread);
239
-                        if ((size_t)-1 == toread_check) {
240
-                            cli_dbgmsg("PNG: Failed to read from map.\n");
241
-                            if (outbuf) {
242
-                                free(outbuf);
243
-                                outbuf = NULL;
244
-                            }
245
-                            return CL_EPARSE;
246
-                        }
247
-                        if (toread > toread_check) {
248
-                            cli_dbgmsg("PNG: EOF while reading data\n");
249
-                            if (outbuf) {
250
-                                free(outbuf);
251
-                                outbuf = NULL;
252
-                            }
253
-                            return CL_EPARSE;
325
+                    /* extend output capacity if needed,*/
326
+                    if (zstrm.avail_out == 0) {
327
+                        uint8_t *decompressed_data_tmp = NULL;
328
+                        if (!(decompressed_data_tmp = cli_realloc(decompressed_data, decompressed_data_buffer_size + BUFFER_SIZE))) {
329
+                            cli_dbgmsg("PNG: Failed to allocate memory while decompressing image data.\n");
330
+                            status = CL_EMEM;
331
+                            goto done;
254 332
                         }
255
-                        toread = toread_check;
256
-                        offset += toread;
257
-                        zstrm.next_in  = (uint8_t *)buffer;
258
-                        zstrm.avail_in = toread;
259
-                        left_comp_read -= toread;
333
+                        decompressed_data = decompressed_data_tmp;
334
+                        decompressed_data_buffer_size += BUFFER_SIZE;
335
+
336
+                        zstrm.next_out  = decompressed_data + decompressed_data_len;
337
+                        zstrm.avail_out = decompressed_data_buffer_size - decompressed_data_len;
260 338
                     }
261 339
 
262
-                    zstrm.next_out  = (uint8_t *)outbuf;
263
-                    zstrm.avail_out = BUFFER_SIZE;
264
-                    err             = inflate(&zstrm, Z_NO_FLUSH);
265
-                    uncomp_data += (BUFFER_SIZE - zstrm.avail_out);
340
+                    /* inflate! */
341
+                    err = inflate(&zstrm, Z_NO_FLUSH);
342
+                    decompressed_data_len += (decompressed_data_buffer_size - decompressed_data_len - zstrm.avail_out);
266 343
                     if (err != Z_OK && err != Z_STREAM_END) {
267
-                        cli_dbgmsg("PNG: zlib: inflate error\n");
344
+                        cli_dbgmsg("PNG: zlib: inflate error: %d, Image decompression failed!\n", err);
345
+                        inflateEnd(&zstrm);
346
+                        idat_state = PNG_IDAT_DECOMPRESSION_FAILED;
268 347
                         break;
269 348
                     }
270 349
                 }
271
-                inflateEnd(&zstrm);
272
-                if (outbuf) {
273
-                    free(outbuf);
274
-                    outbuf = NULL;
275
-                }
276 350
 
277
-                if (uncomp_data > cur_imagesize && err == Z_STREAM_END) {
278
-                    cli_append_virus(ctx, "Heuristics.PNG.CVE-2010-1205");
279
-                    return CL_VIRUS;
351
+                if (err == Z_STREAM_END) {
352
+                    cli_dbgmsg("  TOTAL decompressed:    %zu\n", decompressed_data_len);
353
+                    inflateEnd(&zstrm);
354
+                    idat_state = PNG_IDAT_DECOMPRESSION_COMPLETE;
355
+
356
+                    if (decompressed_data_len > image_size) {
357
+                        status = cli_append_virus(ctx, "Heuristics.PNG.CVE-2010-1205");
358
+                        goto done;
359
+                    }
360
+                } else {
361
+                    cli_dbgmsg("  Decompressed so far:   %zu  (Additional IDAT chunks expected)\n", decompressed_data_len);
362
+                }
363
+            }
364
+        } else if (strcmp(chunk_type, "IEND") == 0) {
365
+            /*------*
366
+             | IEND |
367
+             *------*/
368
+
369
+            have_IEND = true;
370
+        } else if (strcmp(chunk_type, "pHYs") == 0) {
371
+            /*------*
372
+             | pHYs |
373
+             *------*/
374
+
375
+            if (chunk_data_length != 9) {
376
+                // Could it be CVE-2007-2365?
377
+                cli_dbgmsg("PNG: invalid pHYs length\n");
378
+            }
379
+        } else if (strcmp(chunk_type, "tRNS") == 0) {
380
+            /*------*
381
+             | tRNS |
382
+             *------*/
383
+
384
+            if (color_type == 3) {
385
+                if ((chunk_data_length > 256 || chunk_data_length > num_palette_entries) && !have_PLTE) {
386
+                    status = cli_append_virus(ctx, "Heuristics.PNG.CVE-2004-0597");
387
+                    goto done;
280 388
                 }
281 389
             }
282
-
283 390
         }
284
-        /*------*
285
-         | IEND |
286
-         *------*/
287
-        else if (strcmp(chunkid, "IEND") == 0) {
288 391
 
289
-            have_IEND = 1;
392
+        if (fmap_readn(map, &chunk_crc, offset, PNG_CHUNK_CRC_SIZE) != PNG_CHUNK_CRC_SIZE) {
393
+            cli_dbgmsg("PNG: EOF while reading chunk crc\n");
394
+            if (SCAN_HEURISTIC_BROKEN_MEDIA) {
395
+                cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.PNG.EOFReadingChunkCRC");
396
+                status = CL_EPARSE;
397
+            }
398
+            goto scan_overlay;
399
+        }
400
+        chunk_crc = be32_to_host(chunk_crc);
401
+        cli_dbgmsg("  Chunk CRC:             0x" STDx32 "\n", chunk_crc);
402
+        offset += PNG_CHUNK_CRC_SIZE;
403
+
404
+        if (have_IEND) {
405
+            /*
406
+             * That's all, folks!
407
+             */
290 408
             break;
291
-
292 409
         }
293
-        /*------*
294
-         | pHYs |
295
-         *------*/
296
-        else if (strcmp(chunkid, "pHYs") == 0) {
410
+    }
297 411
 
298
-            if (sz != 9) {
299
-                // Could it be CVE-2007-2365?
300
-                cli_dbgmsg("PNG: invalid pHYS length\n");
301
-            }
302
-        }
303
-        /*------*
304
-         | tRNS |
305
-         *------*/
306
-        else if (strcmp(chunkid, "tRNS") == 0) {
307
-
308
-            if (ityp == 3) {
309
-                if ((sz > 256 || sz > nplte) && !have_PLTE) {
310
-                    cli_append_virus(ctx, "Heuristics.PNG.CVE-2004-0597");
311
-                    return CL_VIRUS;
312
-                }
412
+    if (!have_IEND) {
413
+        cli_dbgmsg("PNG: EOF before IEND chunk!\n");
414
+    }
313 415
 
314
-                offset += (sz - toread) + 4;
315
-            }
416
+    if (idat_state == PNG_IDAT_DECOMPRESSION_IN_PROGRESS) {
417
+        cli_dbgmsg("PNG: EOF before Image data decompression completed, truncated or malformed file?\n");
418
+    }
316 419
 
317
-            // Is there an overlay?
318
-            if (have_IEND && (map->len - (offset + 4) > 0))
319
-                return cli_magic_scan_nested_fmap_type(map, offset + 4, map->len - (offset + 4), ctx, CL_TYPE_ANY, NULL);
420
+scan_overlay:
421
+    if (status == CL_EPARSE) {
422
+        /* We added with cli_append_possibly_unwanted so it will alert at the end if nothing else matches. */
423
+        status = CL_CLEAN;
424
+    }
320 425
 
321
-            return CL_SUCCESS;
322
-        }
426
+    /* Check if there's an overlay, and scan it if one exists. */
427
+    if (map->len - offset > 0) {
428
+        cli_dbgmsg("PNG: Found " STDu64 " additional data after end of PNG! Scanning as a nested file.\n", map->len - offset);
429
+        status = cli_magic_scan_nested_fmap_type(map, offset, map->len - offset, ctx, CL_TYPE_ANY, NULL);
430
+        goto done;
431
+    }
432
+
433
+    status = CL_CLEAN;
434
+
435
+done:
436
+    if (NULL != decompressed_data) {
437
+        free(decompressed_data);
323 438
     }
324
-    return CL_SUCCESS;
439
+    if (idat_state == PNG_IDAT_DECOMPRESSION_IN_PROGRESS) {
440
+        inflateEnd(&zstrm);
441
+    }
442
+
443
+    return status;
325 444
 }
... ...
@@ -4170,31 +4170,32 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type)
4170 4170
             break;
4171 4171
 
4172 4172
         case CL_TYPE_GRAPHICS:
4173
-            if (SCAN_HEURISTICS && (DCONF_OTHER & OTHER_CONF_JPEG))
4174
-                ret = cli_scanjpeg(ctx);
4175
-
4176
-            if (ctx->img_validate && SCAN_HEURISTICS && ret != CL_VIRUS)
4177
-                ret = cli_parsejpeg(ctx);
4178
-
4179
-            if (ctx->img_validate && SCAN_HEURISTICS && ret != CL_VIRUS && ret != CL_EPARSE)
4180
-                ret = cli_parsepng(ctx);
4181
-
4182
-            if (ctx->img_validate && SCAN_HEURISTICS && ret != CL_VIRUS && ret != CL_EPARSE)
4183
-                ret = cli_parsegif(ctx);
4184
-
4185
-            if (ctx->img_validate && SCAN_HEURISTICS && ret != CL_VIRUS && ret != CL_EPARSE)
4186
-                ret = cli_parsetiff(ctx);
4187
-
4173
+            /*
4174
+             * This case is for unhandled graphics types such as BMP.
4175
+             */
4188 4176
             break;
4189 4177
 
4190 4178
         case CL_TYPE_GIF:
4191
-            if (SCAN_HEURISTICS && (DCONF_OTHER & OTHER_CONF_GIF))
4179
+            if (SCAN_HEURISTICS && SCAN_HEURISTIC_BROKEN_MEDIA && (DCONF_OTHER & OTHER_CONF_GIF))
4192 4180
                 ret = cli_parsegif(ctx);
4193 4181
             break;
4194 4182
 
4195 4183
         case CL_TYPE_PNG:
4196 4184
             if (SCAN_HEURISTICS && (DCONF_OTHER & OTHER_CONF_PNG))
4197
-                ret = cli_parsepng(ctx);
4185
+                ret = cli_parsepng(ctx); /* PNG parser detects a couple CVE's as well as Broken.Media */
4186
+            break;
4187
+
4188
+        case CL_TYPE_JPEG:
4189
+            if (SCAN_HEURISTICS && (DCONF_OTHER & OTHER_CONF_JPEG))
4190
+                ret = cli_scanjpeg(ctx); /* This one has some Exploit detection. */
4191
+
4192
+            if (SCAN_HEURISTICS && SCAN_HEURISTIC_BROKEN_MEDIA && (DCONF_OTHER & OTHER_CONF_JPEG) && ret != CL_VIRUS)
4193
+                ret = cli_parsejpeg(ctx);
4194
+            break;
4195
+
4196
+        case CL_TYPE_TIFF:
4197
+            if (SCAN_HEURISTICS && SCAN_HEURISTIC_BROKEN_MEDIA && (DCONF_OTHER & OTHER_CONF_TIFF) && ret != CL_VIRUS)
4198
+                ret = cli_parsetiff(ctx);
4198 4199
             break;
4199 4200
 
4200 4201
         case CL_TYPE_PDF: /* FIXMELIMITS: pdf should be an archive! */
... ...
@@ -37,7 +37,9 @@ struct tiff_ifd {
37 37
 
38 38
 cl_error_t cli_parsetiff(cli_ctx *ctx)
39 39
 {
40
-    fmap_t *map = *ctx->fmap;
40
+    cl_error_t status = CL_ERROR;
41
+
42
+    fmap_t *map = NULL;
41 43
     unsigned char magic[4];
42 44
     int big_endian;
43 45
     uint32_t offset = 0, ifd_count = 0;
... ...
@@ -47,37 +49,58 @@ cl_error_t cli_parsetiff(cli_ctx *ctx)
47 47
 
48 48
     cli_dbgmsg("in cli_parsetiff()\n");
49 49
 
50
+    if (NULL == ctx) {
51
+        cli_dbgmsg("TIFF: passed context was NULL\n");
52
+        status = CL_EARG;
53
+        goto done;
54
+    }
55
+    map = *ctx->fmap;
56
+
50 57
     /* check the magic */
51
-    if (fmap_readn(map, magic, offset, 4) != 4)
52
-        return CL_SUCCESS;
58
+    if (fmap_readn(map, magic, offset, 4) != 4) {
59
+        status = CL_CLEAN;
60
+        goto done;
61
+    }
53 62
     offset += 4;
54 63
 
55 64
     if (!memcmp(magic, "\x4d\x4d\x00\x2a", 4))
56 65
         big_endian = 1;
57 66
     else if (!memcmp(magic, "\x49\x49\x2a\x00", 4))
58 67
         big_endian = 0;
59
-    else
60
-        return CL_SUCCESS; /* Not a TIFF file */
68
+    else {
69
+        status = CL_CLEAN; /* Not a TIFF file */
70
+        goto done;
71
+    }
61 72
 
62 73
     cli_dbgmsg("cli_parsetiff: %s-endian tiff file\n", big_endian ? "big" : "little");
63 74
 
64 75
     /* acquire offset of first IFD */
65
-    if (fmap_readn(map, &offset, offset, 4) != 4)
66
-        return CL_EPARSE;
76
+    if (fmap_readn(map, &offset, offset, 4) != 4) {
77
+        cli_dbgmsg("cli_parsetiff: Failed to acquire offset of first IFD, file appears to be truncated.\n");
78
+        cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.TIFF.EOFReadingFirstIFDOffset");
79
+        status = CL_EPARSE;
80
+        goto done;
81
+    }
67 82
     offset = tiff32_to_host(big_endian, offset);
68 83
 
69 84
     cli_dbgmsg("cli_parsetiff: first IFD located @ offset %u\n", offset);
70 85
 
71 86
     if (!offset) {
72
-        cli_errmsg("cli_parsetiff: invalid offset for first IFD\n");
73
-        return CL_EPARSE;
87
+        cli_errmsg("cli_parsetiff: Invalid offset for first IFD\n");
88
+        cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.TIFF.InvalidIFDOffset");
89
+        status = CL_EPARSE;
90
+        goto done;
74 91
     }
75 92
 
76 93
     /* each IFD represents a subfile, though only the first one normally matters */
77 94
     do {
78 95
         /* acquire number of directory entries in current IFD */
79
-        if (fmap_readn(map, &num_entries, offset, 2) != 2)
80
-            return CL_EPARSE;
96
+        if (fmap_readn(map, &num_entries, offset, 2) != 2) {
97
+            cli_dbgmsg("cli_parsetiff: Failed to acquire number of directory entries in current IFD, file appears to be truncated.\n");
98
+            cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.TIFF.EOFReadingNumIFDDirectoryEntries");
99
+            status = CL_EPARSE;
100
+            goto done;
101
+        }
81 102
         offset += 2;
82 103
         num_entries = tiff16_to_host(big_endian, num_entries);
83 104
 
... ...
@@ -85,8 +108,12 @@ cl_error_t cli_parsetiff(cli_ctx *ctx)
85 85
 
86 86
         /* transverse IFD entries */
87 87
         for (i = 0; i < num_entries; i++) {
88
-            if (fmap_readn(map, &entry, offset, sizeof(entry)) != sizeof(entry))
89
-                return CL_EPARSE;
88
+            if (fmap_readn(map, &entry, offset, sizeof(entry)) != sizeof(entry)) {
89
+                cli_dbgmsg("cli_parsetiff: Failed to read next IFD entry, file appears to be truncated.\n");
90
+                cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.TIFF.EOFReadingIFDEntry");
91
+                status = CL_EPARSE;
92
+                goto done;
93
+            }
90 94
             offset += sizeof(entry);
91 95
 
92 96
             entry.tag    = tiff16_to_host(big_endian, entry.tag);
... ...
@@ -146,7 +173,8 @@ cl_error_t cli_parsetiff(cli_ctx *ctx)
146 146
                 if (entry.value + value_size > map->len) {
147 147
                     cli_warnmsg("cli_parsetiff: TFD entry field %u exceeds bounds of TIFF file [%llu > %llu]\n",
148 148
                                 i, (long long unsigned)(entry.value + value_size), (long long unsigned)map->len);
149
-                    return cli_append_virus(ctx, "Heuristics.TIFF.OutOfBoundsAccess");
149
+                    status = cli_append_virus(ctx, "Heuristics.Broken.Media.TIFF.OutOfBoundsAccess");
150
+                    goto done;
150 151
                 }
151 152
             }
152 153
         }
... ...
@@ -154,12 +182,24 @@ cl_error_t cli_parsetiff(cli_ctx *ctx)
154 154
         ifd_count++;
155 155
 
156 156
         /* acquire next IFD location, gets 0 if last IFD */
157
-        if (fmap_readn(map, &offset, offset, sizeof(offset)) != sizeof(offset))
158
-            return CL_EPARSE;
157
+        if (fmap_readn(map, &offset, offset, sizeof(offset)) != sizeof(offset)) {
158
+            cli_dbgmsg("cli_parsetiff: Failed to aquire next IFD location, file appears to be truncated.\n");
159
+            cli_append_possibly_unwanted(ctx, "Heuristics.Broken.Media.TIFF.EOFReadingChunkCRC");
160
+            status = CL_EPARSE;
161
+            goto done;
162
+        }
159 163
         offset = tiff32_to_host(big_endian, offset);
160 164
     } while (offset);
161 165
 
162 166
     cli_dbgmsg("cli_parsetiff: examined %u IFD(s)\n", ifd_count);
163 167
 
164
-    return CL_SUCCESS;
168
+    status = CL_CLEAN;
169
+
170
+done:
171
+    if (status == CL_EPARSE) {
172
+        /* We added with cli_append_possibly_unwanted so it will alert at the end if nothing else matches. */
173
+        status = CL_CLEAN;
174
+    }
175
+
176
+    return status;
165 177
 }
... ...
@@ -54,6 +54,8 @@
54 54
 #include "libgen.h"
55 55
 #endif
56 56
 
57
+/* clang-format off */
58
+
57 59
 #ifdef _WIN32
58 60
 #define CLAMKEY "Software\\ClamAV"
59 61
 #endif
... ...
@@ -61,12 +63,12 @@
61 61
 #define MAXCMDOPTS 150
62 62
 
63 63
 #define MATCH_NUMBER "^[0-9]+$"
64
-#define MATCH_SIZE "^[0-9]+[KM]?$"
65
-#define MATCH_BOOL "^(yes|true|1|no|false|0)$"
64
+#define MATCH_SIZE   "^[0-9]+[KM]?$"
65
+#define MATCH_BOOL   "^(yes|true|1|no|false|0)$"
66 66
 
67 67
 #define FLAG_MULTIPLE 1 /* option can be used multiple times */
68 68
 #define FLAG_REQUIRED 2 /* arg is required, even if there's a default value */
69
-#define FLAG_HIDDEN 4   /* don't print in clamconf --generate-config */
69
+#define FLAG_HIDDEN   4 /* don't print in clamconf --generate-config */
70 70
 #define FLAG_REG_CASE 8 /* case-sensitive regex matching */
71 71
 
72 72
 #ifdef _WIN32
... ...
@@ -100,6 +102,8 @@ char _CONFDIR_MILTER[MAX_PATH]    = BACKUP_CONFDIR "\\clamav-milter.conf";
100 100
 
101 101
 #endif
102 102
 
103
+/* clang-format on */
104
+
103 105
 const struct clam_option __clam_options[] = {
104 106
     /* name, longopt, sopt, argtype, regex, num, str, flags, owner, description, suggested */
105 107
 
... ...
@@ -382,7 +386,9 @@ const struct clam_option __clam_options[] = {
382 382
 
383 383
     {"ScanOLE2", "scan-ole2", 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 1, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "This option enables scanning of OLE2 files, such as Microsoft Office\ndocuments and .msi files.\nIf you turn off this option, the original files will still be scanned, but\nwithout additional processing.", "yes"},
384 384
 
385
-    {"AlertBrokenExecutables", "alert-broken", 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 0, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "With this option enabled clamav will try to detect broken executables\n(both PE and ELF) and alert on them with the Broken.Executable heuristic signature.", "yes"},
385
+    {"AlertBrokenExecutables", "alert-broken", 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 0, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "With this option enabled clamav will try to detect broken executables\n(PE, ELF, & Mach-O) and alert on them with a Broken.Executable heuristic signature.", "yes"},
386
+
387
+    {"AlertBrokenMedia", "alert-broken-media", 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 0, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "With this option enabled clamav will try to detect broken media files\n(JPEG, TIFF, PNG, GIF) and alert on them with a Broken.Media heuristic signature.", "yes"},
386 388
 
387 389
     {"AlertEncrypted", "alert-encrypted", 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 0, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "Alert on encrypted archives and documents (encrypted .zip, .7zip, .rar, .pdf).", "no"},
388 390
 
... ...
@@ -274,6 +274,11 @@ TCPAddr 127.0.0.1
274 274
 # Default: no
275 275
 #AlertBrokenExecutables yes
276 276
 
277
+# With this option clamav will try to detect broken media file (JPEG,
278
+# TIFF, PNG, GIF) and alert on them with a Broken.Media heuristic signature.
279
+# Default: no
280
+#AlertBrokenMedia yes
281
+
277 282
 # Alert on encrypted archives _and_ documents with heuristic signature
278 283
 # (encrypted .zip, .7zip, .rar, .pdf).
279 284
 # Default: no