From: ZX41R Date: Fri, 8 May 2026 13:50:26 +0000 (+0000) Subject: Improve write-disk fuzzer coverage X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=88b5efe09aed2ac49fc4251e8f1d3b15bd28f106;p=thirdparty%2Flibarchive.git Improve write-disk fuzzer coverage --- diff --git a/contrib/oss-fuzz/libarchive_write_disk_fuzzer.cc b/contrib/oss-fuzz/libarchive_write_disk_fuzzer.cc index 056bd1639..7392e394a 100644 --- a/contrib/oss-fuzz/libarchive_write_disk_fuzzer.cc +++ b/contrib/oss-fuzz/libarchive_write_disk_fuzzer.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -17,10 +18,134 @@ static constexpr size_t kMaxInputSize = 64 * 1024; static char g_temp_dir[256] = {0}; +static char g_start_dir[512] = {0}; +static char g_extract_dir[512] = {0}; +static char g_outside_dir[512] = {0}; +static char g_sentinel_path[512] = {0}; + +static int write_file(const char* path, const char* data) { + FILE* f = fopen(path, "wb"); + size_t len = strlen(data); + size_t written; + if (f == NULL) { + return -1; + } + written = fwrite(data, 1, len, f); + if (fclose(f) != 0) { + return -1; + } + return written == len ? 0 : -1; +} + +static int sentinel_is_unchanged() { + char buf[32]; + FILE* f = fopen(g_sentinel_path, "rb"); + if (f == NULL) { + return 0; + } + size_t n = fread(buf, 1, sizeof(buf) - 1, f); + fclose(f); + buf[n] = '\0'; + return strcmp(buf, "outside-original") == 0; +} + +static int reset_temp_tree() { + if (g_start_dir[0] != '\0') { + if (chdir(g_start_dir) != 0) { + return -1; + } + } + remove_directory_tree(g_temp_dir); + if (mkdir(g_temp_dir, 0700) != 0) { + return -1; + } + + snprintf(g_extract_dir, sizeof(g_extract_dir), "%s/extract", g_temp_dir); + snprintf(g_outside_dir, sizeof(g_outside_dir), "%s/outside", g_temp_dir); + snprintf(g_sentinel_path, sizeof(g_sentinel_path), "%s/outside/sentinel", + g_temp_dir); + + if (mkdir(g_extract_dir, 0700) != 0 || mkdir(g_outside_dir, 0700) != 0) { + return -1; + } + + char existing[512]; + char existing_dir[512]; + char nested_existing[512]; + snprintf(existing, sizeof(existing), "%s/extract/existing", g_temp_dir); + snprintf(existing_dir, sizeof(existing_dir), "%s/extract/dir", g_temp_dir); + snprintf(nested_existing, sizeof(nested_existing), + "%s/extract/dir/existing", g_temp_dir); + if (mkdir(existing_dir, 0700) != 0) { + return -1; + } + + return write_file(g_sentinel_path, "outside-original") || + write_file(existing, "inside") || + write_file(nested_existing, "inside"); +} + +static void consume_path(DataConsumer* consumer, char* out, size_t out_size) { + static const char* const kPaths[] = { + "file", + "dir/file", + "dir/./file", + "dir//file", + "link", + "link/file", + "link/./file", + "link//file", + "../outside/sentinel", + "../outside/new-file", + "./../outside/sentinel", + "dir/../file", + "hardlink", + "hardlink-with-data", + "symlink", + "existing", + "dir/existing", + }; + uint8_t selector = consumer->consume_byte(); + if ((selector & 0x80) != 0) { + snprintf(out, out_size, "%s/extract/absolute-%u", g_temp_dir, selector); + } else if ((selector & 0x40) != 0) { + snprintf(out, out_size, "%s/outside/sentinel", g_temp_dir); + } else { + snprintf(out, out_size, "%s", + kPaths[selector % (sizeof(kPaths) / sizeof(kPaths[0]))]); + } +} + +static void consume_linkname(DataConsumer* consumer, char* out, + size_t out_size) { + static const char* const kTargets[] = { + "existing", + "dir/existing", + "link/sentinel", + "link/./sentinel", + "link//sentinel", + "../outside/sentinel", + "./../outside/sentinel", + "dir/../existing", + "missing-target", + }; + uint8_t selector = consumer->consume_byte(); + if ((selector & 0x80) != 0) { + snprintf(out, out_size, "%s/outside/sentinel", g_temp_dir); + } else if ((selector & 0x40) != 0) { + snprintf(out, out_size, "%s/extract/existing", g_temp_dir); + } else { + snprintf(out, out_size, "%s", + kTargets[selector % (sizeof(kTargets) / sizeof(kTargets[0]))]); + } +} extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) { (void)argc; (void)argv; + if (getcwd(g_start_dir, sizeof(g_start_dir)) == NULL) { + g_start_dir[0] = '\0'; + } // Create a temporary directory for extraction snprintf(g_temp_dir, sizeof(g_temp_dir), "/tmp/fuzz_extract_XXXXXX"); if (mkdtemp(g_temp_dir) == NULL) { @@ -38,10 +163,19 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) { return 0; } + if (reset_temp_tree() != 0 || chdir(g_extract_dir) != 0) { + return 0; + } + DataConsumer consumer(buf, len); struct archive *disk = archive_write_disk_new(); if (disk == NULL) { + if (g_start_dir[0] != '\0') { + if (chdir(g_start_dir) != 0) { + return 0; + } + } return 0; } @@ -54,8 +188,12 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) { if (opt_flags & 0x08) flags |= ARCHIVE_EXTRACT_FFLAGS; if (opt_flags & 0x10) flags |= ARCHIVE_EXTRACT_OWNER; if (opt_flags & 0x20) flags |= ARCHIVE_EXTRACT_XATTR; - if (opt_flags & 0x40) flags |= ARCHIVE_EXTRACT_SECURE_SYMLINKS; - if (opt_flags & 0x80) flags |= ARCHIVE_EXTRACT_SECURE_NODOTDOT; + if (opt_flags & 0x40) flags |= ARCHIVE_EXTRACT_UNLINK; + if (opt_flags & 0x80) flags |= ARCHIVE_EXTRACT_SAFE_WRITES; + + flags |= ARCHIVE_EXTRACT_SECURE_SYMLINKS | + ARCHIVE_EXTRACT_SECURE_NODOTDOT | + ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS; archive_write_disk_set_options(disk, flags); archive_write_disk_set_standard_lookup(disk); @@ -66,45 +204,45 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) { struct archive_entry *entry = archive_entry_new(); if (entry == NULL) break; - // Build a safe path within our temp directory - char safe_path[512]; - const char *name = consumer.consume_string(32); - snprintf(safe_path, sizeof(safe_path), "%s/%s", g_temp_dir, name); - - // Sanitize path to prevent traversal - char *p = safe_path; - while (*p) { - if (p[0] == '.' && p[1] == '.') { - p[0] = '_'; - p[1] = '_'; - } - p++; - } - - archive_entry_set_pathname(entry, safe_path); + char pathname[512]; + char linkname[512]; + consume_path(&consumer, pathname, sizeof(pathname)); + archive_entry_set_pathname(entry, pathname); - uint8_t ftype = consumer.consume_byte() % 3; + uint8_t ftype = consumer.consume_byte() % 4; mode_t mode; switch (ftype) { case 0: mode = S_IFREG | 0644; break; case 1: mode = S_IFDIR | 0755; break; + case 2: mode = S_IFLNK | 0777; break; default: mode = S_IFREG | 0644; break; } archive_entry_set_mode(entry, mode); + if (S_ISLNK(mode)) { + consume_linkname(&consumer, linkname, sizeof(linkname)); + archive_entry_set_symlink(entry, linkname); + archive_entry_set_size(entry, 0); + } else if (ftype == 3) { + consume_linkname(&consumer, linkname, sizeof(linkname)); + archive_entry_set_hardlink(entry, linkname); + } + archive_entry_set_uid(entry, 1000); archive_entry_set_gid(entry, 1000); archive_entry_set_mtime(entry, consumer.consume_i64(), 0); + uint8_t data_buf[256]; + size_t data_len = 0; + if (S_ISREG(mode)) { + data_len = consumer.consume_bytes(data_buf, 256); + archive_entry_set_size(entry, data_len); + } + // Write the entry header if (archive_write_header(disk, entry) == ARCHIVE_OK) { - if (S_ISREG(mode)) { - uint8_t data_buf[256]; - size_t data_len = consumer.consume_bytes(data_buf, 256); - archive_entry_set_size(entry, data_len); - if (data_len > 0) { - archive_write_data(disk, data_buf, data_len); - } + if (S_ISREG(mode) && data_len > 0) { + archive_write_data(disk, data_buf, data_len); } archive_write_finish_entry(disk); } @@ -116,6 +254,16 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) { archive_write_close(disk); archive_write_free(disk); + if (!sentinel_is_unchanged()) { + abort(); + } + + if (g_start_dir[0] != '\0') { + if (chdir(g_start_dir) != 0) { + abort(); + } + } + // Clean up extracted files using nftw (safer than system()) remove_directory_tree(g_temp_dir); // Recreate the temp directory for next iteration