... | ... |
@@ -5,14 +5,17 @@ Note: This file refers to the source tarball. Things described here may differ |
5 | 5 |
|
6 | 6 |
## 0.101.3 |
7 | 7 |
|
8 |
-ClamAV 0.101.3 is a patch release... |
|
8 |
+ClamAV 0.101.3 is a patch release to address a vulnerability to non-recursive |
|
9 |
+zip bombs. |
|
9 | 10 |
|
10 |
-- Fixes for the following vulnerabilities affecting 0.101.2 and prior: |
|
11 |
- - |
|
11 |
+A Denial-of-Service (DoS) vulnerability may occur when scanning a zip bomb as a |
|
12 |
+result of excessively long scan times. The issue is resolved by detecting the |
|
13 |
+overlapping local file headers which characterize the non-recursive zip bomb |
|
14 |
+described by David Fifield, |
|
15 |
+[here](https://www.bamsoftware.com/hacks/zipbomb/). |
|
12 | 16 |
|
13 |
-Additional thanks to the following community members for submitting bug reports: |
|
14 |
- |
|
15 |
-- |
|
17 |
+Thank you to Hanno Böck for reporting the issue as it relates to ClamAV, |
|
18 |
+[here](https://bugzilla.clamav.net/show_bug.cgi?id=12356). |
|
16 | 19 |
|
17 | 20 |
## 0.101.2 |
18 | 21 |
|
... | ... |
@@ -54,6 +54,8 @@ |
54 | 54 |
#define UNZIP_PRIVATE |
55 | 55 |
#include "unzip.h" |
56 | 56 |
|
57 |
+#define ZIP_MAX_NUM_OVERLAPPING_FILES 5 |
|
58 |
+ |
|
57 | 59 |
#define ZIP_CRC32(r,c,b,l) \ |
58 | 60 |
do { \ |
59 | 61 |
r = crc32(~c,b,l); \ |
... | ... |
@@ -493,14 +495,14 @@ static inline int zdecrypt(const uint8_t *src, uint32_t csize, uint32_t usize, c |
493 | 493 |
if (pass_zip) |
494 | 494 |
pass_zip = pass_zip->next; |
495 | 495 |
else |
496 |
- pass_any = pass_any->next; |
|
496 |
+ pass_any = pass_any->next; |
|
497 | 497 |
} |
498 | 498 |
|
499 | 499 |
cli_dbgmsg("cli_unzip: decrypt - skipping encrypted file, no valid passwords\n"); |
500 | 500 |
return CL_SUCCESS; |
501 | 501 |
} |
502 | 502 |
|
503 |
-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) { |
|
503 |
+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) { |
|
504 | 504 |
const uint8_t *lh, *zip; |
505 | 505 |
char name[256]; |
506 | 506 |
uint32_t csize, usize; |
... | ... |
@@ -563,7 +565,7 @@ static unsigned int lhdr(fmap_t *map, uint32_t loff,uint32_t zsize, unsigned int |
563 | 563 |
} |
564 | 564 |
virus_found = 1; |
565 | 565 |
} |
566 |
- |
|
566 |
+ |
|
567 | 567 |
if(LH_flags & F_USEDD) { |
568 | 568 |
cli_dbgmsg("cli_unzip: lh - has data desc\n"); |
569 | 569 |
if(!ch) { |
... | ... |
@@ -581,6 +583,11 @@ static unsigned int lhdr(fmap_t *map, uint32_t loff,uint32_t zsize, unsigned int |
581 | 581 |
zip+=LH_elen; |
582 | 582 |
zsize-=LH_elen; |
583 | 583 |
|
584 |
+ if (NULL != file_local_header_size) |
|
585 |
+ *file_local_header_size = zip - lh; |
|
586 |
+ if (NULL != file_local_data_size) |
|
587 |
+ *file_local_data_size = csize; |
|
588 |
+ |
|
584 | 589 |
if (!csize) { /* FIXME: what's used for method0 files? csize or usize? Nothing in the specs, needs testing */ |
585 | 590 |
cli_dbgmsg("cli_unzip: lh - skipping empty file\n"); |
586 | 591 |
} else { |
... | ... |
@@ -589,6 +596,7 @@ static unsigned int lhdr(fmap_t *map, uint32_t loff,uint32_t zsize, unsigned int |
589 | 589 |
fmap_unneed_off(map, loff, SIZEOF_LH); |
590 | 590 |
return 0; |
591 | 591 |
} |
592 |
+ |
|
592 | 593 |
if(LH_flags & F_ENCR) { |
593 | 594 |
if(fmap_need_ptr_once(map, zip, csize)) |
594 | 595 |
*ret = zdecrypt(zip, csize, usize, lh, fu, ctx, tmpd, zcb); |
... | ... |
@@ -624,12 +632,19 @@ static unsigned int lhdr(fmap_t *map, uint32_t loff,uint32_t zsize, unsigned int |
624 | 624 |
return zip-lh; |
625 | 625 |
} |
626 | 626 |
|
627 |
-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) { |
|
627 |
+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) { |
|
628 | 628 |
char name[256]; |
629 | 629 |
int last = 0; |
630 | 630 |
const uint8_t *ch; |
631 | 631 |
int virus_found = 0; |
632 | 632 |
|
633 |
+ if (NULL != file_local_offset) |
|
634 |
+ *file_local_offset = 0; |
|
635 |
+ if (NULL != file_local_header_size) |
|
636 |
+ *file_local_header_size = 0; |
|
637 |
+ if (NULL != file_local_data_size) |
|
638 |
+ *file_local_data_size = 0; |
|
639 |
+ |
|
633 | 640 |
if(!(ch = fmap_need_off(map, coff, SIZEOF_CH)) || CH_magic != 0x02014b50) { |
634 | 641 |
if(ch) fmap_unneed_ptr(map, ch, SIZEOF_CH); |
635 | 642 |
cli_dbgmsg("cli_unzip: ch - wrkcomplete\n"); |
... | ... |
@@ -674,7 +689,9 @@ static unsigned int chdr(fmap_t *map, uint32_t coff, uint32_t zsize, unsigned in |
674 | 674 |
|
675 | 675 |
if (!requests) { |
676 | 676 |
if(CH_off<zsize-SIZEOF_LH) { |
677 |
- lhdr(map, CH_off, zsize-CH_off, fu, fc, ch, ret, ctx, tmpd, 1, zip_scan_cb); |
|
677 |
+ if (NULL != file_local_offset) |
|
678 |
+ *file_local_offset = CH_off; |
|
679 |
+ 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); |
|
678 | 680 |
} else cli_dbgmsg("cli_unzip: ch - local hdr out of file\n"); |
679 | 681 |
} |
680 | 682 |
else { |
... | ... |
@@ -685,7 +702,7 @@ static unsigned int chdr(fmap_t *map, uint32_t coff, uint32_t zsize, unsigned in |
685 | 685 |
for (i = 0; i < requests->namecnt; ++i) { |
686 | 686 |
cli_dbgmsg("checking for %i: %s\n", i, requests->names[i]); |
687 | 687 |
|
688 |
- len = MIN(sizeof(name)-1, requests->namelens[i]); |
|
688 |
+ len = MIN(sizeof(name)-1, requests->namelens[i]); |
|
689 | 689 |
if (!strncmp(requests->names[i], name, len)) { |
690 | 690 |
requests->match = 1; |
691 | 691 |
requests->found = i; |
... | ... |
@@ -712,6 +729,13 @@ int cli_unzip(cli_ctx *ctx) { |
712 | 712 |
#if HAVE_JSON |
713 | 713 |
int toval = 0; |
714 | 714 |
#endif |
715 |
+ int bZipBombDetected = 0; |
|
716 |
+ uint32_t cur_file_local_offset = 0; |
|
717 |
+ uint32_t cur_file_local_header_size = 0; |
|
718 |
+ uint32_t cur_file_local_data_size = 0; |
|
719 |
+ uint32_t prev_file_local_offset = 0; |
|
720 |
+ uint32_t prev_file_local_header_size = 0; |
|
721 |
+ uint32_t prev_file_local_data_size = 0; |
|
715 | 722 |
|
716 | 723 |
cli_dbgmsg("in cli_unzip\n"); |
717 | 724 |
fsize = (uint32_t)map->len; |
... | ... |
@@ -744,20 +768,48 @@ int cli_unzip(cli_ctx *ctx) { |
744 | 744 |
} |
745 | 745 |
|
746 | 746 |
if(coff) { |
747 |
+ uint32_t nOverlappingFiles = 0; |
|
748 |
+ |
|
747 | 749 |
cli_dbgmsg("cli_unzip: central @%x\n", coff); |
748 |
- while((coff=chdr(map, coff, fsize, &fu, fc+1, &ret, ctx, tmpd, NULL))) { |
|
750 |
+ 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))) { |
|
749 | 751 |
fc++; |
750 | 752 |
if (ctx->engine->maxfiles && fu>=ctx->engine->maxfiles) { |
751 | 753 |
cli_dbgmsg("cli_unzip: Files limit reached (max: %u)\n", ctx->engine->maxfiles); |
752 | 754 |
ret=CL_EMAXFILES; |
753 | 755 |
} |
756 |
+ /* |
|
757 |
+ * Detect overlapping files and zip bombs. |
|
758 |
+ */ |
|
759 |
+ 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)) || |
|
760 |
+ ((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))) && |
|
761 |
+ (cur_file_local_header_size + cur_file_local_data_size > 0)) { |
|
762 |
+ /* Overlapping file detected */ |
|
763 |
+ nOverlappingFiles++; |
|
764 |
+ |
|
765 |
+ cli_dbgmsg("cli_unzip: Overlapping files detected.\n"); |
|
766 |
+ cli_dbgmsg(" previous file end: %u\n", prev_file_local_offset + prev_file_local_header_size + prev_file_local_data_size); |
|
767 |
+ cli_dbgmsg(" current file start: %u\n", cur_file_local_offset); |
|
768 |
+ if (ZIP_MAX_NUM_OVERLAPPING_FILES < nOverlappingFiles) { |
|
769 |
+ if (SCAN_HEURISTICS) { |
|
770 |
+ ret = cli_append_virus(ctx, "Heuristics.Zip.OverlappingFiles"); |
|
771 |
+ virus_found = 1; |
|
772 |
+ } else { |
|
773 |
+ ret = CL_EFORMAT; |
|
774 |
+ } |
|
775 |
+ bZipBombDetected = 1; |
|
776 |
+ } |
|
777 |
+ } |
|
778 |
+ prev_file_local_offset = cur_file_local_offset; |
|
779 |
+ prev_file_local_header_size = cur_file_local_header_size; |
|
780 |
+ prev_file_local_data_size = cur_file_local_data_size; |
|
781 |
+ |
|
754 | 782 |
#if HAVE_JSON |
755 | 783 |
if (cli_json_timeout_cycle_check(ctx, &toval) != CL_SUCCESS) { |
756 | 784 |
ret=CL_ETIMEOUT; |
757 | 785 |
} |
758 | 786 |
#endif |
759 | 787 |
if (ret != CL_CLEAN) { |
760 |
- if (ret == CL_VIRUS && SCAN_ALLMATCHES) { |
|
788 |
+ if (ret == CL_VIRUS && SCAN_ALLMATCHES && !bZipBombDetected) { |
|
761 | 789 |
ret = CL_CLEAN; |
762 | 790 |
virus_found = 1; |
763 | 791 |
} else |
... | ... |
@@ -769,7 +821,7 @@ int cli_unzip(cli_ctx *ctx) { |
769 | 769 |
ret = CL_VIRUS; |
770 | 770 |
if(fu<=(fc/4)) { /* FIXME: make up a sane ratio or remove the whole logic */ |
771 | 771 |
fc = 0; |
772 |
- while (ret==CL_CLEAN && lhoff<fsize && (coff=lhdr(map, lhoff, fsize-lhoff, &fu, fc+1, NULL, &ret, ctx, tmpd, 1, zip_scan_cb))) { |
|
772 |
+ 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))) { |
|
773 | 773 |
fc++; |
774 | 774 |
lhoff+=coff; |
775 | 775 |
if (SCAN_ALLMATCHES && ret == CL_VIRUS) { |
... | ... |
@@ -816,7 +868,7 @@ int unzip_single_internal(cli_ctx *ctx, off_t lhoffl, zip_cb zcb) |
816 | 816 |
return CL_CLEAN; |
817 | 817 |
} |
818 | 818 |
|
819 |
- lhdr(map, lhoffl, fsize, &fu, 0, NULL, &ret, ctx, NULL, 0, zcb); |
|
819 |
+ lhdr(map, lhoffl, fsize, &fu, 0, NULL, &ret, ctx, NULL, 0, zcb, NULL, NULL); |
|
820 | 820 |
|
821 | 821 |
return ret; |
822 | 822 |
} |
... | ... |
@@ -886,7 +938,7 @@ int unzip_search(cli_ctx *ctx, fmap_t *map, struct zip_requests *requests) |
886 | 886 |
|
887 | 887 |
if(coff) { |
888 | 888 |
cli_dbgmsg("unzip_search: central @%x\n", coff); |
889 |
- while(ret==CL_CLEAN && (coff=chdr(zmap, coff, fsize, NULL, fc+1, &ret, ctx, NULL, requests))) { |
|
889 |
+ while(ret==CL_CLEAN && (coff=chdr(zmap, coff, fsize, NULL, fc+1, &ret, ctx, NULL, requests, NULL, NULL, NULL))) { |
|
890 | 890 |
if (requests->match) { |
891 | 891 |
ret=CL_VIRUS; |
892 | 892 |
} |