xref: /freebsd/contrib/pkgconf/fuzzer/spdxtool-fuzzer.c (revision 592efe252472a3385acf36b1f49ecf710a7f3d9c)
1 /*
2  * spdxtool-fuzzer.c
3  * SPDX SBOM serializer fuzzing harness
4  *
5  * SPDX-License-Identifier: pkgconf
6  *
7  * Copyright (c) 2026 pkgconf authors (see AUTHORS).
8  *
9  * Permission to use, copy, modify, and/or distribute this software for any
10  * purpose with or without fee is hereby granted, provided that the above
11  * copyright notice and this permission notice appear in all copies.
12  *
13  * This software is provided 'as is' and without any warranty, express or
14  * implied.  In no event shall the authors be liable for any damages arising
15  * from the use of this software.
16  */
17 
18 #include <libpkgconf/stdinc.h>
19 #include <libpkgconf/libpkgconf.h>
20 
21 #include "util.h"
22 #include "generate.h"
23 
24 #include "alloc-inject.h"
25 
26 int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
27 
28 /* bound the number of injection rounds per input to keep executions cheap */
29 #define ALLOC_FAIL_MAX 4096
30 
31 /* the fuzzer input is split on NUL bytes into up to this many .pc files, named
32  * a.pc, b.pc, ... so that Requires/Conflicts/Provides between them resolve, and
33  * the resulting graph is serialized to an SPDX SBOM.
34  */
35 #define UNIVERSE_MAX 4
36 #define SOLVE_MAXDEPTH 10
37 
38 static const char universe_names[UNIVERSE_MAX] = { 'a', 'b', 'c', 'd' };
39 
40 static const char *
environ_lookup_handler(const pkgconf_client_t * client,const char * key)41 environ_lookup_handler(const pkgconf_client_t *client, const char *key)
42 {
43 	(void) client;
44 	(void) key;
45 
46 	return NULL;
47 }
48 
49 static int
write_all(int fd,const uint8_t * data,size_t size)50 write_all(int fd, const uint8_t *data, size_t size)
51 {
52 	while (size > 0)
53 	{
54 		ssize_t n = write(fd, data, size);
55 
56 		if (n < 0)
57 		{
58 			if (errno == EINTR)
59 				continue;
60 			return -1;
61 		}
62 
63 		data += n;
64 		size -= n;
65 	}
66 
67 	return 0;
68 }
69 
70 static void
write_pkg(const char * dir,char name,const uint8_t * data,size_t size)71 write_pkg(const char *dir, char name, const uint8_t *data, size_t size)
72 {
73 	char path[PKGCONF_ITEM_SIZE];
74 	int fd;
75 
76 	snprintf(path, sizeof path, "%s/%c.pc", dir, name);
77 
78 	fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
79 	if (fd < 0)
80 		return;
81 
82 	write_all(fd, data, size);
83 	close(fd);
84 }
85 
86 static void
write_universe(const char * dir,const uint8_t * data,size_t size)87 write_universe(const char *dir, const uint8_t *data, size_t size)
88 {
89 	size_t start = 0, idx = 0;
90 
91 	for (size_t i = 0; i < size && idx < UNIVERSE_MAX; i++)
92 	{
93 		if (data[i] == 0x00)
94 		{
95 			write_pkg(dir, universe_names[idx++], data + start, i - start);
96 			start = i + 1;
97 		}
98 	}
99 
100 	if (idx < UNIVERSE_MAX)
101 		write_pkg(dir, universe_names[idx], data + start, size - start);
102 }
103 
104 static void
cleanup_universe(const char * dir)105 cleanup_universe(const char *dir)
106 {
107 	char path[PKGCONF_ITEM_SIZE];
108 
109 	for (size_t i = 0; i < UNIVERSE_MAX; i++)
110 	{
111 		snprintf(path, sizeof path, "%s/%c.pc", dir, universe_names[i]);
112 		unlink(path);
113 	}
114 
115 	rmdir(dir);
116 }
117 
118 /*
119  * Solve a freshly-written universe rooted at "a", then drive the real spdxtool
120  * SBOM serialization pipeline (cli/spdxtool/generate.c) over the result.
121  */
122 static void
run_spdx(const pkgconf_cross_personality_t * pers,const char * dir,FILE * out)123 run_spdx(const pkgconf_cross_personality_t *pers, const char *dir, FILE *out)
124 {
125 	pkgconf_client_t *client = pkgconf_client_new(NULL, NULL, pers, NULL, environ_lookup_handler);
126 	if (client == NULL)
127 		return;
128 
129 	pkgconf_client_set_flags(client, PKGCONF_PKG_PKGF_SEARCH_PRIVATE);
130 	pkgconf_path_add(dir, &client->dir_list, false);
131 
132 	/* the serializers read these back via the client's global tuples */
133 	spdxtool_util_set_uri_root(client, "https://example.com/test");
134 	spdxtool_util_set_uri_separator_colon(client, false);
135 	spdxtool_util_set_spdx_license(client, "CC0-1.0");
136 	spdxtool_util_set_spdx_version(client, "3.0.1");
137 
138 	pkgconf_pkg_t world = {
139 		.id = "virtual:world",
140 		.realname = "virtual world package",
141 		.flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL,
142 	};
143 	pkgconf_list_t pkgq = PKGCONF_LIST_INITIALIZER;
144 
145 	pkgconf_queue_push(&pkgq, "a");
146 
147 	if (pkgconf_queue_solve(client, &pkgq, &world, SOLVE_MAXDEPTH))
148 		/* fixed timestamp keeps the harness deterministic */
149 		spdxtool_generate(client, &world, out, SOLVE_MAXDEPTH,
150 			"2020-01-01T00:00:00Z", "_:creationinfo_1", "Default");
151 
152 	pkgconf_solution_free(client, &world);
153 	pkgconf_queue_free(&pkgq);
154 	pkgconf_client_free(client);
155 }
156 
157 int
LLVMFuzzerTestOneInput(const uint8_t * data,size_t size)158 LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
159 {
160 	if (size == 0)
161 		return 0;
162 
163 	char dir[] = "/tmp/pkgconf-fuzz-spdx-XXXXXX";
164 	if (mkdtemp(dir) == NULL)
165 		return 0;
166 
167 	write_universe(dir, data, size);
168 
169 	/* discard the serialized output; opened outside the injection loop so the
170 	 * allocations behind it are never the fault-injection target.
171 	 */
172 	FILE *out = fopen("/dev/null", "w");
173 	if (out == NULL)
174 	{
175 		cleanup_universe(dir);
176 		return 0;
177 	}
178 
179 	pkgconf_cross_personality_t *pers = pkgconf_cross_personality_default();
180 
181 	/* baseline run with all allocations succeeding */
182 	run_spdx(pers, dir, out);
183 
184 	/* then fail each allocation site reachable by this input, one at a time */
185 	for (unsigned long i = 1; i <= ALLOC_FAIL_MAX; i++)
186 	{
187 		alloc_inject_arm(i);
188 		run_spdx(pers, dir, out);
189 		alloc_inject_disarm();
190 
191 		if (!alloc_inject_fired())
192 			break;
193 	}
194 
195 	pkgconf_cross_personality_deinit(pers);
196 	fclose(out);
197 	cleanup_universe(dir);
198 
199 	return 0;
200 }
201