1 /* Fuzz testing for the nanopb core.
2 * Attempts to verify all the properties defined in the security model document.
3 */
4
5 #include <pb_decode.h>
6 #include <pb_encode.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <assert.h>
11 #include <time.h>
12 #include <malloc_wrappers.h>
13 #include "alltypes_static.pb.h"
14 #include "alltypes_pointer.pb.h"
15
16 static uint64_t random_seed;
17
18 /* Uses xorshift64 here instead of rand() for both speed and
19 * reproducibility across platforms. */
rand_word()20 static uint32_t rand_word()
21 {
22 random_seed ^= random_seed >> 12;
23 random_seed ^= random_seed << 25;
24 random_seed ^= random_seed >> 27;
25 return random_seed * 2685821657736338717ULL;
26 }
27
28 /* Get a random integer in range, with approximately flat distribution. */
rand_int(int min,int max)29 static int rand_int(int min, int max)
30 {
31 return rand_word() % (max + 1 - min) + min;
32 }
33
rand_bool()34 static bool rand_bool()
35 {
36 return rand_word() & 1;
37 }
38
39 /* Get a random byte, with skewed distribution.
40 * Important corner cases like 0xFF, 0x00 and 0xFE occur more
41 * often than other values. */
rand_byte()42 static uint8_t rand_byte()
43 {
44 uint32_t w = rand_word();
45 uint8_t b = w & 0xFF;
46 if (w & 0x100000)
47 b >>= (w >> 8) & 7;
48 if (w & 0x200000)
49 b <<= (w >> 12) & 7;
50 if (w & 0x400000)
51 b ^= 0xFF;
52 return b;
53 }
54
55 /* Get a random length, with skewed distribution.
56 * Favors the shorter lengths, but always atleast 1. */
rand_len(size_t max)57 static size_t rand_len(size_t max)
58 {
59 uint32_t w = rand_word();
60 size_t s;
61 if (w & 0x800000)
62 w &= 3;
63 else if (w & 0x400000)
64 w &= 15;
65 else if (w & 0x200000)
66 w &= 255;
67
68 s = (w % max);
69 if (s == 0)
70 s = 1;
71
72 return s;
73 }
74
75 /* Fills a buffer with random data with skewed distribution. */
rand_fill(uint8_t * buf,size_t count)76 static void rand_fill(uint8_t *buf, size_t count)
77 {
78 while (count--)
79 *buf++ = rand_byte();
80 }
81
82 /* Fill with random protobuf-like data */
rand_fill_protobuf(uint8_t * buf,size_t min_bytes,size_t max_bytes,int min_tag)83 static size_t rand_fill_protobuf(uint8_t *buf, size_t min_bytes, size_t max_bytes, int min_tag)
84 {
85 pb_ostream_t stream = pb_ostream_from_buffer(buf, max_bytes);
86
87 while(stream.bytes_written < min_bytes)
88 {
89 pb_wire_type_t wt = rand_int(0, 3);
90 if (wt == 3) wt = 5; /* Gap in values */
91
92 if (!pb_encode_tag(&stream, wt, rand_int(min_tag, min_tag + 512)))
93 break;
94
95 if (wt == PB_WT_VARINT)
96 {
97 uint64_t value;
98 rand_fill((uint8_t*)&value, sizeof(value));
99 pb_encode_varint(&stream, value);
100 }
101 else if (wt == PB_WT_64BIT)
102 {
103 uint64_t value;
104 rand_fill((uint8_t*)&value, sizeof(value));
105 pb_encode_fixed64(&stream, &value);
106 }
107 else if (wt == PB_WT_32BIT)
108 {
109 uint32_t value;
110 rand_fill((uint8_t*)&value, sizeof(value));
111 pb_encode_fixed32(&stream, &value);
112 }
113 else if (wt == PB_WT_STRING)
114 {
115 size_t len;
116 uint8_t *buf;
117
118 if (min_bytes > stream.bytes_written)
119 len = rand_len(min_bytes - stream.bytes_written);
120 else
121 len = 0;
122
123 buf = malloc(len);
124 pb_encode_varint(&stream, len);
125 rand_fill(buf, len);
126 pb_write(&stream, buf, len);
127 free(buf);
128 }
129 }
130
131 return stream.bytes_written;
132 }
133
134 /* Given a buffer of data, mess it up a bit */
rand_mess(uint8_t * buf,size_t count)135 static void rand_mess(uint8_t *buf, size_t count)
136 {
137 int m = rand_int(0, 3);
138
139 if (m == 0)
140 {
141 /* Replace random substring */
142 int s = rand_int(0, count - 1);
143 int l = rand_len(count - s);
144 rand_fill(buf + s, l);
145 }
146 else if (m == 1)
147 {
148 /* Swap random bytes */
149 int a = rand_int(0, count - 1);
150 int b = rand_int(0, count - 1);
151 int x = buf[a];
152 buf[a] = buf[b];
153 buf[b] = x;
154 }
155 else if (m == 2)
156 {
157 /* Duplicate substring */
158 int s = rand_int(0, count - 2);
159 int l = rand_len((count - s) / 2);
160 memcpy(buf + s + l, buf + s, l);
161 }
162 else if (m == 3)
163 {
164 /* Add random protobuf noise */
165 int s = rand_int(0, count - 1);
166 int l = rand_len(count - s);
167 rand_fill_protobuf(buf + s, l, count - s, 1);
168 }
169 }
170
171 /* Some default data to put in the message */
172 static const alltypes_static_AllTypes initval = alltypes_static_AllTypes_init_default;
173
174 /* Check the invariants defined in security model on decoded structure */
sanity_check_static(alltypes_static_AllTypes * msg)175 static void sanity_check_static(alltypes_static_AllTypes *msg)
176 {
177 bool truebool = true;
178 bool falsebool = false;
179
180 /* TODO: Add more checks, or rather, generate them automatically */
181 assert(strlen(msg->req_string) < sizeof(msg->req_string));
182 assert(strlen(msg->opt_string) < sizeof(msg->opt_string));
183 if (msg->rep_string_count > 0)
184 {
185 assert(strlen(msg->rep_string[0]) < sizeof(msg->rep_string[0]));
186 }
187 assert(memcmp(&msg->req_bool, &truebool, sizeof(bool)) == 0 ||
188 memcmp(&msg->req_bool, &falsebool, sizeof(bool)) == 0);
189 assert(memcmp(&msg->has_opt_bool, &truebool, sizeof(bool)) == 0 ||
190 memcmp(&msg->has_opt_bool, &falsebool, sizeof(bool)) == 0);
191 assert(memcmp(&msg->opt_bool, &truebool, sizeof(bool)) == 0 ||
192 memcmp(&msg->opt_bool, &falsebool, sizeof(bool)) == 0);
193 assert(msg->rep_bool_count <= pb_arraysize(alltypes_static_AllTypes, rep_bool));
194 if (msg->rep_bool_count > 0)
195 {
196 assert(memcmp(&msg->rep_bool[0], &truebool, sizeof(bool)) == 0 ||
197 memcmp(&msg->rep_bool[0], &falsebool, sizeof(bool)) == 0);
198 }
199 }
200
201 #define BUFSIZE 4096
202
do_static_encode(uint8_t * buffer,size_t * msglen)203 static bool do_static_encode(uint8_t *buffer, size_t *msglen)
204 {
205 pb_ostream_t stream;
206 bool status;
207
208 /* Allocate a message and fill it with defaults */
209 alltypes_static_AllTypes *msg = malloc_with_check(sizeof(alltypes_static_AllTypes));
210 memcpy(msg, &initval, sizeof(initval));
211
212 /* Apply randomness to the data before encoding */
213 while (rand_int(0, 7))
214 rand_mess((uint8_t*)msg, sizeof(alltypes_static_AllTypes));
215
216 stream = pb_ostream_from_buffer(buffer, BUFSIZE);
217 status = pb_encode(&stream, alltypes_static_AllTypes_fields, msg);
218 assert(stream.bytes_written <= BUFSIZE);
219 assert(stream.bytes_written <= alltypes_static_AllTypes_size);
220
221 *msglen = stream.bytes_written;
222 pb_release(alltypes_static_AllTypes_fields, msg);
223 free_with_check(msg);
224
225 return status;
226 }
227
228 /* Append or prepend protobuf noise */
do_protobuf_noise(uint8_t * buffer,size_t * msglen)229 static void do_protobuf_noise(uint8_t *buffer, size_t *msglen)
230 {
231 int m = rand_int(0, 2);
232 size_t max_size = BUFSIZE - 32 - *msglen;
233 if (m == 1)
234 {
235 /* Prepend */
236 uint8_t *tmp = malloc_with_check(BUFSIZE);
237 size_t s = rand_fill_protobuf(tmp, rand_len(max_size), BUFSIZE - *msglen, 512);
238 memmove(buffer + s, buffer, *msglen);
239 memcpy(buffer, tmp, s);
240 free_with_check(tmp);
241 *msglen += s;
242 }
243 else if (m == 2)
244 {
245 /* Append */
246 size_t s = rand_fill_protobuf(buffer + *msglen, rand_len(max_size), BUFSIZE - *msglen, 512);
247 *msglen += s;
248 }
249 }
250
do_static_decode(uint8_t * buffer,size_t msglen,bool assert_success)251 static bool do_static_decode(uint8_t *buffer, size_t msglen, bool assert_success)
252 {
253 pb_istream_t stream;
254 bool status;
255
256 alltypes_static_AllTypes *msg = malloc_with_check(sizeof(alltypes_static_AllTypes));
257 rand_fill((uint8_t*)msg, sizeof(alltypes_static_AllTypes));
258 stream = pb_istream_from_buffer(buffer, msglen);
259 status = pb_decode(&stream, alltypes_static_AllTypes_fields, msg);
260
261 if (status)
262 {
263 sanity_check_static(msg);
264 }
265
266 if (!status && assert_success)
267 {
268 /* Anything that was successfully encoded, should be decodeable.
269 * One exception: strings without null terminator are encoded up
270 * to end of buffer, but refused on decode because the terminator
271 * would not fit. */
272 if (strcmp(stream.errmsg, "string overflow") != 0)
273 assert(status);
274 }
275
276 free_with_check(msg);
277 return status;
278 }
279
do_pointer_decode(uint8_t * buffer,size_t msglen,bool assert_success)280 static bool do_pointer_decode(uint8_t *buffer, size_t msglen, bool assert_success)
281 {
282 pb_istream_t stream;
283 bool status;
284 alltypes_pointer_AllTypes *msg;
285
286 msg = malloc_with_check(sizeof(alltypes_pointer_AllTypes));
287 memset(msg, 0, sizeof(alltypes_pointer_AllTypes));
288 stream = pb_istream_from_buffer(buffer, msglen);
289
290 assert(get_alloc_count() == 0);
291 status = pb_decode(&stream, alltypes_pointer_AllTypes_fields, msg);
292
293 if (assert_success)
294 assert(status);
295
296 pb_release(alltypes_pointer_AllTypes_fields, msg);
297 assert(get_alloc_count() == 0);
298
299 free_with_check(msg);
300
301 return status;
302 }
303
304 /* Do a decode -> encode -> decode -> encode roundtrip */
do_static_roundtrip(uint8_t * buffer,size_t msglen)305 static void do_static_roundtrip(uint8_t *buffer, size_t msglen)
306 {
307 bool status;
308 uint8_t *buf2 = malloc_with_check(BUFSIZE);
309 uint8_t *buf3 = malloc_with_check(BUFSIZE);
310 size_t msglen2, msglen3;
311 alltypes_static_AllTypes *msg1 = malloc_with_check(sizeof(alltypes_static_AllTypes));
312 alltypes_static_AllTypes *msg2 = malloc_with_check(sizeof(alltypes_static_AllTypes));
313 memset(msg1, 0, sizeof(alltypes_static_AllTypes));
314 memset(msg2, 0, sizeof(alltypes_static_AllTypes));
315
316 {
317 pb_istream_t stream = pb_istream_from_buffer(buffer, msglen);
318 status = pb_decode(&stream, alltypes_static_AllTypes_fields, msg1);
319 assert(status);
320 sanity_check_static(msg1);
321 }
322
323 {
324 pb_ostream_t stream = pb_ostream_from_buffer(buf2, BUFSIZE);
325 status = pb_encode(&stream, alltypes_static_AllTypes_fields, msg1);
326 assert(status);
327 msglen2 = stream.bytes_written;
328 }
329
330 {
331 pb_istream_t stream = pb_istream_from_buffer(buf2, msglen2);
332 status = pb_decode(&stream, alltypes_static_AllTypes_fields, msg2);
333 assert(status);
334 sanity_check_static(msg2);
335 }
336
337 {
338 pb_ostream_t stream = pb_ostream_from_buffer(buf3, BUFSIZE);
339 status = pb_encode(&stream, alltypes_static_AllTypes_fields, msg2);
340 assert(status);
341 msglen3 = stream.bytes_written;
342 }
343
344 assert(msglen2 == msglen3);
345 assert(memcmp(buf2, buf3, msglen2) == 0);
346
347 free_with_check(msg1);
348 free_with_check(msg2);
349 free_with_check(buf2);
350 free_with_check(buf3);
351 }
352
353 /* Do decode -> encode -> decode -> encode roundtrip */
do_pointer_roundtrip(uint8_t * buffer,size_t msglen)354 static void do_pointer_roundtrip(uint8_t *buffer, size_t msglen)
355 {
356 bool status;
357 uint8_t *buf2 = malloc_with_check(BUFSIZE);
358 uint8_t *buf3 = malloc_with_check(BUFSIZE);
359 size_t msglen2, msglen3;
360 alltypes_pointer_AllTypes *msg1 = malloc_with_check(sizeof(alltypes_pointer_AllTypes));
361 alltypes_pointer_AllTypes *msg2 = malloc_with_check(sizeof(alltypes_pointer_AllTypes));
362 memset(msg1, 0, sizeof(alltypes_pointer_AllTypes));
363 memset(msg2, 0, sizeof(alltypes_pointer_AllTypes));
364
365 {
366 pb_istream_t stream = pb_istream_from_buffer(buffer, msglen);
367 status = pb_decode(&stream, alltypes_pointer_AllTypes_fields, msg1);
368 assert(status);
369 }
370
371 {
372 pb_ostream_t stream = pb_ostream_from_buffer(buf2, BUFSIZE);
373 status = pb_encode(&stream, alltypes_pointer_AllTypes_fields, msg1);
374 assert(status);
375 msglen2 = stream.bytes_written;
376 }
377
378 {
379 pb_istream_t stream = pb_istream_from_buffer(buf2, msglen2);
380 status = pb_decode(&stream, alltypes_pointer_AllTypes_fields, msg2);
381 assert(status);
382 }
383
384 {
385 pb_ostream_t stream = pb_ostream_from_buffer(buf3, BUFSIZE);
386 status = pb_encode(&stream, alltypes_pointer_AllTypes_fields, msg2);
387 assert(status);
388 msglen3 = stream.bytes_written;
389 }
390
391 assert(msglen2 == msglen3);
392 assert(memcmp(buf2, buf3, msglen2) == 0);
393
394 pb_release(alltypes_pointer_AllTypes_fields, msg1);
395 pb_release(alltypes_pointer_AllTypes_fields, msg2);
396 free_with_check(msg1);
397 free_with_check(msg2);
398 free_with_check(buf2);
399 free_with_check(buf3);
400 }
401
run_iteration()402 static void run_iteration()
403 {
404 uint8_t *buffer = malloc_with_check(BUFSIZE);
405 size_t msglen;
406 bool status;
407
408 rand_fill(buffer, BUFSIZE);
409
410 if (do_static_encode(buffer, &msglen))
411 {
412 do_protobuf_noise(buffer, &msglen);
413
414 status = do_static_decode(buffer, msglen, true);
415
416 if (status)
417 do_static_roundtrip(buffer, msglen);
418
419 status = do_pointer_decode(buffer, msglen, true);
420
421 if (status)
422 do_pointer_roundtrip(buffer, msglen);
423
424 /* Apply randomness to the encoded data */
425 while (rand_bool())
426 rand_mess(buffer, BUFSIZE);
427
428 /* Apply randomness to encoded data length */
429 if (rand_bool())
430 msglen = rand_int(0, BUFSIZE);
431
432 status = do_static_decode(buffer, msglen, false);
433 do_pointer_decode(buffer, msglen, status);
434
435 if (status)
436 {
437 do_static_roundtrip(buffer, msglen);
438 do_pointer_roundtrip(buffer, msglen);
439 }
440 }
441
442 free_with_check(buffer);
443 }
444
main(int argc,char ** argv)445 int main(int argc, char **argv)
446 {
447 int i;
448 if (argc > 1)
449 {
450 random_seed = atol(argv[1]);
451 }
452 else
453 {
454 random_seed = time(NULL);
455 }
456
457 fprintf(stderr, "Random seed: %llu\n", (long long unsigned)random_seed);
458
459 for (i = 0; i < 10000; i++)
460 {
461 run_iteration();
462 }
463
464 return 0;
465 }
466
467