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
21extern int fuzz_save_corpus;
22
23static bool debug;
24static unsigned int flags = MUTATE_ALL;
25static unsigned long long test_fail;
26static unsigned long long test_total;
27static unsigned long long mutate_fail;
28static unsigned long long mutate_total;
29
30int LLVMFuzzerInitialize(int *, char ***);
31int LLVMFuzzerTestOneInput(const uint8_t *, size_t);
32size_t LLVMFuzzerCustomMutator(uint8_t *, size_t, size_t, unsigned int);
33
34static int
35save_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;
72fail:
73	if (fd != -1)
74		close(fd);
75	free(buf);
76	free(p);
77
78	return status;
79}
80
81static int
82save_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
125static void
126parse_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
143int
144LLVMFuzzerInitialize(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
163int
164LLVMFuzzerTestOneInput(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
191size_t
192LLVMFuzzerCustomMutator(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