zip-reader: Report corrupted Zip archives.
authorBen Pfaff <blp@cs.stanford.edu>
Mon, 20 Feb 2023 20:01:16 +0000 (12:01 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Mon, 20 Feb 2023 20:01:16 +0000 (12:01 -0800)
src/libpspp/zip-reader.c
tests/libpspp/zip.at

index 00249e0bae41be64128720410c0079c7e819a230..38cba29566e1615fa5a558396981aad21906ad0b 100644 (file)
@@ -32,6 +32,7 @@
 #include "libpspp/integer-format.h"
 #include "libpspp/str.h"
 
+#include "gl/crc.h"
 #include "gl/xalloc.h"
 
 #include "gettext.h"
@@ -46,6 +47,10 @@ struct zip_member
   uint32_t offset;            /* Starting offset in file. */
   uint32_t comp_size;         /* Length of member file data, in bytes. */
   uint32_t ucomp_size;        /* Uncompressed length of member file data, in bytes. */
+
+  uint32_t expected_crc;
+  uint32_t accumulated_crc;
+
   const struct decompressor *decompressor;
 
   size_t bytes_unread;       /* Number of bytes left in the member available for reading */
@@ -93,6 +98,7 @@ struct zip_entry
   uint32_t offset;            /* Starting offset in file. */
   uint32_t comp_size;         /* Length of member file data, in bytes. */
   uint32_t ucomp_size;        /* Uncompressed length of member file data, in bytes. */
+  uint32_t expected_crc;      /* CRC32 of uncompressed data. */
   char *name;                 /* Name of member file. */
 };
 
@@ -230,10 +236,19 @@ zip_member_read (struct zip_member *zm, void *buf, size_t bytes)
     return 0;
 
   int bytes_read = zm->decompressor->read (zm, buf, bytes);
-  if (bytes_read < 0)
+  if (bytes_read <= 0)
     return bytes_read;
 
   zm->bytes_unread -= bytes_read;
+  zm->accumulated_crc = crc32_update (zm->accumulated_crc, buf, bytes_read);
+  if (!zm->bytes_unread && zm->accumulated_crc != zm->expected_crc)
+    {
+      zm->error = xasprintf (_("%s: corrupt archive reading member \"%s\": "
+                               "bad CRC %#"PRIx32" (expected %"PRIx32")"),
+                             zm->file_name, zm->member_name,
+                             zm->accumulated_crc, zm->expected_crc);
+      return -1;
+    }
 
   return bytes_read;
 }
@@ -291,7 +306,7 @@ zip_header_read_next (FILE *file, const char *file_name,
   get_u16 (file);       /* comp_type */
   get_u16 (file);       /* time */
   get_u16 (file);       /* date */
-  get_u32 (file);       /* expected_crc */
+  ze->expected_crc = get_u32 (file);
   ze->comp_size = get_u32 (file);
   ze->ucomp_size = get_u32 (file);
   uint16_t nlen = get_u16 (file);
@@ -444,16 +459,16 @@ zip_member_open (struct zip_reader *zr, const char *member,
                        zr->file_name, strerror (errno));
 
   struct zip_member *zm = xmalloc (sizeof *zm);
-  zm->file_name = xstrdup (zr->file_name);
-  zm->member_name = xstrdup (member);
-  zm->fp = fp;
-  zm->offset = ze->offset;
-  zm->comp_size = ze->comp_size;
-  zm->ucomp_size = ze->ucomp_size;
-  zm->decompressor = NULL;
-  zm->bytes_unread = ze->ucomp_size;
-  zm->aux = NULL;
-  zm->error = NULL;
+  *zm = (struct zip_member) {
+    .file_name = xstrdup (zr->file_name),
+    .member_name = xstrdup (member),
+    .fp = fp,
+    .offset = ze->offset,
+    .comp_size = ze->comp_size,
+    .ucomp_size = ze->ucomp_size,
+    .bytes_unread = ze->ucomp_size,
+    .expected_crc = ze->expected_crc,
+  };
 
   char *error;
   if (0 != fseeko (zm->fp, zm->offset, SEEK_SET))
index 5b21d50f5854791e06b60a6424c069a170da3ddc..bcc550457ab718a96f0280427d010a4ec2fd29d0 100644 (file)
@@ -57,6 +57,29 @@ AT_CHECK([
 ])
 AT_CLEANUP
 
+AT_SETUP([zip - detect corruption on unzip])
+AT_KEYWORDS([compression])
+mkdir write
+cd write
+AT_DATA([data.txt], [xyzzy
+])
+AT_CHECK([zip-test w ../foo.zip data.txt])
+cd ..
+
+mkdir extract
+cd extract
+AT_CHECK([zip-test r ../foo.zip data.txt])
+AT_CHECK([cat data.txt], [0], [xyzzy
+])
+cd ..
+
+mkdir error
+cd error
+sed 's/xyzzy/XYZZY/' < ../foo.zip > ../corrupted.zip
+AT_CHECK([zip-test r ../corrupted.zip data.txt], [1], [], [dnl
+Unzip failed: ../corrupted.zip: corrupt archive reading member "data.txt": bad CRC 0x2a2bcd20 (expected e1bd2a6e)
+])
+AT_CLEANUP
 
 AT_SETUP([zip to pipe])
 AT_KEYWORDS([compression])