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