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.
... | ... |
@@ -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; |
... | ... |
@@ -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 }, |
... | ... |
@@ -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 |