]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Merge pull request #3018 from tbkka/tbkka-mtree-null-deref-014
authorTim Kientzle <kientzle@acm.org>
Sat, 9 May 2026 02:03:11 +0000 (19:03 -0700)
committerMartin Matuska <martin@matuska.de>
Tue, 23 Jun 2026 08:26:53 +0000 (10:26 +0200)
[MTREE] NULL pointer deref during archive close

(cherry picked from commit bcb158d409940525195b9201e95f4f043418a029)

Makefile.am
libarchive/archive_write_set_format_mtree.c
libarchive/test/CMakeLists.txt
libarchive/test/test_write_format_mtree_null_deref.bin.uu [new file with mode: 0644]
libarchive/test/test_write_format_mtree_null_deref.c [new file with mode: 0644]

index c8b5991acd95f2d22a941a214166b867171e2e15..42c7388669d5de86702d848b2a3e6b4a2ae0ae56 100644 (file)
@@ -673,6 +673,7 @@ libarchive_test_SOURCES= \
        libarchive/test/test_write_format_mtree_no_separator.c \
        libarchive/test/test_write_format_mtree_preset_digests.c \
        libarchive/test/test_write_format_mtree_quoted_filename.c\
+       libarchive/test/test_write_format_mtree_null_deref.c \
        libarchive/test/test_write_format_pax.c \
        libarchive/test/test_write_format_raw.c \
        libarchive/test/test_write_format_raw_b64.c \
@@ -1127,6 +1128,7 @@ libarchive_test_EXTRA_DIST=\
        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_mtree_null_deref.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 08a2cd21abebe7f501371b7e2107244e51e21ac7..36e00191c8c76afdae3dbe59307ac3ffd5b3e3e3 100644 (file)
@@ -1146,6 +1146,8 @@ write_mtree_entry_tree(struct archive_write *a)
        int ret;
 
        do {
+               if (np->dir_info == NULL)
+                       break;
                if (mtree->output_global_set) {
                        /*
                         * Collect attribute information to know which value
index 0acb73fa9ba358026dac25779653da65dc5a6012..500257043c00669d7e46187e12ceabad0ebddaa0 100644 (file)
@@ -305,6 +305,7 @@ IF(ENABLE_TEST)
     test_write_format_mtree_no_separator.c
     test_write_format_mtree_preset_digests.c
     test_write_format_mtree_quoted_filename.c
+    test_write_format_mtree_null_deref.c
     test_write_format_pax.c
     test_write_format_raw.c
     test_write_format_raw_b64.c
diff --git a/libarchive/test/test_write_format_mtree_null_deref.bin.uu b/libarchive/test/test_write_format_mtree_null_deref.bin.uu
new file mode 100644 (file)
index 0000000..20588d3
--- /dev/null
@@ -0,0 +1,4 @@
+begin 644 test_write_format_mtree_null_deref.bin
+&IRXN`$"W
+`
+end
diff --git a/libarchive/test/test_write_format_mtree_null_deref.c b/libarchive/test/test_write_format_mtree_null_deref.c
new file mode 100644 (file)
index 0000000..5e1dbfc
--- /dev/null
@@ -0,0 +1,173 @@
+/*-
+ * 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 MTREE writer, matching the protocol
+ * in fuzzers/custom/fuzz_writer_mtree.cc.
+ */
+DEFINE_TEST(test_write_format_mtree_null_deref)
+{
+       const char *refname = "test_write_format_mtree_null_deref.bin";
+       FILE *f;
+       uint8_t raw[16384];
+       size_t rawsize;
+       struct fuzz_consumer consumer;
+       uint8_t opts, 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 >= 4))
+               return;
+
+       fuzz_consumer_init(&consumer, raw, rawsize);
+       opts = fuzz_consume_byte(&consumer);
+       num_entries = (fuzz_consume_byte(&consumer) % 8) + 1;
+
+       a = archive_write_new();
+       if (!assert(a != NULL))
+               return;
+
+       if (opts & 0x01)
+               archive_write_set_format_mtree_classic(a);
+       else
+               archive_write_set_format_mtree(a);
+
+       if (opts & 0x02)
+               archive_write_set_options(a, "mtree:all");
+       if (opts & 0x04)
+               archive_write_set_options(a, "mtree:use-set");
+       if (opts & 0x08)
+               archive_write_set_options(a, "mtree:indent");
+       if (opts & 0x10)
+               archive_write_set_options(a, "mtree:dironly");
+
+       out_buf = malloc(256 * 1024);
+       if (!assert(out_buf != NULL)) {
+               archive_write_free(a);
+               return;
+       }
+       if (archive_write_open_memory(a, out_buf, 256 * 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) % 5;
+               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_IFBLK);
+                       archive_entry_set_perm(entry, 0600);
+                       archive_entry_set_rdev(entry,
+                           fuzz_consume_u16(&consumer));
+                       break;
+               case 4:
+                       archive_entry_set_filetype(entry, AE_IFIFO);
+                       archive_entry_set_perm(entry, 0644);
+                       break;
+               }
+
+               archive_entry_set_uid(entry, fuzz_consume_byte(&consumer));
+               archive_entry_set_gid(entry, fuzz_consume_byte(&consumer));
+               archive_entry_set_mtime(entry,
+                   1700000000 + fuzz_consume_u16(&consumer), 0);
+               archive_entry_set_uname(entry, "user");
+               archive_entry_set_gname(entry, "group");
+
+               if (fuzz_consumer_remaining(&consumer) > 1 &&
+                   (fuzz_consume_byte(&consumer) & 0x01))
+                       archive_entry_copy_fflags_text(entry, "uappnd,uchg");
+
+               if (ftype == 0) {
+                       file_size = fuzz_consume_byte(&consumer) % 128;
+                       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[128];
+                       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 tree traversal; must not crash. */
+       archive_write_close(a);
+       archive_write_free(a);
+       free(out_buf);
+}