diff options
Diffstat (limited to 'fuzzer/spdxtool-fuzzer.c')
| -rw-r--r-- | fuzzer/spdxtool-fuzzer.c | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/fuzzer/spdxtool-fuzzer.c b/fuzzer/spdxtool-fuzzer.c new file mode 100644 index 000000000000..3db2fb0fd5c8 --- /dev/null +++ b/fuzzer/spdxtool-fuzzer.c @@ -0,0 +1,200 @@ +/* + * 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 number of injection rounds per input to keep executions cheap */ +#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 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); + + /* then fail each allocation site reachable by this input, one at a time */ + for (unsigned long i = 1; i <= ALLOC_FAIL_MAX; i++) + { + alloc_inject_arm(i); + run_spdx(pers, dir, out); + alloc_inject_disarm(); + + if (!alloc_inject_fired()) + break; + } + + pkgconf_cross_personality_deinit(pers); + fclose(out); + cleanup_universe(dir); + + return 0; +} |
