115 lines
3.5 KiB
C++
Vendored
115 lines
3.5 KiB
C++
Vendored
/*
|
|
* Archive entry link resolver fuzzer for libarchive
|
|
* Targets archive_entry_linkify (complexity: 775, zero coverage)
|
|
*/
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "archive.h"
|
|
#include "archive_entry.h"
|
|
#include "fuzz_helpers.h"
|
|
|
|
static constexpr size_t kMaxInputSize = 64 * 1024; // 64KB
|
|
|
|
// Simple data consumer
|
|
|
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
|
|
if (len == 0 || len > kMaxInputSize) {
|
|
return 0;
|
|
}
|
|
|
|
DataConsumer consumer(buf, len);
|
|
|
|
// Create a link resolver
|
|
struct archive_entry_linkresolver *resolver = archive_entry_linkresolver_new();
|
|
if (resolver == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
// Set the format strategy based on input
|
|
uint8_t strategy = consumer.consume_byte() % 5;
|
|
int format;
|
|
switch (strategy) {
|
|
case 0: format = ARCHIVE_FORMAT_TAR_GNUTAR; break;
|
|
case 1: format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE; break;
|
|
case 2: format = ARCHIVE_FORMAT_CPIO_POSIX; break;
|
|
case 3: format = ARCHIVE_FORMAT_CPIO_SVR4_NOCRC; break;
|
|
default: format = ARCHIVE_FORMAT_TAR_USTAR; break;
|
|
}
|
|
archive_entry_linkresolver_set_strategy(resolver, format);
|
|
|
|
// Create multiple entries to test linkify with hardlinks
|
|
struct archive_entry *entries[32];
|
|
int num_entries = 0;
|
|
|
|
while (!consumer.empty() && num_entries < 32 && consumer.remaining() > 20) {
|
|
struct archive_entry *entry = archive_entry_new();
|
|
if (entry == NULL) break;
|
|
|
|
// Set pathname
|
|
archive_entry_set_pathname(entry, consumer.consume_string(64));
|
|
|
|
// Set inode and device for hardlink detection
|
|
archive_entry_set_ino(entry, consumer.consume_i64());
|
|
archive_entry_set_dev(entry, consumer.consume_u32());
|
|
archive_entry_set_nlink(entry, (consumer.consume_byte() % 5) + 1);
|
|
|
|
// Set mode (regular file or directory)
|
|
uint8_t ftype = consumer.consume_byte() % 2;
|
|
mode_t mode = ftype ? (S_IFDIR | 0755) : (S_IFREG | 0644);
|
|
archive_entry_set_mode(entry, mode);
|
|
|
|
archive_entry_set_size(entry, consumer.consume_i64() & 0xFFFF);
|
|
archive_entry_set_uid(entry, consumer.consume_u32() & 0xFFFF);
|
|
archive_entry_set_gid(entry, consumer.consume_u32() & 0xFFFF);
|
|
|
|
entries[num_entries++] = entry;
|
|
}
|
|
|
|
// Now run all entries through the linkresolver
|
|
for (int i = 0; i < num_entries; i++) {
|
|
struct archive_entry *entry = entries[i];
|
|
struct archive_entry *spare = NULL;
|
|
|
|
// This is the main function we want to fuzz (zero coverage)
|
|
archive_entry_linkify(resolver, &entry, &spare);
|
|
|
|
// Update entries[i] to reflect ownership changes from linkify.
|
|
// If linkify cached the entry internally, entry is now NULL and the
|
|
// resolver owns the object. If linkify swapped it with a previously
|
|
// cached entry, entry points to that other object.
|
|
entries[i] = entry;
|
|
|
|
// Free any entry returned via spare (complete hardlink pair)
|
|
if (spare != NULL) {
|
|
archive_entry_free(spare);
|
|
}
|
|
}
|
|
|
|
// Free remaining entries from the resolver (drain loop)
|
|
struct archive_entry *entry = NULL;
|
|
struct archive_entry *spare = NULL;
|
|
while (1) {
|
|
archive_entry_linkify(resolver, &entry, &spare);
|
|
if (entry == NULL)
|
|
break;
|
|
archive_entry_free(entry);
|
|
entry = NULL;
|
|
if (spare != NULL) {
|
|
archive_entry_free(spare);
|
|
spare = NULL;
|
|
}
|
|
}
|
|
|
|
// Free all our created entries that were NOT consumed by the resolver
|
|
for (int i = 0; i < num_entries; i++) {
|
|
if (entries[i] != NULL) {
|
|
archive_entry_free(entries[i]);
|
|
}
|
|
}
|
|
|
|
archive_entry_linkresolver_free(resolver);
|
|
return 0;
|
|
}
|