1From 1aae47fa8935654a84403768f32c03ecbb1be470 Mon Sep 17 00:00:00 2001 2From: Mark Adler <madler@alumni.caltech.edu> 3Date: Tue, 11 Jun 2019 22:01:18 -0700 4Subject: [PATCH 2/3] Detect and reject a zip bomb using overlapped entries. 5 6This detects an invalid zip file that has at least one entry that 7overlaps with another entry or with the central directory to the 8end of the file. A Fifield zip bomb uses overlapped local entries 9to vastly increase the potential inflation ratio. Such an invalid 10zip file is rejected. 11 12See https://www.bamsoftware.com/hacks/zipbomb/ for David Fifield's 13analysis, construction, and examples of such zip bombs. 14 15The detection maintains a list of covered spans of the zip files 16so far, where the central directory to the end of the file and any 17bytes preceding the first entry at zip file offset zero are 18considered covered initially. Then as each entry is decompressed 19or tested, it is considered covered. When a new entry is about to 20be processed, its initial offset is checked to see if it is 21contained by a covered span. If so, the zip file is rejected as 22invalid. 23 24This commit depends on a preceding commit: "Fix bug in 25undefer_input() that misplaced the input state." 26 27CVE: CVE-2019-13232 28Upstream-Status: Backport 29[https://github.com/madler/unzip/commit/47b3ceae397d21bf822bc2ac73052a4b1daf8e1c] 30 31Signed-off-by: Dan Tran <dantran@microsoft.com> 32--- 33 extract.c | 190 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 34 globals.c | 1 + 35 globals.h | 3 + 36 process.c | 10 +++ 37 unzip.h | 1 + 38 5 files changed, 204 insertions(+), 1 deletion(-) 39 40diff --git a/extract.c b/extract.c 41index 24db2a8..2bb72ba 100644 42--- a/extract.c 43+++ b/extract.c 44@@ -321,6 +321,125 @@ static ZCONST char Far UnsupportedExtraField[] = 45 "\nerror: unsupported extra-field compression type (%u)--skipping\n"; 46 static ZCONST char Far BadExtraFieldCRC[] = 47 "error [%s]: bad extra-field CRC %08lx (should be %08lx)\n"; 48+static ZCONST char Far NotEnoughMemCover[] = 49+ "error: not enough memory for bomb detection\n"; 50+static ZCONST char Far OverlappedComponents[] = 51+ "error: invalid zip file with overlapped components (possible zip bomb)\n"; 52+ 53+ 54+ 55+ 56+ 57+/* A growable list of spans. */ 58+typedef zoff_t bound_t; 59+typedef struct { 60+ bound_t beg; /* start of the span */ 61+ bound_t end; /* one past the end of the span */ 62+} span_t; 63+typedef struct { 64+ span_t *span; /* allocated, distinct, and sorted list of spans */ 65+ size_t num; /* number of spans in the list */ 66+ size_t max; /* allocated number of spans (num <= max) */ 67+} cover_t; 68+ 69+/* 70+ * Return the index of the first span in cover whose beg is greater than val. 71+ * If there is no such span, then cover->num is returned. 72+ */ 73+static size_t cover_find(cover, val) 74+ cover_t *cover; 75+ bound_t val; 76+{ 77+ size_t lo = 0, hi = cover->num; 78+ while (lo < hi) { 79+ size_t mid = (lo + hi) >> 1; 80+ if (val < cover->span[mid].beg) 81+ hi = mid; 82+ else 83+ lo = mid + 1; 84+ } 85+ return hi; 86+} 87+ 88+/* Return true if val lies within any one of the spans in cover. */ 89+static int cover_within(cover, val) 90+ cover_t *cover; 91+ bound_t val; 92+{ 93+ size_t pos = cover_find(cover, val); 94+ return pos > 0 && val < cover->span[pos - 1].end; 95+} 96+ 97+/* 98+ * Add a new span to the list, but only if the new span does not overlap any 99+ * spans already in the list. The new span covers the values beg..end-1. beg 100+ * must be less than end. 101+ * 102+ * Keep the list sorted and merge adjacent spans. Grow the allocated space for 103+ * the list as needed. On success, 0 is returned. If the new span overlaps any 104+ * existing spans, then 1 is returned and the new span is not added to the 105+ * list. If the new span is invalid because beg is greater than or equal to 106+ * end, then -1 is returned. If the list needs to be grown but the memory 107+ * allocation fails, then -2 is returned. 108+ */ 109+static int cover_add(cover, beg, end) 110+ cover_t *cover; 111+ bound_t beg; 112+ bound_t end; 113+{ 114+ size_t pos; 115+ int prec, foll; 116+ 117+ if (beg >= end) 118+ /* The new span is invalid. */ 119+ return -1; 120+ 121+ /* Find where the new span should go, and make sure that it does not 122+ overlap with any existing spans. */ 123+ pos = cover_find(cover, beg); 124+ if ((pos > 0 && beg < cover->span[pos - 1].end) || 125+ (pos < cover->num && end > cover->span[pos].beg)) 126+ return 1; 127+ 128+ /* Check for adjacencies. */ 129+ prec = pos > 0 && beg == cover->span[pos - 1].end; 130+ foll = pos < cover->num && end == cover->span[pos].beg; 131+ if (prec && foll) { 132+ /* The new span connects the preceding and following spans. Merge the 133+ following span into the preceding span, and delete the following 134+ span. */ 135+ cover->span[pos - 1].end = cover->span[pos].end; 136+ cover->num--; 137+ memmove(cover->span + pos, cover->span + pos + 1, 138+ (cover->num - pos) * sizeof(span_t)); 139+ } 140+ else if (prec) 141+ /* The new span is adjacent only to the preceding span. Extend the end 142+ of the preceding span. */ 143+ cover->span[pos - 1].end = end; 144+ else if (foll) 145+ /* The new span is adjacent only to the following span. Extend the 146+ beginning of the following span. */ 147+ cover->span[pos].beg = beg; 148+ else { 149+ /* The new span has gaps between both the preceding and the following 150+ spans. Assure that there is room and insert the span. */ 151+ if (cover->num == cover->max) { 152+ size_t max = cover->max == 0 ? 16 : cover->max << 1; 153+ span_t *span = realloc(cover->span, max * sizeof(span_t)); 154+ if (span == NULL) 155+ return -2; 156+ cover->span = span; 157+ cover->max = max; 158+ } 159+ memmove(cover->span + pos + 1, cover->span + pos, 160+ (cover->num - pos) * sizeof(span_t)); 161+ cover->num++; 162+ cover->span[pos].beg = beg; 163+ cover->span[pos].end = end; 164+ } 165+ return 0; 166+} 167 168 169 170@@ -376,6 +495,29 @@ int extract_or_test_files(__G) /* return PK-type error code */ 171 } 172 #endif /* !SFX || SFX_EXDIR */ 173 174+ /* One more: initialize cover structure for bomb detection. Start with a 175+ span that covers the central directory though the end of the file. */ 176+ if (G.cover == NULL) { 177+ G.cover = malloc(sizeof(cover_t)); 178+ if (G.cover == NULL) { 179+ Info(slide, 0x401, ((char *)slide, 180+ LoadFarString(NotEnoughMemCover))); 181+ return PK_MEM; 182+ } 183+ ((cover_t *)G.cover)->span = NULL; 184+ ((cover_t *)G.cover)->max = 0; 185+ } 186+ ((cover_t *)G.cover)->num = 0; 187+ if ((G.extra_bytes != 0 && 188+ cover_add((cover_t *)G.cover, 0, G.extra_bytes) != 0) || 189+ cover_add((cover_t *)G.cover, 190+ G.extra_bytes + G.ecrec.offset_start_central_directory, 191+ G.ziplen) != 0) { 192+ Info(slide, 0x401, ((char *)slide, 193+ LoadFarString(NotEnoughMemCover))); 194+ return PK_MEM; 195+ } 196+ 197 /*--------------------------------------------------------------------------- 198 The basic idea of this function is as follows. Since the central di- 199 rectory lies at the end of the zipfile and the member files lie at the 200@@ -593,7 +735,8 @@ int extract_or_test_files(__G) /* return PK-type error code */ 201 if (error > error_in_archive) 202 error_in_archive = error; 203 /* ...and keep going (unless disk full or user break) */ 204- if (G.disk_full > 1 || error_in_archive == IZ_CTRLC) { 205+ if (G.disk_full > 1 || error_in_archive == IZ_CTRLC || 206+ error == PK_BOMB) { 207 /* clear reached_end to signal premature stop ... */ 208 reached_end = FALSE; 209 /* ... and cancel scanning the central directory */ 210@@ -1062,6 +1205,11 @@ static int extract_or_test_entrylist(__G__ numchunk, 211 212 /* seek_zipf(__G__ pInfo->offset); */ 213 request = G.pInfo->offset + G.extra_bytes; 214+ if (cover_within((cover_t *)G.cover, request)) { 215+ Info(slide, 0x401, ((char *)slide, 216+ LoadFarString(OverlappedComponents))); 217+ return PK_BOMB; 218+ } 219 inbuf_offset = request % INBUFSIZ; 220 bufstart = request - inbuf_offset; 221 222@@ -1593,6 +1741,18 @@ reprompt: 223 return IZ_CTRLC; /* cancel operation by user request */ 224 } 225 #endif 226+ error = cover_add((cover_t *)G.cover, request, 227+ G.cur_zipfile_bufstart + (G.inptr - G.inbuf)); 228+ if (error < 0) { 229+ Info(slide, 0x401, ((char *)slide, 230+ LoadFarString(NotEnoughMemCover))); 231+ return PK_MEM; 232+ } 233+ if (error != 0) { 234+ Info(slide, 0x401, ((char *)slide, 235+ LoadFarString(OverlappedComponents))); 236+ return PK_BOMB; 237+ } 238 #ifdef MACOS /* MacOS is no preemptive OS, thus call event-handling by hand */ 239 UserStop(); 240 #endif 241@@ -1994,6 +2154,34 @@ static int extract_or_test_member(__G) /* return PK-type error code */ 242 } 243 244 undefer_input(__G); 245+ 246+ if ((G.lrec.general_purpose_bit_flag & 8) != 0) { 247+ /* skip over data descriptor (harder than it sounds, due to signature 248+ * ambiguity) 249+ */ 250+# define SIG 0x08074b50 251+# define LOW 0xffffffff 252+ uch buf[12]; 253+ unsigned shy = 12 - readbuf((char *)buf, 12); 254+ ulg crc = shy ? 0 : makelong(buf); 255+ ulg clen = shy ? 0 : makelong(buf + 4); 256+ ulg ulen = shy ? 0 : makelong(buf + 8); /* or high clen if ZIP64 */ 257+ if (crc == SIG && /* if not SIG, no signature */ 258+ (G.lrec.crc32 != SIG || /* if not SIG, have signature */ 259+ (clen == SIG && /* if not SIG, no signature */ 260+ ((G.lrec.csize & LOW) != SIG || /* if not SIG, have signature */ 261+ (ulen == SIG && /* if not SIG, no signature */ 262+ (G.zip64 ? G.lrec.csize >> 32 : G.lrec.ucsize) != SIG 263+ /* if not SIG, have signature */ 264+ ))))) 265+ /* skip four more bytes to account for signature */ 266+ shy += 4 - readbuf((char *)buf, 4); 267+ if (G.zip64) 268+ shy += 8 - readbuf((char *)buf, 8); /* skip eight more for ZIP64 */ 269+ if (shy) 270+ error = PK_ERR; 271+ } 272+ 273 return error; 274 275 } /* end function extract_or_test_member() */ 276diff --git a/globals.c b/globals.c 277index fa8cca5..1e0f608 100644 278--- a/globals.c 279+++ b/globals.c 280@@ -181,6 +181,7 @@ Uz_Globs *globalsCtor() 281 # if (!defined(NO_TIMESTAMPS)) 282 uO.D_flag=1; /* default to '-D', no restoration of dir timestamps */ 283 # endif 284+ G.cover = NULL; /* not allocated yet */ 285 #endif 286 287 uO.lflag=(-1); 288diff --git a/globals.h b/globals.h 289index 11b7215..2bdcdeb 100644 290--- a/globals.h 291+++ b/globals.h 292@@ -260,12 +260,15 @@ typedef struct Globals { 293 ecdir_rec ecrec; /* used in unzip.c, extract.c */ 294 z_stat statbuf; /* used by main, mapname, check_for_newer */ 295 296+ int zip64; /* true if Zip64 info in extra field */ 297+ 298 int mem_mode; 299 uch *outbufptr; /* extract.c static */ 300 ulg outsize; /* extract.c static */ 301 int reported_backslash; /* extract.c static */ 302 int disk_full; 303 int newfile; 304+ void **cover; /* used in extract.c for bomb detection */ 305 306 int didCRlast; /* fileio static */ 307 ulg numlines; /* fileio static: number of lines printed */ 308diff --git a/process.c b/process.c 309index a3c1a4d..208619c 100644 310--- a/process.c 311+++ b/process.c 312@@ -637,6 +637,13 @@ void free_G_buffers(__G) /* releases all memory allocated in global vars */ 313 } 314 #endif 315 316+ /* Free the cover span list and the cover structure. */ 317+ if (G.cover != NULL) { 318+ free(*(G.cover)); 319+ free(G.cover); 320+ G.cover = NULL; 321+ } 322+ 323 } /* end function free_G_buffers() */ 324 325 326@@ -1905,6 +1912,7 @@ int getZip64Data(__G__ ef_buf, ef_len) 327 328 #define Z64FLGS 0xffff 329 #define Z64FLGL 0xffffffff 330+ G.zip64 = FALSE; 331 332 if (ef_len == 0 || ef_buf == NULL) 333 return PK_COOL; 334@@ -1964,6 +1972,8 @@ int getZip64Data(__G__ ef_buf, ef_len) 335 G.crec.disk_number_start = (zuvl_t)makelong(offset + ef_buf); 336 offset += 4; 337 } 338+ 339+ G.zip64 = TRUE; 340 #if 0 341 break; /* Expect only one EF_PKSZ64 block. */ 342 #endif /* 0 */ 343diff --git a/unzip.h b/unzip.h 344index 5b2a326..ed24a5b 100644 345--- a/unzip.h 346+++ b/unzip.h 347@@ -645,6 +645,7 @@ typedef struct _Uzp_cdir_Rec { 348 #define PK_NOZIP 9 /* zipfile not found */ 349 #define PK_PARAM 10 /* bad or illegal parameters specified */ 350 #define PK_FIND 11 /* no files found */ 351+#define PK_BOMB 12 /* likely zip bomb */ 352 #define PK_DISK 50 /* disk full */ 353 #define PK_EOF 51 /* unexpected EOF */ 354 355-- 3562.22.0.vfs.1.1.57.gbaf16c8 357