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