]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
[ISO9660] Fix NULL deref and Joliet ID overflow
authorTim Kientzle <tkientzle@apple.com>
Fri, 8 May 2026 22:41:18 +0000 (15:41 -0700)
committerTim Kientzle <tkientzle@apple.com>
Fri, 8 May 2026 22:42:20 +0000 (15:42 -0700)
Makefile.am
libarchive/archive_write_set_format_iso9660.c
libarchive/test/CMakeLists.txt
libarchive/test/test_write_format_iso9660_bugs.c [new file with mode: 0644]
libarchive/test/test_write_format_iso9660_joliet_overflow.bin.uu [new file with mode: 0644]
libarchive/test/test_write_format_iso9660_null_deref.bin.uu [new file with mode: 0644]
libarchive/test/test_write_format_iso9660_underflow.bin.uu [new file with mode: 0644]

index fdf315fbc3988966f22eb56728887d22c9d03163..c8b5991acd95f2d22a941a214166b867171e2e15 100644 (file)
@@ -660,6 +660,7 @@ libarchive_test_SOURCES= \
        libarchive/test/test_write_format_gnutar_filenames.c \
        libarchive/test/test_write_format_iso9660.c \
        libarchive/test/test_write_format_iso9660_boot.c \
+       libarchive/test/test_write_format_iso9660_bugs.c \
        libarchive/test/test_write_format_iso9660_empty.c \
        libarchive/test/test_write_format_iso9660_filename.c \
        libarchive/test/test_write_format_iso9660_zisofs.c \
@@ -1123,6 +1124,9 @@ libarchive_test_EXTRA_DIST=\
        libarchive/test/test_write_disk_hfs_compression.tgz.uu \
        libarchive/test/test_write_disk_mac_metadata.tar.gz.uu \
        libarchive/test/test_write_disk_no_hfs_compression.tgz.uu \
+       libarchive/test/test_write_format_iso9660_joliet_overflow.bin.uu \
+       libarchive/test/test_write_format_iso9660_null_deref.bin.uu \
+       libarchive/test/test_write_format_iso9660_underflow.bin.uu \
        libarchive/test/test_write_format_xar_strcpy_overlap.bin.uu \
        libarchive/test/test_write_format_xar_underflow.bin.uu \
        libarchive/test/CMakeLists.txt \
