1 # --- T2-COPYRIGHT-NOTE-BEGIN ---
2 # T2 SDE: package/*/unzip/CVE-2019-13232.patch
3 # Copyright (C) 2021 The T2 SDE Project
5 # This Copyright note is generated by scripts/Create-CopyPatch,
6 # more information can be found in the files COPYING and README.
8 # This patch file is dual-licensed. It is available under the license the
9 # patched project is licensed under, as long as it is an OpenSource license
10 # as defined at http://www.opensource.org/ (e.g. BSD, X11) or under the terms
11 # of the GNU General Public License version 2 as used by the T2 SDE.
12 # --- T2-COPYRIGHT-NOTE-END ---
14 From 47b3ceae397d21bf822bc2ac73052a4b1daf8e1c Mon Sep 17 00:00:00 2001
15 From: Mark Adler <madler@alumni.caltech.edu>
16 Date: Tue, 11 Jun 2019 22:01:18 -0700
17 Subject: [PATCH] Detect and reject a zip bomb using overlapped entries.
19 This detects an invalid zip file that has at least one entry that
20 overlaps with another entry or with the central directory to the
21 end of the file. A Fifield zip bomb uses overlapped local entries
22 to vastly increase the potential inflation ratio. Such an invalid
25 See https://www.bamsoftware.com/hacks/zipbomb/ for David Fifield's
26 analysis, construction, and examples of such zip bombs.
28 The detection maintains a list of covered spans of the zip files
29 so far, where the central directory to the end of the file and any
30 bytes preceding the first entry at zip file offset zero are
31 considered covered initially. Then as each entry is decompressed
32 or tested, it is considered covered. When a new entry is about to
33 be processed, its initial offset is checked to see if it is
34 contained by a covered span. If so, the zip file is rejected as
37 This commit depends on a preceding commit: "Fix bug in
38 undefer_input() that misplaced the input state."
40 extract.c | 190 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
45 5 files changed, 205 insertions(+), 1 deletion(-)
47 diff --git a/extract.c b/extract.c
48 index 1acd769..0973a33 100644
51 @@ -319,6 +319,125 @@ static ZCONST char Far UnsupportedExtraField[] =
52 "\nerror: unsupported extra-field compression type (%u)--skipping\n";
53 static ZCONST char Far BadExtraFieldCRC[] =
54 "error [%s]: bad extra-field CRC %08lx (should be %08lx)\n";
55 +static ZCONST char Far NotEnoughMemCover[] =
56 + "error: not enough memory for bomb detection\n";
57 +static ZCONST char Far OverlappedComponents[] =
58 + "error: invalid zip file with overlapped components (possible zip bomb)\n";
64 +/* A growable list of spans. */
65 +typedef zoff_t bound_t;
67 + bound_t beg; /* start of the span */
68 + bound_t end; /* one past the end of the span */
71 + span_t *span; /* allocated, distinct, and sorted list of spans */
72 + size_t num; /* number of spans in the list */
73 + size_t max; /* allocated number of spans (num <= max) */
77 + * Return the index of the first span in cover whose beg is greater than val.
78 + * If there is no such span, then cover->num is returned.
80 +static size_t cover_find(cover, val)
84 + size_t lo = 0, hi = cover->num;
86 + size_t mid = (lo + hi) >> 1;
87 + if (val < cover->span[mid].beg)
95 +/* Return true if val lies within any one of the spans in cover. */
96 +static int cover_within(cover, val)
100 + size_t pos = cover_find(cover, val);
101 + return pos > 0 && val < cover->span[pos - 1].end;
105 + * Add a new span to the list, but only if the new span does not overlap any
106 + * spans already in the list. The new span covers the values beg..end-1. beg
107 + * must be less than end.
109 + * Keep the list sorted and merge adjacent spans. Grow the allocated space for
110 + * the list as needed. On success, 0 is returned. If the new span overlaps any
111 + * existing spans, then 1 is returned and the new span is not added to the
112 + * list. If the new span is invalid because beg is greater than or equal to
113 + * end, then -1 is returned. If the list needs to be grown but the memory
114 + * allocation fails, then -2 is returned.
116 +static int cover_add(cover, beg, end)
125 + /* The new span is invalid. */
128 + /* Find where the new span should go, and make sure that it does not
129 + overlap with any existing spans. */
130 + pos = cover_find(cover, beg);
131 + if ((pos > 0 && beg < cover->span[pos - 1].end) ||
132 + (pos < cover->num && end > cover->span[pos].beg))
135 + /* Check for adjacencies. */
136 + prec = pos > 0 && beg == cover->span[pos - 1].end;
137 + foll = pos < cover->num && end == cover->span[pos].beg;
138 + if (prec && foll) {
139 + /* The new span connects the preceding and following spans. Merge the
140 + following span into the preceding span, and delete the following
142 + cover->span[pos - 1].end = cover->span[pos].end;
144 + memmove(cover->span + pos, cover->span + pos + 1,
145 + (cover->num - pos) * sizeof(span_t));
148 + /* The new span is adjacent only to the preceding span. Extend the end
149 + of the preceding span. */
150 + cover->span[pos - 1].end = end;
152 + /* The new span is adjacent only to the following span. Extend the
153 + beginning of the following span. */
154 + cover->span[pos].beg = beg;
156 + /* The new span has gaps between both the preceding and the following
157 + spans. Assure that there is room and insert the span. */
158 + if (cover->num == cover->max) {
159 + size_t max = cover->max == 0 ? 16 : cover->max << 1;
160 + span_t *span = realloc(cover->span, max * sizeof(span_t));
163 + cover->span = span;
166 + memmove(cover->span + pos + 1, cover->span + pos,
167 + (cover->num - pos) * sizeof(span_t));
169 + cover->span[pos].beg = beg;
170 + cover->span[pos].end = end;
177 @@ -374,6 +493,29 @@ int extract_or_test_files(__G) /* return PK-type error code */
179 #endif /* !SFX || SFX_EXDIR */
181 + /* One more: initialize cover structure for bomb detection. Start with a
182 + span that covers the central directory though the end of the file. */
183 + if (G.cover == NULL) {
184 + G.cover = malloc(sizeof(cover_t));
185 + if (G.cover == NULL) {
186 + Info(slide, 0x401, ((char *)slide,
187 + LoadFarString(NotEnoughMemCover)));
190 + ((cover_t *)G.cover)->span = NULL;
191 + ((cover_t *)G.cover)->max = 0;
193 + ((cover_t *)G.cover)->num = 0;
194 + if ((G.extra_bytes != 0 &&
195 + cover_add((cover_t *)G.cover, 0, G.extra_bytes) != 0) ||
196 + cover_add((cover_t *)G.cover,
197 + G.extra_bytes + G.ecrec.offset_start_central_directory,
199 + Info(slide, 0x401, ((char *)slide,
200 + LoadFarString(NotEnoughMemCover)));
204 /*---------------------------------------------------------------------------
205 The basic idea of this function is as follows. Since the central di-
206 rectory lies at the end of the zipfile and the member files lie at the
207 @@ -591,7 +733,8 @@ int extract_or_test_files(__G) /* return PK-type error code */
208 if (error > error_in_archive)
209 error_in_archive = error;
210 /* ...and keep going (unless disk full or user break) */
211 - if (G.disk_full > 1 || error_in_archive == IZ_CTRLC) {
212 + if (G.disk_full > 1 || error_in_archive == IZ_CTRLC ||
213 + error == PK_BOMB) {
214 /* clear reached_end to signal premature stop ... */
216 /* ... and cancel scanning the central directory */
217 @@ -1060,6 +1203,11 @@ static int extract_or_test_entrylist(__G__ numchunk,
219 /* seek_zipf(__G__ pInfo->offset); */
220 request = G.pInfo->offset + G.extra_bytes;
221 + if (cover_within((cover_t *)G.cover, request)) {
222 + Info(slide, 0x401, ((char *)slide,
223 + LoadFarString(OverlappedComponents)));
226 inbuf_offset = request % INBUFSIZ;
227 bufstart = request - inbuf_offset;
229 @@ -1591,6 +1739,18 @@ static int extract_or_test_entrylist(__G__ numchunk,
230 return IZ_CTRLC; /* cancel operation by user request */
233 + error = cover_add((cover_t *)G.cover, request,
234 + G.cur_zipfile_bufstart + (G.inptr - G.inbuf));
236 + Info(slide, 0x401, ((char *)slide,
237 + LoadFarString(NotEnoughMemCover)));
241 + Info(slide, 0x401, ((char *)slide,
242 + LoadFarString(OverlappedComponents)));
245 #ifdef MACOS /* MacOS is no preemptive OS, thus call event-handling by hand */
248 @@ -1992,6 +2152,34 @@ static int extract_or_test_member(__G) /* return PK-type error code */
253 + if ((G.lrec.general_purpose_bit_flag & 8) != 0) {
254 + /* skip over data descriptor (harder than it sounds, due to signature
257 +# define SIG 0x08074b50
258 +# define LOW 0xffffffff
260 + unsigned shy = 12 - readbuf((char *)buf, 12);
261 + ulg crc = shy ? 0 : makelong(buf);
262 + ulg clen = shy ? 0 : makelong(buf + 4);
263 + ulg ulen = shy ? 0 : makelong(buf + 8); /* or high clen if ZIP64 */
264 + if (crc == SIG && /* if not SIG, no signature */
265 + (G.lrec.crc32 != SIG || /* if not SIG, have signature */
266 + (clen == SIG && /* if not SIG, no signature */
267 + ((G.lrec.csize & LOW) != SIG || /* if not SIG, have signature */
268 + (ulen == SIG && /* if not SIG, no signature */
269 + (G.zip64 ? G.lrec.csize >> 32 : G.lrec.ucsize) != SIG
270 + /* if not SIG, have signature */
272 + /* skip four more bytes to account for signature */
273 + shy += 4 - readbuf((char *)buf, 4);
275 + shy += 8 - readbuf((char *)buf, 8); /* skip eight more for ZIP64 */
282 } /* end function extract_or_test_member() */
283 diff --git a/globals.c b/globals.c
284 index fa8cca5..1e0f608 100644
287 @@ -181,6 +181,7 @@ Uz_Globs *globalsCtor()
288 # if (!defined(NO_TIMESTAMPS))
289 uO.D_flag=1; /* default to '-D', no restoration of dir timestamps */
291 + G.cover = NULL; /* not allocated yet */
295 diff --git a/globals.h b/globals.h
296 index 11b7215..2bdcdeb 100644
299 @@ -260,12 +260,15 @@ typedef struct Globals {
300 ecdir_rec ecrec; /* used in unzip.c, extract.c */
301 z_stat statbuf; /* used by main, mapname, check_for_newer */
303 + int zip64; /* true if Zip64 info in extra field */
306 uch *outbufptr; /* extract.c static */
307 ulg outsize; /* extract.c static */
308 int reported_backslash; /* extract.c static */
311 + void **cover; /* used in extract.c for bomb detection */
313 int didCRlast; /* fileio static */
314 ulg numlines; /* fileio static: number of lines printed */
315 diff --git a/process.c b/process.c
316 index 1e9a1e1..d2e4dc3 100644
319 @@ -637,6 +637,13 @@ void free_G_buffers(__G) /* releases all memory allocated in global vars */
323 + /* Free the cover span list and the cover structure. */
324 + if (G.cover != NULL) {
330 } /* end function free_G_buffers() */
333 @@ -1890,6 +1897,8 @@ int getZip64Data(__G__ ef_buf, ef_len)
334 #define Z64FLGS 0xffff
335 #define Z64FLGL 0xffffffff
339 if (ef_len == 0 || ef_buf == NULL)
342 @@ -1927,6 +1936,8 @@ int getZip64Data(__G__ ef_buf, ef_len)
344 break; /* Expect only one EF_PKSZ64 block. */
350 /* Skip this extra field block. */
351 diff --git a/unzip.h b/unzip.h
352 index 5b2a326..ed24a5b 100644
355 @@ -645,6 +645,7 @@ typedef struct _Uzp_cdir_Rec {
356 #define PK_NOZIP 9 /* zipfile not found */
357 #define PK_PARAM 10 /* bad or illegal parameters specified */
358 #define PK_FIND 11 /* no files found */
359 +#define PK_BOMB 12 /* likely zip bomb */
360 #define PK_DISK 50 /* disk full */
361 #define PK_EOF 51 /* unexpected EOF */
363 From 6d351831be705cc26d897db44f878a978f4138fc Mon Sep 17 00:00:00 2001
364 From: Mark Adler <madler@alumni.caltech.edu>
365 Date: Thu, 25 Jul 2019 20:43:17 -0700
366 Subject: [PATCH] Do not raise a zip bomb alert for a misplaced central
369 There is a zip-like file in the Firefox distribution, omni.ja,
370 which is a zip container with the central directory placed at the
371 start of the file instead of after the local entries as required
372 by the zip standard. This commit marks the actual location of the
373 central directory, as well as the end of central directory records,
374 as disallowed locations. This now permits such containers to not
375 raise a zip bomb alert, where in fact there are no overlaps.
377 extract.c | 25 +++++++++++++++++++------
379 unzpriv.h | 10 ++++++++++
380 3 files changed, 35 insertions(+), 6 deletions(-)
382 diff --git a/extract.c b/extract.c
383 index 0973a33..1b73cb0 100644
386 @@ -493,8 +493,11 @@ int extract_or_test_files(__G) /* return PK-type error code */
388 #endif /* !SFX || SFX_EXDIR */
390 - /* One more: initialize cover structure for bomb detection. Start with a
391 - span that covers the central directory though the end of the file. */
392 + /* One more: initialize cover structure for bomb detection. Start with
393 + spans that cover any extra bytes at the start, the central directory,
394 + the end of central directory record (including the Zip64 end of central
395 + directory locator, if present), and the Zip64 end of central directory
396 + record, if present. */
397 if (G.cover == NULL) {
398 G.cover = malloc(sizeof(cover_t));
399 if (G.cover == NULL) {
400 @@ -506,15 +509,25 @@ int extract_or_test_files(__G) /* return PK-type error code */
401 ((cover_t *)G.cover)->max = 0;
403 ((cover_t *)G.cover)->num = 0;
404 - if ((G.extra_bytes != 0 &&
405 - cover_add((cover_t *)G.cover, 0, G.extra_bytes) != 0) ||
406 - cover_add((cover_t *)G.cover,
407 + if (cover_add((cover_t *)G.cover,
408 G.extra_bytes + G.ecrec.offset_start_central_directory,
410 + G.extra_bytes + G.ecrec.offset_start_central_directory +
411 + G.ecrec.size_central_directory) != 0) {
412 Info(slide, 0x401, ((char *)slide,
413 LoadFarString(NotEnoughMemCover)));
416 + if ((G.extra_bytes != 0 &&
417 + cover_add((cover_t *)G.cover, 0, G.extra_bytes) != 0) ||
418 + (G.ecrec.have_ecr64 &&
419 + cover_add((cover_t *)G.cover, G.ecrec.ec64_start,
420 + G.ecrec.ec64_end) != 0) ||
421 + cover_add((cover_t *)G.cover, G.ecrec.ec_start,
422 + G.ecrec.ec_end) != 0) {
423 + Info(slide, 0x401, ((char *)slide,
424 + LoadFarString(OverlappedComponents)));
428 /*---------------------------------------------------------------------------
429 The basic idea of this function is as follows. Since the central di-
430 diff --git a/process.c b/process.c
431 index d2e4dc3..d75d405 100644
434 @@ -1408,6 +1408,10 @@ static int find_ecrec64(__G__ searchlen) /* return PK-class error */
436 /* Now, we are (almost) sure that we have a Zip64 archive. */
437 G.ecrec.have_ecr64 = 1;
438 + G.ecrec.ec_start -= ECLOC64_SIZE+4;
439 + G.ecrec.ec64_start = ecrec64_start_offset;
440 + G.ecrec.ec64_end = ecrec64_start_offset +
441 + 12 + makeint64(&byterec[ECREC64_LENGTH]);
443 /* Update the "end-of-central-dir offset" for later checks. */
444 G.real_ecrec_offset = ecrec64_start_offset;
445 @@ -1542,6 +1546,8 @@ static int find_ecrec(__G__ searchlen) /* return PK-class error */
446 makelong(&byterec[OFFSET_START_CENTRAL_DIRECTORY]);
447 G.ecrec.zipfile_comment_length =
448 makeword(&byterec[ZIPFILE_COMMENT_LENGTH]);
449 + G.ecrec.ec_start = G.real_ecrec_offset;
450 + G.ecrec.ec_end = G.ecrec.ec_start + 22 + G.ecrec.zipfile_comment_length;
452 /* Now, we have to read the archive comment, BEFORE the file pointer
453 is moved away backwards to seek for a Zip64 ECLOC64 structure.
454 diff --git a/unzpriv.h b/unzpriv.h
455 index dc9eff5..297b3c7 100644
458 @@ -2185,6 +2185,16 @@ typedef struct VMStimbuf {
459 int have_ecr64; /* valid Zip64 ecdir-record exists */
460 int is_zip64_archive; /* Zip64 ecdir-record is mandatory */
461 ush zipfile_comment_length;
462 + zusz_t ec_start, ec_end; /* offsets of start and end of the
463 + end of central directory record,
464 + including if present the Zip64
465 + end of central directory locator,
466 + which immediately precedes the
467 + end of central directory record */
468 + zusz_t ec64_start, ec64_end; /* if have_ecr64 is true, then these
469 + are the offsets of the start and
470 + end of the Zip64 end of central
471 + directory record */
475 From 41beb477c5744bc396fa1162ee0c14218ec12213 Mon Sep 17 00:00:00 2001
476 From: Mark Adler <madler@alumni.caltech.edu>
477 Date: Mon, 27 May 2019 08:20:32 -0700
478 Subject: [PATCH] Fix bug in undefer_input() that misplaced the input state.
482 1 file changed, 3 insertions(+), 1 deletion(-)
484 diff --git a/fileio.c b/fileio.c
485 index c042987..bc00d74 100644
488 @@ -530,8 +530,10 @@ void undefer_input(__G)
489 * This condition was checked when G.incnt_leftover was set > 0 in
490 * defer_leftover_input(), and it is NOT allowed to touch G.csize
491 * before calling undefer_input() when (G.incnt_leftover > 0)
492 - * (single exception: see read_byte()'s "G.csize <= 0" handling) !!
493 + * (single exception: see readbyte()'s "G.csize <= 0" handling) !!
497 G.incnt = G.incnt_leftover + (int)G.csize;
498 G.inptr = G.inptr_leftover - (int)G.csize;
499 G.incnt_leftover = 0;