File clamav-zipbomb.patch of Package clamav.13406
--- libclamav/unzip.c.orig
+++ libclamav/unzip.c
@@ -54,6 +54,8 @@
#define UNZIP_PRIVATE
#include "unzip.h"
+#define ZIP_MAX_NUM_OVERLAPPING_FILES 5
+
#define ZIP_CRC32(r,c,b,l) \
do { \
r = crc32(~c,b,l); \
@@ -500,7 +502,7 @@ static inline int zdecrypt(const uint8_t
return CL_SUCCESS;
}
-static unsigned int lhdr(fmap_t *map, uint32_t loff,uint32_t zsize, unsigned int *fu, unsigned int fc, const uint8_t *ch, int *ret, cli_ctx *ctx, char *tmpd, int detect_encrypted, zip_cb zcb) {
+static unsigned int lhdr(fmap_t *map, uint32_t loff,uint32_t zsize, unsigned int *fu, unsigned int fc, const uint8_t *ch, int *ret, cli_ctx *ctx, char *tmpd, int detect_encrypted, zip_cb zcb, uint32_t *file_local_header_size, uint32_t* file_local_data_size) {
const uint8_t *lh, *zip;
char name[256];
uint32_t csize, usize;
@@ -581,6 +583,11 @@ static unsigned int lhdr(fmap_t *map, ui
zip+=LH_elen;
zsize-=LH_elen;
+ if (NULL != file_local_header_size)
+ *file_local_header_size = zip - lh;
+ if (NULL != file_local_data_size)
+ *file_local_data_size = csize;
+
if (!csize) { /* FIXME: what's used for method0 files? csize or usize? Nothing in the specs, needs testing */
cli_dbgmsg("cli_unzip: lh - skipping empty file\n");
} else {
@@ -624,12 +631,19 @@ static unsigned int lhdr(fmap_t *map, ui
return zip-lh;
}
-static unsigned int chdr(fmap_t *map, uint32_t coff, uint32_t zsize, unsigned int *fu, unsigned int fc, int *ret, cli_ctx *ctx, char *tmpd, struct zip_requests *requests) {
+static unsigned int chdr(fmap_t *map, uint32_t coff, uint32_t zsize, unsigned int *fu, unsigned int fc, int *ret, cli_ctx *ctx, char *tmpd, struct zip_requests *requests, uint32_t *file_local_offset, uint32_t *file_local_header_size, uint32_t *file_local_data_size) {
char name[256];
int last = 0;
const uint8_t *ch;
int virus_found = 0;
+ if (NULL != file_local_offset)
+ *file_local_offset = 0;
+ if (NULL != file_local_header_size)
+ *file_local_header_size = 0;
+ if (NULL != file_local_data_size)
+ *file_local_data_size = 0;
+
if(!(ch = fmap_need_off(map, coff, SIZEOF_CH)) || CH_magic != 0x02014b50) {
if(ch) fmap_unneed_ptr(map, ch, SIZEOF_CH);
cli_dbgmsg("cli_unzip: ch - wrkcomplete\n");
@@ -674,7 +688,9 @@ static unsigned int chdr(fmap_t *map, ui
if (!requests) {
if(CH_off<zsize-SIZEOF_LH) {
- lhdr(map, CH_off, zsize-CH_off, fu, fc, ch, ret, ctx, tmpd, 1, zip_scan_cb);
+ if (NULL != file_local_offset)
+ *file_local_offset = CH_off;
+ lhdr(map, CH_off, zsize-CH_off, fu, fc ,ch, ret, ctx, tmpd, 1, zip_scan_cb, file_local_header_size, file_local_data_size);
} else cli_dbgmsg("cli_unzip: ch - local hdr out of file\n");
}
else {
@@ -712,6 +728,13 @@ int cli_unzip(cli_ctx *ctx) {
#if HAVE_JSON
int toval = 0;
#endif
+ int bZipBombDetected = 0;
+ uint32_t cur_file_local_offset = 0;
+ uint32_t cur_file_local_header_size = 0;
+ uint32_t cur_file_local_data_size = 0;
+ uint32_t prev_file_local_offset = 0;
+ uint32_t prev_file_local_header_size = 0;
+ uint32_t prev_file_local_data_size = 0;
cli_dbgmsg("in cli_unzip\n");
fsize = (uint32_t)map->len;
@@ -744,20 +767,47 @@ int cli_unzip(cli_ctx *ctx) {
}
if(coff) {
+ uint32_t nOverlappingFiles = 0;
+
cli_dbgmsg("cli_unzip: central @%x\n", coff);
- while((coff=chdr(map, coff, fsize, &fu, fc+1, &ret, ctx, tmpd, NULL))) {
+ while((coff=chdr(map, coff, fsize, &fu, fc+1, &ret, ctx, tmpd, NULL, &cur_file_local_offset, &cur_file_local_header_size, &cur_file_local_data_size))) {
fc++;
if (ctx->engine->maxfiles && fu>=ctx->engine->maxfiles) {
cli_dbgmsg("cli_unzip: Files limit reached (max: %u)\n", ctx->engine->maxfiles);
ret=CL_EMAXFILES;
}
+ /*
+ * Detect overlapping files and zip bombs.
+ */
+ if ((((cur_file_local_offset > prev_file_local_offset) && (cur_file_local_offset < prev_file_local_offset + prev_file_local_header_size + prev_file_local_data_size)) ||
+ ((prev_file_local_offset > cur_file_local_offset) && (prev_file_local_offset < cur_file_local_offset + cur_file_local_header_size + cur_file_local_data_size))) &&
+ (cur_file_local_header_size + cur_file_local_data_size > 0)) {
+ /* Overlapping file detected */
+ nOverlappingFiles++;
+
+ cli_dbgmsg("clip_unzip: Overlapping files detected.\n");
+ cli_dbgmsg(" previous file end: %u\n", prev_file_local_offset + prev_file_local_header_size + prev_file_local_data_size);
+ cli_dbgmsg(" current file start: %u\n", cur_file_local_offset);
+ if (ZIP_MAX_NUM_OVERLAPPING_FILES < nOverlappingFiles) {
+ if (SCAN_ALGO) {
+ ret = cli_append_virus(ctx, "Heuristics.Zip.OverlappingFiles");
+ virus_found = 1;
+ } else {
+ ret = CL_EFORMAT;
+ }
+ bZipBombDetected = 1;
+ }
+ }
+ prev_file_local_offset = cur_file_local_offset;
+ prev_file_local_header_size = cur_file_local_header_size;
+ prev_file_local_data_size = cur_file_local_data_size;
#if HAVE_JSON
if (cli_json_timeout_cycle_check(ctx, &toval) != CL_SUCCESS) {
ret=CL_ETIMEOUT;
}
#endif
if (ret != CL_CLEAN) {
- if (ret == CL_VIRUS && SCAN_ALL) {
+ if (ret == CL_VIRUS && SCAN_ALL && !bZipBombDetected) {
ret = CL_CLEAN;
virus_found = 1;
} else
@@ -769,7 +819,7 @@ int cli_unzip(cli_ctx *ctx) {
ret = CL_VIRUS;
if(fu<=(fc/4)) { /* FIXME: make up a sane ratio or remove the whole logic */
fc = 0;
- while (ret==CL_CLEAN && lhoff<fsize && (coff=lhdr(map, lhoff, fsize-lhoff, &fu, fc+1, NULL, &ret, ctx, tmpd, 1, zip_scan_cb))) {
+ while (ret==CL_CLEAN && lhoff<fsize && (coff=lhdr(map, lhoff, fsize-lhoff, &fu, fc+1, NULL, &ret, ctx, tmpd, 1, zip_scan_cb, NULL, NULL))) {
fc++;
lhoff+=coff;
if (SCAN_ALL && ret == CL_VIRUS) {
@@ -816,7 +866,7 @@ int unzip_single_internal(cli_ctx *ctx,
return CL_CLEAN;
}
- lhdr(map, lhoffl, fsize, &fu, 0, NULL, &ret, ctx, NULL, 0, zcb);
+ lhdr(map, lhoffl, fsize, &fu, 0, NULL, &ret, ctx, NULL, 0, zcb, NULL, NULL);
return ret;
}
@@ -886,7 +936,7 @@ int unzip_search(cli_ctx *ctx, fmap_t *m
if(coff) {
cli_dbgmsg("unzip_search: central @%x\n", coff);
- while(ret==CL_CLEAN && (coff=chdr(zmap, coff, fsize, NULL, fc+1, &ret, ctx, NULL, requests))) {
+ while(ret==CL_CLEAN && (coff=chdr(zmap, coff, fsize, NULL, fc+1, &ret, ctx, NULL, requests, NULL, NULL, NULL))) {
if (requests->match) {
ret=CL_VIRUS;
}