index 1e293588bb91389bc1d6bc4dde8f83dbe81cbc89..576b3ba52b0c7506b3e5bdc453dded1a5ff18fee 100644 (file)
@@ -4281,9 +4281,11 @@ _write_path_table(struct archive_write *a, int type_m, int depth,
                        set_num_731(bp+3, np->dir_location);
                /* Parent Directory Number */
                if (type_m)
-                       set_num_722(bp+7, np->parent->dir_number);
+                       set_num_722(bp+7,
+                           np->parent != NULL ? np->parent->dir_number : 0);
                else
-                       set_num_721(bp+7, np->parent->dir_number);
+                       set_num_721(bp+7,
+                           np->parent != NULL ? np->parent->dir_number : 0);
                /* Directory Identifier */
                if (np->identifier == NULL)
                        bp[9] = 0;
@@ -5914,10 +5916,21 @@ idr_extend_identifier(struct idrent *wnp, int numsize, int nullsize)
 {
        unsigned char *p;
        int wnp_ext_off;
+       int new_id_len;
 
        wnp_ext_off = wnp->isoent->ext_off;
        if (wnp->noff + numsize != wnp_ext_off) {
-               p = (unsigned char *)wnp->isoent->identifier;
+               new_id_len = wnp->noff + numsize + wnp->isoent->ext_len;
+               /*
+                * Reallocate the identifier buffer to fit the expanded
+                * name.  Add extra space for a version suffix (";1")
+                * that may be appended later.
+                */
+               p = (unsigned char *)realloc(wnp->isoent->identifier,
+                   new_id_len + nullsize + numsize + 4);
+               if (p == NULL)
+                       return;
+               wnp->isoent->identifier = (char *)p;
                /* Extend the filename; foo.c --> foo___.c */
                memmove(p + wnp->noff + numsize, p + wnp_ext_off,
                    wnp->isoent->ext_len + nullsize);
@@ -6829,7 +6842,15 @@ _compare_path_table(const void *v1, const void *v2)
        p2 = *((const struct isoent **)(uintptr_t)v2);
 
        /* Compare parent directory number */
-       cmp = p1->parent->dir_number - p2->parent->dir_number;
+       if (p1->parent == NULL || p2->parent == NULL) {
+               if (p1->parent == p2->parent)
+                       cmp = 0;
+               else if (p1->parent == NULL)
+                       return (-1);
+               else
+                       return (1);
+       } else
+               cmp = p1->parent->dir_number - p2->parent->dir_number;
        if (cmp != 0)
                return (cmp);
 
@@ -6872,7 +6893,15 @@ _compare_path_table_joliet(const void *v1, const void *v2)
        p2 = *((const struct isoent **)(uintptr_t)v2);
 
        /* Compare parent directory number */
-       cmp = p1->parent->dir_number - p2->parent->dir_number;
+       if (p1->parent == NULL || p2->parent == NULL) {
+               if (p1->parent == p2->parent)
+                       cmp = 0;
+               else if (p1->parent == NULL)
+                       return (-1);
+               else
+                       return (1);
+       } else
+               cmp = p1->parent->dir_number - p2->parent->dir_number;
        if (cmp != 0)
                return (cmp);
 
index 0340c85995035c6e7c41ecd3b8d6f2b0ae0a7536..0acb73fa9ba358026dac25779653da65dc5a6012 100644 (file)
@@ -295,6 +295,7 @@ IF(ENABLE_TEST)
     test_write_format_iso9660_empty.c
     test_write_format_iso9660_filename.c
     test_write_format_iso9660_zisofs.c
+    test_write_format_iso9660_bugs.c
     test_write_format_mtree.c
     test_write_format_mtree_absolute.c
     test_write_format_mtree_absolute_path.c
diff --git a/libarchive/test/test_write_format_iso9660_bugs.c b/libarchive/test/test_write_format_iso9660_bugs.c
new file mode 100644 (file)
index 0000000..5315c82
--- /dev/null
@@ -0,0 +1,193 @@
+/*-
+ * Copyright (c) 2025 Tim Kientzle
+ * 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"
+#include "test_fuzz_consumer.h"
+
+#include <stdlib.h>
+
+/*
+ * Replay a fuzzer binary through the ISO9660 writer, matching the protocol
+ * in fuzzers/custom/fuzz_writer_iso9660.cc.
+ */
+static void
+replay_iso9660_writer(const char *refname)
+{
+       FILE *f;
+       uint8_t raw[32768];
+       size_t rawsize;
+       struct fuzz_consumer consumer;
+       uint8_t opts1, opts2, num_entries;
+       struct archive *a;
+       struct archive_entry *entry;
+       size_t used;
+       void *out_buf;
+       int i;
+
+       extract_reference_file(refname);
+       f = fopen(refname, "rb");
+       if (!assert(f != NULL))
+               return;
+       rawsize = fread(raw, 1, sizeof(raw), f);
+       fclose(f);
+       if (!assert(rawsize >= 6))
+               return;
+
+       fuzz_consumer_init(&consumer, raw, rawsize);
+       opts1 = fuzz_consume_byte(&consumer);
+       opts2 = fuzz_consume_byte(&consumer);
+       num_entries = (fuzz_consume_byte(&consumer) % 6) + 1;
+
+       a = archive_write_new();
+       if (!assert(a != NULL))
+               return;
+
+       archive_write_set_format_iso9660(a);
+
+       if (opts1 & 0x01)
+               archive_write_set_options(a, "iso9660:rockridge");
+       if (opts1 & 0x02)
+               archive_write_set_options(a, "iso9660:joliet");
+       if (opts1 & 0x04)
+               archive_write_set_options(a, "iso9660:iso-level=3");
+       if (opts1 & 0x08)
+               archive_write_set_options(a, "iso9660:iso-level=4");
+       if (opts1 & 0x10)
+               archive_write_set_options(a, "iso9660:allow-vernum");
+       if (opts1 & 0x20)
+               archive_write_set_options(a, "iso9660:allow-period");
+       if (opts1 & 0x40)
+               archive_write_set_options(a, "iso9660:allow-lowercase");
+       if (opts1 & 0x80)
+               archive_write_set_options(a, "iso9660:allow-multidot");
+
+       if (opts2 & 0x02)
+               archive_write_set_options(a,
+                   "iso9660:publisher=FuzzPublisher");
+       if (opts2 & 0x04)
+               archive_write_set_options(a,
+                   "iso9660:volume-id=FUZZISO");
+
+       out_buf = malloc(4 * 1024 * 1024);
+       if (!assert(out_buf != NULL)) {
+               archive_write_free(a);
+               return;
+       }
+       if (archive_write_open_memory(a, out_buf, 4 * 1024 * 1024, &used)
+           != ARCHIVE_OK) {
+               archive_write_free(a);
+               free(out_buf);
+               return;
+       }
+
+       entry = archive_entry_new();
+       if (!assert(entry != NULL)) {
+               archive_write_free(a);
+               free(out_buf);
+               return;
+       }
+
+       for (i = 0; i < num_entries && fuzz_consumer_remaining(&consumer) > 2;
+           i++) {
+               const char *name;
+               uint8_t ftype;
+               uint32_t file_size = 0;
+
+               archive_entry_clear(entry);
+
+               name = fuzz_consume_string(&consumer, 128);
+               if (name[0] == '\0')
+                       name = "FILE.TXT";
+               archive_entry_set_pathname(entry, name);
+
+               ftype = fuzz_consume_byte(&consumer) % 4;
+               switch (ftype) {
+               case 0:
+                       archive_entry_set_filetype(entry, AE_IFREG);
+                       archive_entry_set_perm(entry, 0644);
+                       break;
+               case 1:
+                       archive_entry_set_filetype(entry, AE_IFDIR);
+                       archive_entry_set_perm(entry, 0755);
+                       break;
+               case 2:
+                       archive_entry_set_filetype(entry, AE_IFLNK);
+                       archive_entry_set_perm(entry, 0777);
+                       archive_entry_set_symlink(entry,
+                           fuzz_consume_string(&consumer, 64));
+                       break;
+               case 3:
+                       archive_entry_set_filetype(entry, AE_IFREG);
+                       archive_entry_set_perm(entry, 0755);
+                       break;
+               }
+
+               archive_entry_set_uid(entry, 1000);
+               archive_entry_set_gid(entry, 1000);
+               archive_entry_set_mtime(entry,
+                   1700000000 + fuzz_consume_u16(&consumer), 0);
+               archive_entry_set_uname(entry, "user");
+               archive_entry_set_gname(entry, "group");
+
+               if (ftype == 0 || ftype == 3) {
+                       file_size = fuzz_consume_u16(&consumer) % 2048;
+                       archive_entry_set_size(entry, file_size);
+               }
+
+               if (archive_write_header(a, entry) != ARCHIVE_OK)
+                       continue;
+
+               if (file_size > 0 &&
+                   fuzz_consumer_remaining(&consumer) > 0) {
+                       size_t to_write = file_size;
+                       uint8_t data[2048];
+                       if (to_write > fuzz_consumer_remaining(&consumer))
+                               to_write =
+                                   fuzz_consumer_remaining(&consumer);
+                       fuzz_consume_bytes(&consumer, data, to_write);
+                       archive_write_data(a, data, to_write);
+               }
+       }
+
+       archive_entry_free(entry);
+       /* Close triggers path table construction; must not crash. */
+       archive_write_close(a);
+       archive_write_free(a);
+       free(out_buf);
+}
+
+DEFINE_TEST(test_write_format_iso9660_null_deref)
+{
+       replay_iso9660_writer("test_write_format_iso9660_null_deref.bin");
+}
+
+DEFINE_TEST(test_write_format_iso9660_underflow)
+{
+       replay_iso9660_writer("test_write_format_iso9660_underflow.bin");
+}
+
+DEFINE_TEST(test_write_format_iso9660_joliet_overflow)
+{
+       replay_iso9660_writer("test_write_format_iso9660_joliet_overflow.bin");
+}
diff --git a/libarchive/test/test_write_format_iso9660_joliet_overflow.bin.uu b/libarchive/test/test_write_format_iso9660_joliet_overflow.bin.uu
new file mode 100644 (file)
index 0000000..5e83566
--- /dev/null
@@ -0,0 +1,4 @@
+begin 644 test_write_format_iso9660_joliet_overflow.bin
+/_0"!_P`O_X$&@8$O<X&!
+`
+end
diff --git a/libarchive/test/test_write_format_iso9660_null_deref.bin.uu b/libarchive/test/test_write_format_iso9660_null_deref.bin.uu
new file mode 100644 (file)
index 0000000..991301f
--- /dev/null
@@ -0,0 +1,5 @@
+begin 644 test_write_format_iso9660_null_deref.bin
+M00`O02\O+S%`'2\O22\O22\O24=.3U)Z0"\R+R<O+TDO+T4O24=.3U)Z0"\R
+,+R<O+TDO+T4O,2]%
+`
+end
diff --git a/libarchive/test/test_write_format_iso9660_underflow.bin.uu b/libarchive/test/test_write_format_iso9660_underflow.bin.uu
new file mode 100644 (file)
index 0000000..0a29dde
--- /dev/null
@@ -0,0 +1,5 @@
+begin 644 test_write_format_iso9660_underflow.bin
+MN;FYN;FYN;FYN;FYN;FYN;FYN;FYN;FYN;FYN;FYN;FYN;FYN;FYN;FYN;FY
+FN;FY02Z^+BXN+RXN+R])1TY/4BXN+BXN+BXL+B])1TY/+H,N+@``
+`
+end