Line data Source code
1 : /* Fuzz testing for the nanopb core.
2 : * Attempts to verify all the properties defined in the security model document.
3 : *
4 : * This program can run in three configurations:
5 : * - Standalone fuzzer, generating its own inputs and testing against them.
6 : * - Fuzzing target, reading input on stdin.
7 : * - LLVM libFuzzer target, taking input as a function argument.
8 : */
9 :
10 : #include <pb_decode.h>
11 : #include <pb_encode.h>
12 : #include <stdio.h>
13 : #include <stdlib.h>
14 : #include <string.h>
15 : #include <assert.h>
16 : #include <malloc_wrappers.h>
17 : #include "random_data.h"
18 : #include "validation.h"
19 : #include "flakystream.h"
20 : #include "test_helpers.h"
21 : #include "alltypes_static.pb.h"
22 : #include "alltypes_pointer.pb.h"
23 : #include "alltypes_callback.pb.h"
24 : #include "alltypes_proto3_static.pb.h"
25 : #include "alltypes_proto3_pointer.pb.h"
26 :
27 : /* Longer buffer size allows hitting more branches, but lowers performance. */
28 : #ifndef FUZZTEST_BUFSIZE
29 : #define FUZZTEST_BUFSIZE 256*1024
30 : #endif
31 : #ifndef FUZZTEST_MAX_STANDALONE_BUFSIZE
32 : #define FUZZTEST_MAX_STANDALONE_BUFSIZE 16384
33 : #endif
34 : static size_t g_bufsize = FUZZTEST_BUFSIZE;
35 :
36 : /* Focusing on a single test case at a time improves fuzzing performance.
37 : * If no test case is specified, enable all tests.
38 : */
39 : #if !defined(FUZZTEST_PROTO2_STATIC) && \
40 : !defined(FUZZTEST_PROTO3_STATIC) && \
41 : !defined(FUZZTEST_PROTO2_POINTER) && \
42 : !defined(FUZZTEST_PROTO3_POINTER) && \
43 : !defined(FUZZTEST_IO_ERRORS)
44 : #define FUZZTEST_PROTO2_STATIC
45 : #define FUZZTEST_PROTO3_STATIC
46 : #define FUZZTEST_PROTO2_POINTER
47 : #define FUZZTEST_PROTO3_POINTER
48 : #define FUZZTEST_IO_ERRORS
49 : #endif
50 :
51 12575 : static uint32_t xor32_checksum(const void *data, size_t len)
52 : {
53 12575 : const uint8_t *buf = (const uint8_t*)data;
54 12575 : uint32_t checksum = 1234;
55 47389481 : for (; len > 0; len--)
56 : {
57 47376906 : checksum ^= checksum << 13;
58 47376906 : checksum ^= checksum >> 17;
59 47376906 : checksum ^= checksum << 5;
60 47376906 : checksum += *buf++;
61 : }
62 12575 : return checksum;
63 : }
64 :
65 29082 : static bool do_decode(const uint8_t *buffer, size_t msglen, size_t structsize, const pb_msgdesc_t *msgtype, unsigned flags, bool assert_success)
66 : {
67 : bool status;
68 : pb_istream_t stream;
69 29082 : size_t initial_alloc_count = get_alloc_count();
70 29082 : uint8_t *buf2 = malloc_with_check(g_bufsize); /* This is just to match the amount of memory allocations in do_roundtrips(). */
71 29082 : void *msg = malloc_with_check(structsize);
72 29082 : alltypes_static_TestExtension extmsg = alltypes_static_TestExtension_init_zero;
73 29082 : pb_extension_t ext = pb_extension_init_zero;
74 29082 : assert(msg);
75 :
76 29082 : memset(msg, 0, structsize);
77 29082 : ext.type = &alltypes_static_TestExtension_testextension;
78 29082 : ext.dest = &extmsg;
79 29082 : ext.next = NULL;
80 :
81 29082 : if (msgtype == alltypes_static_AllTypes_fields)
82 : {
83 14541 : ((alltypes_static_AllTypes*)msg)->extensions = &ext;
84 : }
85 14541 : else if (msgtype == alltypes_pointer_AllTypes_fields)
86 : {
87 4847 : ((alltypes_pointer_AllTypes*)msg)->extensions = &ext;
88 : }
89 :
90 29082 : stream = pb_istream_from_buffer(buffer, msglen);
91 29082 : status = pb_decode_ex(&stream, msgtype, msg, flags);
92 :
93 29082 : if (status)
94 : {
95 7343 : validate_message(msg, structsize, msgtype);
96 : }
97 :
98 29082 : if (assert_success)
99 : {
100 2336 : if (!status) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
101 2336 : assert(status);
102 : }
103 :
104 29082 : pb_release(msgtype, msg);
105 29082 : free_with_check(msg);
106 29082 : free_with_check(buf2);
107 29082 : assert(get_alloc_count() == initial_alloc_count);
108 :
109 29082 : return status;
110 : }
111 :
112 16047 : static bool do_stream_decode(const uint8_t *buffer, size_t msglen, size_t fail_after, size_t structsize, const pb_msgdesc_t *msgtype, bool assert_success)
113 : {
114 : bool status;
115 : flakystream_t stream;
116 16047 : size_t initial_alloc_count = get_alloc_count();
117 16047 : void *msg = malloc_with_check(structsize);
118 16047 : assert(msg);
119 :
120 16047 : memset(msg, 0, structsize);
121 16047 : flakystream_init(&stream, buffer, msglen, fail_after);
122 16047 : status = pb_decode(&stream.stream, msgtype, msg);
123 :
124 16047 : if (status)
125 : {
126 6353 : validate_message(msg, structsize, msgtype);
127 : }
128 :
129 16047 : if (assert_success)
130 : {
131 6353 : if (!status) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream.stream));
132 6353 : assert(status);
133 : }
134 :
135 16047 : pb_release(msgtype, msg);
136 16047 : free_with_check(msg);
137 16047 : assert(get_alloc_count() == initial_alloc_count);
138 :
139 16047 : return status;
140 : }
141 :
142 : static int g_sentinel;
143 :
144 461485 : static bool field_callback(pb_istream_t *stream, const pb_field_t *field, void **arg)
145 : {
146 461485 : assert(stream);
147 461485 : assert(field);
148 461485 : assert(*arg == &g_sentinel);
149 461485 : return pb_read(stream, NULL, stream->bytes_left);
150 : }
151 :
152 4874 : static bool submsg_callback(pb_istream_t *stream, const pb_field_t *field, void **arg)
153 : {
154 4874 : assert(stream);
155 4874 : assert(field);
156 4874 : assert(*arg == &g_sentinel);
157 4874 : return true;
158 : }
159 :
160 5771 : bool do_callback_decode(const uint8_t *buffer, size_t msglen, bool assert_success)
161 : {
162 : bool status;
163 : pb_istream_t stream;
164 5771 : size_t initial_alloc_count = get_alloc_count();
165 5771 : alltypes_callback_AllTypes *msg = malloc_with_check(sizeof(alltypes_callback_AllTypes));
166 5771 : assert(msg);
167 :
168 5771 : memset(msg, 0, sizeof(alltypes_callback_AllTypes));
169 5771 : stream = pb_istream_from_buffer(buffer, msglen);
170 :
171 5771 : msg->rep_int32.funcs.decode = &field_callback;
172 5771 : msg->rep_int32.arg = &g_sentinel;
173 5771 : msg->rep_string.funcs.decode = &field_callback;
174 5771 : msg->rep_string.arg = &g_sentinel;
175 5771 : msg->rep_farray.funcs.decode = &field_callback;
176 5771 : msg->rep_farray.arg = &g_sentinel;
177 5771 : msg->req_limits.int64_min.funcs.decode = &field_callback;
178 5771 : msg->req_limits.int64_min.arg = &g_sentinel;
179 5771 : msg->cb_oneof.funcs.decode = &submsg_callback;
180 5771 : msg->cb_oneof.arg = &g_sentinel;
181 :
182 5771 : status = pb_decode(&stream, alltypes_callback_AllTypes_fields, msg);
183 :
184 5771 : if (assert_success)
185 : {
186 924 : if (!status) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
187 924 : assert(status);
188 : }
189 :
190 5771 : pb_release(alltypes_callback_AllTypes_fields, msg);
191 5771 : free_with_check(msg);
192 5771 : assert(get_alloc_count() == initial_alloc_count);
193 :
194 5771 : return status;
195 : }
196 :
197 : /* Do a decode -> encode -> decode -> encode roundtrip */
198 6353 : void do_roundtrip(const uint8_t *buffer, size_t msglen, size_t structsize, const pb_msgdesc_t *msgtype)
199 : {
200 : bool status;
201 : uint32_t checksum2, checksum3;
202 : size_t msglen2, msglen3;
203 6353 : uint8_t *buf2 = malloc_with_check(g_bufsize);
204 6353 : void *msg = malloc_with_check(structsize);
205 :
206 : /* For proto2 types, we also test extension fields */
207 6353 : alltypes_static_TestExtension extmsg = alltypes_static_TestExtension_init_zero;
208 6353 : pb_extension_t ext = pb_extension_init_zero;
209 6353 : pb_extension_t **ext_field = NULL;
210 6353 : ext.type = &alltypes_static_TestExtension_testextension;
211 6353 : ext.dest = &extmsg;
212 6353 : ext.next = NULL;
213 :
214 6353 : assert(buf2 && msg);
215 :
216 6353 : if (msgtype == alltypes_static_AllTypes_fields)
217 : {
218 924 : ext_field = &((alltypes_static_AllTypes*)msg)->extensions;
219 : }
220 5429 : else if (msgtype == alltypes_pointer_AllTypes_fields)
221 : {
222 961 : ext_field = &((alltypes_pointer_AllTypes*)msg)->extensions;
223 : }
224 :
225 : /* Decode and encode the input data.
226 : * This will bring it into canonical format.
227 : */
228 : {
229 6353 : pb_istream_t stream = pb_istream_from_buffer(buffer, msglen);
230 6353 : memset(msg, 0, structsize);
231 6353 : if (ext_field) *ext_field = &ext;
232 6353 : status = pb_decode(&stream, msgtype, msg);
233 6353 : if (!status) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
234 6353 : assert(status);
235 :
236 6353 : validate_message(msg, structsize, msgtype);
237 : }
238 :
239 : {
240 6353 : pb_ostream_t stream = pb_ostream_from_buffer(buf2, g_bufsize);
241 6353 : status = pb_encode(&stream, msgtype, msg);
242 :
243 : /* Some messages expand when re-encoding and might no longer fit
244 : * in the buffer. */
245 6353 : if (!status && strcmp(PB_GET_ERROR(&stream), "stream full") != 0)
246 : {
247 0 : fprintf(stderr, "pb_encode: %s\n", PB_GET_ERROR(&stream));
248 0 : assert(status);
249 : }
250 :
251 6353 : msglen2 = stream.bytes_written;
252 6353 : checksum2 = xor32_checksum(buf2, msglen2);
253 : }
254 :
255 6353 : pb_release(msgtype, msg);
256 :
257 : /* Then decode from canonical format and re-encode. Result should remain the same. */
258 6353 : if (status)
259 : {
260 6222 : pb_istream_t stream = pb_istream_from_buffer(buf2, msglen2);
261 6222 : memset(msg, 0, structsize);
262 6222 : if (ext_field) *ext_field = &ext;
263 6222 : status = pb_decode(&stream, msgtype, msg);
264 6222 : if (!status) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
265 6222 : assert(status);
266 :
267 6222 : validate_message(msg, structsize, msgtype);
268 : }
269 :
270 6353 : if (status)
271 : {
272 6222 : pb_ostream_t stream = pb_ostream_from_buffer(buf2, g_bufsize);
273 6222 : status = pb_encode(&stream, msgtype, msg);
274 6222 : if (!status) fprintf(stderr, "pb_encode: %s\n", PB_GET_ERROR(&stream));
275 6222 : assert(status);
276 6222 : msglen3 = stream.bytes_written;
277 6222 : checksum3 = xor32_checksum(buf2, msglen3);
278 :
279 6222 : assert(msglen2 == msglen3);
280 6222 : assert(checksum2 == checksum3);
281 : }
282 :
283 6353 : pb_release(msgtype, msg);
284 6353 : free_with_check(msg);
285 6353 : free_with_check(buf2);
286 6353 : }
287 :
288 : /* Run all enabled test cases for a given input */
289 4847 : void do_roundtrips(const uint8_t *data, size_t size, bool expect_valid)
290 : {
291 4847 : size_t initial_alloc_count = get_alloc_count();
292 : PB_UNUSED(expect_valid); /* Potentially unused depending on configuration */
293 :
294 : #ifdef FUZZTEST_PROTO2_STATIC
295 4847 : if (do_decode(data, size, sizeof(alltypes_static_AllTypes), alltypes_static_AllTypes_fields, 0, expect_valid))
296 : {
297 924 : do_roundtrip(data, size, sizeof(alltypes_static_AllTypes), alltypes_static_AllTypes_fields);
298 924 : do_stream_decode(data, size, SIZE_MAX, sizeof(alltypes_static_AllTypes), alltypes_static_AllTypes_fields, true);
299 924 : do_callback_decode(data, size, true);
300 : }
301 : #endif
302 :
303 : #ifdef FUZZTEST_PROTO3_STATIC
304 4847 : if (do_decode(data, size, sizeof(alltypes_proto3_static_AllTypes), alltypes_proto3_static_AllTypes_fields, 0, expect_valid))
305 : {
306 2008 : do_roundtrip(data, size, sizeof(alltypes_proto3_static_AllTypes), alltypes_proto3_static_AllTypes_fields);
307 2008 : do_stream_decode(data, size, SIZE_MAX, sizeof(alltypes_proto3_static_AllTypes), alltypes_proto3_static_AllTypes_fields, true);
308 : }
309 : #endif
310 :
311 : #ifdef FUZZTEST_PROTO2_POINTER
312 4847 : if (do_decode(data, size, sizeof(alltypes_pointer_AllTypes), alltypes_pointer_AllTypes_fields, 0, expect_valid))
313 : {
314 961 : do_roundtrip(data, size, sizeof(alltypes_pointer_AllTypes), alltypes_pointer_AllTypes_fields);
315 961 : do_stream_decode(data, size, SIZE_MAX, sizeof(alltypes_pointer_AllTypes), alltypes_pointer_AllTypes_fields, true);
316 : }
317 : #endif
318 :
319 : #ifdef FUZZTEST_PROTO3_POINTER
320 4847 : if (do_decode(data, size, sizeof(alltypes_proto3_pointer_AllTypes), alltypes_proto3_pointer_AllTypes_fields, 0, expect_valid))
321 : {
322 2460 : do_roundtrip(data, size, sizeof(alltypes_proto3_pointer_AllTypes), alltypes_proto3_pointer_AllTypes_fields);
323 2460 : do_stream_decode(data, size, SIZE_MAX, sizeof(alltypes_proto3_pointer_AllTypes), alltypes_proto3_pointer_AllTypes_fields, true);
324 : }
325 : #endif
326 :
327 : #ifdef FUZZTEST_IO_ERRORS
328 : {
329 4847 : size_t orig_max_alloc_bytes = get_max_alloc_bytes();
330 : /* Test decoding when error conditions occur.
331 : * The decoding will end either when running out of memory or when stream returns IO error.
332 : * Testing proto2 is enough for good coverage here, as it has a superset of the field types of proto3.
333 : */
334 4847 : set_max_alloc_bytes(get_alloc_bytes() + 4096);
335 4847 : do_stream_decode(data, size, size - 16, sizeof(alltypes_static_AllTypes), alltypes_static_AllTypes_fields, false);
336 4847 : do_stream_decode(data, size, size - 16, sizeof(alltypes_pointer_AllTypes), alltypes_pointer_AllTypes_fields, false);
337 4847 : set_max_alloc_bytes(orig_max_alloc_bytes);
338 : }
339 :
340 : /* Test pb_decode_ex() modes */
341 4847 : do_decode(data, size, sizeof(alltypes_static_AllTypes), alltypes_static_AllTypes_fields, PB_DECODE_NOINIT | PB_DECODE_DELIMITED, false);
342 4847 : do_decode(data, size, sizeof(alltypes_static_AllTypes), alltypes_static_AllTypes_fields, PB_DECODE_NULLTERMINATED, false);
343 :
344 : /* Test callbacks also when message is not valid */
345 4847 : do_callback_decode(data, size, false);
346 : #endif
347 :
348 4847 : assert(get_alloc_count() == initial_alloc_count);
349 4847 : }
350 :
351 : /* Fuzzer stub for Google OSSFuzz integration */
352 3679 : int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
353 : {
354 3679 : if (size > g_bufsize)
355 0 : return 0;
356 :
357 3679 : do_roundtrips(data, size, false);
358 :
359 3679 : return 0;
360 : }
361 :
362 : #ifndef LLVMFUZZER
363 :
364 1000 : static bool generate_base_message(uint8_t *buffer, size_t *msglen)
365 : {
366 : pb_ostream_t stream;
367 : bool status;
368 : static const alltypes_static_AllTypes initval = alltypes_static_AllTypes_init_default;
369 :
370 : /* Allocate a message and fill it with defaults */
371 1000 : alltypes_static_AllTypes *msg = malloc_with_check(sizeof(alltypes_static_AllTypes));
372 1000 : memcpy(msg, &initval, sizeof(initval));
373 :
374 : /* Apply randomness to the data before encoding */
375 7742 : while (rand_int(0, 7))
376 6742 : rand_mess((uint8_t*)msg, sizeof(alltypes_static_AllTypes));
377 :
378 1000 : msg->extensions = NULL;
379 :
380 1000 : stream = pb_ostream_from_buffer(buffer, g_bufsize);
381 1000 : status = pb_encode(&stream, alltypes_static_AllTypes_fields, msg);
382 1000 : assert(stream.bytes_written <= g_bufsize);
383 1000 : assert(stream.bytes_written <= alltypes_static_AllTypes_size);
384 :
385 1000 : *msglen = stream.bytes_written;
386 1000 : pb_release(alltypes_static_AllTypes_fields, msg);
387 1000 : free_with_check(msg);
388 :
389 1000 : return status;
390 : }
391 :
392 : /* Stand-alone fuzzer iteration, generates random data itself */
393 1000 : static void run_iteration()
394 : {
395 1000 : uint8_t *buffer = malloc_with_check(g_bufsize);
396 : size_t msglen;
397 :
398 : /* Fill the whole buffer with noise, to prepare for length modifications */
399 1000 : rand_fill(buffer, g_bufsize);
400 :
401 1000 : if (generate_base_message(buffer, &msglen))
402 : {
403 584 : rand_protobuf_noise(buffer, g_bufsize, &msglen);
404 :
405 : /* At this point the message should always be valid */
406 584 : do_roundtrips(buffer, msglen, true);
407 :
408 : /* Apply randomness to the encoded data */
409 1246 : while (rand_bool())
410 662 : rand_mess(buffer, g_bufsize);
411 :
412 : /* Apply randomness to encoded data length */
413 584 : if (rand_bool())
414 281 : msglen = rand_int(0, g_bufsize);
415 :
416 : /* In this step the message may be valid or invalid */
417 584 : do_roundtrips(buffer, msglen, false);
418 : }
419 :
420 1000 : free_with_check(buffer);
421 1000 : assert(get_alloc_count() == 0);
422 1000 : }
423 :
424 3680 : int main(int argc, char **argv)
425 : {
426 : int i;
427 : int iterations;
428 :
429 3680 : if (argc >= 2)
430 : {
431 : /* Run in stand-alone mode */
432 1 : if (g_bufsize > FUZZTEST_MAX_STANDALONE_BUFSIZE)
433 1 : g_bufsize = FUZZTEST_MAX_STANDALONE_BUFSIZE;
434 :
435 1 : random_set_seed(strtoul(argv[1], NULL, 0));
436 1 : iterations = (argc >= 3) ? atol(argv[2]) : 10000;
437 :
438 1001 : for (i = 0; i < iterations; i++)
439 : {
440 1000 : printf("Iteration %d/%d, seed %lu\n", i, iterations, (unsigned long)random_get_seed());
441 1000 : run_iteration();
442 : }
443 : }
444 : else
445 : {
446 : /* Run as a stub for afl-fuzz and similar */
447 : uint8_t *buffer;
448 : size_t msglen;
449 :
450 3679 : buffer = malloc_with_check(g_bufsize);
451 :
452 : SET_BINARY_MODE(stdin);
453 3679 : msglen = fread(buffer, 1, g_bufsize, stdin);
454 3679 : LLVMFuzzerTestOneInput(buffer, msglen);
455 :
456 3679 : if (!feof(stdin))
457 : {
458 : /* Read any leftover input data if our buffer is smaller than
459 : * message size. */
460 4 : fprintf(stderr, "Warning: input message too long\n");
461 4 : while (fread(buffer, 1, g_bufsize, stdin) == g_bufsize);
462 : }
463 :
464 3679 : free_with_check(buffer);
465 : }
466 :
467 3680 : return 0;
468 : }
469 : #endif
|