Coverage Report

Created: 2023-12-08 06:48

/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
}