mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-11-07 04:36:52 -10:00
Obtained from https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-3.0.9.tar.gz SHA1: a390f016a7a5ff419c9eef9e2b2c01fe86c11971 - SHA256: 32310a33bb97425ebc0707cb5af59f9dc05d10282dcd4db628a45ed904993dc6 - SHA512: 30776d31f860910286b96fbf60a19b5a6d1e73cb6a1077a019d763ae6e5ed1e8d81bc3649978c83f40e5e90ae779f7e530c513b700c8e73bb6399883dd6ef7ab - Sponsored by: The FreeBSD Foundation
212 lines
5.1 KiB
C
212 lines
5.1 KiB
C
/*
|
|
* spdxtool-fuzzer.c
|
|
* SPDX SBOM serializer fuzzing harness
|
|
*
|
|
* SPDX-License-Identifier: pkgconf
|
|
*
|
|
* Copyright (c) 2026 pkgconf authors (see AUTHORS).
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* This software is provided 'as is' and without any warranty, express or
|
|
* implied. In no event shall the authors be liable for any damages arising
|
|
* from the use of this software.
|
|
*/
|
|
|
|
#include <libpkgconf/stdinc.h>
|
|
#include <libpkgconf/libpkgconf.h>
|
|
|
|
#include "util.h"
|
|
#include "generate.h"
|
|
|
|
#include "alloc-inject.h"
|
|
|
|
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
|
|
|
|
/* Bound the allocation site selected for fault injection. Exhaustively
|
|
* replaying every allocation for every libFuzzer input makes this relatively
|
|
* expensive solve-and-serialize target prohibitively slow. */
|
|
#define ALLOC_FAIL_MAX 4096
|
|
|
|
/* the fuzzer input is split on NUL bytes into up to this many .pc files, named
|
|
* a.pc, b.pc, ... so that Requires/Conflicts/Provides between them resolve, and
|
|
* the resulting graph is serialized to an SPDX SBOM.
|
|
*/
|
|
#define UNIVERSE_MAX 4
|
|
#define SOLVE_MAXDEPTH 10
|
|
|
|
static const char universe_names[UNIVERSE_MAX] = { 'a', 'b', 'c', 'd' };
|
|
|
|
static unsigned long
|
|
allocation_failure_for_input(const uint8_t *data, size_t size)
|
|
{
|
|
uint32_t hash = UINT32_C(2166136261);
|
|
|
|
for (size_t i = 0; i < size; i++)
|
|
{
|
|
hash ^= data[i];
|
|
hash *= UINT32_C(16777619);
|
|
}
|
|
|
|
return (unsigned long)(hash % ALLOC_FAIL_MAX) + 1;
|
|
}
|
|
|
|
static const char *
|
|
environ_lookup_handler(const pkgconf_client_t *client, const char *key)
|
|
{
|
|
(void) client;
|
|
(void) key;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
write_all(int fd, const uint8_t *data, size_t size)
|
|
{
|
|
while (size > 0)
|
|
{
|
|
ssize_t n = write(fd, data, size);
|
|
|
|
if (n < 0)
|
|
{
|
|
if (errno == EINTR)
|
|
continue;
|
|
return -1;
|
|
}
|
|
|
|
data += n;
|
|
size -= n;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
write_pkg(const char *dir, char name, const uint8_t *data, size_t size)
|
|
{
|
|
char path[PKGCONF_ITEM_SIZE];
|
|
int fd;
|
|
|
|
snprintf(path, sizeof path, "%s/%c.pc", dir, name);
|
|
|
|
fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
|
|
if (fd < 0)
|
|
return;
|
|
|
|
write_all(fd, data, size);
|
|
close(fd);
|
|
}
|
|
|
|
static void
|
|
write_universe(const char *dir, const uint8_t *data, size_t size)
|
|
{
|
|
size_t start = 0, idx = 0;
|
|
|
|
for (size_t i = 0; i < size && idx < UNIVERSE_MAX; i++)
|
|
{
|
|
if (data[i] == 0x00)
|
|
{
|
|
write_pkg(dir, universe_names[idx++], data + start, i - start);
|
|
start = i + 1;
|
|
}
|
|
}
|
|
|
|
if (idx < UNIVERSE_MAX)
|
|
write_pkg(dir, universe_names[idx], data + start, size - start);
|
|
}
|
|
|
|
static void
|
|
cleanup_universe(const char *dir)
|
|
{
|
|
char path[PKGCONF_ITEM_SIZE];
|
|
|
|
for (size_t i = 0; i < UNIVERSE_MAX; i++)
|
|
{
|
|
snprintf(path, sizeof path, "%s/%c.pc", dir, universe_names[i]);
|
|
unlink(path);
|
|
}
|
|
|
|
rmdir(dir);
|
|
}
|
|
|
|
/*
|
|
* Solve a freshly-written universe rooted at "a", then drive the real spdxtool
|
|
* SBOM serialization pipeline (cli/spdxtool/generate.c) over the result.
|
|
*/
|
|
static void
|
|
run_spdx(const pkgconf_cross_personality_t *pers, const char *dir, FILE *out)
|
|
{
|
|
pkgconf_client_t *client = pkgconf_client_new(NULL, NULL, pers, NULL, environ_lookup_handler);
|
|
if (client == NULL)
|
|
return;
|
|
|
|
pkgconf_client_set_flags(client, PKGCONF_PKG_PKGF_SEARCH_PRIVATE);
|
|
pkgconf_path_add(dir, &client->dir_list, false);
|
|
|
|
/* the serializers read these back via the client's global tuples */
|
|
spdxtool_util_set_uri_root(client, "https://example.com/test");
|
|
spdxtool_util_set_uri_separator_colon(client, false);
|
|
spdxtool_util_set_spdx_license(client, "CC0-1.0");
|
|
spdxtool_util_set_spdx_version(client, "3.0.1");
|
|
|
|
pkgconf_pkg_t world = {
|
|
.id = "virtual:world",
|
|
.realname = "virtual world package",
|
|
.flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL,
|
|
};
|
|
pkgconf_list_t pkgq = PKGCONF_LIST_INITIALIZER;
|
|
|
|
pkgconf_queue_push(&pkgq, "a");
|
|
|
|
if (pkgconf_queue_solve(client, &pkgq, &world, SOLVE_MAXDEPTH))
|
|
/* fixed timestamp keeps the harness deterministic */
|
|
spdxtool_generate(client, &world, out, SOLVE_MAXDEPTH,
|
|
"2020-01-01T00:00:00Z", "_:creationinfo_1", "Default");
|
|
|
|
pkgconf_solution_free(client, &world);
|
|
pkgconf_queue_free(&pkgq);
|
|
pkgconf_client_free(client);
|
|
}
|
|
|
|
int
|
|
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
|
{
|
|
if (size == 0)
|
|
return 0;
|
|
|
|
char dir[] = "/tmp/pkgconf-fuzz-spdx-XXXXXX";
|
|
if (mkdtemp(dir) == NULL)
|
|
return 0;
|
|
|
|
write_universe(dir, data, size);
|
|
|
|
/* discard the serialized output; opened outside the injection loop so the
|
|
* allocations behind it are never the fault-injection target.
|
|
*/
|
|
FILE *out = fopen("/dev/null", "w");
|
|
if (out == NULL)
|
|
{
|
|
cleanup_universe(dir);
|
|
return 0;
|
|
}
|
|
|
|
pkgconf_cross_personality_t *pers = pkgconf_cross_personality_default();
|
|
|
|
/* baseline run with all allocations succeeding */
|
|
run_spdx(pers, dir, out);
|
|
|
|
/* Exercise one deterministic allocation failure per input. Mutations vary
|
|
* the selected site, while keeping the per-input cost constant. */
|
|
alloc_inject_arm(allocation_failure_for_input(data, size));
|
|
run_spdx(pers, dir, out);
|
|
alloc_inject_disarm();
|
|
|
|
pkgconf_cross_personality_deinit(pers);
|
|
fclose(out);
|
|
cleanup_universe(dir);
|
|
|
|
return 0;
|
|
}
|