diff options
| author | Pierre Pronchery <khorben@FreeBSD.org> | 2026-06-26 17:34:10 +0000 |
|---|---|---|
| committer | Pierre Pronchery <khorben@FreeBSD.org> | 2026-06-29 05:21:50 +0000 |
| commit | 0cf7106da9f36671ef62142c27de98eee9d874d6 (patch) | |
| tree | 48090361499358411adb1d0bb16ca768e7c226a1 | |
| parent | b0fc1b7a2fe8fcb18ee407227591bf25a86ffbaf (diff) | |
| -rw-r--r-- | Makefile.am | 2 | ||||
| -rw-r--r-- | Makefile.lite | 2 | ||||
| -rw-r--r-- | cli/bomtool/main.c | 42 | ||||
| -rw-r--r-- | cli/spdxtool/core.c | 28 | ||||
| -rw-r--r-- | cli/spdxtool/generate.c | 190 | ||||
| -rw-r--r-- | cli/spdxtool/generate.h | 29 | ||||
| -rw-r--r-- | cli/spdxtool/main.c | 217 | ||||
| -rw-r--r-- | cli/spdxtool/serialize.c | 4 | ||||
| -rw-r--r-- | cli/spdxtool/software.c | 49 | ||||
| -rw-r--r-- | configure.ac | 2 | ||||
| -rw-r--r-- | fuzzer/meson.build | 23 | ||||
| -rw-r--r-- | fuzzer/spdxtool-fuzzer.c | 200 | ||||
| -rw-r--r-- | libpkgconf/libpkgconf.h | 4 | ||||
| -rw-r--r-- | libpkgconf/license.c | 8 | ||||
| -rw-r--r-- | meson.build | 23 |
15 files changed, 568 insertions, 255 deletions
diff --git a/Makefile.am b/Makefile.am index 601e35e7ef71..28f84fffe351 100644 --- a/Makefile.am +++ b/Makefile.am @@ -613,6 +613,7 @@ noinst_HEADERS = \ cli/getopt_long.h \ cli/renderer-msvc.h \ cli/spdxtool/core.h \ + cli/spdxtool/generate.h \ cli/spdxtool/serialize.h \ cli/spdxtool/simplelicensing.h \ cli/spdxtool/software.h \ @@ -632,6 +633,7 @@ spdxtool_SOURCES = \ cli/spdxtool/serialize.c \ cli/spdxtool/simplelicensing.c \ cli/spdxtool/util.c \ + cli/spdxtool/generate.c \ cli/getopt_long.c spdxtool_CPPFLAGS = -I$(top_srcdir)/libpkgconf -I$(top_srcdir)/cli -I$(top_srcdir)/cli/spdxtool diff --git a/Makefile.lite b/Makefile.lite index 2b2c1d147084..90319026f549 100644 --- a/Makefile.lite +++ b/Makefile.lite @@ -60,7 +60,7 @@ all: pkgconf-lite libpkgconf/config.h: @echo '#define PACKAGE_NAME "pkgconf-lite"' >> $@ @echo '#define PACKAGE_BUGREPORT "https://git.dereferenced.org/pkgconf/pkgconf/issues"' >> $@ - @echo '#define PACKAGE_VERSION "2.9.92"' >> $@ + @echo '#define PACKAGE_VERSION "2.9.93"' >> $@ @echo '#define PACKAGE PACKAGE_NAME " " PACKAGE_VERSION' >> $@ pkgconf-lite: preflight libpkgconf/config.h ${PKGCONF_OBJS} diff --git a/cli/bomtool/main.c b/cli/bomtool/main.c index a41502e5a0cf..3a6f81aeb0bf 100644 --- a/cli/bomtool/main.c +++ b/cli/bomtool/main.c @@ -450,37 +450,25 @@ main(int argc, char *argv[]) if ((want_flags & PKG_HELP) == PKG_HELP) return usage(); - while (1) - { - const char *package = argv[pkg_optind]; - - if (package == NULL) - break; - - while (isspace((unsigned char)package[0])) - package++; + /* Join the remaining arguments into a single query string, as the main + * pkgconf CLI does, and let the dependency parser handle module names, + * comparison operators and versions. + */ + pkgconf_buffer_t queryparams = PKGCONF_BUFFER_INITIALIZER; - /* skip empty packages */ - if (package[0] == '\0') { - pkg_optind++; - continue; - } + while (pkg_optind < argc && argv[pkg_optind] != NULL) + { + if (pkgconf_buffer_len(&queryparams) > 0) + pkgconf_buffer_push_byte(&queryparams, ' '); - if (argv[pkg_optind + 1] == NULL || !PKGCONF_IS_OPERATOR_CHAR(*(argv[pkg_optind + 1]))) - { - pkgconf_queue_push(&pkgq, package); - pkg_optind++; - } - else - { - char packagebuf[PKGCONF_BUFSIZE]; + pkgconf_buffer_append(&queryparams, argv[pkg_optind]); + pkg_optind++; + } - snprintf(packagebuf, sizeof packagebuf, "%s %s %s", package, argv[pkg_optind + 1], argv[pkg_optind + 2]); - pkg_optind += 3; + if (pkgconf_buffer_len(&queryparams) > 0) + pkgconf_queue_push(&pkgq, pkgconf_buffer_str(&queryparams)); - pkgconf_queue_push(&pkgq, packagebuf); - } - } + pkgconf_buffer_finalize(&queryparams); if (pkgq.head == NULL) { diff --git a/cli/spdxtool/core.c b/cli/spdxtool/core.c index 7dc7a8b41cce..62268b1b10a2 100644 --- a/cli/spdxtool/core.c +++ b/cli/spdxtool/core.c @@ -419,15 +419,24 @@ spdxtool_core_spdx_document_to_object(pkgconf_client_t *client, spdxtool_core_sp if (!(spdxtool_serialize_object_add_string(object_list, "type", spdx->type) && spdxtool_serialize_object_add_string(object_list, "creationInfo", spdx->creation_info) && - spdxtool_serialize_object_add_string(object_list, "spdxId", spdx->spdx_id) && - spdxtool_serialize_object_add_array(object_list, "rootElement", root_element_array) && - spdxtool_serialize_object_add_array(object_list, "element", element_array))) + spdxtool_serialize_object_add_string(object_list, "spdxId", spdx->spdx_id))) { goto err; } + /* object_add_array always takes ownership of the array (it is freed even on + * failure), so clear our reference before checking the result to avoid a + * double free at the error label. + */ + bool ok = spdxtool_serialize_object_add_array(object_list, "rootElement", root_element_array); root_element_array = NULL; + if (!ok) + goto err; + + ok = spdxtool_serialize_object_add_array(object_list, "element", element_array); element_array = NULL; + if (!ok) + goto err; ret = spdxtool_serialize_value_object(object_list); object_list = NULL; @@ -813,13 +822,20 @@ spdxtool_core_relationship_to_object(pkgconf_client_t *client, const spdxtool_co if (!(spdxtool_serialize_object_add_string(object_list, "type", relationship->type) && spdxtool_serialize_object_add_string(object_list, "creationInfo", relationship->creation_info) && spdxtool_serialize_object_add_string(object_list, "spdxId", relationship->spdx_id) && - spdxtool_serialize_object_add_string(object_list, "from", relationship->from) && - spdxtool_serialize_object_add_array(object_list, "to", to) && - spdxtool_serialize_object_add_string(object_list, "relationshipType", relationship->relationship_type))) + spdxtool_serialize_object_add_string(object_list, "from", relationship->from))) { + /* none of the above transfers ownership of `to` */ + spdxtool_serialize_array_free(to); goto err; } + /* object_add_array always takes ownership of `to` (it is freed even on failure) */ + if (!spdxtool_serialize_object_add_array(object_list, "to", to)) + goto err; + + if (!spdxtool_serialize_object_add_string(object_list, "relationshipType", relationship->relationship_type)) + goto err; + if (relationship->scope != NULL && !spdxtool_serialize_object_add_string(object_list, "scope", relationship->scope)) { diff --git a/cli/spdxtool/generate.c b/cli/spdxtool/generate.c new file mode 100644 index 000000000000..c5d8b29229c4 --- /dev/null +++ b/cli/spdxtool/generate.c @@ -0,0 +1,190 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + */ + +#include <libpkgconf/stdinc.h> +#include <libpkgconf/libpkgconf.h> +#include "util.h" +#include "core.h" +#include "software.h" +#include "serialize.h" +#include "simplelicensing.h" +#include "generate.h" + +// NOTE: this function is passed to pkgconf_pkg_traverse +static void +generate_spdx_package(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *ptr) +{ + spdxtool_core_spdx_document_t *document = (spdxtool_core_spdx_document_t *)ptr; + pkgconf_node_t *node = NULL; + spdxtool_software_sbom_t *sbom = NULL; + char *package_spdx = NULL; + char *spdx_id_string = NULL; + char sep = spdxtool_util_get_uri_separator(client); + + if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) + return; + + spdx_id_string = spdxtool_util_get_spdx_id_string(client, "software_Sbom", pkg->id); + if (!spdx_id_string) + goto err; + + sbom = spdxtool_software_sbom_new(client, spdx_id_string, document->creation_info, "build"); + free(spdx_id_string); + spdx_id_string = NULL; + if (!sbom) + goto err; + + sbom->spdx_document = document; + sbom->rootElement = pkg; + + package_spdx = spdxtool_util_get_spdx_id_string(client, "Package", pkg->id); + if (!package_spdx) + goto err; + + pkgconf_tuple_add(client, &pkg->vars, "spdxId", package_spdx, false, 0); + free(package_spdx); + package_spdx = NULL; + + pkgconf_tuple_add(client, &pkg->vars, "creationInfo", document->creation_info, false, 0); + pkgconf_tuple_add(client, &pkg->vars, "agent", document->agent, false, 0); + + if (pkg->maintainer != NULL) + { + const char *supplier = spdxtool_core_spdx_document_add_maintainer(client, document, pkg->maintainer); + if (!supplier) + goto err; + + pkgconf_tuple_add(client, &pkg->vars, "suppliedBy", supplier, false, 0); + } + + if (pkg->license.head != NULL) + { + pkgconf_buffer_t spdx_id_buf = PKGCONF_BUFFER_INITIALIZER; + + pkgconf_buffer_append_fmt(&spdx_id_buf, "%s%chasDeclaredLicense", pkg->id, sep); + char *spdx_id_name = pkgconf_buffer_freeze(&spdx_id_buf); + if (!spdx_id_name) + goto err; + + package_spdx = spdxtool_util_get_spdx_id_string(client, "Relationship", spdx_id_name); + free(spdx_id_name); + if (!package_spdx) + goto err; + + pkgconf_tuple_add(client, &pkg->vars, "hasDeclaredLicense", package_spdx, false, 0); + free(package_spdx); + package_spdx = NULL; + + pkgconf_buffer_t concluded_buf = PKGCONF_BUFFER_INITIALIZER; + pkgconf_buffer_append_fmt(&concluded_buf, "%s%chasConcludedLicense", pkg->id, sep); + spdx_id_name = pkgconf_buffer_freeze(&concluded_buf); + if (!spdx_id_name) + goto err; + + package_spdx = spdxtool_util_get_spdx_id_string(client, "Relationship", spdx_id_name); + free(spdx_id_name); + if (!package_spdx) + goto err; + + pkgconf_tuple_add(client, &pkg->vars, "hasConcludedLicense", package_spdx, false, 0); + free(package_spdx); + package_spdx = NULL; + + PKGCONF_FOREACH_LIST_ENTRY(pkg->license.head, node) + { + const pkgconf_license_t *license = node->data; + if (license->type == PKGCONF_LICENSE_EXPRESSION) + { + if (!spdxtool_core_spdx_document_add_license(client, document, license->data)) + goto err; + } + } + } + + node = calloc(1, sizeof(pkgconf_node_t)); + if (!node) + goto err; + + pkgconf_node_insert_tail(node, sbom, &document->rootElement); + return; + +err: + pkgconf_error(client, "generate_spdx_package: failed for %s", pkg->id); + free(package_spdx); + free(spdx_id_string); + spdxtool_software_sbom_free(sbom); +} + +bool +spdxtool_generate(pkgconf_client_t *client, pkgconf_pkg_t *world, FILE *out, int maxdepth, + const char *creation_time, const char *creation_id, const char *agent_name) +{ + const char *agent_name_string = agent_name ? agent_name : "Default"; + const char *creation_id_string = creation_id ? creation_id : "_:creationinfo_1"; + + spdxtool_core_agent_t *agent = spdxtool_core_agent_new(client, creation_id_string, agent_name_string); + if (!agent) + { + pkgconf_error(client, "Could not create agent struct"); + return false; + } + + spdxtool_core_creation_info_t *creation = spdxtool_core_creation_info_new(client, agent->spdx_id, creation_id_string, creation_time); + if (!creation) + { + pkgconf_error(client, "Could not create creation info struct"); + spdxtool_core_agent_free(agent); + return false; + } + + char *spdx_id_int = spdxtool_util_get_spdx_id_int(client, "spdxDocument"); + spdxtool_core_spdx_document_t *document = spdxtool_core_spdx_document_new(client, spdx_id_int, creation_id_string, agent->spdx_id); + free(spdx_id_int); + if (!document) + { + pkgconf_error(client, "Could not create document"); + spdxtool_core_creation_info_free(creation); + spdxtool_core_agent_free(agent); + return false; + } + + int eflag = pkgconf_pkg_traverse(client, world, generate_spdx_package, document, maxdepth, 0); + if (eflag != PKGCONF_PKG_ERRF_OK) + { + spdxtool_core_spdx_document_free(document); + spdxtool_core_creation_info_free(creation); + spdxtool_core_agent_free(agent); + return false; + } + + spdxtool_serialize_value_t *root = spdxtool_serialize_sbom(client, agent, creation, document); + if (!root) + { + spdxtool_core_spdx_document_free(document); + spdxtool_core_creation_info_free(creation); + spdxtool_core_agent_free(agent); + return false; + } + + pkgconf_buffer_t buffer = PKGCONF_BUFFER_INITIALIZER; + spdxtool_serialize_value_to_buf(&buffer, root, 0); + spdxtool_serialize_value_free(root); + + bool ret = pkgconf_output_file_fmt(out, "%s\n", pkgconf_buffer_str(&buffer)); + pkgconf_buffer_finalize(&buffer); + + spdxtool_core_spdx_document_free(document); + spdxtool_core_creation_info_free(creation); + spdxtool_core_agent_free(agent); + + if (!ret) + pkgconf_error(client, "spdxtool: Could not output to file: %s", strerror(errno)); + return ret; +} diff --git a/cli/spdxtool/generate.h b/cli/spdxtool/generate.h new file mode 100644 index 000000000000..818b8e4d9192 --- /dev/null +++ b/cli/spdxtool/generate.h @@ -0,0 +1,29 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * Portions of this software were developed by + * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from + * the FreeBSD Foundation + */ + +#ifndef SPDXTOOL_GENERATE_H +#define SPDXTOOL_GENERATE_H + +#include <stdio.h> +#include <libpkgconf/libpkgconf.h> + +/* + * Build an SPDX SBOM for a solved dependency graph and write it to *out*. + * + * The dependency graph rooted at *world* must already have been solved (e.g. + * with pkgconf_queue_solve) so that every package's match is populated. The + * spdxtool_util_set_* configuration (URI root, separator, version, license) + * must have been applied to *client* beforehand. + */ +bool spdxtool_generate(pkgconf_client_t *client, pkgconf_pkg_t *world, FILE *out, + int maxdepth, const char *creation_time, const char *creation_id, + const char *agent_name); + +#endif diff --git a/cli/spdxtool/main.c b/cli/spdxtool/main.c index 7d52abe56670..a514341b93ab 100644 --- a/cli/spdxtool/main.c +++ b/cli/spdxtool/main.c @@ -17,6 +17,7 @@ #include "software.h" #include "serialize.h" #include "simplelicensing.h" +#include "generate.h" #define PKG_VERSION (((uint64_t) 1) << 1) #define PKG_ABOUT (((uint64_t) 1) << 2) @@ -55,177 +56,6 @@ error_handler(const char *msg, const pkgconf_client_t *client, void *data) return true; } -// NOTE: this function is passed to pkgconf_pkg_traverse -static void -generate_spdx_package(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *ptr) -{ - spdxtool_core_spdx_document_t *document = (spdxtool_core_spdx_document_t *)ptr; - pkgconf_node_t *node = NULL; - spdxtool_software_sbom_t *sbom = NULL; - char *package_spdx = NULL; - char *spdx_id_string = NULL; - char sep = spdxtool_util_get_uri_separator(client); - - if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) - return; - - spdx_id_string = spdxtool_util_get_spdx_id_string(client, "software_Sbom", pkg->id); - if (!spdx_id_string) - goto err; - - sbom = spdxtool_software_sbom_new(client, spdx_id_string, document->creation_info, "build"); - free(spdx_id_string); - spdx_id_string = NULL; - if (!sbom) - goto err; - - sbom->spdx_document = document; - sbom->rootElement = pkg; - - package_spdx = spdxtool_util_get_spdx_id_string(client, "Package", pkg->id); - if (!package_spdx) - goto err; - - pkgconf_tuple_add(client, &pkg->vars, "spdxId", package_spdx, false, 0); - free(package_spdx); - package_spdx = NULL; - - pkgconf_tuple_add(client, &pkg->vars, "creationInfo", document->creation_info, false, 0); - pkgconf_tuple_add(client, &pkg->vars, "agent", document->agent, false, 0); - - if (pkg->maintainer != NULL) - { - const char *supplier = spdxtool_core_spdx_document_add_maintainer(client, document, pkg->maintainer); - if (!supplier) - goto err; - - pkgconf_tuple_add(client, &pkg->vars, "suppliedBy", supplier, false, 0); - } - - if (pkg->license.head != NULL) - { - pkgconf_buffer_t spdx_id_buf = PKGCONF_BUFFER_INITIALIZER; - - pkgconf_buffer_append_fmt(&spdx_id_buf, "%s%chasDeclaredLicense", pkg->id, sep); - char *spdx_id_name = pkgconf_buffer_freeze(&spdx_id_buf); - if (!spdx_id_name) - goto err; - - package_spdx = spdxtool_util_get_spdx_id_string(client, "Relationship", spdx_id_name); - free(spdx_id_name); - if (!package_spdx) - goto err; - - pkgconf_tuple_add(client, &pkg->vars, "hasDeclaredLicense", package_spdx, false, 0); - free(package_spdx); - package_spdx = NULL; - - pkgconf_buffer_t concluded_buf = PKGCONF_BUFFER_INITIALIZER; - pkgconf_buffer_append_fmt(&concluded_buf, "%s%chasConcludedLicense", pkg->id, sep); - spdx_id_name = pkgconf_buffer_freeze(&concluded_buf); - if (!spdx_id_name) - goto err; - - package_spdx = spdxtool_util_get_spdx_id_string(client, "Relationship", spdx_id_name); - free(spdx_id_name); - if (!package_spdx) - goto err; - - pkgconf_tuple_add(client, &pkg->vars, "hasConcludedLicense", package_spdx, false, 0); - free(package_spdx); - package_spdx = NULL; - - PKGCONF_FOREACH_LIST_ENTRY(pkg->license.head, node) - { - const pkgconf_license_t *license = node->data; - if (license->type == PKGCONF_LICENSE_EXPRESSION) - { - if (!spdxtool_core_spdx_document_add_license(client, document, license->data)) - goto err; - } - } - } - - node = calloc(1, sizeof(pkgconf_node_t)); - if (!node) - goto err; - - pkgconf_node_insert_tail(node, sbom, &document->rootElement); - return; - -err: - pkgconf_error(client, "generate_spdx_package: failed for %s", pkg->id); - free(package_spdx); - free(spdx_id_string); - spdxtool_software_sbom_free(sbom); -} - -static bool -generate_spdx(pkgconf_client_t *client, pkgconf_pkg_t *world, const char *creation_time, const char *creation_id, const char *agent_name) -{ - const char *agent_name_string = agent_name ? agent_name : "Default"; - const char *creation_id_string = creation_id ? creation_id : "_:creationinfo_1"; - - spdxtool_core_agent_t *agent = spdxtool_core_agent_new(client, creation_id_string, agent_name_string); - if (!agent) - { - pkgconf_error(client, "Could not create agent struct"); - return false; - } - - spdxtool_core_creation_info_t *creation = spdxtool_core_creation_info_new(client, agent->spdx_id, creation_id_string, creation_time); - if (!creation) - { - pkgconf_error(client, "Could not create creation info struct"); - spdxtool_core_agent_free(agent); - return false; - } - - char *spdx_id_int = spdxtool_util_get_spdx_id_int(client, "spdxDocument"); - spdxtool_core_spdx_document_t *document = spdxtool_core_spdx_document_new(client, spdx_id_int, creation_id_string, agent->spdx_id); - free(spdx_id_int); - if (!document) - { - pkgconf_error(client, "Could not create document"); - spdxtool_core_creation_info_free(creation); - spdxtool_core_agent_free(agent); - return false; - } - - int eflag = pkgconf_pkg_traverse(client, world, generate_spdx_package, document, maximum_traverse_depth, 0); - if (eflag != PKGCONF_PKG_ERRF_OK) - { - spdxtool_core_spdx_document_free(document); - spdxtool_core_creation_info_free(creation); - spdxtool_core_agent_free(agent); - return false; - } - - spdxtool_serialize_value_t *root = spdxtool_serialize_sbom(client, agent, creation, document); - if (!root) - { - spdxtool_core_spdx_document_free(document); - spdxtool_core_creation_info_free(creation); - spdxtool_core_agent_free(agent); - return false; - } - - pkgconf_buffer_t buffer = PKGCONF_BUFFER_INITIALIZER; - spdxtool_serialize_value_to_buf(&buffer, root, 0); - spdxtool_serialize_value_free(root); - - bool ret = pkgconf_output_file_fmt(sbom_out, "%s\n", pkgconf_buffer_str(&buffer)); - pkgconf_buffer_finalize(&buffer); - - spdxtool_core_spdx_document_free(document); - spdxtool_core_creation_info_free(creation); - spdxtool_core_agent_free(agent); - - if (!ret) - pkgconf_error(client, "spdxtool: Could not output to file: %s", strerror(errno)); - return ret; -} - static int version(void) { @@ -365,38 +195,25 @@ main(int argc, char *argv[]) if ((want_flags & PKG_HELP) == PKG_HELP) return usage(); - while (1) - { - const char *package = argv[pkg_optind]; - - if (package == NULL) - break; - - while (isspace((unsigned char)package[0])) - package++; + /* Join the remaining arguments into a single query string, as the main + * pkgconf CLI does, and let the dependency parser handle module names, + * comparison operators and versions. + */ + pkgconf_buffer_t queryparams = PKGCONF_BUFFER_INITIALIZER; - /* skip empty packages */ - if (package[0] == '\0') - { - pkg_optind++; - continue; - } + while (pkg_optind < argc && argv[pkg_optind] != NULL) + { + if (pkgconf_buffer_len(&queryparams) > 0) + pkgconf_buffer_push_byte(&queryparams, ' '); - if (argv[pkg_optind + 1] == NULL || !PKGCONF_IS_OPERATOR_CHAR(*(argv[pkg_optind + 1]))) - { - pkgconf_queue_push(&pkgq, package); - pkg_optind++; - } - else - { - char packagebuf[PKGCONF_BUFSIZE]; + pkgconf_buffer_append(&queryparams, argv[pkg_optind]); + pkg_optind++; + } - snprintf(packagebuf, sizeof packagebuf, "%s %s %s", package, argv[pkg_optind + 1], argv[pkg_optind + 2]); - pkg_optind += 3; + if (pkgconf_buffer_len(&queryparams) > 0) + pkgconf_queue_push(&pkgq, pkgconf_buffer_str(&queryparams)); - pkgconf_queue_push(&pkgq, packagebuf); - } - } + pkgconf_buffer_finalize(&queryparams); if (!pkgconf_queue_solve(&pkg_client, &pkgq, &world, maximum_traverse_depth)) { @@ -409,7 +226,7 @@ main(int argc, char *argv[]) spdxtool_util_set_spdx_license(&pkg_client, bom_license); spdxtool_util_set_spdx_version(&pkg_client, spdx_version); - if (!generate_spdx(&pkg_client, &world, creation_time, creation_id, agent_name)) + if (!spdxtool_generate(&pkg_client, &world, sbom_out, maximum_traverse_depth, creation_time, creation_id, agent_name)) { ret = EXIT_FAILURE; goto out; diff --git a/cli/spdxtool/serialize.c b/cli/spdxtool/serialize.c index fc5c460d77cb..813095b8b7ee 100644 --- a/cli/spdxtool/serialize.c +++ b/cli/spdxtool/serialize.c @@ -47,8 +47,8 @@ serialize_escape_string(pkgconf_buffer_t *buffer, const char *s) pkgconf_buffer_append(buffer, "\\t"); break; default: - if (*p < 0x20) - pkgconf_buffer_append_fmt(buffer, "\\u%04x", (unsigned int)*p); + if ((unsigned char) *p < 0x20) + pkgconf_buffer_append_fmt(buffer, "\\u%04x", (unsigned int)(unsigned char) *p); else pkgconf_buffer_push_byte(buffer, *p); } diff --git a/cli/spdxtool/software.c b/cli/spdxtool/software.c index ac76cf73e7de..c95ec84cbdfa 100644 --- a/cli/spdxtool/software.c +++ b/cli/spdxtool/software.c @@ -137,6 +137,10 @@ spdxtool_software_sbom_to_object(pkgconf_client_t *client, spdxtool_software_sbo pkgconf_pkg_t *match = dep->match; pkgconf_buffer_t relationship_buf = PKGCONF_BUFFER_INITIALIZER; + /* an unresolved (but tolerated) dependency has no match */ + if (match == NULL) + continue; + pkgconf_buffer_append_fmt(&relationship_buf, "%s%cdependsOn%c%s", sbom->rootElement->id, sep, sep, match->id); char *relationship_str = pkgconf_buffer_freeze(&relationship_buf); if (!relationship_str) @@ -168,6 +172,10 @@ spdxtool_software_sbom_to_object(pkgconf_client_t *client, spdxtool_software_sbo pkgconf_pkg_t *match = dep->match; pkgconf_buffer_t relationship_buf = PKGCONF_BUFFER_INITIALIZER; + /* an unresolved (but tolerated) dependency has no match */ + if (match == NULL) + continue; + pkgconf_buffer_append_fmt(&relationship_buf, "%s%cdependsOn%c%s", sbom->rootElement->id, sep, sep, match->id); char *relationship_str = pkgconf_buffer_freeze(&relationship_buf); if (!relationship_str) @@ -236,17 +244,24 @@ spdxtool_software_sbom_to_object(pkgconf_client_t *client, spdxtool_software_sbo goto err; } - if (!spdxtool_serialize_object_add_array(object_list, "software_sbomType", sbom_type_array)) - goto err; + /* object_add_array always takes ownership of the array (it is freed even on + * failure), so clear our reference before checking the result to avoid a + * double free at the error label. + */ + bool ok = spdxtool_serialize_object_add_array(object_list, "software_sbomType", sbom_type_array); sbom_type_array = NULL; - - if (!spdxtool_serialize_object_add_array(object_list, "rootElement", root_element_array)) + if (!ok) goto err; - root_element_array = NULL; - if (!spdxtool_serialize_object_add_array(object_list, "element", element_array)) + ok = spdxtool_serialize_object_add_array(object_list, "rootElement", root_element_array); + root_element_array = NULL; + if (!ok) goto err; + + ok = spdxtool_serialize_object_add_array(object_list, "element", element_array); element_array = NULL; + if (!ok) + goto err; if (!spdxtool_core_spdx_document_add_package(client, sbom->spdx_document, sbom->rootElement)) goto err; @@ -343,9 +358,14 @@ spdxtool_software_package_to_object(pkgconf_client_t *client, pkgconf_pkg_t *pkg goto err; } - if (!spdxtool_serialize_object_add_array(object_list, "originatedBy", originated_by)) - goto err; + /* object_add_array always takes ownership of the array (it is freed even on + * failure), so clear our reference before checking the result to avoid a + * double free at the error label. + */ + bool ok = spdxtool_serialize_object_add_array(object_list, "originatedBy", originated_by); originated_by = NULL; + if (!ok) + goto err; supplier = spdxtool_util_tuple_lookup(client, &pkg->vars, "suppliedBy"); if (supplier) @@ -357,9 +377,10 @@ spdxtool_software_package_to_object(pkgconf_client_t *client, pkgconf_pkg_t *pkg if (!spdxtool_serialize_array_add_string(supplied_by, supplier)) goto err; - if (!spdxtool_serialize_object_add_array(object_list, "suppliedBy", supplied_by)) - goto err; + ok = spdxtool_serialize_object_add_array(object_list, "suppliedBy", supplied_by); supplied_by = NULL; + if (!ok) + goto err; } if (!serialize_copyright_lines_to_object(object_list, &pkg->copyright)) @@ -442,6 +463,10 @@ spdxtool_software_package_to_object(pkgconf_client_t *client, pkgconf_pkg_t *pkg pkgconf_pkg_t *match = dep->match; pkgconf_buffer_t relationship_buf = PKGCONF_BUFFER_INITIALIZER; + /* an unresolved (but tolerated) dependency has no match */ + if (match == NULL) + continue; + pkgconf_buffer_append_fmt(&relationship_buf, "%s%cdependsOn%c%s", pkg->id, sep, sep, match->id); char *relationship_str = pkgconf_buffer_freeze(&relationship_buf); if (!relationship_str) @@ -484,6 +509,10 @@ spdxtool_software_package_to_object(pkgconf_client_t *client, pkgconf_pkg_t *pkg pkgconf_pkg_t *match = dep->match; pkgconf_buffer_t relationship_buf = PKGCONF_BUFFER_INITIALIZER; + /* an unresolved (but tolerated) dependency has no match */ + if (match == NULL) + continue; + pkgconf_buffer_append_fmt(&relationship_buf, "%s%cdependsOn%c%s", pkg->id, sep, sep, match->id); char *relationship_str = pkgconf_buffer_freeze(&relationship_buf); if (!relationship_str) diff --git a/configure.ac b/configure.ac index 6778b38a4075..ec5906a564ce 100644 --- a/configure.ac +++ b/configure.ac @@ -12,7 +12,7 @@ dnl implied. In no event shall the authors be liable for any damages arising dnl from the use of this software. AC_PREREQ([2.71]) -AC_INIT([pkgconf],[2.9.92],[https://github.com/pkgconf/pkgconf/issues/new]) +AC_INIT([pkgconf],[2.9.93],[https://github.com/pkgconf/pkgconf/issues/new]) AC_MSG_WARN([ =================================================================== Autotools support is deprecated and will be removed in pkgconf 3.1. diff --git a/fuzzer/meson.build b/fuzzer/meson.build index 534d6347f8bd..66594da647e3 100644 --- a/fuzzer/meson.build +++ b/fuzzer/meson.build @@ -30,9 +30,27 @@ solver_fuzzer_exe = executable( link_args: fuzzer_link_args, ) +spdxtool_fuzzer_exe = executable( + 'spdxtool-fuzzer', + 'spdxtool-fuzzer.c', + 'alloc-inject.c', + '../cli/spdxtool/core.c', + '../cli/spdxtool/software.c', + '../cli/spdxtool/serialize.c', + '../cli/spdxtool/simplelicensing.c', + '../cli/spdxtool/util.c', + '../cli/spdxtool/generate.c', + dependencies: dep_libpkgconf, + include_directories: include_directories('..', '../cli/spdxtool'), + install: false, + c_args: ['-fsanitize=fuzzer'], + link_args: fuzzer_link_args, +) + fuzz_root = join_paths(meson.project_build_root(), 'fuzz') corpus_dir = join_paths(fuzz_root, 'corpus') solver_corpus_dir = join_paths(fuzz_root, 'solver-corpus') +spdxtool_corpus_dir = join_paths(fuzz_root, 'spdxtool-corpus') seed_dir = join_paths(meson.project_source_root(), 'tests', 'lib1') solver_seed_dir = join_paths(meson.project_source_root(), 'fuzzer', 'solver-corpus') @@ -50,3 +68,8 @@ run_target( 'fuzz-solver', command: [solver_fuzzer_exe, solver_corpus_dir, solver_seed_dir] ) + +run_target( + 'fuzz-spdxtool', + command: [spdxtool_fuzzer_exe, spdxtool_corpus_dir, solver_seed_dir] +) 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; +} diff --git a/libpkgconf/libpkgconf.h b/libpkgconf/libpkgconf.h index 22b6c97a94f8..de646a2a54c5 100644 --- a/libpkgconf/libpkgconf.h +++ b/libpkgconf/libpkgconf.h @@ -71,8 +71,8 @@ typedef struct pkgconf_license_ pkgconf_license_t; #define PKGCONF_FOREACH_LIST_ENTRY_REVERSE(tail, value) \ for ((value) = (tail); (value) != NULL; (value) = (value)->prev) -#define LIBPKGCONF_VERSION 20992 -#define LIBPKGCONF_VERSION_STR "2.9.92" +#define LIBPKGCONF_VERSION 20993 +#define LIBPKGCONF_VERSION_STR "2.9.93" struct pkgconf_queue_ { pkgconf_node_t iter; diff --git a/libpkgconf/license.c b/libpkgconf/license.c index e9bef533584f..7c7cb21802f0 100644 --- a/libpkgconf/license.c +++ b/libpkgconf/license.c @@ -228,15 +228,15 @@ pkgconf_license_evaluate_str(pkgconf_client_t *client, pkgconf_list_t *license_l } pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_BRACKET_CLOSE, ")"); } - else if (!strncasecmp(cur_word, "and", 3)) + else if (!strcasecmp(cur_word, "and")) { pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_AND, "AND"); } - else if (!strncasecmp(cur_word, "or", 2)) + else if (!strcasecmp(cur_word, "or")) { pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_OR, "OR"); } - else if (!strncasecmp(cur_word, "with", 2)) + else if (!strcasecmp(cur_word, "with")) { pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_WITH, "WITH"); } @@ -316,7 +316,7 @@ pkgconf_license_render(pkgconf_client_t *client, const pkgconf_list_t *list, pkg frag_string = PKGCONF_BUFFER_FROM_STR(license->data); pkgconf_buffer_append(buf, pkgconf_buffer_str_or_empty(frag_string)); - if (license->type == PKGCONF_LICENSE_BRACKET_OPEN || (node->next != NULL && ((const pkgconf_license_t *)node->next)->type == PKGCONF_LICENSE_BRACKET_CLOSE)) + if (license->type == PKGCONF_LICENSE_BRACKET_OPEN || (node->next != NULL && ((const pkgconf_license_t *)node->next->data)->type == PKGCONF_LICENSE_BRACKET_CLOSE)) { is_delim = false; } diff --git a/meson.build b/meson.build index 358a15035dfc..798095165d94 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('pkgconf', 'c', - version : '2.9.92', + version : '2.9.93', license : 'ISC', default_options : [ 'c_std=c99', @@ -95,16 +95,29 @@ foreach macro, value : feature_macros endforeach feature_defines = '\n'.join(feature_define_lines) +have_reallocarray = false foreach f : check_functions name = f[0].to_upper().underscorify() if cc.has_function(f[0], prefix : feature_defines + '\n#include <@0@>'.format(f[1])) and cc.has_header_symbol(f[1], f[0], prefix : feature_defines) cdata.set('HAVE_@0@'.format(name), 1) cdata.set('HAVE_DECL_@0@'.format(name), 1) + if f[0] == 'reallocarray' + have_reallocarray = true + endif else cdata.set('HAVE_DECL_@0@'.format(name), 0) endif endforeach +# The fuzzers link fuzzer/alloc-inject, which wraps reallocarray() and so +# references __real_reallocarray; --wrap can only bind that to a reallocarray the +# libc actually provides. On platforms too old to have it (e.g. glibc < 2.26) +# the link would fail, so disable fuzzing there (mirrors the OOM-test gate below). +if fuzzing and not have_reallocarray + warning('fuzzing requested, but the C library lacks reallocarray() (e.g. glibc < 2.26); disabling') + fuzzing = false +endif + # nl_langinfo_l() needs locale.h for locale_t in addition to langinfo.h (on # macOS it also needs xlocale.h), so it can't share the single-header # check_functions loop above. @@ -257,6 +270,7 @@ spdxtool_exe = executable('spdxtool', 'cli/spdxtool/serialize.c', 'cli/spdxtool/simplelicensing.c', 'cli/spdxtool/util.c', + 'cli/spdxtool/generate.c', 'cli/getopt_long.c', windows_manifest, link_with : libpkgconf, @@ -323,8 +337,13 @@ test('api-serialize', test_api_serialize_exe) # fault injector through the linker's --wrap, so they are built wherever --wrap # is supported: glibc and musl Linux (GNU ld / lld), and skipped on macOS ld64 # and MSVC. +# +# alloc-inject wraps reallocarray() and so references __real_reallocarray; --wrap +# can only bind that to a reallocarray the libc actually provides. On platforms +# too old to have it (e.g. glibc < 2.26) the link would fail, so skip there. build_oom_tests = host_machine.system() == 'linux' \ - and cc.has_link_argument('-Wl,--wrap=calloc') + and cc.has_link_argument('-Wl,--wrap=calloc') \ + and have_reallocarray # Allocator set wrapped by fuzzer/alloc-inject.c. oom_wrap_args = [ |
