From: Tim Kientzle Date: Sat, 9 May 2026 02:03:11 +0000 (-0700) Subject: Merge pull request #3018 from tbkka/tbkka-mtree-null-deref-014 X-Git-Tag: v3.8.8~135 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=d8c51eadaa31373b38f7df17b099185666bbbcfe;p=thirdparty%2Flibarchive.git Merge pull request #3018 from tbkka/tbkka-mtree-null-deref-014 [MTREE] NULL pointer deref during archive close (cherry picked from commit bcb158d409940525195b9201e95f4f043418a029) --- diff --git a/Makefile.am b/Makefile.am index c8b5991ac..42c738866 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 \ diff --git a/libarchive/archive_write_set_format_mtree.c b/libarchive/archive_write_set_format_mtree.c index 08a2cd21a..36e00191c 100644 --- a/libarchive/archive_write_set_format_mtree.c +++ b/libarchive/archive_write_set_format_mtree.c @@ -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 diff --git a/libarchive/test/CMakeLists.txt b/libarchive/test/CMakeLists.txt index 0acb73fa9..500257043 100644 --- a/libarchive/test/CMakeLists.txt +++ b/libarchive/test/CMakeLists.txt @@ -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 index 000000000..20588d313 --- /dev/null +++ b/libarchive/test/test_write_format_mtree_null_deref.bin.uu @@ -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 index 000000000..5e1dbfca9 --- /dev/null +++ b/libarchive/test/test_write_format_mtree_null_deref.c @@ -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 + +/* + * 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); +}