Browse code

Re-applying libmspack adjustments to extract and scan files from archives that are non-standard and may appear to be corrupted or may in fact be corrupted. Differences from previous implementation: Fewer debug log messages. No normalization of filenames found in CAB archives. Implemented new param that enabled the best-effort attempt to extract possibly malformed archives. Used set_param() to enabled the FIXMSZIP option where it was previously hardcoded. Opted to provide the -Wno-unused-parameter CFLAG to the compiler in place of explicitely indicating unused parameters in each function. Omitted changes to mszipd.c and also omitted quantum decompression (qtmd.c) infinite loop protection because it appears to have been fixed in the newer libmspack.

Micah Snyder authored on 2018/09/17 08:24:40
Showing 10 changed files
... ...
@@ -177,7 +177,7 @@ endif
177 177
 # libmspack version:
178 178
 LIBMSPACK_VERSION = 0:7:1
179 179
 
180
-libclammspack_la_CFLAGS = -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -I@top_srcdir@/libclammspack/mspack
180
+libclammspack_la_CFLAGS = -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Wno-unused-parameter -I@top_srcdir@/libclammspack/mspack
181 181
 libclammspack_la_LDFLAGS = -version-info $(LIBMSPACK_VERSION) -no-undefined -export-symbols-regex '^mspack_'
182 182
 
183 183
 # if VERSIONSCRIPT
... ...
@@ -1177,7 +1177,7 @@ SUBDIRS = $(am__append_6)
1177 1177
 
1178 1178
 # libmspack version:
1179 1179
 LIBMSPACK_VERSION = 0:5:0
1180
-libclammspack_la_CFLAGS = -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -I@top_srcdir@/libclammspack/mspack
1180
+libclammspack_la_CFLAGS = -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Wno-unused-parameter -I@top_srcdir@/libclammspack/mspack
1181 1181
 libclammspack_la_LDFLAGS = -version-info $(LIBMSPACK_VERSION) -no-undefined -export-symbols-regex '^mspack_'
1182 1182
 
1183 1183
 # if VERSIONSCRIPT
... ...
@@ -369,6 +369,9 @@ int cli_scanmscab(cli_ctx *ctx, off_t sfx_offset)
369 369
 		return CL_EUNPACK;
370 370
 	}
371 371
 
372
+	cab_d->set_param(cab_d, MSCABD_PARAM_FIXMSZIP, 1);
373
+	cab_d->set_param(cab_d, MSCABD_PARAM_SALVAGE, 1);
374
+
372 375
 	cab_h = cab_d->open(cab_d, (char *)&mspack_fmap);
