xref: /freebsd/contrib/libfido2/fuzz/libfuzzer.c (revision 9c77fb6aaa366cbabc80ee1b834bcfe4df135491)
1 /*
2  * Copyright (c) 2019-2022 Yubico AB. All rights reserved.
3  * Use of this source code is governed by a BSD-style
4  * license that can be found in the LICENSE file.
5  * SPDX-License-Identifier: BSD-2-Clause
6  */
7 
8 #include <openssl/sha.h>
9 
10 #include <err.h>
11 #include <fcntl.h>
12 #include <limits.h>
13 #include <stdbool.h>
14 #include <stdint.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <unistd.h>
19 
20 #include "mutator_aux.h"
21 
22 extern int fuzz_save_corpus;
23 
24 static bool debug;
25 static unsigned int flags = MUTATE_ALL;
26 static unsigned long long test_fail;
27 static unsigned long long test_total;
28 static unsigned long long mutate_fail;
29 static unsigned long long mutate_total;
30 
31 int LLVMFuzzerInitialize(int *, char ***);
32 int LLVMFuzzerTestOneInput(const uint8_t *, size_t);
33 size_t LLVMFuzzerCustomMutator(uint8_t *, size_t, size_t, unsigned int);
34 
35 static int
36 save_seed(const char *opt)
37 {
38 	const char *path;
39 	int fd = -1, status = 1;
40 	void *buf = NULL;
41 	const size_t buflen = MAXCORPUS;
42 	size_t n;
43 	struct param *p = NULL;
44 
45 	if ((path = strchr(opt, '=')) == NULL || strlen(++path) == 0) {
46 		warnx("usage: --fido-save-seed=<path>");
47 		goto fail;
48 	}
49 
50 	if ((fd = open(path, O_CREAT|O_TRUNC|O_WRONLY, 0644)) == -1) {
51 		warn("open %s", path);
52 		goto fail;
53 	}
54 
55 	if ((buf = malloc(buflen)) == NULL) {
56 		warn("malloc");
57 		goto fail;
58 	}
59 
60 	n = pack_dummy(buf, buflen);
61 
62 	if ((p = unpack(buf, n)) == NULL) {
63 		warnx("unpack");
64 		goto fail;
65 	}
66 
67 	if (write(fd, buf, n) != (ssize_t)n) {
68 		warn("write %s", path);
69 		goto fail;
70 	}
71 
72 	status = 0;
73 fail:
74 	if (fd != -1)
75 		close(fd);
76 	free(buf);
77 	free(p);
78 
79 	return status;
80 }
81 
82 static int
83 save_corpus(const struct param *p)
84 {
85 	uint8_t blob[MAXCORPUS], dgst[SHA256_DIGEST_LENGTH];
86 	size_t blob_len;
87 	char path[PATH_MAX];
88 	int r, fd;
89 
90 	if ((blob_len = pack(blob, sizeof(blob), p)) == 0 ||
91 	    blob_len > sizeof(blob)) {
92 		warnx("pack");
93 		return -1;
94 	}
95 
96 	if (SHA256(blob, blob_len, dgst) != dgst) {
97 		warnx("sha256");
98 		return -1;
99 	}
100 
101 	if ((r = snprintf(path, sizeof(path), "saved_corpus_%02x%02x%02x%02x"
102 	    "%02x%02x%02x%02x", dgst[0], dgst[1], dgst[2], dgst[3], dgst[4],
103 	    dgst[5], dgst[6], dgst[7])) < 0 || (size_t)r >= sizeof(path)) {
104 		warnx("snprintf");
105 		return -1;
106 	}
107 
108 	if ((fd = open(path, O_CREAT|O_TRUNC|O_WRONLY, 0644)) == -1) {
109 		warn("open %s", path);
110 		return -1;
111 	}
112 
113 	if (write(fd, blob, blob_len) != (ssize_t)blob_len) {
114 		warn("write");
115 		r = -1;
116 	} else {
117 		warnx("wrote %s", path);
118 		r = 0;
119 	}
120 
121 	close(fd);
122 
123 	return r;
124 }
125 
126 static void
127 parse_mutate_flags(const char *opt, unsigned int *mutate_flags)
128 {
129 	const char *f;
130 
131 	if ((f = strchr(opt, '=')) == NULL || strlen(++f) == 0)
132 		errx(1, "usage: --fido-mutate=<flag>");
133 
134 	if (strcmp(f, "seed") == 0)
135 		*mutate_flags |= MUTATE_SEED;
136 	else if (strcmp(f, "param") == 0)
137 		*mutate_flags |= MUTATE_PARAM;
138 	else if (strcmp(f, "wiredata") == 0)
139 		*mutate_flags |= MUTATE_WIREDATA;
140 	else
141 		errx(1, "--fido-mutate: unknown flag '%s'", f);
142 }
143 
144 int
145 LLVMFuzzerInitialize(int *argc, char ***argv)
146 {
147 	unsigned int mutate_flags = 0;
148 
149 	for (int i = 0; i < *argc; i++)
150 		if (strcmp((*argv)[i], "--fido-debug") == 0) {
151 			debug = 1;
152 		} else if (strncmp((*argv)[i], "--fido-save-seed=", 17) == 0) {
153 			exit(save_seed((*argv)[i]));
154 		} else if (strncmp((*argv)[i], "--fido-mutate=", 14) == 0) {
155 			parse_mutate_flags((*argv)[i], &mutate_flags);
156 		}
157 
158 	if (mutate_flags)
159 		flags = mutate_flags;
160 
161 	return 0;
162 }
163 
164 int
165 LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
166 {
167 	struct param *p;
168 
169 	if (size > MAXCORPUS)
170 		return 0;
171 
172 	if (++test_total % 100000 == 0 && debug) {
173 		double r = (double)test_fail/(double)test_total * 100.0;
174 		fprintf(stderr, "%s: %llu/%llu (%.2f%%)\n", __func__,
175 		    test_fail, test_total, r);
176 	}
177 
178 	if ((p = unpack(data, size)) == NULL)
179 		test_fail++;
180 	else {
181 		fuzz_save_corpus = 0;
182 		test(p);
183 		if (fuzz_save_corpus && save_corpus(p) < 0)
184 			fprintf(stderr, "%s: failed to save corpus\n",
185 			    __func__);
186 		free(p);
187 	}
188 
189 	return 0;
190 }
191 
192 size_t
193 LLVMFuzzerCustomMutator(uint8_t *data, size_t size, size_t maxsize,
194     unsigned int seed) NO_MSAN
195 {
196 	struct param *p;
197 	uint8_t blob[MAXCORPUS];
198 	size_t blob_len;
199 
200 	memset(&p, 0, sizeof(p));
201 
202 #ifdef WITH_MSAN
203 	__msan_unpoison(data, maxsize);
204 #endif
205 
206 	if (++mutate_total % 100000 == 0 && debug) {
207 		double r = (double)mutate_fail/(double)mutate_total * 100.0;
208 		fprintf(stderr, "%s: %llu/%llu (%.2f%%)\n", __func__,
209 		    mutate_fail, mutate_total, r);
210 	}
211 
212 	if ((p = unpack(data, size)) == NULL) {
213 		mutate_fail++;
214 		return pack_dummy(data, maxsize);
215 	}
216 
217 	mutate(p, seed, flags);
218 
219 	if ((blob_len = pack(blob, sizeof(blob), p)) == 0 ||
220 	    blob_len > sizeof(blob) || blob_len > maxsize) {
221 		mutate_fail++;
222 		free(p);
223 		return 0;
224 	}
225 
226 	free(p);
227 
228 	memcpy(data, blob, blob_len);
229 
230 	return blob_len;
231 }
232