xref: /freebsd/contrib/pkgconf/fuzzer/solver-fuzzer.c (revision 592efe252472a3385acf36b1f49ecf710a7f3d9c)
1 /*
2  * solver-fuzzer.c
3  * dependency solver 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 "alloc-inject.h"
22 
23 int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
24 
25 /* bound the number of injection rounds per input to keep executions cheap */
26 #define ALLOC_FAIL_MAX 4096
27 
28 /* the fuzzer input is split on NUL bytes into up to this many .pc files, named
29  * a.pc, b.pc, ... so that Requires/Conflicts/Provides between them resolve.
30  */
31 #define UNIVERSE_MAX 4
32 #define SOLVE_MAXDEPTH 10
33 
34 static const char universe_names[UNIVERSE_MAX] = { 'a', 'b', 'c', 'd' };
35 
36 static const char *
environ_lookup_handler(const pkgconf_client_t * client,const char * key)37 environ_lookup_handler(const pkgconf_client_t *client, const char *key)
38 {
39 	(void) client;
40 	(void) key;
41 
42 	return NULL;
43 }
44 
45 static int
write_all(int fd,const uint8_t * data,size_t size)46 write_all(int fd, const uint8_t *data, size_t size)
47 {
48 	while (size > 0)
49 	{
50 		ssize_t n = write(fd, data, size);
51 
52 		if (n < 0)
53 		{
54 			if (errno == EINTR)
55 				continue;
56 			return -1;
57 		}
58 
59 		data += n;
60 		size -= n;
61 	}
62 
63 	return 0;
64 }
65 
66 static void
write_pkg(const char * dir,char name,const uint8_t * data,size_t size)67 write_pkg(const char *dir, char name, const uint8_t *data, size_t size)
68 {
69 	char path[PKGCONF_ITEM_SIZE];
70 	int fd;
71 
72 	snprintf(path, sizeof path, "%s/%c.pc", dir, name);
73 
74 	fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
75 	if (fd < 0)
76 		return;
77 
78 	write_all(fd, data, size);
79 	close(fd);
80 }
81 
82 static void
write_universe(const char * dir,const uint8_t * data,size_t size)83 write_universe(const char *dir, const uint8_t *data, size_t size)
84 {
85 	size_t start = 0, idx = 0;
86 
87 	for (size_t i = 0; i < size && idx < UNIVERSE_MAX; i++)
88 	{
89 		if (data[i] == 0x00)
90 		{
91 			write_pkg(dir, universe_names[idx++], data + start, i - start);
92 			start = i + 1;
93 		}
94 	}
95 
96 	if (idx < UNIVERSE_MAX)
97 		write_pkg(dir, universe_names[idx], data + start, size - start);
98 }
99 
100 static void
cleanup_universe(const char * dir)101 cleanup_universe(const char *dir)
102 {
103 	char path[PKGCONF_ITEM_SIZE];
104 
105 	for (size_t i = 0; i < UNIVERSE_MAX; i++)
106 	{
107 		snprintf(path, sizeof path, "%s/%c.pc", dir, universe_names[i]);
108 		unlink(path);
109 	}
110 
111 	rmdir(dir);
112 }
113 
114 static void
run_solve(const pkgconf_cross_personality_t * pers,const char * dir)115 run_solve(const pkgconf_cross_personality_t *pers, const char *dir)
116 {
117 	pkgconf_client_t *client = pkgconf_client_new(NULL, NULL, pers, NULL, environ_lookup_handler);
118 	if (client == NULL)
119 		return;
120 
121 	pkgconf_path_add(dir, &client->dir_list, false);
122 
123 	pkgconf_pkg_t world = {
124 		.id = "virtual:world",
125 		.realname = "virtual world package",
126 		.flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL,
127 	};
128 	pkgconf_list_t pkgq = PKGCONF_LIST_INITIALIZER;
129 
130 	pkgconf_queue_push(&pkgq, "a");
131 
132 	if (pkgconf_queue_solve(client, &pkgq, &world, SOLVE_MAXDEPTH))
133 	{
134 		pkgconf_list_t cflags = PKGCONF_LIST_INITIALIZER;
135 		pkgconf_list_t libs = PKGCONF_LIST_INITIALIZER;
136 		pkgconf_buffer_t render = PKGCONF_BUFFER_INITIALIZER;
137 
138 		pkgconf_pkg_cflags(client, &world, &cflags, SOLVE_MAXDEPTH);
139 		pkgconf_pkg_libs(client, &world, &libs, SOLVE_MAXDEPTH);
140 
141 		pkgconf_fragment_render_buf(&cflags, &render, true, NULL, ' ');
142 		pkgconf_fragment_render_buf(&libs, &render, true, NULL, ' ');
143 
144 		pkgconf_buffer_finalize(&render);
145 		pkgconf_fragment_free(&cflags);
146 		pkgconf_fragment_free(&libs);
147 	}
148 
149 	pkgconf_solution_free(client, &world);
150 	pkgconf_queue_free(&pkgq);
151 	pkgconf_client_free(client);
152 }
153 
154 int
LLVMFuzzerTestOneInput(const uint8_t * data,size_t size)155 LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
156 {
157 	if (size == 0)
158 		return 0;
159 
160 	char dir[] = "/tmp/pkgconf-fuzz-univ-XXXXXX";
161 	if (mkdtemp(dir) == NULL)
162 		return 0;
163 
164 	write_universe(dir, data, size);
165 
166 	pkgconf_cross_personality_t *pers = pkgconf_cross_personality_default();
167 
168 	/* baseline run with all allocations succeeding */
169 	run_solve(pers, dir);
170 
171 	/* then fail each allocation site reachable by this input, one at a time */
172 	for (unsigned long i = 1; i <= ALLOC_FAIL_MAX; i++)
173 	{
174 		alloc_inject_arm(i);
175 		run_solve(pers, dir);
176 		alloc_inject_disarm();
177 
178 		if (!alloc_inject_fired())
179 			break;
180 	}
181 
182 	pkgconf_cross_personality_deinit(pers);
183 	cleanup_universe(dir);
184 
185 	return 0;
186 }
187