]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
iso9660: add a test for the 32-bit zisofs overflow case
authorDustin L. Howett <dustin@howett.net>
Wed, 24 Jun 2026 14:30:41 +0000 (09:30 -0500)
committerDustin L. Howett <dustin@howett.net>
Wed, 24 Jun 2026 15:51:48 +0000 (10:51 -0500)
Writing a file near 4 GiB causes an overflow when we calculate the
number of block pointers we need to store, which leads to us allocating
the wrong amount of memory to store them.

This is visible with ASan:

Exercising: libarchive 3.9.0dev zlib/1.3.1 liblzma/5.4.4 bz2lib/1.0.8 libzstd/1.5.6 cng/2.0 libb2/bundled
706: test_write_format_iso9660_zisofs_overflow
=================================================================
==40444==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x08c004b4 at pc 0x006acb3b bp 0x002edb44 sp 0x002edb38
WRITE of size 1 at 0x08c004b4 thread T0
    #0 0x006acb3a in archive_le32enc libarchive\archive_endian.h:219
    #1 0x006c81d0 in zisofs_write_to_temp libarchive\archive_write_set_format_iso9660.c:7760
    #2 0x006ca1bd in write_iso9660_data libarchive\archive_write_set_format_iso9660.c:1785

Makefile.am
libarchive/test/CMakeLists.txt
libarchive/test/test_write_format_iso9660_zisofs_overflow.c [new file with mode: 0644]

index b7c18158fa59c220986d43539c1244fc1e7711eb..49f1273201bdb148f4fc73d408c41281414d4324 100644 (file)
@@ -676,6 +676,7 @@ libarchive_test_SOURCES= \
        libarchive/test/test_write_format_iso9660_joliet_id.c \
        libarchive/test/test_write_format_iso9660_rockridge.c \
        libarchive/test/test_write_format_iso9660_zisofs.c \
+       libarchive/test/test_write_format_iso9660_zisofs_overflow.c \
        libarchive/test/test_write_format_mtree.c \
        libarchive/test/test_write_format_mtree_absolute.c \
        libarchive/test/test_write_format_mtree_absolute_path.c \
index 2bff4f48408f364c04ab152658033f4e9c26fe54..bf3ebf806b0c63421606dafce80639748f269c38 100644 (file)
@@ -304,6 +304,7 @@ IF(ENABLE_TEST)
     test_write_format_iso9660_joliet_id.c
     test_write_format_iso9660_rockridge.c
     test_write_format_iso9660_zisofs.c
+    test_write_format_iso9660_zisofs_overflow.c
     test_write_format_iso9660_bugs.c
     test_write_format_mtree.c
     test_write_format_mtree_absolute.c
diff --git a/libarchive/test/test_write_format_iso9660_zisofs_overflow.c b/libarchive/test/test_write_format_iso9660_zisofs_overflow.c
new file mode 100644 (file)
index 0000000..12be811
--- /dev/null
@@ -0,0 +1,127 @@
+/*-SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Dustin L. Howett
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "test.h"
+
+/*
+ * Writing a file to zisofs which is near the 4 GiB limit triggered an
+ * overflow and wraparound in calculating how much space we needed to
+ * store compressed block pointers, which led to an out-of-bounds write
+ * to the heap.
+ *
+ * This test can only fail under ASan.
+ */
+DEFINE_TEST(test_write_format_iso9660_zisofs_overflow)
+{
+       struct archive *a;
+       struct archive_entry *ae;
+       unsigned char *inbuff, *arcbuff;
+       size_t buffsize = 2048 * 288, writesize = 1048576, entrysize = UINT32_MAX - 32000, used;
+       int r;
+
+       /* ISO9660 format: Create a new archive in memory. */
+       assert((a = archive_write_new()) != NULL);
+       assertEqualIntA(a, 0, archive_write_set_format_iso9660(a));
+       assertEqualIntA(a, 0, archive_write_add_filter_none(a));
+       r = archive_write_set_option(a, NULL, "zisofs", "1");
+       if (r == ARCHIVE_FATAL) {
+               skipping("zisofs option not supported on this platform");
+               assertEqualInt(ARCHIVE_OK, archive_write_free(a));
+               return;
+       }
+
+       arcbuff = malloc(buffsize);
+       assert(arcbuff != NULL);
+       if (arcbuff == NULL)
+               return;
+
+       inbuff = calloc(writesize, 1);
+       assert(inbuff != NULL);
+       if (inbuff == NULL) {
+               free(arcbuff);
+               return;
+       }
+
+       assertEqualIntA(a, 0, archive_write_set_option(a, NULL, "pad", NULL));
+       assertEqualIntA(a, 0, archive_write_open_memory(a, arcbuff, buffsize, &used));
+
+       /*
+        * "file1" is almost exactly 4 GiB; it's enough to trigger a block pointer counting
+        * overflow in zisofs.
+        */
+       assert((ae = archive_entry_new()) != NULL);
+       archive_entry_set_atime(ae, 2, 20);
+       archive_entry_set_birthtime(ae, 3, 30);
+       archive_entry_set_ctime(ae, 4, 40);
+       archive_entry_set_mtime(ae, 5, 50);
+       archive_entry_copy_pathname(ae, "file1");
+       archive_entry_set_mode(ae, S_IFREG | 0755);
+       archive_entry_set_size(ae, entrysize);
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
+       archive_entry_free(ae);
+       while (entrysize > writesize) {
+               assertEqualIntA(a, writesize, archive_write_data(a, inbuff, writesize));
+               entrysize -= writesize;
+       }
+       /* remainder */
+       if (0 != (writesize = entrysize))
+               assertEqualIntA(a, writesize, archive_write_data(a, inbuff, writesize));
+
+       /* Close out the archive. */
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
+       assertEqualInt(ARCHIVE_OK, archive_write_free(a));
+
+       failure("The ISO image size should be 589824 bytes.");
+       assertEqualInt(used, 2048 * 288);
+
+       /*
+        * Read ISO image (basic sanity check).
+        */
+       assert((a = archive_read_new()) != NULL);
+       assertEqualIntA(a, 0, archive_read_support_format_all(a));
+       assertEqualIntA(a, 0, archive_read_support_filter_all(a));
+       assertEqualIntA(a, 0, archive_read_open_memory(a, arcbuff, used));
+
+       /*
+        * Skip the root directory
+        */
+       assertEqualIntA(a, 0, archive_read_next_header(a, &ae));
+
+       /*
+        * file1
+        */
+       assertEqualIntA(a, 0, archive_read_next_header(a, &ae));
+
+       /*
+        * Verify the end of the archive.
+        */
+       assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae));
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a));
+       assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+
+       free(arcbuff);
+       free(inbuff);
+}