/src/clamav/libclamav/unzip.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (C) 2013-2023 Cisco Systems, Inc. and/or its affiliates. All rights reserved. |
3 | | * Copyright (C) 2007-2013 Sourcefire, Inc. |
4 | | * |
5 | | * Authors: Alberto Wu |
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 | | /* FIXME: get a clue about masked stuff */ |
23 | | |
24 | | #if HAVE_CONFIG_H |
25 | | #include "clamav-config.h" |
26 | | #endif |
27 | | |
28 | | #include <sys/types.h> |
29 | | #include <sys/stat.h> |
30 | | #include <fcntl.h> |
31 | | #ifdef HAVE_UNISTD_H |
32 | | #include <unistd.h> |
33 | | #endif |
34 | | #if HAVE_STRING_H |
35 | | #include <string.h> |
36 | | #endif |
37 | | #include <stdlib.h> |
38 | | #include <stdio.h> |
39 | | |
40 | | #include <zlib.h> |
41 | | #include "inflate64.h" |
42 | | #if HAVE_BZLIB_H |
43 | | #include <bzlib.h> |
44 | | #endif |
45 | | |
46 | | #include "explode.h" |
47 | | #include "others.h" |
48 | | #include "clamav.h" |
49 | | #include "scanners.h" |
50 | | #include "matcher.h" |
51 | | #include "fmap.h" |
52 | | #include "json_api.h" |
53 | | #include "str.h" |
54 | | |
55 | | #define UNZIP_PRIVATE |
56 | | #include "unzip.h" |
57 | | |
58 | | // clang-format off |
59 | 59.9k | #define ZIP_MAGIC_CENTRAL_DIRECTORY_RECORD_BEGIN (0x02014b50) |
60 | 199M | #define ZIP_MAGIC_CENTRAL_DIRECTORY_RECORD_END (0x06054b50) |
61 | 3.21M | #define ZIP_MAGIC_LOCAL_FILE_HEADER (0x04034b50) |
62 | 614 | #define ZIP_MAGIC_FILE_BEGIN_SPLIT_OR_SPANNED (0x08074b50) |
63 | | // clang-format on |
64 | | |
65 | | // Non-malicious zips in enterprise critical JAR-ZIPs have been observed with a 1-byte overlap. |
66 | | // The goal with overlap detection is to alert on non-recursive zip bombs, so this tiny overlap isn't a concern. |
67 | | // We'll allow a 2-byte overlap so we don't alert on such zips. |
68 | 27.0k | #define ZIP_RECORD_OVERLAP_FUDGE_FACTOR 2 |
69 | 2.40k | #define ZIP_MAX_NUM_OVERLAPPING_FILES 5 |
70 | | |
71 | | #define ZIP_CRC32(r, c, b, l) \ |
72 | 0 | do { \ |
73 | 0 | r = crc32(~c, b, l); \ |
74 | 0 | r = ~r; \ |
75 | 0 | } while (0) |
76 | | |
77 | 46.1k | #define ZIP_RECORDS_CHECK_BLOCKSIZE 100 |
78 | | struct zip_record { |
79 | | uint32_t local_header_offset; |
80 | | uint32_t local_header_size; |
81 | | uint32_t compressed_size; |
82 | | uint32_t uncompressed_size; |
83 | | uint16_t method; |
84 | | uint16_t flags; |
85 | | int encrypted; |
86 | | char *original_filename; |
87 | | }; |
88 | | |
89 | | static int wrap_inflateinit2(void *a, int b) |
90 | 149k | { |
91 | 149k | return inflateInit2(a, b); |
92 | 149k | } |
93 | | |
94 | | /** |
95 | | * @brief uncompress file from zip |
96 | | * |
97 | | * @param src pointer to compressed data |
98 | | * @param csize size of compressed data |
99 | | * @param usize expected size of uncompressed data |
100 | | * @param method compression method |
101 | | * @param flags local header flags |
102 | | * @param[in,out] num_files_unzipped current number of files that have been unzipped |
103 | | * @param[in,out] ctx scan context |
104 | | * @param tmpd temp directory path name |
105 | | * @param zcb callback function to invoke after extraction (default: scan) |
106 | | * @return cl_error_t CL_EPARSE = could not apply a password |
107 | | */ |
108 | | static cl_error_t unz( |
109 | | const uint8_t *src, |
110 | | uint32_t csize, |
111 | | uint32_t usize, |
112 | | uint16_t method, |
113 | | uint16_t flags, |
114 | | unsigned int *num_files_unzipped, |
115 | | cli_ctx *ctx, |
116 | | char *tmpd, |
117 | | zip_cb zcb, |
118 | | const char *original_filename, |
119 | | bool decrypted) |
120 | 1.19M | { |
121 | 1.19M | char obuf[BUFSIZ] = {0}; |
122 | 1.19M | char *tempfile = NULL; |
123 | 1.19M | int out_file, ret = CL_CLEAN; |
124 | 1.19M | int res = 1; |
125 | 1.19M | size_t written = 0; |
126 | | |
127 | 1.19M | if (tmpd) { |
128 | 0 | if (ctx->engine->keeptmp && (NULL != original_filename)) { |
129 | 0 | if (!(tempfile = cli_gentemp_with_prefix(tmpd, original_filename))) return CL_EMEM; |
130 | 0 | } else { |
131 | 0 | if (!(tempfile = cli_gentemp(tmpd))) return CL_EMEM; |
132 | 0 | } |
133 | 1.19M | } else { |
134 | 1.19M | if (ctx->engine->keeptmp && (NULL != original_filename)) { |
135 | 0 | if (!(tempfile = cli_gentemp_with_prefix(ctx->sub_tmpdir, original_filename))) return CL_EMEM; |
136 | 1.19M | } else { |
137 | 1.19M | if (!(tempfile = cli_gentemp(ctx->sub_tmpdir))) return CL_EMEM; |
138 | 1.19M | } |
139 | 1.19M | } |
140 | 1.19M | if ((out_file = open(tempfile, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR)) == -1) { |
141 | 0 | cli_warnmsg("cli_unzip: failed to create temporary file %s\n", tempfile); |
142 | 0 | free(tempfile); |
143 | 0 | return CL_ETMPFILE; |
144 | 0 | } |
145 | 1.19M | switch (method) { |
146 | 473k | case ALG_STORED: |
147 | 473k | if (csize < usize) { |
148 | 46.2k | unsigned int fake = *num_files_unzipped + 1; |
149 | 46.2k | cli_dbgmsg("cli_unzip: attempting to inflate stored file with inconsistent size\n"); |
150 | 46.2k | if (CL_CLEAN == (ret = unz(src, csize, usize, ALG_DEFLATE, 0, &fake, ctx, |
151 | 46.2k | tmpd, zcb, original_filename, decrypted))) { |
152 | 46.2k | (*num_files_unzipped)++; |
153 | 46.2k | res = fake - (*num_files_unzipped); |
154 | 46.2k | } else |
155 | 7 | break; |
156 | 46.2k | } |
157 | 473k | if (res == 1) { |
158 | 449k | if (ctx->engine->maxfilesize && csize > ctx->engine->maxfilesize) { |
159 | 0 | cli_dbgmsg("cli_unzip: trimming output size to maxfilesize (%lu)\n", |
160 | 0 | (long unsigned int)ctx->engine->maxfilesize); |
161 | 0 | csize = ctx->engine->maxfilesize; |
162 | 0 | } |
163 | 449k | if (cli_writen(out_file, src, csize) != csize) |
164 | 0 | ret = CL_EWRITE; |
165 | 449k | else |
166 | 449k | res = 0; |
167 | 449k | } |
168 | 473k | break; |
169 | | |
170 | 149k | case ALG_DEFLATE: |
171 | 470k | case ALG_DEFLATE64: { |
172 | 470k | union { |
173 | 470k | z_stream64 strm64; |
174 | 470k | z_stream strm; |
175 | 470k | } strm; |
176 | 470k | typedef int (*unz_init_)(void *, int); |
177 | 470k | typedef int (*unz_unz_)(void *, int); |
178 | 470k | typedef int (*unz_end_)(void *); |
179 | 470k | unz_init_ unz_init; |
180 | 470k | unz_unz_ unz_unz; |
181 | 470k | unz_end_ unz_end; |
182 | 470k | int wbits; |
183 | 470k | void **next_in; |
184 | 470k | void **next_out; |
185 | 470k | unsigned int *avail_in; |
186 | 470k | unsigned int *avail_out; |
187 | | |
188 | 470k | if (method == ALG_DEFLATE64) { |
189 | 320k | unz_init = (unz_init_)inflate64Init2; |
190 | 320k | unz_unz = (unz_unz_)inflate64; |
191 | 320k | unz_end = (unz_end_)inflate64End; |
192 | 320k | next_in = (void *)&strm.strm64.next_in; |
193 | 320k | next_out = (void *)&strm.strm64.next_out; |
194 | 320k | avail_in = &strm.strm64.avail_in; |
195 | 320k | avail_out = &strm.strm64.avail_out; |
196 | 320k | wbits = MAX_WBITS64; |
197 | 320k | } else { |
198 | 149k | unz_init = (unz_init_)wrap_inflateinit2; |
199 | 149k | unz_unz = (unz_unz_)inflate; |
200 | 149k | unz_end = (unz_end_)inflateEnd; |
201 | 149k | next_in = (void *)&strm.strm.next_in; |
202 | 149k | next_out = (void *)&strm.strm.next_out; |
203 | 149k | avail_in = &strm.strm.avail_in; |
204 | 149k | avail_out = &strm.strm.avail_out; |
205 | 149k | wbits = MAX_WBITS; |
206 | 149k | } |
207 | | |
208 | 470k | memset(&strm, 0, sizeof(strm)); |
209 | | |
210 | 470k | *next_in = (void *)src; |
211 | 470k | *next_out = obuf; |
212 | 470k | *avail_in = csize; |
213 | 470k | *avail_out = sizeof(obuf); |
214 | 470k | if (unz_init(&strm, -wbits) != Z_OK) { |
215 | 0 | cli_dbgmsg("cli_unzip: zinit failed\n"); |
216 | 0 | break; |
217 | 0 | } |
218 | 845k | while (1) { |
219 | 1.11M | while ((res = unz_unz(&strm, Z_NO_FLUSH)) == Z_OK) { |
220 | 271k | }; |
221 | 845k | if (*avail_out != sizeof(obuf)) { |
222 | 375k | written += sizeof(obuf) - (*avail_out); |
223 | 375k | if (ctx->engine->maxfilesize && written > ctx->engine->maxfilesize) { |
224 | 0 | cli_dbgmsg("cli_unzip: trimming output size to maxfilesize (%lu)\n", (long unsigned int)ctx->engine->maxfilesize); |
225 | 0 | res = Z_STREAM_END; |
226 | 0 | break; |
227 | 0 | } |
228 | 375k | if (cli_writen(out_file, obuf, sizeof(obuf) - (*avail_out)) != (size_t)(sizeof(obuf) - (*avail_out))) { |
229 | 0 | cli_warnmsg("cli_unzip: falied to write %lu inflated bytes\n", (unsigned long int)sizeof(obuf) - (*avail_out)); |
230 | 0 | ret = CL_EWRITE; |
231 | 0 | res = 100; |
232 | 0 | break; |
233 | 0 | } |
234 | 375k | *next_out = obuf; |
235 | 375k | *avail_out = sizeof(obuf); |
236 | 375k | continue; |
237 | 375k | } |
238 | 470k | break; |
239 | 845k | } |
240 | 470k | unz_end(&strm); |
241 | 470k | if ((res == Z_STREAM_END) | (res == Z_BUF_ERROR)) res = 0; |
242 | 470k | break; |
243 | 470k | } |
244 | | |
245 | 0 | #if HAVE_BZLIB_H |
246 | | #ifdef NOBZ2PREFIX |
247 | | #define BZ2_bzDecompress bzDecompress |
248 | | #define BZ2_bzDecompressEnd bzDecompressEnd |
249 | | #define BZ2_bzDecompressInit bzDecompressInit |
250 | | #endif |
251 | | |
252 | 896 | case ALG_BZIP2: { |
253 | 896 | bz_stream strm; |
254 | 896 | memset(&strm, 0, sizeof(strm)); |
255 | 896 | strm.next_in = (char *)src; |
256 | 896 | strm.next_out = obuf; |
257 | 896 | strm.avail_in = csize; |
258 | 896 | strm.avail_out = sizeof(obuf); |
259 | 896 | if (BZ2_bzDecompressInit(&strm, 0, 0) != BZ_OK) { |
260 | 0 | cli_dbgmsg("cli_unzip: bzinit failed\n"); |
261 | 0 | break; |
262 | 0 | } |
263 | 896 | while ((res = BZ2_bzDecompress(&strm)) == BZ_OK || res == BZ_STREAM_END) { |
264 | 2 | if (strm.avail_out != sizeof(obuf)) { |
265 | 2 | written += sizeof(obuf) - strm.avail_out; |
266 | 2 | if (ctx->engine->maxfilesize && written > ctx->engine->maxfilesize) { |
267 | 0 | cli_dbgmsg("cli_unzip: trimming output size to maxfilesize (%lu)\n", (unsigned long int)ctx->engine->maxfilesize); |
268 | 0 | res = BZ_STREAM_END; |
269 | 0 | break; |
270 | 0 | } |
271 | 2 | if (cli_writen(out_file, obuf, sizeof(obuf) - strm.avail_out) != (size_t)(sizeof(obuf) - strm.avail_out)) { |
272 | 0 | cli_warnmsg("cli_unzip: falied to write %lu bunzipped bytes\n", (long unsigned int)sizeof(obuf) - strm.avail_out); |
273 | 0 | ret = CL_EWRITE; |
274 | 0 | res = 100; |
275 | 0 | break; |
276 | 0 | } |
277 | 2 | strm.next_out = obuf; |
278 | 2 | strm.avail_out = sizeof(obuf); |
279 | 2 | if (res == BZ_OK) continue; /* after returning BZ_STREAM_END once, decompress returns an error */ |
280 | 2 | } |
281 | 2 | break; |
282 | 2 | } |
283 | 896 | BZ2_bzDecompressEnd(&strm); |
284 | 896 | if (res == BZ_STREAM_END) res = 0; |
285 | 896 | break; |
286 | 896 | } |
287 | 0 | #endif /* HAVE_BZLIB_H */ |
288 | | |
289 | 60.7k | case ALG_IMPLODE: { |
290 | 60.7k | struct xplstate strm; |
291 | 60.7k | strm.next_in = (void *)src; |
292 | 60.7k | strm.next_out = (uint8_t *)obuf; |
293 | 60.7k | strm.avail_in = csize; |
294 | 60.7k | strm.avail_out = sizeof(obuf); |
295 | 60.7k | if (explode_init(&strm, flags) != EXPLODE_OK) { |
296 | 0 | cli_dbgmsg("cli_unzip: explode_init() failed\n"); |
297 | 0 | break; |
298 | 0 | } |
299 | 73.4k | while ((res = explode(&strm)) == EXPLODE_OK) { |
300 | 32.8k | if (strm.avail_out != sizeof(obuf)) { |
301 | 12.7k | written += sizeof(obuf) - strm.avail_out; |
302 | 12.7k | if (ctx->engine->maxfilesize && written > ctx->engine->maxfilesize) { |
303 | 0 | cli_dbgmsg("cli_unzip: trimming output size to maxfilesize (%lu)\n", (unsigned long int)ctx->engine->maxfilesize); |
304 | 0 | res = 0; |
305 | 0 | break; |
306 | 0 | } |
307 | 12.7k | if (cli_writen(out_file, obuf, sizeof(obuf) - strm.avail_out) != (size_t)(sizeof(obuf) - strm.avail_out)) { |
308 | 0 | cli_warnmsg("cli_unzip: falied to write %lu exploded bytes\n", (unsigned long int)sizeof(obuf) - strm.avail_out); |
309 | 0 | ret = CL_EWRITE; |
310 | 0 | res = 100; |
311 | 0 | break; |
312 | 0 | } |
313 | 12.7k | strm.next_out = (uint8_t *)obuf; |
314 | 12.7k | strm.avail_out = sizeof(obuf); |
315 | 12.7k | continue; |
316 | 12.7k | } |
317 | 20.1k | break; |
318 | 32.8k | } |
319 | 60.7k | break; |
320 | 60.7k | } |
321 | | |
322 | 3.16k | case ALG_LZMA: |
323 | | /* easy but there's not a single sample in the zoo */ |
324 | | |
325 | | #if !HAVE_BZLIB_H |
326 | | case ALG_BZIP2: |
327 | | #endif |
328 | 6.00k | case ALG_SHRUNK: |
329 | 14.2k | case ALG_REDUCE1: |
330 | 14.3k | case ALG_REDUCE2: |
331 | 19.7k | case ALG_REDUCE3: |
332 | 23.1k | case ALG_REDUCE4: |
333 | 23.5k | case ALG_TOKENZD: |
334 | 28.6k | case ALG_OLDTERSE: |
335 | 30.4k | case ALG_RSVD1: |
336 | 34.3k | case ALG_RSVD2: |
337 | 34.6k | case ALG_RSVD3: |
338 | 36.8k | case ALG_RSVD4: |
339 | 39.2k | case ALG_RSVD5: |
340 | 41.1k | case ALG_NEWTERSE: |
341 | 42.1k | case ALG_LZ77: |
342 | 46.3k | case ALG_WAVPACK: |
343 | 47.4k | case ALG_PPMD: |
344 | 47.4k | cli_dbgmsg("cli_unzip: unsupported method (%d)\n", method); |
345 | 47.4k | break; |
346 | 137k | default: |
347 | 137k | cli_dbgmsg("cli_unzip: unknown method (%d)\n", method); |
348 | 137k | break; |
349 | 1.19M | } |
350 | | |
351 | 1.19M | if (!res) { |
352 | 697k | (*num_files_unzipped)++; |
353 | 697k | cli_dbgmsg("cli_unzip: extracted to %s\n", tempfile); |
354 | 697k | if (lseek(out_file, 0, SEEK_SET) == -1) { |
355 | 0 | cli_dbgmsg("cli_unzip: call to lseek() failed\n"); |
356 | 0 | free(tempfile); |
357 | 0 | close(out_file); |
358 | 0 | return CL_ESEEK; |
359 | 0 | } |
360 | 697k | ret = zcb(out_file, tempfile, ctx, original_filename, decrypted); |
361 | 697k | close(out_file); |
362 | 697k | if (!ctx->engine->keeptmp) |
363 | 697k | if (cli_unlink(tempfile)) ret = CL_EUNLINK; |
364 | 697k | free(tempfile); |
365 | 697k | return ret; |
366 | 697k | } |
367 | | |
368 | 492k | close(out_file); |
369 | 492k | if (!ctx->engine->keeptmp) |
370 | 492k | if (cli_unlink(tempfile)) ret = CL_EUNLINK; |
371 | 492k | free(tempfile); |
372 | 492k | cli_dbgmsg("cli_unzip: extraction failed\n"); |
373 | 492k | return ret; |
374 | 1.19M | } |
375 | | |
376 | | /* zip update keys, taken from zip specification */ |
377 | | static inline void zupdatekey(uint32_t key[3], unsigned char input) |
378 | 0 | { |
379 | 0 | unsigned char tmp[1]; |
380 | |
|
381 | 0 | tmp[0] = input; |
382 | 0 | ZIP_CRC32(key[0], key[0], tmp, 1); |
383 | |
|
384 | 0 | key[1] = key[1] + (key[0] & 0xff); |
385 | 0 | key[1] = key[1] * 134775813 + 1; |
386 | |
|
387 | 0 | tmp[0] = key[1] >> 24; |
388 | 0 | ZIP_CRC32(key[2], key[2], tmp, 1); |
389 | 0 | } |
390 | | |
391 | | /* zip init keys */ |
392 | | static inline void zinitkey(uint32_t key[3], struct cli_pwdb *password) |
393 | 0 | { |
394 | 0 | int i; |
395 | | |
396 | | /* initialize keys, these are specified but the zip specification */ |
397 | 0 | key[0] = 305419896L; |
398 | 0 | key[1] = 591751049L; |
399 | 0 | key[2] = 878082192L; |
400 | | |
401 | | /* update keys with password */ |
402 | 0 | for (i = 0; i < password->length; i++) |
403 | 0 | zupdatekey(key, password->passwd[i]); |
404 | 0 | } |
405 | | |
406 | | /* zip decrypt byte */ |
407 | | static inline unsigned char zdecryptbyte(uint32_t key[3]) |
408 | 0 | { |
409 | 0 | unsigned short temp; |
410 | 0 | temp = key[2] | 2; |
411 | 0 | return ((temp * (temp ^ 1)) >> 8); |
412 | 0 | } |
413 | | |
414 | | /** |
415 | | * @brief zip decrypt. |
416 | | * |
417 | | * TODO - search for strong encryption header (0x0017) and handle them |
418 | | * |
419 | | * @param src |
420 | | * @param csize size of compressed data; includes the decryption header |
421 | | * @param usize expected size of uncompressed data |
422 | | * @param local_header |
423 | | * @param[in,out] num_files_unzipped current number of files that have been unzipped |
424 | | * @param[in,out] ctx scan context |
425 | | * @param tmpd temp directory path name |
426 | | * @param zcb callback function to invoke after extraction (default: scan) |
427 | | * @return cl_error_t CL_EPARSE = could not apply a password |
428 | | */ |
429 | | static inline cl_error_t zdecrypt( |
430 | | const uint8_t *src, |
431 | | uint32_t csize, |
432 | | uint32_t usize, |
433 | | const uint8_t *local_header, |
434 | | unsigned int *num_files_unzipped, |
435 | | cli_ctx *ctx, |
436 | | char *tmpd, |
437 | | zip_cb zcb, |
438 | | const char *original_filename) |
439 | 9.00k | { |
440 | 9.00k | cl_error_t ret; |
441 | 9.00k | int v = 0; |
442 | 9.00k | uint32_t i; |
443 | 9.00k | uint32_t key[3]; |
444 | 9.00k | uint8_t encryption_header[12]; /* encryption header buffer */ |
445 | 9.00k | struct cli_pwdb *password, *pass_any, *pass_zip; |
446 | | |
447 | 9.00k | if (!ctx || !ctx->engine) |
448 | 0 | return CL_ENULLARG; |
449 | | |
450 | | /* dconf */ |
451 | 9.00k | if (ctx->dconf && !(ctx->dconf->archive & ARCH_CONF_PASSWD)) { |
452 | 0 | cli_dbgmsg("cli_unzip: decrypt - skipping encrypted file\n"); |
453 | 0 | return CL_SUCCESS; |
454 | 0 | } |
455 | | |
456 | 9.00k | pass_any = ctx->engine->pwdbs[CLI_PWDB_ANY]; |
457 | 9.00k | pass_zip = ctx->engine->pwdbs[CLI_PWDB_ZIP]; |
458 | | |
459 | 9.00k | while (pass_any || pass_zip) { |
460 | 0 | password = pass_zip ? pass_zip : pass_any; |
461 | |
|
462 | 0 | zinitkey(key, password); |
463 | | |
464 | | /* decrypting the encryption header */ |
465 | 0 | memcpy(encryption_header, src, SIZEOF_ENCRYPTION_HEADER); |
466 | |
|
467 | 0 | for (i = 0; i < SIZEOF_ENCRYPTION_HEADER; i++) { |
468 | 0 | encryption_header[i] ^= zdecryptbyte(key); |
469 | 0 | zupdatekey(key, encryption_header[i]); |
470 | 0 | } |
471 | | |
472 | | /* verify that the password is correct */ |
473 | 0 | if (LOCAL_HEADER_version > 20) { /* higher than 2.0 */ |
474 | 0 | uint16_t a = encryption_header[SIZEOF_ENCRYPTION_HEADER - 1]; |
475 | |
|
476 | 0 | if (LOCAL_HEADER_flags & F_USEDD) { |
477 | 0 | cli_dbgmsg("cli_unzip: decrypt - (v%u) >> 0x%02x 0x%x (moddate)\n", LOCAL_HEADER_version, a, LOCAL_HEADER_mtime); |
478 | 0 | if (a == ((LOCAL_HEADER_mtime >> 8) & 0xff)) |
479 | 0 | v = 1; |
480 | 0 | } else { |
481 | 0 | cli_dbgmsg("cli_unzip: decrypt - (v%u) >> 0x%02x 0x%x (crc32)\n", LOCAL_HEADER_version, a, LOCAL_HEADER_crc32); |
482 | 0 | if (a == ((LOCAL_HEADER_crc32 >> 24) & 0xff)) |
483 | 0 | v = 1; |
484 | 0 | } |
485 | 0 | } else { |
486 | 0 | uint16_t a = encryption_header[SIZEOF_ENCRYPTION_HEADER - 1], b = encryption_header[SIZEOF_ENCRYPTION_HEADER - 2]; |
487 | |
|
488 | 0 | if (LOCAL_HEADER_flags & F_USEDD) { |
489 | 0 | cli_dbgmsg("cli_unzip: decrypt - (v%u) >> 0x0000%02x%02x 0x%x (moddate)\n", LOCAL_HEADER_version, a, b, LOCAL_HEADER_mtime); |
490 | 0 | if ((uint32_t)(b | (a << 8)) == (LOCAL_HEADER_mtime & 0xffff)) |
491 | 0 | v = 1; |
492 | 0 | } else { |
493 | 0 | cli_dbgmsg("cli_unzip: decrypt - (v%u) >> 0x0000%02x%02x 0x%x (crc32)\n", LOCAL_HEADER_version, encryption_header[SIZEOF_ENCRYPTION_HEADER - 1], encryption_header[SIZEOF_ENCRYPTION_HEADER - 2], LOCAL_HEADER_crc32); |
494 | 0 | if ((uint32_t)(b | (a << 8)) == ((LOCAL_HEADER_crc32 >> 16) & 0xffff)) |
495 | 0 | v = 1; |
496 | 0 | } |
497 | 0 | } |
498 | |
|
499 | 0 | if (v) { |
500 | 0 | char name[1024], obuf[BUFSIZ]; |
501 | 0 | char *tempfile = name; |
502 | 0 | size_t written = 0, total = 0; |
503 | 0 | fmap_t *dcypt_map; |
504 | 0 | const uint8_t *dcypt_zip; |
505 | 0 | int out_file; |
506 | |
|
507 | 0 | cli_dbgmsg("cli_unzip: decrypt - password [%s] matches\n", password->name); |
508 | | |
509 | | /* output decrypted data to tempfile */ |
510 | 0 | if (tmpd) { |
511 | 0 | snprintf(name, sizeof(name), "%s" PATHSEP "zip.decrypt.%03u", tmpd, *num_files_unzipped); |
512 | 0 | name[sizeof(name) - 1] = '\0'; |
513 | 0 | } else { |
514 | 0 | if (!(tempfile = cli_gentemp_with_prefix(ctx->sub_tmpdir, "zip-decrypt"))) return CL_EMEM; |
515 | 0 | } |
516 | 0 | if ((out_file = open(tempfile, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR)) == -1) { |
517 | 0 | cli_warnmsg("cli_unzip: decrypt - failed to create temporary file %s\n", tempfile); |
518 | 0 | if (!tmpd) free(tempfile); |
519 | 0 | return CL_ETMPFILE; |
520 | 0 | } |
521 | | |
522 | 0 | for (i = 12; i < csize; i++) { |
523 | 0 | obuf[written] = src[i] ^ zdecryptbyte(key); |
524 | 0 | zupdatekey(key, obuf[written]); |
525 | |
|
526 | 0 | written++; |
527 | 0 | if (written >= BUFSIZ) { |
528 | 0 | if (cli_writen(out_file, obuf, written) != written) { |
529 | 0 | ret = CL_EWRITE; |
530 | 0 | goto zd_clean; |
531 | 0 | } |
532 | 0 | total += written; |
533 | 0 | written = 0; |
534 | 0 | } |
535 | 0 | } |
536 | 0 | if (written) { |
537 | 0 | if (cli_writen(out_file, obuf, written) != written) { |
538 | 0 | ret = CL_EWRITE; |
539 | 0 | goto zd_clean; |
540 | 0 | } |
541 | 0 | total += written; |
542 | 0 | written = 0; |
543 | 0 | } |
544 | | |
545 | 0 | cli_dbgmsg("cli_unzip: decrypt - decrypted %zu bytes to %s\n", total, tempfile); |
546 | | |
547 | | /* decrypt data to new fmap -> buffer */ |
548 | 0 | if (!(dcypt_map = fmap(out_file, 0, total, NULL))) { |
549 | 0 | cli_warnmsg("cli_unzip: decrypt - failed to create fmap on decrypted file %s\n", tempfile); |
550 | 0 | ret = CL_EMAP; |
551 | 0 | goto zd_clean; |
552 | 0 | } |
553 | | |
554 | 0 | if (!(dcypt_zip = fmap_need_off_once(dcypt_map, 0, total))) { |
555 | 0 | cli_warnmsg("cli_unzip: decrypt - failed to acquire buffer on decrypted file %s\n", tempfile); |
556 | 0 | funmap(dcypt_map); |
557 | 0 | ret = CL_EREAD; |
558 | 0 | goto zd_clean; |
559 | 0 | } |
560 | | |
561 | | /* call unz on decrypted output */ |
562 | 0 | ret = unz(dcypt_zip, csize - SIZEOF_ENCRYPTION_HEADER, usize, LOCAL_HEADER_method, LOCAL_HEADER_flags, |
563 | 0 | num_files_unzipped, ctx, tmpd, zcb, original_filename, true); |
564 | | |
565 | | /* clean-up and return */ |
566 | 0 | funmap(dcypt_map); |
567 | 0 | zd_clean: |
568 | 0 | close(out_file); |
569 | 0 | if (!ctx->engine->keeptmp) |
570 | 0 | if (cli_unlink(tempfile)) { |
571 | 0 | if (!tmpd) free(tempfile); |
572 | 0 | return CL_EUNLINK; |
573 | 0 | } |
574 | 0 | if (!tmpd) free(tempfile); |
575 | 0 | return ret; |
576 | 0 | } |
577 | | |
578 | 0 | if (pass_zip) |
579 | 0 | pass_zip = pass_zip->next; |
580 | 0 | else |
581 | 0 | pass_any = pass_any->next; |
582 | 0 | } |
583 | | |
584 | 9.00k | cli_dbgmsg("cli_unzip: decrypt - skipping encrypted file, no valid passwords\n"); |
585 | 9.00k | return CL_SUCCESS; |
586 | 9.00k | } |
587 | | |
588 | | /** |
589 | | * @brief Parse, extract, and scan a file using the local file header. |
590 | | * |
591 | | * Usage of the `record` parameter will alter behavior so it only collect file record metadata and does not extract or scan any files. |
592 | | * |
593 | | * @param map fmap for the file |
594 | | * @param loff offset of the local file header |
595 | | * @param zsize size of the zip file |
596 | | * @param[in,out] num_files_unzipped current number of files that have been unzipped |
597 | | * @param file_count current number of files that have been discovered |
598 | | * @param central_header offset of central directory header |
599 | | * @param[out] ret The status code |
600 | | * @param[in,out] ctx scan context |
601 | | * @param tmpd temp directory path name |
602 | | * @param detect_encrypted bool: if encrypted files should raise heuristic alert |
603 | | * @param zcb callback function to invoke after extraction (default: scan) |
604 | | * @param record (optional) a pointer to a struct to store file record information. |
605 | | * @return unsigned int returns the size of the file header + file data, so zip file can be indexed without the central directory |
606 | | */ |
607 | | static unsigned int parse_local_file_header( |
608 | | fmap_t *map, |
609 | | uint32_t loff, |
610 | | uint32_t zsize, |
611 | | unsigned int *num_files_unzipped, |
612 | | unsigned int file_count, |
613 | | const uint8_t *central_header, /* pointer to central header. */ |
614 | | cl_error_t *ret, |
615 | | cli_ctx *ctx, |
616 | | char *tmpd, |
617 | | int detect_encrypted, |
618 | | zip_cb zcb, |
619 | | struct zip_record *record) |
620 | 3.21M | { |
621 | 3.21M | const uint8_t *local_header, *zip; |
622 | 3.21M | char name[256]; |
623 | 3.21M | char *original_filename = NULL; |
624 | 3.21M | uint32_t csize, usize; |
625 | 3.21M | unsigned int size_of_fileheader_and_data = 0; |
626 | | |
627 | 3.21M | uint32_t nsize = 0; |
628 | 3.21M | const char *src = NULL; |
629 | | |
630 | 3.21M | if (!(local_header = fmap_need_off(map, loff, SIZEOF_LOCAL_HEADER))) { |
631 | 0 | cli_dbgmsg("cli_unzip: local header - out of file\n"); |
632 | 0 | goto done; |
633 | 0 | } |
634 | 3.21M | if (LOCAL_HEADER_magic != ZIP_MAGIC_LOCAL_FILE_HEADER) { |
635 | 1.11k | if (!central_header) |
636 | 264 | cli_dbgmsg("cli_unzip: local header - wrkcomplete\n"); |
637 | 848 | else |
638 | 848 | cli_dbgmsg("cli_unzip: local header - bad magic\n"); |
639 | 1.11k | fmap_unneed_off(map, loff, SIZEOF_LOCAL_HEADER); |
640 | 1.11k | goto done; |
641 | 1.11k | } |
642 | | |
643 | 3.21M | zip = local_header + SIZEOF_LOCAL_HEADER; |
644 | 3.21M | zsize -= SIZEOF_LOCAL_HEADER; |
645 | | |
646 | 3.21M | memset(name, '\0', 256); |
647 | | |
648 | 3.21M | if (zsize <= LOCAL_HEADER_flen) { |
649 | 980k | cli_dbgmsg("cli_unzip: local header - fname out of file\n"); |
650 | 980k | fmap_unneed_off(map, loff, SIZEOF_LOCAL_HEADER); |
651 | 980k | goto done; |
652 | 980k | } |
653 | | |
654 | 2.23M | nsize = (LOCAL_HEADER_flen >= sizeof(name)) ? sizeof(name) - 1 : LOCAL_HEADER_flen; |
655 | 2.23M | src = fmap_need_ptr_once(map, zip, nsize); |
656 | 2.23M | if (nsize && (NULL != src)) { |
657 | 1.36M | memcpy(name, zip, nsize); |
658 | 1.36M | name[nsize] = '\0'; |
659 | 1.36M | if (CL_SUCCESS != cli_basename(name, nsize, &original_filename)) { |
660 | 187k | original_filename = NULL; |
661 | 187k | } |
662 | 1.36M | } else { |
663 | 872k | name[0] = '\0'; |
664 | 872k | } |
665 | | |
666 | 2.23M | zip += LOCAL_HEADER_flen; |
667 | 2.23M | zsize -= LOCAL_HEADER_flen; |
668 | | |
669 | 2.23M | cli_dbgmsg("cli_unzip: local header - ZMDNAME:%d:%s:%u:%u:%x:%u:%u:%u\n", |
670 | 2.23M | ((LOCAL_HEADER_flags & F_ENCR) != 0), name, LOCAL_HEADER_usize, LOCAL_HEADER_csize, LOCAL_HEADER_crc32, LOCAL_HEADER_method, file_count, ctx->recursion_level); |
671 | | /* ZMDfmt virname:encrypted(0-1):filename(exact|*):usize(exact|*):csize(exact|*):crc32(exact|*):method(exact|*):fileno(exact|*):maxdepth(exact|*) */ |
672 | | |
673 | | /* Scan file header metadata. */ |
674 | 2.23M | if (cli_matchmeta(ctx, name, LOCAL_HEADER_csize, LOCAL_HEADER_usize, (LOCAL_HEADER_flags & F_ENCR) != 0, file_count, LOCAL_HEADER_crc32, NULL) == CL_VIRUS) { |
675 | 0 | *ret = CL_VIRUS; |
676 | 0 | goto done; |
677 | 0 | } |
678 | | |
679 | 2.23M | if (LOCAL_HEADER_flags & F_MSKED) { |
680 | 203k | cli_dbgmsg("cli_unzip: local header - header has got unusable masked data\n"); |
681 | | /* FIXME: need to find/craft a sample */ |
682 | 203k | fmap_unneed_off(map, loff, SIZEOF_LOCAL_HEADER); |
683 | 203k | goto done; |
684 | 203k | } |
685 | | |
686 | 2.03M | if (detect_encrypted && (LOCAL_HEADER_flags & F_ENCR) && SCAN_HEURISTIC_ENCRYPTED_ARCHIVE) { |
687 | 284 | cl_error_t fp_check; |
688 | 284 | cli_dbgmsg("cli_unzip: Encrypted files found in archive.\n"); |
689 | 284 | fp_check = cli_append_potentially_unwanted(ctx, "Heuristics.Encrypted.Zip"); |
690 | 284 | if (fp_check != CL_SUCCESS) { |
691 | 0 | *ret = fp_check; |
692 | 0 | fmap_unneed_off(map, loff, SIZEOF_LOCAL_HEADER); |
693 | 0 | goto done; |
694 | 0 | } |
695 | 284 | } |
696 | | |
697 | 2.03M | if (LOCAL_HEADER_flags & F_USEDD) { |
698 | 63.0k | cli_dbgmsg("cli_unzip: local header - has data desc\n"); |
699 | 63.0k | if (!central_header) { |
700 | 62.3k | fmap_unneed_off(map, loff, SIZEOF_LOCAL_HEADER); |
701 | 62.3k | goto done; |
702 | 62.3k | } else { |
703 | 656 | usize = CENTRAL_HEADER_usize; |
704 | 656 | csize = CENTRAL_HEADER_csize; |
705 | 656 | } |
706 | 1.96M | } else { |
707 | 1.96M | usize = LOCAL_HEADER_usize; |
708 | 1.96M | csize = LOCAL_HEADER_csize; |
709 | 1.96M | } |
710 | | |
711 | 1.96M | if (zsize <= LOCAL_HEADER_elen) { |
712 | 234k | cli_dbgmsg("cli_unzip: local header - extra out of file\n"); |
713 | 234k | fmap_unneed_off(map, loff, SIZEOF_LOCAL_HEADER); |
714 | 234k | goto done; |
715 | 234k | } |
716 | 1.73M | zip += LOCAL_HEADER_elen; |
717 | 1.73M | zsize -= LOCAL_HEADER_elen; |
718 | | |
719 | 1.73M | if (!csize) { /* FIXME: what's used for method0 files? csize or usize? Nothing in the specs, needs testing */ |
720 | 64.4k | cli_dbgmsg("cli_unzip: local header - skipping empty file\n"); |
721 | 1.66M | } else { |
722 | 1.66M | if (zsize < csize) { |
723 | 500k | cli_dbgmsg("cli_unzip: local header - stream out of file\n"); |
724 | 500k | fmap_unneed_off(map, loff, SIZEOF_LOCAL_HEADER); |
725 | 500k | goto done; |
726 | 500k | } |
727 | | |
728 | | /* Don't actually unzip if we're just collecting the file record information (offset, sizes) */ |
729 | 1.16M | if (NULL == record) { |
730 | 1.13M | if (LOCAL_HEADER_flags & F_ENCR) { |
731 | 8.90k | if (fmap_need_ptr_once(map, zip, csize)) |
732 | 8.90k | *ret = zdecrypt(zip, csize, usize, local_header, num_files_unzipped, ctx, tmpd, zcb, original_filename); |
733 | 1.12M | } else { |
734 | 1.12M | if (fmap_need_ptr_once(map, zip, csize)) |
735 | 1.12M | *ret = unz(zip, csize, usize, LOCAL_HEADER_method, LOCAL_HEADER_flags, num_files_unzipped, |
736 | 1.12M | ctx, tmpd, zcb, original_filename, false); |
737 | 1.12M | } |
738 | 1.13M | } else { |
739 | 29.8k | if ((NULL == original_filename) || |
740 | 29.8k | (CL_SUCCESS != cli_basename(original_filename, strlen(original_filename), &record->original_filename))) { |
741 | 498 | record->original_filename = NULL; |
742 | 498 | } |
743 | 29.8k | record->local_header_offset = loff; |
744 | 29.8k | record->local_header_size = zip - local_header; |
745 | 29.8k | record->compressed_size = csize; |
746 | 29.8k | record->uncompressed_size = usize; |
747 | 29.8k | record->method = LOCAL_HEADER_method; |
748 | 29.8k | record->flags = LOCAL_HEADER_flags; |
749 | 29.8k | record->encrypted = (LOCAL_HEADER_flags & F_ENCR) ? 1 : 0; |
750 | | |
751 | 29.8k | *ret = CL_SUCCESS; |
752 | 29.8k | } |
753 | | |
754 | 1.16M | zip += csize; |
755 | 1.16M | zsize -= csize; |
756 | 1.16M | } |
757 | | |
758 | 1.23M | fmap_unneed_off(map, loff, SIZEOF_LOCAL_HEADER); /* unneed now. block is guaranteed to exists till the next need */ |
759 | 1.23M | if (LOCAL_HEADER_flags & F_USEDD) { |
760 | 621 | if (zsize < 12) { |
761 | 7 | cli_dbgmsg("cli_unzip: local header - data desc out of file\n"); |
762 | 7 | goto done; |
763 | 7 | } |
764 | 614 | zsize -= 12; |
765 | 614 | if (fmap_need_ptr_once(map, zip, 4)) { |
766 | 614 | if (cli_readint32(zip) == ZIP_MAGIC_FILE_BEGIN_SPLIT_OR_SPANNED) { |
767 | 190 | if (zsize < 4) { |
768 | 0 | cli_dbgmsg("cli_unzip: local header - data desc out of file\n"); |
769 | 0 | goto done; |
770 | 0 | } |
771 | 190 | zip += 4; |
772 | 190 | } |
773 | 614 | } |
774 | 614 | zip += 12; |
775 | 614 | } |
776 | | |
777 | | /* Success */ |
778 | 1.23M | size_of_fileheader_and_data = zip - local_header; |
779 | | |
780 | 3.21M | done: |
781 | 3.21M | if (NULL != original_filename) { |
782 | 1.17M | free(original_filename); |
783 | 1.17M | } |
784 | | |
785 | 3.21M | return size_of_fileheader_and_data; |
786 | 1.23M | } |
787 | | |
788 | | /** |
789 | | * @brief Parse, extract, and scan a file by iterating the central directory. |
790 | | * |
791 | | * Usage of the `record` parameter will alter behavior so it only collect file record metadata and does not extract or scan any files. |
792 | | * |
793 | | * @param map fmap for the file |
794 | | * @param coff offset of the file header in the central directory |
795 | | * @param zsize size of the zip file |
796 | | * @param[in,out] num_files_unzipped current number of files that have been unzipped |
797 | | * @param file_count current number of files that have been discovered |
798 | | * @param[out] ret The status code |
799 | | * @param[in,out] ctx scan context |
800 | | * @param tmpd temp directory path name |
801 | | * @param requests (optional) structure use to search the zip for files by name |
802 | | * @param record (optional) a pointer to a struct to store file record information. |
803 | | * @return unsigned int returns the size of the file header in the central directory, or 0 if no more files |
804 | | */ |
805 | | static unsigned int |
806 | | parse_central_directory_file_header( |
807 | | fmap_t *map, |
808 | | uint32_t coff, |
809 | | uint32_t zsize, |
810 | | unsigned int *num_files_unzipped, |
811 | | unsigned int file_count, |
812 | | cl_error_t *ret, |
813 | | cli_ctx *ctx, |
814 | | char *tmpd, |
815 | | struct zip_requests *requests, |
816 | | struct zip_record *record) |
817 | 63.2k | { |
818 | 63.2k | char name[256]; |
819 | 63.2k | int last = 0; |
820 | 63.2k | const uint8_t *central_header = NULL; |
821 | | |
822 | 63.2k | *ret = CL_EPARSE; |
823 | | |
824 | 63.2k | if (cli_checktimelimit(ctx) != CL_SUCCESS) { |
825 | 0 | cli_dbgmsg("cli_unzip: central header - Time limit reached (max: %u)\n", ctx->engine->maxscantime); |
826 | 0 | last = 1; |
827 | 0 | *ret = CL_ETIMEOUT; |
828 | 0 | goto done; |
829 | 0 | } |
830 | | |
831 | 63.2k | if (!(central_header = fmap_need_off(map, coff, SIZEOF_CENTRAL_HEADER)) || CENTRAL_HEADER_magic != ZIP_MAGIC_CENTRAL_DIRECTORY_RECORD_BEGIN) { |
832 | 8.32k | if (central_header) { |
833 | 5.00k | fmap_unneed_ptr(map, central_header, SIZEOF_CENTRAL_HEADER); |
834 | 5.00k | central_header = NULL; |
835 | 5.00k | } |
836 | 8.32k | cli_dbgmsg("cli_unzip: central header - wrkcomplete\n"); |
837 | 8.32k | last = 1; |
838 | 8.32k | goto done; |
839 | 8.32k | } |
840 | 54.9k | coff += SIZEOF_CENTRAL_HEADER; |
841 | | |
842 | 54.9k | cli_dbgmsg("cli_unzip: central header - flags %x - method %x - csize %x - usize %x - flen %x - elen %x - clen %x - disk %x - off %x\n", |
843 | 54.9k | CENTRAL_HEADER_flags, CENTRAL_HEADER_method, CENTRAL_HEADER_csize, CENTRAL_HEADER_usize, CENTRAL_HEADER_flen, CENTRAL_HEADER_extra_len, CENTRAL_HEADER_comment_len, CENTRAL_HEADER_disk_num, CENTRAL_HEADER_off); |
844 | | |
845 | 54.9k | if (zsize - coff <= CENTRAL_HEADER_flen) { |
846 | 1.59k | cli_dbgmsg("cli_unzip: central header - fname out of file\n"); |
847 | 1.59k | last = 1; |
848 | 1.59k | goto done; |
849 | 1.59k | } |
850 | | |
851 | 53.3k | name[0] = '\0'; |
852 | 53.3k | if (!last) { |
853 | 53.3k | unsigned int size = (CENTRAL_HEADER_flen >= sizeof(name)) ? sizeof(name) - 1 : CENTRAL_HEADER_flen; |
854 | 53.3k | const char *src = fmap_need_off_once(map, coff, size); |
855 | 53.3k | if (src) { |
856 | 49.8k | memcpy(name, src, size); |
857 | 49.8k | name[size] = '\0'; |
858 | 49.8k | cli_dbgmsg("cli_unzip: central header - fname: %s\n", name); |
859 | 49.8k | } |
860 | 53.3k | } |
861 | 53.3k | coff += CENTRAL_HEADER_flen; |
862 | | |
863 | | /* requests do not supply a ctx; also prevent multiple scans */ |
864 | 53.3k | if (ctx && (CL_VIRUS == cli_matchmeta(ctx, name, CENTRAL_HEADER_csize, CENTRAL_HEADER_usize, (CENTRAL_HEADER_flags & F_ENCR) != 0, file_count, CENTRAL_HEADER_crc32, NULL))) { |
865 | 0 | last = 1; |
866 | 0 | *ret = CL_VIRUS; |
867 | 0 | goto done; |
868 | 0 | } |
869 | | |
870 | 53.3k | if (zsize - coff <= CENTRAL_HEADER_extra_len && !last) { |
871 | 1.61k | cli_dbgmsg("cli_unzip: central header - extra out of file\n"); |
872 | 1.61k | last = 1; |
873 | 1.61k | } |
874 | 53.3k | coff += CENTRAL_HEADER_extra_len; |
875 | | |
876 | 53.3k | if (zsize - coff < CENTRAL_HEADER_comment_len && !last) { |
877 | 1.47k | cli_dbgmsg("cli_unzip: central header - comment out of file\n"); |
878 | 1.47k | last = 1; |
879 | 1.47k | } |
880 | 53.3k | coff += CENTRAL_HEADER_comment_len; |
881 | | |
882 | 53.3k | if (!requests) { |
883 | 32.5k | if (CENTRAL_HEADER_off < zsize - SIZEOF_LOCAL_HEADER) { |
884 | 31.7k | parse_local_file_header(map, |
885 | 31.7k | CENTRAL_HEADER_off, |
886 | 31.7k | zsize - CENTRAL_HEADER_off, |
887 | 31.7k | num_files_unzipped, |
888 | 31.7k | file_count, |
889 | 31.7k | central_header, |
890 | 31.7k | ret, |
891 | 31.7k | ctx, |
892 | 31.7k | tmpd, |
893 | 31.7k | 1, |
894 | 31.7k | zip_scan_cb, |
895 | 31.7k | record); |
896 | 31.7k | } else { |
897 | 739 | cli_dbgmsg("cli_unzip: central header - local hdr out of file\n"); |
898 | 739 | } |
899 | 32.5k | } else { |
900 | 20.7k | int i; |
901 | 20.7k | size_t len; |
902 | | |
903 | 20.7k | if (!last) { |
904 | 58.0k | for (i = 0; i < requests->namecnt; ++i) { |
905 | 40.1k | cli_dbgmsg("cli_unzip: central header - checking for %i: %s\n", i, requests->names[i]); |
906 | | |
907 | 40.1k | len = MIN(sizeof(name) - 1, requests->namelens[i]); |
908 | 40.1k | if (!strncmp(requests->names[i], name, len)) { |
909 | 9.90k | requests->match = 1; |
910 | 9.90k | requests->found = i; |
911 | 9.90k | requests->loff = CENTRAL_HEADER_off; |
912 | 9.90k | } |
913 | 40.1k | } |
914 | 17.9k | } |
915 | 20.7k | *ret = CL_SUCCESS; |
916 | 20.7k | } |
917 | | |
918 | 63.2k | done: |
919 | 63.2k | if (NULL != central_header) { |
920 | 54.9k | fmap_unneed_ptr(map, central_header, SIZEOF_CENTRAL_HEADER); |
921 | 54.9k | } |
922 | | |
923 | 63.2k | return (last ? 0 : coff); |
924 | 53.3k | } |
925 | | |
926 | | /** |
927 | | * @brief Sort zip_record structures based on local file offset. |
928 | | * |
929 | | * @param first |
930 | | * @param second |
931 | | * @return int 1 if first record's offset is higher than second's. |
932 | | * @return int 0 if first and second record offsets are equal. |
933 | | * @return int -1 if first record's offset is less than second's. |
934 | | */ |
935 | | static int sort_by_file_offset(const void *first, const void *second) |
936 | 82.7k | { |
937 | 82.7k | const struct zip_record *a = (const struct zip_record *)first; |
938 | 82.7k | const struct zip_record *b = (const struct zip_record *)second; |
939 | | |
940 | | /* Avoid return x - y, which can cause undefined behaviour |
941 | | because of signed integer overflow. */ |
942 | 82.7k | if (a->local_header_offset < b->local_header_offset) |
943 | 41.4k | return -1; |
944 | 41.3k | else if (a->local_header_offset > b->local_header_offset) |
945 | 40.6k | return 1; |
946 | | |
947 | 634 | return 0; |
948 | 82.7k | } |
949 | | |
950 | | /** |
951 | | * @brief Create a catalogue of the central directory. |
952 | | * |
953 | | * This function indexes every file in the central directory. |
954 | | * It creates a zip record catalogue and sorts them by file entry offset. |
955 | | * Then it iterates the sorted file records looking for overlapping files. |
956 | | * |
957 | | * The caller is responsible for freeing the catalogue. |
958 | | * The catalogue may contain duplicate items, which should be skipped. |
959 | | * |
960 | | * @param ctx The scanning context |
961 | | * @param map The file map |
962 | | * @param fsize The file size |
963 | | * @param coff The central directory offset |
964 | | * @param[out] catalogue A catalogue of zip_records found in the central directory. |
965 | | * @param[out] num_records The number of records in the catalogue. |
966 | | * @return cl_error_t CL_CLEAN if no overlapping files |
967 | | * @return cl_error_t CL_VIRUS if overlapping files and heuristic alerts are enabled |
968 | | * @return cl_error_t CL_EFORMAT if overlapping files and heuristic alerts are disabled |
969 | | * @return cl_error_t CL_ETIMEOUT if the scan time limit is exceeded. |
970 | | * @return cl_error_t CL_EMEM for memory allocation errors. |
971 | | */ |
972 | | cl_error_t index_the_central_directory( |
973 | | cli_ctx *ctx, |
974 | | fmap_t *map, |
975 | | uint32_t fsize, |
976 | | uint32_t coff, |
977 | | struct zip_record **catalogue, |
978 | | size_t *num_records) |
979 | 3.90k | { |
980 | 3.90k | cl_error_t status = CL_CLEAN; |
981 | 3.90k | cl_error_t ret = CL_CLEAN; |
982 | | |
983 | 3.90k | size_t num_record_blocks = 0; |
984 | 3.90k | size_t index = 0; |
985 | | |
986 | 3.90k | struct zip_record *zip_catalogue = NULL; |
987 | 3.90k | size_t records_count = 0; |
988 | 3.90k | struct zip_record *curr_record = NULL; |
989 | 3.90k | struct zip_record *prev_record = NULL; |
990 | 3.90k | uint32_t num_overlapping_files = 0; |
991 | 3.90k | bool exceeded_max_files = false; |
992 | | |
993 | 3.90k | if (NULL == catalogue || NULL == num_records) { |
994 | 0 | cli_errmsg("index_the_central_directory: Invalid NULL arguments\n"); |
995 | 0 | goto done; |
996 | 0 | } |
997 | | |
998 | 3.90k | *catalogue = NULL; |
999 | 3.90k | *num_records = 0; |
1000 | | |
1001 | 3.90k | zip_catalogue = (struct zip_record *)cli_malloc(sizeof(struct zip_record) * ZIP_RECORDS_CHECK_BLOCKSIZE); |
1002 | 3.90k | if (NULL == zip_catalogue) { |
1003 | 0 | status = CL_EMEM; |
1004 | 0 | goto done; |
1005 | 0 | } |
1006 | 3.90k | num_record_blocks = 1; |
1007 | 3.90k | memset(zip_catalogue, 0, sizeof(struct zip_record) * ZIP_RECORDS_CHECK_BLOCKSIZE); |
1008 | | |
1009 | 3.90k | cli_dbgmsg("cli_unzip: checking for non-recursive zip bombs...\n"); |
1010 | | |
1011 | 36.1k | do { |
1012 | 36.1k | coff = parse_central_directory_file_header(map, |
1013 | 36.1k | coff, |
1014 | 36.1k | fsize, |
1015 | 36.1k | NULL, // num_files_unziped not required |
1016 | 36.1k | index + 1, |
1017 | 36.1k | &ret, |
1018 | 36.1k | ctx, |
1019 | 36.1k | NULL, // tmpd not required |
1020 | 36.1k | NULL, |
1021 | 36.1k | &(zip_catalogue[records_count])); |
1022 | | |
1023 | 36.1k | if (CL_EPARSE != ret) { |
1024 | | // Found a record. |
1025 | 29.8k | records_count++; |
1026 | 29.8k | } |
1027 | | |
1028 | 36.1k | if (0 == coff) { |
1029 | | // No more files (previous was last). |
1030 | 3.90k | break; |
1031 | 3.90k | } |
1032 | | |
1033 | 32.2k | if (ret == CL_VIRUS) { |
1034 | 0 | status = CL_VIRUS; |
1035 | 0 | goto done; |
1036 | 0 | } |
1037 | | |
1038 | 32.2k | index++; |
1039 | | |
1040 | 32.2k | if (cli_checktimelimit(ctx) != CL_SUCCESS) { |
1041 | 0 | cli_dbgmsg("cli_unzip: Time limit reached (max: %u)\n", ctx->engine->maxscantime); |
1042 | 0 | status = CL_ETIMEOUT; |
1043 | 0 | goto done; |
1044 | 0 | } |
1045 | | |
1046 | | /* stop checking file entries if we'll exceed maxfiles */ |
1047 | 32.2k | if (ctx->engine->maxfiles && records_count >= ctx->engine->maxfiles) { |
1048 | 0 | cli_dbgmsg("cli_unzip: Files limit reached (max: %u)\n", ctx->engine->maxfiles); |
1049 | 0 | cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFiles"); |
1050 | 0 | exceeded_max_files = true; // Set a bool so we can return the correct status code later. |
1051 | | // We still need to scan the files we found while reviewing the file records up to this limit. |
1052 | 0 | break; |
1053 | 0 | } |
1054 | | |
1055 | 32.2k | if (records_count % ZIP_RECORDS_CHECK_BLOCKSIZE == 0) { |
1056 | 1.52k | struct zip_record *zip_catalogue_new = NULL; |
1057 | | |
1058 | 1.52k | cli_dbgmsg(" cli_unzip: Exceeded zip record block size, allocating more space...\n"); |
1059 | | |
1060 | | /* allocate more space for zip records */ |
1061 | 1.52k | if (sizeof(struct zip_record) * ZIP_RECORDS_CHECK_BLOCKSIZE * (num_record_blocks + 1) < |
1062 | 1.52k | sizeof(struct zip_record) * ZIP_RECORDS_CHECK_BLOCKSIZE * (num_record_blocks)) { |
1063 | 0 | cli_errmsg("cli_unzip: Number of file records in zip will exceed the max for current architecture (integer overflow)\n"); |
1064 | 0 | status = CL_EFORMAT; |
1065 | 0 | goto done; |
1066 | 0 | } |
1067 | | |
1068 | 1.52k | zip_catalogue_new = cli_realloc(zip_catalogue, sizeof(struct zip_record) * ZIP_RECORDS_CHECK_BLOCKSIZE * (num_record_blocks + 1)); |
1069 | 1.52k | if (NULL == zip_catalogue_new) { |
1070 | 0 | status = CL_EMEM; |
1071 | 0 | goto done; |
1072 | 0 | } |
1073 | 1.52k | zip_catalogue = zip_catalogue_new; |
1074 | 1.52k | zip_catalogue_new = NULL; |
1075 | | |
1076 | 1.52k | num_record_blocks++; |
1077 | | /* zero out the memory for the new records */ |
1078 | 1.52k | memset(&(zip_catalogue[records_count]), 0, sizeof(struct zip_record) * (ZIP_RECORDS_CHECK_BLOCKSIZE * num_record_blocks - records_count)); |
1079 | 1.52k | } |
1080 | 32.2k | } while (1); |
1081 | | |
1082 | 3.90k | if (ret == CL_VIRUS) { |
1083 | 0 | status = CL_VIRUS; |
1084 | 0 | goto done; |
1085 | 0 | } |
1086 | | |
1087 | 3.90k | if (records_count > 1) { |
1088 | | /* |
1089 | | * Sort the records by local file offset |
1090 | | */ |
1091 | 2.45k | cli_qsort(zip_catalogue, records_count, sizeof(struct zip_record), sort_by_file_offset); |
1092 | | |
1093 | | /* |
1094 | | * Detect overlapping files. |
1095 | | */ |
1096 | 29.5k | for (index = 1; index < records_count; index++) { |
1097 | 27.0k | prev_record = &(zip_catalogue[index - 1]); |
1098 | 27.0k | curr_record = &(zip_catalogue[index]); |
1099 | | |
1100 | 27.0k | uint32_t prev_record_size = prev_record->local_header_size + prev_record->compressed_size; |
1101 | 27.0k | uint32_t curr_record_size = curr_record->local_header_size + curr_record->compressed_size; |
1102 | 27.0k | uint32_t prev_record_end; |
1103 | 27.0k | uint32_t curr_record_end; |
1104 | | |
1105 | | /* Check for integer overflow in 32bit size & offset values */ |
1106 | 27.0k | if ((UINT32_MAX - prev_record_size < prev_record->local_header_offset) || |
1107 | 27.0k | (UINT32_MAX - curr_record_size < curr_record->local_header_offset)) { |
1108 | 0 | cli_dbgmsg("cli_unzip: Integer overflow detected; invalid data sizes in zip file headers.\n"); |
1109 | 0 | status = CL_EFORMAT; |
1110 | 0 | goto done; |
1111 | 0 | } |
1112 | | |
1113 | 27.0k | prev_record_end = prev_record->local_header_offset + prev_record_size; |
1114 | 27.0k | curr_record_end = curr_record->local_header_offset + curr_record_size; |
1115 | | |
1116 | 27.0k | if (((curr_record->local_header_offset >= prev_record->local_header_offset) && (curr_record->local_header_offset + ZIP_RECORD_OVERLAP_FUDGE_FACTOR < prev_record_end)) || |
1117 | 27.0k | ((prev_record->local_header_offset >= curr_record->local_header_offset) && (prev_record->local_header_offset + ZIP_RECORD_OVERLAP_FUDGE_FACTOR < curr_record_end))) { |
1118 | | /* Overlapping file detected */ |
1119 | 2.78k | num_overlapping_files++; |
1120 | | |
1121 | 2.78k | if ((curr_record->local_header_offset == prev_record->local_header_offset) && |
1122 | 2.78k | (curr_record->local_header_size == prev_record->local_header_size) && |
1123 | 2.78k | (curr_record->compressed_size == prev_record->compressed_size)) { |
1124 | 380 | cli_dbgmsg("cli_unzip: Ignoring duplicate file entry @ 0x%x.\n", curr_record->local_header_offset); |
1125 | 2.40k | } else { |
1126 | 2.40k | cli_dbgmsg("cli_unzip: Overlapping files detected.\n"); |
1127 | 2.40k | cli_dbgmsg(" previous file end: %u\n", prev_record_end); |
1128 | 2.40k | cli_dbgmsg(" current file start: %u\n", curr_record->local_header_offset); |
1129 | | |
1130 | 2.40k | if (ZIP_MAX_NUM_OVERLAPPING_FILES < num_overlapping_files) { |
1131 | 9 | if (SCAN_HEURISTICS) { |
1132 | 9 | status = cli_append_potentially_unwanted(ctx, "Heuristics.Zip.OverlappingFiles"); |
1133 | 9 | } else { |
1134 | 0 | status = CL_EFORMAT; |
1135 | 0 | } |
1136 | 9 | goto done; |
1137 | 9 | } |
1138 | 2.40k | } |
1139 | 2.78k | } |
1140 | | |
1141 | 27.0k | if (cli_checktimelimit(ctx) != CL_SUCCESS) { |
1142 | 0 | cli_dbgmsg("cli_unzip: Time limit reached (max: %u)\n", ctx->engine->maxscantime); |
1143 | 0 | status = CL_ETIMEOUT; |
1144 | 0 | goto done; |
1145 | 0 | } |
1146 | 27.0k | } |
1147 | 2.45k | } |
1148 | | |
1149 | 3.89k | *catalogue = zip_catalogue; |
1150 | 3.89k | *num_records = records_count; |
1151 | 3.89k | status = CL_SUCCESS; |
1152 | | |
1153 | 3.90k | done: |
1154 | | |
1155 | 3.90k | if (CL_SUCCESS != status) { |
1156 | 0 | if (NULL != zip_catalogue) { |
1157 | 0 | size_t i; |
1158 | 0 | for (i = 0; i < records_count; i++) { |
1159 | 0 | if (NULL != zip_catalogue[i].original_filename) { |
1160 | 0 | free(zip_catalogue[i].original_filename); |
1161 | 0 | zip_catalogue[i].original_filename = NULL; |
1162 | 0 | } |
1163 | 0 | } |
1164 | 0 | free(zip_catalogue); |
1165 | 0 | zip_catalogue = NULL; |
1166 | 0 | } |
1167 | |
|
1168 | 0 | if (exceeded_max_files) { |
1169 | 0 | status = CL_EMAXFILES; |
1170 | 0 | } |
1171 | 0 | } |
1172 | | |
1173 | 3.90k | return status; |
1174 | 3.89k | } |
1175 | | |
1176 | | cl_error_t cli_unzip(cli_ctx *ctx) |
1177 | 22.2k | { |
1178 | 22.2k | unsigned int file_count = 0, num_files_unzipped = 0; |
1179 | 22.2k | cl_error_t ret = CL_CLEAN; |
1180 | 22.2k | uint32_t fsize, lhoff = 0, coff = 0; |
1181 | 22.2k | fmap_t *map = ctx->fmap; |
1182 | 22.2k | char *tmpd = NULL; |
1183 | 22.2k | const char *ptr; |
1184 | 22.2k | #if HAVE_JSON |
1185 | 22.2k | int toval = 0; |
1186 | 22.2k | #endif |
1187 | 22.2k | struct zip_record *zip_catalogue = NULL; |
1188 | 22.2k | size_t records_count = 0; |
1189 | 22.2k | size_t i; |
1190 | | |
1191 | 22.2k | cli_dbgmsg("in cli_unzip\n"); |
1192 | 22.2k | fsize = (uint32_t)map->len; |
1193 | 22.2k | if (sizeof(off_t) != sizeof(uint32_t) && (size_t)fsize != map->len) { |
1194 | 0 | cli_dbgmsg("cli_unzip: file too big\n"); |
1195 | 0 | ret = CL_CLEAN; |
1196 | 0 | goto done; |
1197 | 0 | } |
1198 | 22.2k | if (fsize < SIZEOF_CENTRAL_HEADER) { |
1199 | 809 | cli_dbgmsg("cli_unzip: file too short\n"); |
1200 | 809 | ret = CL_CLEAN; |
1201 | 809 | goto done; |
1202 | 809 | } |
1203 | | |
1204 | 88.1M | for (coff = fsize - 22; coff > 0; coff--) { /* sizeof(EOC)==22 */ |
1205 | 88.1M | if (!(ptr = fmap_need_off_once(map, coff, 20))) |
1206 | 0 | continue; |
1207 | 88.1M | if (cli_readint32(ptr) == ZIP_MAGIC_CENTRAL_DIRECTORY_RECORD_END) { |
1208 | 4.64k | uint32_t chptr = cli_readint32(&ptr[16]); |
1209 | 4.64k | if (!CLI_ISCONTAINED_0_TO(fsize, chptr, SIZEOF_CENTRAL_HEADER)) continue; |
1210 | 4.06k | coff = chptr; |
1211 | 4.06k | break; |
1212 | 4.64k | } |
1213 | 88.1M | } |
1214 | | |
1215 | 21.4k | if (coff) { |
1216 | 3.90k | cli_dbgmsg("cli_unzip: central directory header offset: @%x\n", coff); |
1217 | | |
1218 | | /* |
1219 | | * Index the central directory first. |
1220 | | */ |
1221 | 3.90k | ret = index_the_central_directory( |
1222 | 3.90k | ctx, |
1223 | 3.90k | map, |
1224 | 3.90k | fsize, |
1225 | 3.90k | coff, |
1226 | 3.90k | &zip_catalogue, |
1227 | 3.90k | &records_count); |
1228 | 3.90k | if (CL_SUCCESS != ret) { |
1229 | 0 | goto done; |
1230 | 0 | } |
1231 | | |
1232 | | /* |
1233 | | * Then decrypt/unzip & scan each unique file entry. |
1234 | | */ |
1235 | 17.1k | for (i = 0; i < records_count; i++) { |
1236 | 14.6k | const uint8_t *compressed_data = NULL; |
1237 | | |
1238 | 14.6k | if ((i > 0) && |
1239 | 14.6k | (zip_catalogue[i].local_header_offset == zip_catalogue[i - 1].local_header_offset) && |
1240 | 14.6k | (zip_catalogue[i].local_header_size == zip_catalogue[i - 1].local_header_size) && |
1241 | 14.6k | (zip_catalogue[i].compressed_size == zip_catalogue[i - 1].compressed_size)) { |
1242 | | |
1243 | | /* Duplicate file entry, skip. */ |
1244 | 354 | cli_dbgmsg("cli_unzip: Skipping unzipping of duplicate file entry: @ 0x%x\n", zip_catalogue[i].local_header_offset); |
1245 | 354 | continue; |
1246 | 354 | } |
1247 | | |
1248 | 14.3k | compressed_data = fmap_need_off(map, zip_catalogue[i].local_header_offset + zip_catalogue[i].local_header_size, SIZEOF_LOCAL_HEADER); |
1249 | | |
1250 | 14.3k | if (zip_catalogue[i].encrypted) { |
1251 | 97 | if (fmap_need_ptr_once(map, compressed_data, zip_catalogue[i].compressed_size)) |
1252 | 94 | ret = zdecrypt( |
1253 | 94 | compressed_data, |
1254 | 94 | zip_catalogue[i].compressed_size, |
1255 | 94 | zip_catalogue[i].uncompressed_size, |
1256 | 94 | fmap_need_off(map, zip_catalogue[i].local_header_offset, SIZEOF_LOCAL_HEADER), |
1257 | 94 | &num_files_unzipped, |
1258 | 94 | ctx, |
1259 | 94 | tmpd, |
1260 | 94 | zip_scan_cb, |
1261 | 94 | zip_catalogue[i].original_filename); |
1262 | 14.2k | } else { |
1263 | 14.2k | if (fmap_need_ptr_once(map, compressed_data, zip_catalogue[i].compressed_size)) |
1264 | 14.2k | ret = unz( |
1265 | 14.2k | compressed_data, |
1266 | 14.2k | zip_catalogue[i].compressed_size, |
1267 | 14.2k | zip_catalogue[i].uncompressed_size, |
1268 | 14.2k | zip_catalogue[i].method, |
1269 | 14.2k | zip_catalogue[i].flags, |
1270 | 14.2k | &num_files_unzipped, |
1271 | 14.2k | ctx, |
1272 | 14.2k | tmpd, |
1273 | 14.2k | zip_scan_cb, |
1274 | 14.2k | zip_catalogue[i].original_filename, |
1275 | 14.2k | false); |
1276 | 14.2k | } |
1277 | | |
1278 | 14.3k | file_count++; |
1279 | | |
1280 | 14.3k | if (ctx->engine->maxfiles && num_files_unzipped >= ctx->engine->maxfiles) { |
1281 | | // Note: this check piggybacks on the MaxFiles setting, but is not actually |
1282 | | // scanning these files or incrementing the ctx->scannedfiles count |
1283 | | // This check is also redundant. zip_scan_cb == cli_magic_scan_desc, |
1284 | | // so we will also check and update the limits for the actual number of scanned |
1285 | | // files inside cli_magic_scan() |
1286 | 0 | cli_dbgmsg("cli_unzip: Files limit reached (max: %u)\n", ctx->engine->maxfiles); |
1287 | 0 | cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFiles"); |
1288 | 0 | ret = CL_EMAXFILES; |
1289 | 0 | } |
1290 | | |
1291 | 14.3k | if (cli_checktimelimit(ctx) != CL_SUCCESS) { |
1292 | 0 | cli_dbgmsg("cli_unzip: Time limit reached (max: %u)\n", ctx->engine->maxscantime); |
1293 | 0 | ret = CL_ETIMEOUT; |
1294 | 0 | goto done; |
1295 | 0 | } |
1296 | | |
1297 | 14.3k | #if HAVE_JSON |
1298 | 14.3k | if (cli_json_timeout_cycle_check(ctx, &toval) != CL_SUCCESS) { |
1299 | 0 | ret = CL_ETIMEOUT; |
1300 | 0 | } |
1301 | 14.3k | #endif |
1302 | 14.3k | if (ret != CL_SUCCESS) { |
1303 | 1.37k | break; |
1304 | 1.37k | } |
1305 | 14.3k | } |
1306 | 17.5k | } else { |
1307 | 17.5k | cli_dbgmsg("cli_unzip: central not found, using localhdrs\n"); |
1308 | 17.5k | } |
1309 | | |
1310 | 21.4k | if (CL_SUCCESS != ret) { |
1311 | | // goto done right away if there was a timeout, an alert, etc. |
1312 | | // This is slightly redundant since the while loop will only happen |
1313 | | // if ret == CL_SUCCESS but it's more explicit. |
1314 | 1.37k | goto done; |
1315 | 1.37k | } |
1316 | | |
1317 | 20.0k | if (0 < num_files_unzipped && num_files_unzipped <= (file_count / 4)) { /* FIXME: make up a sane ratio or remove the whole logic */ |
1318 | 33 | file_count = 0; |
1319 | 111 | while ((ret == CL_CLEAN) && |
1320 | 111 | (lhoff < fsize) && |
1321 | 111 | (0 != (coff = parse_local_file_header(map, |
1322 | 111 | lhoff, |
1323 | 111 | fsize - lhoff, |
1324 | 111 | &num_files_unzipped, |
1325 | 111 | file_count + 1, |
1326 | 111 | NULL, |
1327 | 111 | &ret, |
1328 | 111 | ctx, |
1329 | 111 | tmpd, |
1330 | 111 | 1, |
1331 | 111 | zip_scan_cb, |
1332 | 111 | NULL)))) { |
1333 | 78 | file_count++; |
1334 | 78 | lhoff += coff; |
1335 | | |
1336 | 78 | if (ctx->engine->maxfiles && num_files_unzipped >= ctx->engine->maxfiles) { |
1337 | | // Note: this check piggybacks on the MaxFiles setting, but is not actually |
1338 | | // scanning these files or incrementing the ctx->scannedfiles count |
1339 | | // This check is also redundant. zip_scan_cb == cli_magic_scan_desc, |
1340 | | // so we will also check and update the limits for the actual number of scanned |
1341 | | // files inside cli_magic_scan() |
1342 | 0 | cli_dbgmsg("cli_unzip: Files limit reached (max: %u)\n", ctx->engine->maxfiles); |
1343 | 0 | cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFiles"); |
1344 | 0 | ret = CL_EMAXFILES; |
1345 | 0 | } |
1346 | 78 | #if HAVE_JSON |
1347 | 78 | if (cli_json_timeout_cycle_check(ctx, &toval) != CL_SUCCESS) { |
1348 | 0 | ret = CL_ETIMEOUT; |
1349 | 0 | } |
1350 | 78 | #endif |
1351 | 78 | } |
1352 | 33 | } |
1353 | | |
1354 | 22.2k | done: |
1355 | | |
1356 | 22.2k | if (NULL != zip_catalogue) { |
1357 | | /* Clean up zip record resources */ |
1358 | 33.5k | for (i = 0; i < records_count; i++) { |
1359 | 29.6k | if (NULL != zip_catalogue[i].original_filename) { |
1360 | 29.3k | free(zip_catalogue[i].original_filename); |
1361 | 29.3k | zip_catalogue[i].original_filename = NULL; |
1362 | 29.3k | } |
1363 | 29.6k | } |
1364 | 3.89k | free(zip_catalogue); |
1365 | 3.89k | zip_catalogue = NULL; |
1366 | 3.89k | } |
1367 | | |
1368 | 22.2k | if (NULL != tmpd) { |
1369 | 0 | if (!ctx->engine->keeptmp) { |
1370 | 0 | cli_rmdirs(tmpd); |
1371 | 0 | } |
1372 | 0 | free(tmpd); |
1373 | 0 | } |
1374 | | |
1375 | 22.2k | return ret; |
1376 | 20.0k | } |
1377 | | |
1378 | | cl_error_t unzip_single_internal(cli_ctx *ctx, off_t local_header_offset, zip_cb zcb) |
1379 | 3.23M | { |
1380 | 3.23M | cl_error_t ret = CL_CLEAN; |
1381 | | |
1382 | 3.23M | unsigned int num_files_unzipped = 0; |
1383 | 3.23M | uint32_t fsize; |
1384 | 3.23M | fmap_t *map = ctx->fmap; |
1385 | | |
1386 | 3.23M | cli_dbgmsg("in cli_unzip_single\n"); |
1387 | 3.23M | fsize = (uint32_t)(map->len - local_header_offset); |
1388 | 3.23M | if ((local_header_offset < 0) || |
1389 | 3.23M | ((size_t)local_header_offset > map->len) || |
1390 | 3.23M | ((sizeof(off_t) != sizeof(uint32_t)) && ((size_t)fsize != map->len - local_header_offset))) { |
1391 | | |
1392 | 79 | cli_dbgmsg("cli_unzip: bad offset\n"); |
1393 | 79 | return CL_CLEAN; |
1394 | 79 | } |
1395 | 3.23M | if (fsize < SIZEOF_LOCAL_HEADER) { |
1396 | 51.9k | cli_dbgmsg("cli_unzip: file too short\n"); |
1397 | 51.9k | return CL_CLEAN; |
1398 | 51.9k | } |
1399 | | |
1400 | 3.18M | parse_local_file_header(map, |
1401 | 3.18M | local_header_offset, |
1402 | 3.18M | fsize, |
1403 | 3.18M | &num_files_unzipped, |
1404 | 3.18M | 0, |
1405 | 3.18M | NULL, |
1406 | 3.18M | &ret, |
1407 | 3.18M | ctx, |
1408 | 3.18M | NULL, |
1409 | 3.18M | 0, |
1410 | 3.18M | zcb, |
1411 | 3.18M | NULL); |
1412 | | |
1413 | 3.18M | return ret; |
1414 | 3.23M | } |
1415 | | |
1416 | | cl_error_t cli_unzip_single(cli_ctx *ctx, off_t local_header_offset) |
1417 | 3.22M | { |
1418 | 3.22M | return unzip_single_internal(ctx, local_header_offset, zip_scan_cb); |
1419 | 3.22M | } |
1420 | | |
1421 | | cl_error_t unzip_search_add(struct zip_requests *requests, const char *name, size_t nlen) |
1422 | 73.1k | { |
1423 | 73.1k | cli_dbgmsg("in unzip_search_add\n"); |
1424 | | |
1425 | 73.1k | if (requests->namecnt >= MAX_ZIP_REQUESTS) { |
1426 | 0 | cli_dbgmsg("DEBUGGING MESSAGE GOES HERE!\n"); |
1427 | 0 | return CL_BREAK; |
1428 | 0 | } |
1429 | | |
1430 | 73.1k | cli_dbgmsg("unzip_search_add: adding %s (len %llu)\n", name, (long long unsigned)nlen); |
1431 | | |
1432 | 73.1k | requests->names[requests->namecnt] = name; |
1433 | 73.1k | requests->namelens[requests->namecnt] = nlen; |
1434 | 73.1k | requests->namecnt++; |
1435 | | |
1436 | 73.1k | return CL_SUCCESS; |
1437 | 73.1k | } |
1438 | | |
1439 | | cl_error_t unzip_search(cli_ctx *ctx, fmap_t *map, struct zip_requests *requests) |
1440 | 29.6k | { |
1441 | 29.6k | unsigned int file_count = 0; |
1442 | 29.6k | fmap_t *zmap = map; |
1443 | 29.6k | size_t fsize; |
1444 | 29.6k | uint32_t coff = 0; |
1445 | 29.6k | const char *ptr; |
1446 | 29.6k | cl_error_t ret = CL_CLEAN; |
1447 | 29.6k | #if HAVE_JSON |
1448 | 29.6k | uint32_t toval = 0; |
1449 | 29.6k | #endif |
1450 | 29.6k | cli_dbgmsg("in unzip_search\n"); |
1451 | | |
1452 | 29.6k | if ((!ctx && !map) || !requests) { |
1453 | 0 | return CL_ENULLARG; |
1454 | 0 | } |
1455 | | |
1456 | | /* get priority to given map over ctx->fmap */ |
1457 | 29.6k | if (ctx && !map) |
1458 | 15.1k | zmap = ctx->fmap; |
1459 | 29.6k | fsize = zmap->len; |
1460 | 29.6k | if (sizeof(off_t) != sizeof(uint32_t) && fsize != zmap->len) { |
1461 | 0 | cli_dbgmsg("unzip_search: file too big\n"); |
1462 | 0 | return CL_CLEAN; |
1463 | 0 | } |
1464 | 29.6k | if (fsize < SIZEOF_CENTRAL_HEADER) { |
1465 | 0 | cli_dbgmsg("unzip_search: file too short\n"); |
1466 | 0 | return CL_CLEAN; |
1467 | 0 | } |
1468 | | |
1469 | 110M | for (coff = fsize - 22; coff > 0; coff--) { /* sizeof(EOC)==22 */ |
1470 | 110M | if (!(ptr = fmap_need_off_once(zmap, coff, 20))) |
1471 | 0 | continue; |
1472 | 110M | if (cli_readint32(ptr) == ZIP_MAGIC_CENTRAL_DIRECTORY_RECORD_END) { |
1473 | 28.6k | uint32_t chptr = cli_readint32(&ptr[16]); |
1474 | 28.6k | if (!CLI_ISCONTAINED_0_TO(fsize, chptr, SIZEOF_CENTRAL_HEADER)) continue; |
1475 | 20.1k | coff = chptr; |
1476 | 20.1k | break; |
1477 | 28.6k | } |
1478 | 110M | } |
1479 | | |
1480 | 29.6k | if (coff) { |
1481 | 19.0k | cli_dbgmsg("unzip_search: central directory header offset: @%x\n", coff); |
1482 | 36.9k | while (ret == CL_CLEAN && (coff = parse_central_directory_file_header(zmap, |
1483 | 27.0k | coff, |
1484 | 27.0k | fsize, |
1485 | 27.0k | NULL, |
1486 | 27.0k | file_count + 1, |
1487 | 27.0k | &ret, |
1488 | 27.0k | ctx, |
1489 | 27.0k | NULL, |
1490 | 27.0k | requests, |
1491 | 27.0k | NULL))) { |
1492 | 17.9k | if (requests->match) { |
1493 | 9.90k | ret = CL_VIRUS; |
1494 | 9.90k | } |
1495 | | |
1496 | 17.9k | file_count++; |
1497 | 17.9k | if (ctx && ctx->engine->maxfiles && file_count >= ctx->engine->maxfiles) { |
1498 | | // Note: this check piggybacks on the MaxFiles setting, but is not actually |
1499 | | // scanning these files or incrementing the ctx->scannedfiles count |
1500 | 0 | cli_dbgmsg("cli_unzip: Files limit reached (max: %u)\n", ctx->engine->maxfiles); |
1501 | 0 | cli_append_potentially_unwanted_if_heur_exceedsmax(ctx, "Heuristics.Limits.Exceeded.MaxFiles"); |
1502 | 0 | ret = CL_EMAXFILES; |
1503 | 0 | } |
1504 | 17.9k | #if HAVE_JSON |
1505 | 17.9k | if (ctx && cli_json_timeout_cycle_check(ctx, (int *)(&toval)) != CL_SUCCESS) { |
1506 | 0 | ret = CL_ETIMEOUT; |
1507 | 0 | } |
1508 | 17.9k | #endif |
1509 | 17.9k | } |
1510 | 19.0k | } else { |
1511 | 10.6k | cli_dbgmsg("unzip_search: cannot locate central directory\n"); |
1512 | 10.6k | } |
1513 | | |
1514 | 29.6k | return ret; |
1515 | 29.6k | } |
1516 | | |
1517 | | cl_error_t unzip_search_single(cli_ctx *ctx, const char *name, size_t nlen, uint32_t *loff) |
1518 | 15.1k | { |
1519 | 15.1k | struct zip_requests requests; |
1520 | 15.1k | cl_error_t ret; |
1521 | | |
1522 | 15.1k | cli_dbgmsg("in unzip_search_single\n"); |
1523 | 15.1k | if (!ctx) { |
1524 | 0 | return CL_ENULLARG; |
1525 | 0 | } |
1526 | | |
1527 | 15.1k | memset(&requests, 0, sizeof(struct zip_requests)); |
1528 | | |
1529 | 15.1k | if ((ret = unzip_search_add(&requests, name, nlen)) != CL_SUCCESS) { |
1530 | 0 | return ret; |
1531 | 0 | } |
1532 | | |
1533 | 15.1k | if ((ret = unzip_search(ctx, NULL, &requests)) == CL_VIRUS) { |
1534 | 8.90k | *loff = requests.loff; |
1535 | 8.90k | } |
1536 | | |
1537 | 15.1k | return ret; |
1538 | 15.1k | } |