xref: /freebsd/contrib/libder/tests/fuzz_stream.c (revision 35c0a8c449fd2b7f75029ebed5e10852240f0865)
1 /*-
2  * Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  */
6 
7 #include <sys/param.h>
8 #include <sys/socket.h>
9 
10 #include <assert.h>
11 #include <pthread.h>
12 #include <signal.h>
13 #include <stdbool.h>
14 #include <stdio.h>
15 #include <unistd.h>
16 
17 #include <libder.h>
18 
19 #include "fuzzers.h"
20 
21 struct supply_data {
22 	const uint8_t	*data;
23 	volatile size_t	 datasz;
24 	int		 socket;
25 };
26 
27 static void *
supply_thread(void * data)28 supply_thread(void *data)
29 {
30 	struct supply_data *sdata = data;
31 	size_t sz = sdata->datasz;
32 	ssize_t writesz;
33 
34 	do {
35 		writesz = write(sdata->socket, sdata->data, sz);
36 
37 		data += writesz;
38 		sz -= writesz;
39 	} while (sz != 0 && writesz > 0);
40 
41 	sdata->datasz = sz;
42 	shutdown(sdata->socket, SHUT_RDWR);
43 	close(sdata->socket);
44 
45 	return (NULL);
46 }
47 
48 static int
fuzz_fd(const struct fuzz_params * fparams,const uint8_t * data,size_t sz)49 fuzz_fd(const struct fuzz_params *fparams, const uint8_t *data, size_t sz)
50 {
51 	struct supply_data sdata;
52 	struct libder_ctx *ctx;
53 	struct libder_object *obj;
54 	size_t totalsz;
55 	int sockets[2];
56 	pid_t pid;
57 	int ret;
58 
59 	ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0,
60 	    &sockets[0]);
61 	if (ret == -1)
62 		return (-1);
63 
64 	sdata.data = data;
65 	sdata.datasz = sz;
66 	sdata.socket = sockets[1];
67 	signal(SIGCHLD, SIG_IGN);
68 	pid = fork();
69 	if (pid == -1) {
70 		close(sockets[0]);
71 		close(sockets[1]);
72 		return (-1);
73 	}
74 
75 	if (pid == 0) {
76 		close(sockets[0]);
77 		supply_thread(&sdata);
78 		_exit(0);
79 	} else {
80 		close(sockets[1]);
81 	}
82 
83 	totalsz = 0;
84 	ret = 0;
85 	ctx = libder_open();
86 	libder_set_strict(ctx, !!fparams->strict);
87 	while (totalsz < sz) {
88 		size_t readsz = 0;
89 
90 		obj = libder_read_fd(ctx, sockets[0], &readsz);
91 		libder_obj_free(obj);
92 
93 		/*
94 		 * Even invalid reads should consume at least one byte.
95 		 */
96 		assert(readsz != 0);
97 
98 		totalsz += readsz;
99 		if (readsz == 0)
100 			break;
101 	}
102 
103 	assert(totalsz == sz);
104 	libder_close(ctx);
105 	close(sockets[0]);
106 
107 	return (ret);
108 }
109 
110 static int
fuzz_file(const struct fuzz_params * fparams,const uint8_t * data,size_t sz)111 fuzz_file(const struct fuzz_params *fparams, const uint8_t *data, size_t sz)
112 {
113 	FILE *fp;
114 	struct libder_ctx *ctx;
115 	struct libder_object *obj;
116 	size_t totalsz;
117 	int ret;
118 
119 	if (fparams->buftype >= BUFFER_END)
120 		return (-1);
121 
122 	fp = fmemopen(__DECONST(void *, data), sz, "rb");
123 	assert(fp != NULL);
124 
125 	switch (fparams->buftype) {
126 	case BUFFER_NONE:
127 		setvbuf(fp, NULL, 0, _IONBF);
128 		break;
129 	case BUFFER_FULL:
130 		setvbuf(fp, NULL, 0, _IOFBF);
131 		break;
132 	case BUFFER_END:
133 		assert(0);
134 	}
135 
136 	totalsz = 0;
137 	ret = 0;
138 	ctx = libder_open();
139 	libder_set_strict(ctx, !!fparams->strict);
140 	while (!feof(fp)) {
141 		size_t readsz = 0;
142 
143 		obj = libder_read_file(ctx, fp, &readsz);
144 		libder_obj_free(obj);
145 
146 		if (obj == NULL)
147 			assert(readsz != 0 || feof(fp));
148 		else
149 			assert(readsz != 0);
150 
151 		totalsz += readsz;
152 	}
153 
154 	assert(totalsz == sz);
155 	libder_close(ctx);
156 	fclose(fp);
157 
158 	return (ret);
159 }
160 
161 static int
fuzz_plain(const struct fuzz_params * fparams,const uint8_t * data,size_t sz)162 fuzz_plain(const struct fuzz_params *fparams, const uint8_t *data, size_t sz)
163 {
164 	struct libder_ctx *ctx;
165 	struct libder_object *obj;
166 	int ret;
167 
168 	if (sz == 0)
169 		return (-1);
170 
171 	ret = 0;
172 	ctx = libder_open();
173 	libder_set_strict(ctx, !!fparams->strict);
174 	do {
175 		size_t readsz;
176 
177 		readsz = sz;
178 		obj = libder_read(ctx, data, &readsz);
179 		libder_obj_free(obj);
180 
181 		if (obj == NULL)
182 			assert(readsz != 0 || readsz == sz);
183 		else
184 			assert(readsz != 0);
185 
186 		/*
187 		 * If we hit an entirely invalid segment of the buffer, we'll
188 		 * just skip a byte and try again.
189 		 */
190 		data += MAX(1, readsz);
191 		sz -= MAX(1, readsz);
192 	} while (sz != 0);
193 
194 	libder_close(ctx);
195 
196 	return (ret);
197 };
198 
199 static bool
validate_padding(const struct fuzz_params * fparams)200 validate_padding(const struct fuzz_params *fparams)
201 {
202 	const uint8_t *end = (const void *)(fparams + 1);
203 	const uint8_t *pad = (const uint8_t *)&fparams->PARAM_PAD_START;
204 
205 	while (pad < end) {
206 		if (*pad++ != 0)
207 			return (false);
208 	}
209 
210 	return (true);
211 }
212 
213 int
LLVMFuzzerTestOneInput(const uint8_t * data,size_t sz)214 LLVMFuzzerTestOneInput(const uint8_t *data, size_t sz)
215 {
216 	const struct fuzz_params *fparams;
217 
218 	if (sz <= sizeof(*fparams))
219 		return (-1);
220 
221 	fparams = (const void *)data;
222 	if (fparams->type >= STREAM_END)
223 		return (-1);
224 
225 	if (!validate_padding(fparams))
226 		return (-1);
227 
228 	data += sizeof(*fparams);
229 	sz -= sizeof(*fparams);
230 
231 	if (fparams->type != STREAM_FILE && fparams->buftype != BUFFER_NONE)
232 		return (-1);
233 
234 	switch (fparams->type) {
235 	case STREAM_FD:
236 		return (fuzz_fd(fparams, data, sz));
237 	case STREAM_FILE:
238 		return (fuzz_file(fparams, data, sz));
239 	case STREAM_PLAIN:
240 		return (fuzz_plain(fparams, data, sz));
241 	case STREAM_END:
242 		assert(0);
243 	}
244 
245 	__builtin_trap();
246 }
247