373 376
 	if (!cab_h) {
374 377
 		ret = CL_EFORMAT;
... ...
@@ -1,3 +1,18 @@
1
+18-09-16  Stuart Caie <kyzer@cabextract.org.uk>
2
+
3
+    * cabd.c: add new parameter, MSCABD_PARAM_SALVAGE, which makes CAB file
4
+    reading and extraction more lenient, to allow damaged or mangled CABs
5
+    to be extracted. When enabled, cabd->open() will no longer reject
6
+    cabinets entirely if they have files with invalid folder indicies
7
+    or filenames, but will simply ignore those files. Also, cabd->extract()
8
+    will limit files with invalid lengths to the maximum possible, rather
9
+    than reject them. Finally, invalid data block checksums will produce
10
+    warnings, but won't cause decompression to fail. It's still possible
11
+    for corrupted files to fail extraction, but hopefully more data can
12
+    be extracted before they do. This new parameter doesn't affect the
13
+    existing MSCABD_PARAM_FIXMSZIP parameter, which ignores MSZIP
14
+    decompression failures.
15
+
1 16
 2018-08-09  Stuart Caie <kyzer@cabextract.org.uk>
2 17
 
3 18
 	* Makefile.am: the test file cve-2015-4467-reset-interval-zero.chm is
... ...
@@ -67,7 +67,8 @@
67 67
  * more than 6144 bytes. Quantum has no documentation, but the largest
68 68
  * block seen in the wild is 337 bytes above uncompressed size.
69 69
  */
70
-#define CAB_BLOCKMAX (32768)
70
+#define CAB_BLOCKMAX (65535)
71
+#define CAB_BLOCKSTD (32768)
71 72
 #define CAB_INPUTMAX (CAB_BLOCKMAX+6144)
72 73
 
73 74
 /* There are no more than 65535 data blocks per folder, so a folder cannot
... ...
@@ -75,7 +76,7 @@
75 75
  * one folder, this is also their max offset, length and offset+length limit.
76 76
  */
77 77
 #define CAB_FOLDERMAX (65535)
78
-#define CAB_LENGTHMAX (CAB_BLOCKMAX * CAB_FOLDERMAX)
78
+#define CAB_LENGTHMAX UINT_MAX
79 79
 
80 80
 /* CAB compression definitions */
81 81
 
... ...
@@ -107,7 +108,7 @@ struct mscab_decompressor_p {
107 107
   struct mscab_decompressor base;
108 108
   struct mscabd_decompress_state *d;
109 109
   struct mspack_system *system;
110
-  int param[3]; /* !!! MATCH THIS TO NUM OF PARAMS IN MSPACK.H !!! */
110
+  int param[4]; /* !!! MATCH THIS TO NUM OF PARAMS IN MSPACK.H !!! */
111 111
   int error, read_error;
112 112
 };
113 113
 
... ...
@@ -72,7 +72,7 @@ static void cabd_close(
72 72
   struct mscab_decompressor *base, struct mscabd_cabinet *origcab);
73 73
 static int cabd_read_headers(
74 74
   struct mspack_system *sys, struct mspack_file *fh,
75
-  struct mscabd_cabinet_p *cab, off_t offset, int quiet);
75
+  struct mscabd_cabinet_p *cab, off_t offset, int salvage, int quiet);
76 76
 static char *cabd_read_string(
77 77
   struct mspack_system *sys, struct mspack_file *fh, int *error);
78 78
 
... ...
@@ -157,6 +157,7 @@ struct mscab_decompressor *
157 157
     self->param[MSCABD_PARAM_SEARCHBUF] = 32768;
158 158
     self->param[MSCABD_PARAM_FIXMSZIP]  = 0;
159 159
     self->param[MSCABD_PARAM_DECOMPBUF] = 4096;
160
+    self->param[MSCABD_PARAM_SALVAGE]   = 0;
160 161
   }
161 162
   return (struct mscab_decompressor *) self;
162 163
 }
