Browse code

Fix ZIP parser issue recording empty file entries

If csize (and usize) are 0, like with a directory or other empty file
entry, then the functionionality to record file record information when
indexing the central directory and each associated file record will
neglect to store the `local_header_offset` or `local_header_size`.
That causes problems later after sorting the file records and then
checking for overlapping files.

CLAM-2884

Val S. authored on 2025/10/13 08:12:06
Showing 1 changed files
... ...
@@ -717,17 +717,35 @@ static cl_error_t parse_local_file_header(
717 717
     zip += LOCAL_HEADER_elen;
718 718
     bytes_remaining -= LOCAL_HEADER_elen;
719 719
 
720
-    if (!csize) { /* FIXME: what's used for method0 files? csize or usize? Nothing in the specs, needs testing */
721
-        cli_dbgmsg("cli_unzip: local header - skipping empty file\n");
722
-    } else {
723
-        if (bytes_remaining < csize) {
724
-            cli_dbgmsg("cli_unzip: local header - stream out of file\n");
725
-            status = CL_EPARSE;
726
-            goto done;
727
-        }
720
+    if (bytes_remaining < csize) {
721
+        cli_dbgmsg("cli_unzip: local header - stream out of file\n");
722
+        status = CL_EPARSE;
723
+        goto done;
724
+    }
728 725
 
726
+    if (NULL != record) {
729 727
         /* Don't actually unzip if we're just collecting the file record information (offset, sizes) */
730
-        if (NULL == record) {
728
+        if (NULL == original_filename) {
729
+            record->original_filename = NULL;
730
+        } else {
731
+            record->original_filename = CLI_STRNDUP(original_filename, strlen(original_filename));
732
+        }
733
+        record->local_header_offset = loff;
734
+        record->local_header_size   = zip - local_header;
735
+        record->compressed_size     = csize;
736
+        record->uncompressed_size   = usize;
737
+        record->method              = LOCAL_HEADER_method;
738
+        record->flags               = LOCAL_HEADER_flags;
739
+        record->encrypted           = (LOCAL_HEADER_flags & F_ENCR) ? 1 : 0;
740
+
741
+        status = CL_SUCCESS;
742
+    } else {
743
+        /*
744
+         * Unzip or decompress & then unzip.
745
+         */
746
+        if (!csize) { /* FIXME: what's used for method0 files? csize or usize? Nothing in the specs, needs testing */
747
+            cli_dbgmsg("cli_unzip: local header - skipping empty file\n");
748
+        } else {
731 749
             zip = fmap_need_ptr_once(ctx->fmap, zip, csize);
732 750
             if (NULL == zip) {
733 751
                 cli_dbgmsg("cli_unzip: local header - data out of file\n");
... ...
@@ -751,27 +769,12 @@ static cl_error_t parse_local_file_header(
751 751
                     goto done;
752 752
                 }
753 753
             }
754
-        } else {
755
-            if (NULL == original_filename) {
756
-                record->original_filename = NULL;
757
-            } else {
758
-                record->original_filename = CLI_STRNDUP(original_filename, strlen(original_filename));
759
-            }
760
-            record->local_header_offset = loff;
761
-            record->local_header_size   = zip - local_header;
762
-            record->compressed_size     = csize;
763
-            record->uncompressed_size   = usize;
764
-            record->method              = LOCAL_HEADER_method;
765
-            record->flags               = LOCAL_HEADER_flags;
766
-            record->encrypted           = (LOCAL_HEADER_flags & F_ENCR) ? 1 : 0;
767
-
768
-            status = CL_SUCCESS;
769 754
         }
770
-
771
-        zip += csize;
772
-        bytes_remaining -= csize;
773 755
     }
774 756
 
757
+    zip += csize;
758
+    bytes_remaining -= csize;
759
+
775 760
     if (LOCAL_HEADER_flags & F_USEDD) {
776 761
         if (bytes_remaining < 12) {
777 762
             cli_dbgmsg("cli_unzip: local header - data desc out of file\n");
... ...
@@ -910,8 +913,8 @@ static cl_error_t parse_central_directory_file_header(
910 910
 
911 911
     central_header = fmap_need_off(ctx->fmap, central_file_header_offset, SIZEOF_CENTRAL_HEADER);
912 912
     if (NULL == central_header) {
913
-        cli_dbgmsg("cli_unzip: central header - file header offset out of file\n");
914
-        status = CL_EPARSE;
913
+        cli_dbgmsg("cli_unzip: central header - reached end of central directory.\n");
914
+        status = CL_BREAK;
915 915
         goto done;
916 916
     }
917 917
 
... ...
@@ -1310,7 +1313,8 @@ cl_error_t index_local_file_headers_within_bounds(
1310 1310
     index             = *num_records;
1311 1311
 
1312 1312
     if (start_offset > fsize || end_offset > fsize || start_offset > end_offset) {
1313
-        cli_errmsg("index_local_file_headers_within_bounds: Invalid offset arguments\n");
1313
+        cli_errmsg("index_local_file_headers_within_bounds: Invalid offset arguments: start_offset=%u, end_offset=%u, fsize=%u\n",
1314
+                   start_offset, end_offset, fsize);
1314 1315
         status = CL_EPARSE;
1315 1316
         goto done;
1316 1317
     }