xref: /freebsd/crypto/openssh/regress/unittests/test_helper/fuzz.c (revision 644b4646c7acab87dc20d4e5dd53d2d9da152989)
1 /*	$OpenBSD: fuzz.c,v 1.9 2025/06/13 07:23:07 dtucker Exp $	*/
2 /*
3  * Copyright (c) 2011 Damien Miller <djm@mindrot.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 /* Utility functions/framework for fuzz tests */
19 
20 #include "includes.h"
21 
22 #include <sys/types.h>
23 #include <sys/uio.h>
24 
25 #include <assert.h>
26 #include <ctype.h>
27 #include <stdio.h>
28 #include <stdint.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <signal.h>
32 #include <unistd.h>
33 
34 #include "test_helper.h"
35 #include "atomicio.h"
36 
37 /* #define FUZZ_DEBUG */
38 
39 #ifdef FUZZ_DEBUG
40 # define FUZZ_DBG(x) do { \
41 		printf("%s:%d %s: ", __FILE__, __LINE__, __func__); \
42 		printf x; \
43 		printf("\n"); \
44 		fflush(stdout); \
45 	} while (0)
46 #else
47 # define FUZZ_DBG(x)
48 #endif
49 
50 /* For brevity later */
51 typedef unsigned long long fuzz_ullong;
52 
53 /* For base-64 fuzzing */
54 static const char fuzz_b64chars[] =
55     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
56 
57 struct fuzz {
58 	/* Fuzz method currently in use */
59 	int strategy;
60 
61 	/* Fuzz methods remaining */
62 	int strategies;
63 
64 	/* Original seed data blob */
65 	void *seed;
66 	size_t slen;
67 
68 	/* Current working copy of seed with fuzz mutations applied */
69 	u_char *fuzzed;
70 
71 	/* Used by fuzz methods */
72 	size_t o1, o2;
73 };
74 
75 static const char *
fuzz_ntop(u_int n)76 fuzz_ntop(u_int n)
77 {
78 	switch (n) {
79 	case 0:
80 		return "NONE";
81 	case FUZZ_1_BIT_FLIP:
82 		return "FUZZ_1_BIT_FLIP";
83 	case FUZZ_2_BIT_FLIP:
84 		return "FUZZ_2_BIT_FLIP";
85 	case FUZZ_1_BYTE_FLIP:
86 		return "FUZZ_1_BYTE_FLIP";
87 	case FUZZ_2_BYTE_FLIP:
88 		return "FUZZ_2_BYTE_FLIP";
89 	case FUZZ_TRUNCATE_START:
90 		return "FUZZ_TRUNCATE_START";
91 	case FUZZ_TRUNCATE_END:
92 		return "FUZZ_TRUNCATE_END";
93 	case FUZZ_BASE64:
94 		return "FUZZ_BASE64";
95 	default:
96 		abort();
97 	}
98 }
99 
100 static int
fuzz_fmt(struct fuzz * fuzz,char * s,size_t n)101 fuzz_fmt(struct fuzz *fuzz, char *s, size_t n)
102 {
103 	if (fuzz == NULL)
104 		return -1;
105 
106 	switch (fuzz->strategy) {
107 	case FUZZ_1_BIT_FLIP:
108 		snprintf(s, n, "%s case %zu of %zu (bit: %zu)\n",
109 		    fuzz_ntop(fuzz->strategy),
110 		    fuzz->o1, fuzz->slen * 8, fuzz->o1);
111 		return 0;
112 	case FUZZ_2_BIT_FLIP:
113 		snprintf(s, n, "%s case %llu of %llu (bits: %zu, %zu)\n",
114 		    fuzz_ntop(fuzz->strategy),
115 		    (((fuzz_ullong)fuzz->o2) * fuzz->slen * 8) + fuzz->o1,
116 		    ((fuzz_ullong)fuzz->slen * 8) * fuzz->slen * 8,
117 		    fuzz->o1, fuzz->o2);
118 		return 0;
119 	case FUZZ_1_BYTE_FLIP:
120 		snprintf(s, n, "%s case %zu of %zu (byte: %zu)\n",
121 		    fuzz_ntop(fuzz->strategy),
122 		    fuzz->o1, fuzz->slen, fuzz->o1);
123 		return 0;
124 	case FUZZ_2_BYTE_FLIP:
125 		snprintf(s, n, "%s case %llu of %llu (bytes: %zu, %zu)\n",
126 		    fuzz_ntop(fuzz->strategy),
127 		    (((fuzz_ullong)fuzz->o2) * fuzz->slen) + fuzz->o1,
128 		    ((fuzz_ullong)fuzz->slen) * fuzz->slen,
129 		    fuzz->o1, fuzz->o2);
130 		return 0;
131 	case FUZZ_TRUNCATE_START:
132 		snprintf(s, n, "%s case %zu of %zu (offset: %zu)\n",
133 		    fuzz_ntop(fuzz->strategy),
134 		    fuzz->o1, fuzz->slen, fuzz->o1);
135 		return 0;
136 	case FUZZ_TRUNCATE_END:
137 		snprintf(s, n, "%s case %zu of %zu (offset: %zu)\n",
138 		    fuzz_ntop(fuzz->strategy),
139 		    fuzz->o1, fuzz->slen, fuzz->o1);
140 		return 0;
141 	case FUZZ_BASE64:
142 		assert(fuzz->o2 < sizeof(fuzz_b64chars) - 1);
143 		snprintf(s, n, "%s case %llu of %llu (offset: %zu char: %c)\n",
144 		    fuzz_ntop(fuzz->strategy),
145 		    (fuzz->o1 * (fuzz_ullong)64) + fuzz->o2,
146 		    fuzz->slen * (fuzz_ullong)64, fuzz->o1,
147 		    fuzz_b64chars[fuzz->o2]);
148 		return 0;
149 	default:
150 		return -1;
151 	}
152 }
153 
154 static void
dump(u_char * p,size_t len)155 dump(u_char *p, size_t len)
156 {
157 	size_t i, j;
158 
159 	for (i = 0; i < len; i += 16) {
160 		fprintf(stderr, "%.4zd: ", i);
161 		for (j = i; j < i + 16; j++) {
162 			if (j < len)
163 				fprintf(stderr, "%02x ", p[j]);
164 			else
165 				fprintf(stderr, "   ");
166 		}
167 		fprintf(stderr, " ");
168 		for (j = i; j < i + 16; j++) {
169 			if (j < len) {
170 				if  (isascii(p[j]) && isprint(p[j]))
171 					fprintf(stderr, "%c", p[j]);
172 				else
173 					fprintf(stderr, ".");
174 			}
175 		}
176 		fprintf(stderr, "\n");
177 	}
178 }
179 
180 void
fuzz_dump(struct fuzz * fuzz)181 fuzz_dump(struct fuzz *fuzz)
182 {
183 	char buf[256];
184 
185 	if (fuzz_fmt(fuzz, buf, sizeof(buf)) != 0) {
186 		fprintf(stderr, "%s: fuzz invalid\n", __func__);
187 		abort();
188 	}
189 	fputs(buf, stderr);
190 	fprintf(stderr, "fuzz original %p len = %zu\n", fuzz->seed, fuzz->slen);
191 	dump(fuzz->seed, fuzz->slen);
192 	fprintf(stderr, "fuzz context %p len = %zu\n", fuzz, fuzz_len(fuzz));
193 	dump(fuzz_ptr(fuzz), fuzz_len(fuzz));
194 }
195 
196 static struct fuzz *last_fuzz;
197 
198 static void
siginfo(int unused)199 siginfo(int unused __attribute__((__unused__)))
200 {
201 	char buf[256];
202 
203 	test_info(buf, sizeof(buf));
204 	atomicio(vwrite, STDERR_FILENO, buf, strlen(buf));
205 	if (last_fuzz != NULL) {
206 		fuzz_fmt(last_fuzz, buf, sizeof(buf));
207 		atomicio(vwrite, STDERR_FILENO, buf, strlen(buf));
208 	}
209 }
210 
211 struct fuzz *
fuzz_begin(u_int strategies,const void * p,size_t l)212 fuzz_begin(u_int strategies, const void *p, size_t l)
213 {
214 	struct fuzz *ret = calloc(1, sizeof(*ret));
215 
216 	assert(p != NULL);
217 	assert(ret != NULL);
218 	ret->seed = malloc(l);
219 	assert(ret->seed != NULL);
220 	memcpy(ret->seed, p, l);
221 	ret->slen = l;
222 	ret->strategies = strategies;
223 
224 	assert(ret->slen < SIZE_MAX / 8);
225 	assert(ret->strategies <= (FUZZ_MAX|(FUZZ_MAX-1)));
226 
227 	FUZZ_DBG(("begin, ret = %p", ret));
228 
229 	fuzz_next(ret);
230 
231 	last_fuzz = ret;
232 #ifdef SIGINFO
233 	signal(SIGINFO, siginfo);
234 #endif
235 	signal(SIGUSR1, siginfo);
236 
237 	return ret;
238 }
239 
240 void
fuzz_cleanup(struct fuzz * fuzz)241 fuzz_cleanup(struct fuzz *fuzz)
242 {
243 	FUZZ_DBG(("cleanup, fuzz = %p", fuzz));
244 	last_fuzz = NULL;
245 #ifdef SIGINFO
246 	signal(SIGINFO, SIG_DFL);
247 #endif
248 	signal(SIGUSR1, SIG_DFL);
249 	assert(fuzz != NULL);
250 	assert(fuzz->seed != NULL);
251 	assert(fuzz->fuzzed != NULL);
252 	free(fuzz->seed);
253 	free(fuzz->fuzzed);
254 	free(fuzz);
255 }
256 
257 static int
fuzz_strategy_done(struct fuzz * fuzz)258 fuzz_strategy_done(struct fuzz *fuzz)
259 {
260 	FUZZ_DBG(("fuzz = %p, strategy = %s, o1 = %zu, o2 = %zu, slen = %zu",
261 	    fuzz, fuzz_ntop(fuzz->strategy), fuzz->o1, fuzz->o2, fuzz->slen));
262 
263 	switch (fuzz->strategy) {
264 	case FUZZ_1_BIT_FLIP:
265 		return fuzz->o1 >= fuzz->slen * 8;
266 	case FUZZ_2_BIT_FLIP:
267 		return fuzz->o2 >= fuzz->slen * 8;
268 	case FUZZ_2_BYTE_FLIP:
269 		return fuzz->o2 >= fuzz->slen;
270 	case FUZZ_1_BYTE_FLIP:
271 	case FUZZ_TRUNCATE_START:
272 	case FUZZ_TRUNCATE_END:
273 	case FUZZ_BASE64:
274 		return fuzz->o1 >= fuzz->slen;
275 	default:
276 		abort();
277 	}
278 }
279 
280 void
fuzz_next(struct fuzz * fuzz)281 fuzz_next(struct fuzz *fuzz)
282 {
283 	u_int i;
284 
285 	FUZZ_DBG(("start, fuzz = %p, strategy = %s, strategies = 0x%lx, "
286 	    "o1 = %zu, o2 = %zu, slen = %zu", fuzz, fuzz_ntop(fuzz->strategy),
287 	    (u_long)fuzz->strategies, fuzz->o1, fuzz->o2, fuzz->slen));
288 
289 	if (fuzz->strategy == 0 || fuzz_strategy_done(fuzz)) {
290 		/* If we are just starting out, we need to allocate too */
291 		if (fuzz->fuzzed == NULL) {
292 			FUZZ_DBG(("alloc"));
293 			fuzz->fuzzed = calloc(fuzz->slen, 1);
294 		}
295 		/* Pick next strategy */
296 		FUZZ_DBG(("advance"));
297 		for (i = 1; i <= FUZZ_MAX; i <<= 1) {
298 			if ((fuzz->strategies & i) != 0) {
299 				fuzz->strategy = i;
300 				break;
301 			}
302 		}
303 		FUZZ_DBG(("selected = %u", fuzz->strategy));
304 		if (fuzz->strategy == 0) {
305 			FUZZ_DBG(("done, no more strategies"));
306 			return;
307 		}
308 		fuzz->strategies &= ~(fuzz->strategy);
309 		fuzz->o1 = fuzz->o2 = 0;
310 	}
311 
312 	assert(fuzz->fuzzed != NULL);
313 
314 	switch (fuzz->strategy) {
315 	case FUZZ_1_BIT_FLIP:
316 		assert(fuzz->o1 / 8 < fuzz->slen);
317 		memcpy(fuzz->fuzzed, fuzz->seed, fuzz->slen);
318 		fuzz->fuzzed[fuzz->o1 / 8] ^= 1 << (fuzz->o1 % 8);
319 		fuzz->o1++;
320 		break;
321 	case FUZZ_2_BIT_FLIP:
322 		assert(fuzz->o1 / 8 < fuzz->slen);
323 		assert(fuzz->o2 / 8 < fuzz->slen);
324 		memcpy(fuzz->fuzzed, fuzz->seed, fuzz->slen);
325 		fuzz->fuzzed[fuzz->o1 / 8] ^= 1 << (fuzz->o1 % 8);
326 		fuzz->fuzzed[fuzz->o2 / 8] ^= 1 << (fuzz->o2 % 8);
327 		fuzz->o1++;
328 		if (fuzz->o1 >= fuzz->slen * 8) {
329 			fuzz->o1 = 0;
330 			fuzz->o2++;
331 		}
332 		break;
333 	case FUZZ_1_BYTE_FLIP:
334 		assert(fuzz->o1 < fuzz->slen);
335 		memcpy(fuzz->fuzzed, fuzz->seed, fuzz->slen);
336 		fuzz->fuzzed[fuzz->o1] ^= 0xff;
337 		fuzz->o1++;
338 		break;
339 	case FUZZ_2_BYTE_FLIP:
340 		assert(fuzz->o1 < fuzz->slen);
341 		assert(fuzz->o2 < fuzz->slen);
342 		memcpy(fuzz->fuzzed, fuzz->seed, fuzz->slen);
343 		fuzz->fuzzed[fuzz->o1] ^= 0xff;
344 		fuzz->fuzzed[fuzz->o2] ^= 0xff;
345 		fuzz->o1++;
346 		if (fuzz->o1 >= fuzz->slen) {
347 			fuzz->o1 = 0;
348 			fuzz->o2++;
349 		}
350 		break;
351 	case FUZZ_TRUNCATE_START:
352 	case FUZZ_TRUNCATE_END:
353 		assert(fuzz->o1 < fuzz->slen);
354 		memcpy(fuzz->fuzzed, fuzz->seed, fuzz->slen);
355 		fuzz->o1++;
356 		break;
357 	case FUZZ_BASE64:
358 		assert(fuzz->o1 < fuzz->slen);
359 		assert(fuzz->o2 < sizeof(fuzz_b64chars) - 1);
360 		memcpy(fuzz->fuzzed, fuzz->seed, fuzz->slen);
361 		fuzz->fuzzed[fuzz->o1] = fuzz_b64chars[fuzz->o2];
362 		fuzz->o2++;
363 		if (fuzz->o2 >= sizeof(fuzz_b64chars) - 1) {
364 			fuzz->o2 = 0;
365 			fuzz->o1++;
366 		}
367 		break;
368 	default:
369 		abort();
370 	}
371 
372 	FUZZ_DBG(("done, fuzz = %p, strategy = %s, strategies = 0x%lx, "
373 	    "o1 = %zu, o2 = %zu, slen = %zu", fuzz, fuzz_ntop(fuzz->strategy),
374 	    (u_long)fuzz->strategies, fuzz->o1, fuzz->o2, fuzz->slen));
375 }
376 
377 int
fuzz_matches_original(struct fuzz * fuzz)378 fuzz_matches_original(struct fuzz *fuzz)
379 {
380 	if (fuzz_len(fuzz) != fuzz->slen)
381 		return 0;
382 	return memcmp(fuzz_ptr(fuzz), fuzz->seed, fuzz->slen) == 0;
383 }
384 
385 int
fuzz_done(struct fuzz * fuzz)386 fuzz_done(struct fuzz *fuzz)
387 {
388 	FUZZ_DBG(("fuzz = %p, strategies = 0x%lx", fuzz,
389 	    (u_long)fuzz->strategies));
390 
391 	return fuzz_strategy_done(fuzz) && fuzz->strategies == 0;
392 }
393 
394 size_t
fuzz_len(struct fuzz * fuzz)395 fuzz_len(struct fuzz *fuzz)
396 {
397 	assert(fuzz->fuzzed != NULL);
398 	switch (fuzz->strategy) {
399 	case FUZZ_1_BIT_FLIP:
400 	case FUZZ_2_BIT_FLIP:
401 	case FUZZ_1_BYTE_FLIP:
402 	case FUZZ_2_BYTE_FLIP:
403 	case FUZZ_BASE64:
404 		return fuzz->slen;
405 	case FUZZ_TRUNCATE_START:
406 	case FUZZ_TRUNCATE_END:
407 		assert(fuzz->o1 <= fuzz->slen);
408 		return fuzz->slen - fuzz->o1;
409 	default:
410 		abort();
411 	}
412 }
413 
414 u_char *
fuzz_ptr(struct fuzz * fuzz)415 fuzz_ptr(struct fuzz *fuzz)
416 {
417 	assert(fuzz->fuzzed != NULL);
418 	switch (fuzz->strategy) {
419 	case FUZZ_1_BIT_FLIP:
420 	case FUZZ_2_BIT_FLIP:
421 	case FUZZ_1_BYTE_FLIP:
422 	case FUZZ_2_BYTE_FLIP:
423 	case FUZZ_BASE64:
424 		return fuzz->fuzzed;
425 	case FUZZ_TRUNCATE_START:
426 		assert(fuzz->o1 <= fuzz->slen);
427 		return fuzz->fuzzed + fuzz->o1;
428 	case FUZZ_TRUNCATE_END:
429 		assert(fuzz->o1 <= fuzz->slen);
430 		return fuzz->fuzzed;
431 	default:
432 		abort();
433 	}
434 }
435 
436