... ...
@@ -200,7 +201,7 @@ static struct mscabd_cabinet *cabd_open(struct mscab_decompressor *base,
200 200
   if ((fh = sys->open(sys, filename, MSPACK_SYS_OPEN_READ))) {
201 201
     if ((cab = (struct mscabd_cabinet_p *) sys->alloc(sys, sizeof(struct mscabd_cabinet_p)))) {
202 202
       cab->base.filename = filename;
203
-      error = cabd_read_headers(sys, fh, cab, (off_t) 0, 0);
203
+      error = cabd_read_headers(sys, fh, cab, (off_t) 0, self->param[MSCABD_PARAM_SALVAGE], 0);
204 204
       if (error) {
205 205
 	cabd_close(base, (struct mscabd_cabinet *) cab);
206 206
 	cab = NULL;
... ...
@@ -305,9 +306,9 @@ static void cabd_close(struct mscab_decompressor *base,
305 305
 static int cabd_read_headers(struct mspack_system *sys,
306 306
 			     struct mspack_file *fh,
307 307
 			     struct mscabd_cabinet_p *cab,
308
-			     off_t offset, int quiet)
308
+			     off_t offset, int quiet, int salvage)
309 309
 {
310
-  int num_folders, num_files, folder_resv, i, x;
310
+  int num_folders, num_files, folder_resv, i, x, fidx, fidx_ok, read_string_errno = 0;
311 311
   struct mscabd_folder_p *fol, *linkfol = NULL;
312 312
   struct mscabd_file *file, *linkfile = NULL;
313 313
   unsigned char buf[64];
... ...
@@ -363,6 +364,7 @@ static int cabd_read_headers(struct mspack_system *sys,
363 363
 
364 364
   /* read the reserved-sizes part of header, if present */
365 365
   cab->base.flags = EndGetI16(&buf[cfhead_Flags]);
366
+
366 367
   if (cab->base.flags & cfheadRESERVE_PRESENT) {
367 368
     if (sys->read(fh, &buf[0], cfheadext_SIZEOF) != cfheadext_SIZEOF) {
368 369
       return MSPACK_ERR_READ;
... ...
@@ -390,14 +392,18 @@ static int cabd_read_headers(struct mspack_system *sys,
390 390
 
391 391
   /* read name and info of preceeding cabinet in set, if present */
392 392
   if (cab->base.flags & cfheadPREV_CABINET) {
393
-    cab->base.prevname = cabd_read_string(sys, fh, &x); if (x) return x;
394
-    cab->base.previnfo = cabd_read_string(sys, fh, &x); if (x) return x;
393
+    cab->base.prevname = cabd_read_string(sys, fh, &read_string_errno);
394
+    if (read_string_errno) return read_string_errno;
395
+    cab->base.previnfo = cabd_read_string(sys, fh, &read_string_errno);
396
+    if (read_string_errno) return read_string_errno;
395 397
   }
396 398
 
397 399
   /* read name and info of next cabinet in set, if present */
398 400
   if (cab->base.flags & cfheadNEXT_CABINET) {
399
-    cab->base.nextname = cabd_read_string(sys, fh, &x); if (x) return x;
400
-    cab->base.nextinfo = cabd_read_string(sys, fh, &x); if (x) return x;
401
+    cab->base.nextname = cabd_read_string(sys, fh, &read_string_errno);
402
+    if (read_string_errno) return read_string_errno;
403
+    cab->base.nextinfo = cabd_read_string(sys, fh, &read_string_errno);
404
+    if (read_string_errno) return read_string_errno;
401 405
   }
402 406
 
403 407
   /* read folders */
... ...
@@ -446,25 +452,29 @@ static int cabd_read_headers(struct mspack_system *sys,
446 446
     file->offset   = EndGetI32(&buf[cffile_FolderOffset]);
447 447
 
448 448
     /* set folder pointer */
449
-    x = EndGetI16(&buf[cffile_FolderIndex]);
450
-    if (x < cffileCONTINUED_FROM_PREV) {
451
-      /* normal folder index; count up to the correct folder. the folder
452
-       * pointer will be NULL if folder index is invalid */
453
-      struct mscabd_folder *ifol = cab->base.folders; 
454
-      while (x--) if (ifol) ifol = ifol->next;
455
-      file->folder = ifol;
456
-
457
-      if (!ifol) {
458
-	sys->free(file);
459
-	D(("invalid folder index"))
460
-	return MSPACK_ERR_DATAFORMAT;
449
+    fidx_ok = 1;
450
+    fidx = EndGetI16(&buf[cffile_FolderIndex]);
451
+    if (fidx < cffileCONTINUED_FROM_PREV) {
452
+      /* normal folder index; count up to the correct folder */
453
+      if (fidx < num_folders) {
454
+        struct mscabd_folder *ifol = cab->base.folders;
455
+        while (fidx--) if (ifol) ifol = ifol->next;
456
+        file->folder = ifol;
457
+      }
458
+      else {
459
+        file->folder = NULL;
460
+      }
461
+
462
+      if (!file->folder) {
463
+        D(("invalid folder index"))
464
+        fidx_ok = 0;
461 465
       }
462 466
     }
463 467
     else {
464 468
       /* either CONTINUED_TO_NEXT, CONTINUED_FROM_PREV or
465 469
        * CONTINUED_PREV_AND_NEXT */
466
-      if ((x == cffileCONTINUED_TO_NEXT) ||
467
-	  (x == cffileCONTINUED_PREV_AND_NEXT))
470
+      if ((fidx == cffileCONTINUED_TO_NEXT) ||
471
+          (fidx == cffileCONTINUED_PREV_AND_NEXT))
468 472
       {
469 473
 	/* get last folder */
470 474
 	struct mscabd_folder *ifol = cab->base.folders;
... ...
@@ -476,8 +486,8 @@ static int cabd_read_headers(struct mspack_system *sys,
476 476
 	if (!fol->merge_next) fol->merge_next = file;
477 477
       }
478 478
 
479
-      if ((x == cffileCONTINUED_FROM_PREV) ||
480
-	  (x == cffileCONTINUED_PREV_AND_NEXT))
479
+      if ((fidx == cffileCONTINUED_FROM_PREV) ||
480
+          (fidx == cffileCONTINUED_PREV_AND_NEXT))
481 481
       {
482 482
 	/* get first folder */
483 483
 	file->folder = cab->base.folders;
... ...
@@ -501,10 +511,11 @@ static int cabd_read_headers(struct mspack_system *sys,
501 501
     file->date_y = (x >> 9) + 1980;
502 502
 
503 503
     /* get filename */
504
-    file->filename = cabd_read_string(sys, fh, &x);
505
-    if (x) { 
504
+    file->filename = cabd_read_string(sys, fh, &read_string_errno);
505
+    if (read_string_errno || !fidx_ok) {
506
+      sys->free(file->filename);
506 507
       sys->free(file);
507
-      return x;
508
+      if (salvage) continue; else return read_string_errno;
508 509
     }
509 510
 
510 511
     /* link file entry into file list */
... ...
@@ -513,6 +524,13 @@ static int cabd_read_headers(struct mspack_system *sys,
513 513
     linkfile = file;
514 514
   }
515 515
 
516
+  if (cab->base.files == NULL) {
517
+    /* We never actually added any files to the file list.  Something went wrong.
518
+     * The file header may have been invalid */
519
+    D(("No files found, even though header claimed to have %d files", num_files))
520
+    return MSPACK_ERR_DATAFORMAT;
521
+  }
522
+
516 523
   return MSPACK_ERR_OK;
517 524
 }
518 525
 
... ...
@@ -631,7 +649,7 @@ static int cabd_find(struct mscab_decompressor_p *self, unsigned char *buf,
631 631
   struct mspack_system *sys = self->system;
632 632
   unsigned char *p, *pend, state = 0;
633 633
   unsigned int cablen_u32 = 0, foffset_u32 = 0;
634
-  int false_cabs = 0;
634
+  int false_cabs = 0, salvage = self->param[MSCABD_PARAM_SALVAGE];
635 635
 
636 636
 #if !LARGEFILE_SUPPORT
637 637
   /* detect 32-bit off_t overflow */
... ...
@@ -718,7 +736,7 @@ static int cabd_find(struct mscab_decompressor_p *self, unsigned char *buf,
718 718
 	    return MSPACK_ERR_NOMEMORY;
719 719
 	  }
720 720
 	  cab->base.filename = filename;
721
-	  if (cabd_read_headers(sys, fh, cab, caboff, 1)) {
721
+	  if (cabd_read_headers(sys, fh, cab, caboff, salvage, 1)) {
722 722
 	    /* destroy the failed cabinet */
723 723
 	    cabd_close((struct mscab_decompressor *) self,
724 724
 		       (struct mscabd_cabinet *) cab);
... ...
@@ -994,6 +1012,7 @@ static int cabd_extract(struct mscab_decompressor *base,
994 994
   struct mscabd_folder_p *fol;
995 995
   struct mspack_system *sys;
996 996
   struct mspack_file *fh;
997
+  off_t filelen, maxlen;
997 998
 
998 999
   if (!self) return MSPACK_ERR_ARGS;
999 1000
   if (!file) return self->error = MSPACK_ERR_ARGS;
... ...
@@ -1001,20 +1020,46 @@ static int cabd_extract(struct mscab_decompressor *base,
1001 1001
   sys = self->system;
1002 1002
   fol = (struct mscabd_folder_p *) file->folder;
1003 1003
 
1004
-  /* validate the file's offset and length */
1005
-  if ( (file->offset > CAB_LENGTHMAX) || (file->length > CAB_LENGTHMAX) ||
1006
-      ((file->offset + file->length) > CAB_LENGTHMAX))
1007
-  {
1004
+  /* if offset is beyond 2GB, nothing can be extracted */
1005
+  if (file->offset > CAB_LENGTHMAX) {
1008 1006
     return self->error = MSPACK_ERR_DATAFORMAT;
1009 1007
   }
1010 1008
 
1011
-  /* check if file can be extracted */
1012
-  if ((!fol) || (fol->merge_prev) ||
1013
-      (((file->offset + file->length) / CAB_BLOCKMAX) > fol->base.num_blocks))
1014
-  {
1009
+  /* if file claims to go beyond 2GB either error out,
1010
+   * or in salvage mode reduce file length so it fits 2GB limit
1011
+   */
1012
+  filelen = file->length;
1013
+  if (filelen > CAB_LENGTHMAX || (file->offset + filelen) > CAB_LENGTHMAX) {
1014
+    if (self->param[MSCABD_PARAM_SALVAGE]) {
1015
+      filelen = CAB_LENGTHMAX - file->offset;
1016
+    }
1017
+    else {
1018
+      return self->error = MSPACK_ERR_DATAFORMAT;
1019
+    }
1020
+  }
1021
+
1022
+  /* extraction impossible if no folder, or folder needs predecessor */
1023
+  if (!fol || fol->merge_prev) {
1015 1024
     sys->message(NULL, "ERROR; file \"%s\" cannot be extracted, "
1016
-		 "cabinet set is incomplete.", file->filename);
1017
-    return self->error = MSPACK_ERR_DATAFORMAT;
1025
+                 "cabinet set is incomplete", file->filename);
1026
+    return self->error = MSPACK_ERR_DECRUNCH;
1027
+  }
1028
+
1029
+  /* if file goes beyond what can be decoded, either error,
1030
+   * or in salvage mode reduce file length to what can be decoded
1031
+   */
1032
+  maxlen = fol->base.num_blocks * CAB_BLOCKMAX;
1033
+  if ((file->offset + filelen) > maxlen) {
1034
+    if (self->param[MSCABD_PARAM_SALVAGE]) {
1035
+      sys->message(NULL, "WARNING: can only extract first %"LD" bytes "
1036
+                   " of file \"%s\"", maxlen, file->filename);
1037
+      filelen = maxlen;
1038
+    }
1039
+    else {
1040
+      sys->message(NULL, "ERROR; file \"%s\" cannot be extracted, "
1041
+                   "cabinet set is incomplete", file->filename);
1042
+      return self->error = MSPACK_ERR_DECRUNCH;
1043
+    }
1018 1044
   }
1019 1045
 
1020 1046
   /* allocate generic decompression state */
... ...
@@ -1076,7 +1121,7 @@ static int cabd_extract(struct mscab_decompressor *base,
1076 1076
   self->error = MSPACK_ERR_OK;
1077 1077
 
1078 1078
   /* if file has more than 0 bytes */
1079
-  if (file->length) {
1079
+  if (filelen) {
1080 1080
     off_t bytes;
1081 1081
     int error;
1082 1082
     /* get to correct offset.
... ...
@@ -1085,15 +1130,17 @@ static int cabd_extract(struct mscab_decompressor *base,
1085 1085
      *   and pass back MSPACK_ERR_READ
1086 1086
      */
1087 1087
     self->d->outfh = NULL;
1088
-    if ((bytes = file->offset - self->d->offset)) {
1089
-      error = self->d->decompress(self->d->state, bytes);
1090
-      self->error = (error == MSPACK_ERR_READ) ? self->read_error : error;
1088
+    if ((self->d->comp_type & cffoldCOMPTYPE_MASK) != cffoldCOMPTYPE_LZX) {
1089
+      if ((bytes = file->offset - self->d->offset)) {
1090
+          error = self->d->decompress(self->d->state, bytes);
1091
+          self->error = (error == MSPACK_ERR_READ) ? self->read_error : error;
1092
+      }
1091 1093
     }
1092 1094
 
1093 1095
     /* if getting to the correct offset was error free, unpack file */
1094 1096
     if (!self->error) {
1095 1097
       self->d->outfh = fh;
1096
-      error = self->d->decompress(self->d->state, (off_t) file->length);
1098
+      error = self->d->decompress(self->d->state, filelen);
1097 1099
       self->error = (error == MSPACK_ERR_READ) ? self->read_error : error;
1098 1100
     }
1099 1101
   }
... ...
@@ -1182,8 +1229,9 @@ static int cabd_sys_read(struct mspack_file *file, void *buffer, int bytes) {
1182 1182
   struct mspack_system *sys = self->system;
1183 1183
   int avail, todo, outlen, ignore_cksum;
1184 1184
 
1185
-  ignore_cksum = self->param[MSCABD_PARAM_FIXMSZIP] &&
1186
-    ((self->d->comp_type & cffoldCOMPTYPE_MASK) == cffoldCOMPTYPE_MSZIP);
1185
+  ignore_cksum = self->param[MSCABD_PARAM_SALVAGE] ||
1186
+    (self->param[MSCABD_PARAM_FIXMSZIP] && 
1187
+     ((self->d->comp_type & cffoldCOMPTYPE_MASK) == cffoldCOMPTYPE_MSZIP));
1187 1188
 
1188 1189
   todo = bytes;
1189 1190
   while (todo > 0) {
... ...
@@ -1203,8 +1251,11 @@ static int cabd_sys_read(struct mspack_file *file, void *buffer, int bytes) {
1203 1203
 
1204 1204
       /* check if we're out of input blocks, advance block counter */
1205 1205
       if (self->d->block++ >= self->d->folder->base.num_blocks) {
1206
-	self->read_error = MSPACK_ERR_DATAFORMAT;
1207
-	break;
1206
+        if (!self->param[MSCABD_PARAM_SALVAGE])
1207
+          self->read_error = MSPACK_ERR_DATAFORMAT;
1208
+        else
1209
+          D(("Ran out of CAB input blocks prematurely"))
1210
+        break;
1208 1211
       }
1209 1212
 
1210 1213
       /* read a block */
... ...
@@ -1225,7 +1276,7 @@ static int cabd_sys_read(struct mspack_file *file, void *buffer, int bytes) {
1225 1225
 	  /* special LZX hack -- on the last block, inform LZX of the
1226 1226
 	   * size of the output data stream. */
1227 1227
 	  lzxd_set_output_length((struct lzxd_stream *) self->d->state, (off_t)
1228
-				 ((self->d->block-1) * CAB_BLOCKMAX + outlen));
1228
+				 ((self->d->block-1) * CAB_BLOCKSTD + outlen));
1229 1229
 	}
1230 1230
       }
1231 1231
       else {
... ...
@@ -1329,7 +1380,7 @@ static int cabd_sys_read_block(struct mspack_system *sys,
1329 1329
 
1330 1330
     /* advance to next member in the cabinet set */
1331 1331
     if (!(d->data = d->data->next)) {
1332
-      D(("ran out of splits in cabinet set"))
1332
+      sys->message(d->infh, "WARNING; ran out of cabinets in set. Are any missing?");
1333 1333
       return MSPACK_ERR_DATAFORMAT;
1334 1334
     }
1335 1335
 
... ...
@@ -1447,6 +1498,9 @@ static int cabd_param(struct mscab_decompressor *base, int param, int value) {
1447 1447
     if (value < 4) return MSPACK_ERR_ARGS;
1448 1448
     self->param[MSCABD_PARAM_DECOMPBUF] = value;
1449 1449
     break;
1450
+  case MSCABD_PARAM_SALVAGE:
1451
+    self->param[MSCABD_PARAM_SALVAGE] = value;
1452
+    break;
1450 1453
   default:
1451 1454
     return MSPACK_ERR_ARGS;
1452 1455
   }
... ...
@@ -536,7 +536,7 @@ static int chmd_fast_find(struct mschm_decompressor *base,
536 536
     struct mschm_decompressor_p *self = (struct mschm_decompressor_p *) base;
537 537
     struct mspack_system *sys;
538 538
     struct mspack_file *fh;
539
-    const unsigned char *chunk, *p, *end;
539
+    const unsigned char *chunk, *p = NULL, *end = NULL;
540 540
     int err = MSPACK_ERR_OK, result = -1;
541 541
     unsigned int n, sec;
542 542
 
... ...
@@ -399,7 +399,7 @@ int lzxd_decompress(struct lzxd_stream *lzx, off_t out_bytes) {
399 399
   register unsigned short sym;
400 400
 
401 401
   int match_length, length_footer, extra, verbatim_bits, bytes_todo;
402
-  int this_run, main_element, aligned_bits, j;
402
+  int this_run, main_element, aligned_bits, j, warned = 0;
403 403
   unsigned char *window, *runsrc, *rundest, buf[12];
404 404
   unsigned int frame_size=0, end_frame, match_offset, window_posn;
405 405
   unsigned int R0, R1, R2;
... ...
@@ -435,8 +435,12 @@ int lzxd_decompress(struct lzxd_stream *lzx, off_t out_bytes) {
435 435
     /* have we reached the reset interval? (if there is one?) */
436 436
     if (lzx->reset_interval && ((lzx->frame % lzx->reset_interval) == 0)) {
437 437
       if (lzx->block_remaining) {
438
-	D(("%d bytes remaining at reset interval", lzx->block_remaining))
439
-	return lzx->error = MSPACK_ERR_DECRUNCH;
438
+        /* this is a file format error, we can make a best effort to extract what we can */
439
+        D(("%d bytes remaining at reset interval", lzx->block_remaining))
440
+        if (!warned) {
441
+          lzx->sys->message(NULL, "WARNING; invalid reset interval detected during LZX decompression");
442
+          warned++;
443
+        }
440 444
       }
441 445
 
442 446
       /* re-read the intel header and reset the huffman lengths */
... ...
@@ -934,6 +934,13 @@ struct mscabd_file {
934 934
 #define MSCABD_PARAM_FIXMSZIP  (1)
935 935
 /** mscab_decompressor::set_param() parameter: size of decompression buffer */
936 936
 #define MSCABD_PARAM_DECOMPBUF (2)
937
+/** mscab_decompressor::set_param() parameter: salvage data from bad cabinets?
938
+ * If enabled, open() will skip file with bad folder indices or filenames
939
+ * rather than reject the whole cabinet, and extract() will limit rather than
940
+ * reject files with invalid offsets and lengths, and bad data block checksums
941
+ * will be ignored. Available only in CAB decoder version 2 and above.
942
+ */
943
+#define MSCABD_PARAM_SALVAGE   (3)
937 944
 
938 945
 /** TODO */
939 946
 struct mscab_compressor {
... ...
@@ -27,10 +27,13 @@ int mspack_version(int entity) {
27 27
     * - added mschmd_header::chunk_cache;
28 28
     */
29 29
   case MSPACK_VER_MSCHMD:
30
+  /* CAB decoder version 1 -> 2 changes:
31
+   * - added MSCABD_PARAM_SALVAGE
32
+   */
33
+  case MSPACK_VER_MSCABD:
30 34
     return 2;
31 35
   case MSPACK_VER_LIBRARY:
32 36
   case MSPACK_VER_SYSTEM:
33
-  case MSPACK_VER_MSCABD:
34 37
   case MSPACK_VER_MSSZDDD:
35 38
   case MSPACK_VER_MSKWAJD:
36 39
   case MSPACK_VER_MSOABD: