Refactor fuzztest
diff --git a/tests/fuzztest/SConscript b/tests/fuzztest/SConscript
index 02529b4..f07b5b2 100644
--- a/tests/fuzztest/SConscript
+++ b/tests/fuzztest/SConscript
@@ -31,6 +31,8 @@
p2 = env.NanopbProto(["alltypes_proto3_static", "alltypes_proto3_static.options"])
fuzz = malloc_env.Program(["fuzztest.c",
+ "random_data.c",
+ "validation.c",
"alltypes_pointer.pb.c",
"alltypes_static.pb.c",
"alltypes_proto3_pointer.pb.c",
@@ -49,7 +51,9 @@
env.RunTest(fuzz, ARGS = [str(seed), str(iterations)])
generate_message = malloc_env.Program(["generate_message.c",
+ "random_data.c",
"alltypes_static.pb.c",
"$COMMON/pb_encode.o",
- "$COMMON/pb_common.o"])
+ "$COMMON/pb_common.o",
+ "$COMMON/malloc_wrappers.o"])
diff --git a/tests/fuzztest/fuzztest.c b/tests/fuzztest/fuzztest.c
index 7bdb5c9..20018a6 100644
--- a/tests/fuzztest/fuzztest.c
+++ b/tests/fuzztest/fuzztest.c
@@ -1,5 +1,10 @@
/* Fuzz testing for the nanopb core.
* Attempts to verify all the properties defined in the security model document.
+ *
+ * This program can run in three configurations:
+ * - Standalone fuzzer, generating its own inputs and testing against them.
+ * - Fuzzing target, reading input on stdin.
+ * - LLVM libFuzzer target, taking input as a function argument.
*/
#include <pb_decode.h>
@@ -9,6 +14,8 @@
#include <string.h>
#include <assert.h>
#include <malloc_wrappers.h>
+#include "random_data.h"
+#include "validation.h"
#include "test_helpers.h"
#include "alltypes_static.pb.h"
#include "alltypes_pointer.pb.h"
@@ -16,199 +23,156 @@
#include "alltypes_proto3_pointer.pb.h"
/* Longer buffer size allows hitting more branches, but lowers performance. */
-#ifdef __AVR__
+#if defined(LLVMFUZZER)
+static size_t g_bufsize = 256*1024;
+#elif defined(__AVR__)
static size_t g_bufsize = 2048;
#else
static size_t g_bufsize = 4096;
#endif
+static bool do_decode(const uint8_t *buffer, size_t msglen, size_t structsize, const pb_msgdesc_t *msgtype, bool assert_success)
+{
+ bool status;
+ pb_istream_t stream;
+ size_t initial_alloc_count = get_alloc_count();
+ void *msg = malloc_with_check(structsize);
+
+ memset(msg, 0, structsize);
+ stream = pb_istream_from_buffer(buffer, msglen);
+ status = pb_decode(&stream, msgtype, msg);
+
+ if (status)
+ {
+ validate_message(msg, structsize, msgtype);
+ }
+
+ if (assert_success)
+ {
+ if (!status) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
+ assert(status);
+ }
+
+ pb_release(msgtype, msg);
+ free_with_check(msg);
+ assert(get_alloc_count() == initial_alloc_count);
+
+ return status;
+}
+
+/* Do a decode -> encode -> decode -> encode roundtrip */
+static void do_roundtrip(const uint8_t *buffer, size_t msglen, size_t structsize, const pb_msgdesc_t *msgtype)
+{
+ bool status;
+ uint8_t *buf2 = malloc_with_check(g_bufsize);
+ uint8_t *buf3 = malloc_with_check(g_bufsize);
+ size_t msglen2, msglen3;
+ void *msg = malloc_with_check(structsize);
+
+ /* Decode and encode the input data.
+ * This will bring it into canonical format.
+ */
+ {
+ pb_istream_t stream = pb_istream_from_buffer(buffer, msglen);
+ memset(msg, 0, structsize);
+ status = pb_decode(&stream, msgtype, msg);
+ if (!status) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
+ assert(status);
+
+ validate_message(msg, structsize, msgtype);
+ }
+
+ {
+ pb_ostream_t stream = pb_ostream_from_buffer(buf2, g_bufsize);
+ status = pb_encode(&stream, msgtype, msg);
+ if (!status) fprintf(stderr, "pb_encode: %s\n", PB_GET_ERROR(&stream));
+ assert(status);
+ msglen2 = stream.bytes_written;
+ }
+
+ pb_release(msgtype, msg);
+
+ /* Then decode and encode again. Result should remain the same. */
+ {
+ pb_istream_t stream = pb_istream_from_buffer(buf2, msglen2);
+ memset(msg, 0, structsize);
+ status = pb_decode(&stream, msgtype, msg);
+ if (!status) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
+ assert(status);
+
+ validate_message(msg, structsize, msgtype);
+ }
+
+ {
+ pb_ostream_t stream = pb_ostream_from_buffer(buf3, g_bufsize);
+ status = pb_encode(&stream, msgtype, msg);
+ if (!status) fprintf(stderr, "pb_encode: %s\n", PB_GET_ERROR(&stream));
+ assert(status);
+ msglen3 = stream.bytes_written;
+ }
+
+ assert(msglen2 == msglen3);
+ assert(memcmp(buf2, buf3, msglen2) == 0);
+
+ pb_release(msgtype, msg);
+ free_with_check(msg);
+ free_with_check(buf2);
+ free_with_check(buf3);
+}
+
+void do_roundtrips(const uint8_t *data, size_t size, bool expect_valid)
+{
+ size_t initial_alloc_count = get_alloc_count();
+
+ /* Check decoding as static fields */
+ if (do_decode(data, size, sizeof(alltypes_static_AllTypes), alltypes_static_AllTypes_fields, expect_valid))
+ {
+ /* Any message that is decodeable as proto2 should be decodeable as proto3 */
+ do_roundtrip(data, size, sizeof(alltypes_static_AllTypes), alltypes_static_AllTypes_fields);
+ do_roundtrip(data, size, sizeof(alltypes_proto3_static_AllTypes), alltypes_proto3_static_AllTypes_fields);
+ }
+ else if (do_decode(data, size, sizeof(alltypes_proto3_static_AllTypes), alltypes_proto3_static_AllTypes_fields, expect_valid))
+ {
+ do_roundtrip(data, size, sizeof(alltypes_proto3_static_AllTypes), alltypes_proto3_static_AllTypes_fields);
+ }
+
+ /* Check decoding as pointer fields */
+ if (do_decode(data, size, sizeof(alltypes_pointer_AllTypes), alltypes_pointer_AllTypes_fields, expect_valid))
+ {
+ do_roundtrip(data, size, sizeof(alltypes_pointer_AllTypes), alltypes_pointer_AllTypes_fields);
+ do_roundtrip(data, size, sizeof(alltypes_proto3_pointer_AllTypes), alltypes_proto3_pointer_AllTypes_fields);
+ }
+ else if (do_decode(data, size, sizeof(alltypes_proto3_pointer_AllTypes), alltypes_proto3_pointer_AllTypes_fields, expect_valid))
+ {
+ do_roundtrip(data, size, sizeof(alltypes_proto3_pointer_AllTypes), alltypes_proto3_pointer_AllTypes_fields);
+ }
+
+ assert(get_alloc_count() == initial_alloc_count);
+}
+
+/* Fuzzer stub for Google OSSFuzz integration */
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ /* In some cases, when we re-encode input it can expand close to 2x.
+ * This is because 0xFFFFFFFF in int32 is decoded as -1, but the
+ * canonical encoding for it is 0xFFFFFFFFFFFFFFFF.
+ * Thus we reserve double the space for intermediate buffers.
+ */
+ if (size > g_bufsize / 2)
+ return 0;
+
+ do_roundtrips(data, size, false);
+
+ return 0;
+}
+
#ifndef LLVMFUZZER
-static uint32_t random_seed;
-
-/* Uses xorshift64 here instead of rand() for both speed and
- * reproducibility across platforms. */
-static uint32_t rand_word()
-{
- random_seed ^= random_seed << 13;
- random_seed ^= random_seed >> 17;
- random_seed ^= random_seed << 5;
- return random_seed;
-}
-
-/* Get a random integer in range, with approximately flat distribution. */
-static int rand_int(int min, int max)
-{
- return rand_word() % (max + 1 - min) + min;
-}
-
-static bool rand_bool()
-{
- return rand_word() & 1;
-}
-
-/* Get a random byte, with skewed distribution.
- * Important corner cases like 0xFF, 0x00 and 0xFE occur more
- * often than other values. */
-static uint8_t rand_byte()
-{
- uint32_t w = rand_word();
- uint8_t b = w & 0xFF;
- if (w & 0x100000)
- b >>= (w >> 8) & 7;
- if (w & 0x200000)
- b <<= (w >> 12) & 7;
- if (w & 0x400000)
- b ^= 0xFF;
- return b;
-}
-
-/* Get a random length, with skewed distribution.
- * Favors the shorter lengths, but always atleast 1. */
-static size_t rand_len(size_t max)
-{
- uint32_t w = rand_word();
- size_t s;
- if (w & 0x800000)
- w &= 3;
- else if (w & 0x400000)
- w &= 15;
- else if (w & 0x200000)
- w &= 255;
-
- s = (w % max);
- if (s == 0)
- s = 1;
-
- return s;
-}
-
-/* Fills a buffer with random data with skewed distribution. */
-static void rand_fill(uint8_t *buf, size_t count)
-{
- while (count--)
- *buf++ = rand_byte();
-}
-
-/* Fill with random protobuf-like data */
-static size_t rand_fill_protobuf(uint8_t *buf, size_t min_bytes, size_t max_bytes, int min_tag)
-{
- pb_ostream_t stream = pb_ostream_from_buffer(buf, max_bytes);
-
- while(stream.bytes_written < min_bytes)
- {
- pb_wire_type_t wt = rand_int(0, 3);
- if (wt == 3) wt = 5; /* Gap in values */
-
- if (!pb_encode_tag(&stream, wt, rand_int(min_tag, min_tag + 512)))
- break;
-
- if (wt == PB_WT_VARINT)
- {
- uint64_t value;
- rand_fill((uint8_t*)&value, sizeof(value));
- pb_encode_varint(&stream, value);
- }
- else if (wt == PB_WT_64BIT)
- {
- uint64_t value;
- rand_fill((uint8_t*)&value, sizeof(value));
- pb_encode_fixed64(&stream, &value);
- }
- else if (wt == PB_WT_32BIT)
- {
- uint32_t value;
- rand_fill((uint8_t*)&value, sizeof(value));
- pb_encode_fixed32(&stream, &value);
- }
- else if (wt == PB_WT_STRING)
- {
- size_t len;
- uint8_t *buf;
-
- if (min_bytes > stream.bytes_written)
- len = rand_len(min_bytes - stream.bytes_written);
- else
- len = 0;
-
- buf = malloc(len);
- pb_encode_varint(&stream, len);
- rand_fill(buf, len);
- pb_write(&stream, buf, len);
- free(buf);
- }
- }
-
- return stream.bytes_written;
-}
-
-/* Given a buffer of data, mess it up a bit */
-static void rand_mess(uint8_t *buf, size_t count)
-{
- int m = rand_int(0, 3);
-
- if (m == 0)
- {
- /* Replace random substring */
- int s = rand_int(0, count - 1);
- int l = rand_len(count - s);
- rand_fill(buf + s, l);
- }
- else if (m == 1)
- {
- /* Swap random bytes */
- int a = rand_int(0, count - 1);
- int b = rand_int(0, count - 1);
- int x = buf[a];
- buf[a] = buf[b];
- buf[b] = x;
- }
- else if (m == 2)
- {
- /* Duplicate substring */
- int s = rand_int(0, count - 2);
- int l = rand_len((count - s) / 2);
- memcpy(buf + s + l, buf + s, l);
- }
- else if (m == 3)
- {
- /* Add random protobuf noise */
- int s = rand_int(0, count - 1);
- int l = rand_len(count - s);
- rand_fill_protobuf(buf + s, l, count - s, 1);
- }
-}
-
-/* Append or prepend protobuf noise */
-static void do_protobuf_noise(uint8_t *buffer, size_t *msglen)
-{
- int m = rand_int(0, 2);
- size_t max_size = g_bufsize - 32 - *msglen;
- if (m == 1)
- {
- /* Prepend */
- uint8_t *tmp = malloc_with_check(g_bufsize);
- size_t s = rand_fill_protobuf(tmp, rand_len(max_size), g_bufsize - *msglen, 1000);
- memmove(buffer + s, buffer, *msglen);
- memcpy(buffer, tmp, s);
- free_with_check(tmp);
- *msglen += s;
- }
- else if (m == 2)
- {
- /* Append */
- size_t s = rand_fill_protobuf(buffer + *msglen, rand_len(max_size), g_bufsize - *msglen, 1000);
- *msglen += s;
- }
-}
-
-/* Some default data to put in the message */
-static const alltypes_static_AllTypes initval = alltypes_static_AllTypes_init_default;
-
-static bool do_static_encode(uint8_t *buffer, size_t *msglen)
+static bool generate_base_message(uint8_t *buffer, size_t *msglen)
{
pb_ostream_t stream;
bool status;
+ static const alltypes_static_AllTypes initval = alltypes_static_AllTypes_init_default;
/* Allocate a message and fill it with defaults */
alltypes_static_AllTypes *msg = malloc_with_check(sizeof(alltypes_static_AllTypes));
@@ -230,265 +194,21 @@
return status;
}
-#endif
-
-/* Check the invariants defined in security model on decoded structure */
-static void sanity_check_static(alltypes_static_AllTypes *msg)
-{
- bool truebool = true;
- bool falsebool = false;
-
- /* TODO: Add more checks, or rather, generate them automatically */
- assert(strlen(msg->req_string) < sizeof(msg->req_string));
- assert(strlen(msg->opt_string) < sizeof(msg->opt_string));
- if (msg->rep_string_count > 0)
- {
- assert(strlen(msg->rep_string[0]) < sizeof(msg->rep_string[0]));
- }
- assert(memcmp(&msg->req_bool, &truebool, sizeof(bool)) == 0 ||
- memcmp(&msg->req_bool, &falsebool, sizeof(bool)) == 0);
- assert(memcmp(&msg->has_opt_bool, &truebool, sizeof(bool)) == 0 ||
- memcmp(&msg->has_opt_bool, &falsebool, sizeof(bool)) == 0);
- assert(memcmp(&msg->opt_bool, &truebool, sizeof(bool)) == 0 ||
- memcmp(&msg->opt_bool, &falsebool, sizeof(bool)) == 0);
- assert(msg->rep_bool_count <= pb_arraysize(alltypes_static_AllTypes, rep_bool));
- if (msg->rep_bool_count > 0)
- {
- assert(memcmp(&msg->rep_bool[0], &truebool, sizeof(bool)) == 0 ||
- memcmp(&msg->rep_bool[0], &falsebool, sizeof(bool)) == 0);
- }
-}
-
-static bool do_static_decode(const uint8_t *buffer, size_t msglen, bool assert_success)
-{
- pb_istream_t stream;
- bool status_proto2, status_proto3;
-
- {
- alltypes_static_AllTypes *msg = malloc_with_check(sizeof(alltypes_static_AllTypes));
-#ifdef LLVMFUZZER
- memset(msg, 0xAA, sizeof(alltypes_static_AllTypes));
-#else
- rand_fill((uint8_t*)msg, sizeof(alltypes_static_AllTypes));
-#endif
- stream = pb_istream_from_buffer(buffer, msglen);
- status_proto2 = pb_decode(&stream, alltypes_static_AllTypes_fields, msg);
-
- if (status_proto2)
- {
- sanity_check_static(msg);
- }
-
- if (assert_success)
- {
- if (!status_proto2) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
- assert(status_proto2);
- }
-
- free_with_check(msg);
- }
-
- {
- alltypes_proto3_static_AllTypes *msg = malloc_with_check(sizeof(alltypes_proto3_static_AllTypes));
-#ifdef LLVMFUZZER
- memset(msg, 0xAA, sizeof(alltypes_proto3_static_AllTypes));
-#else
- rand_fill((uint8_t*)msg, sizeof(alltypes_proto3_static_AllTypes));
-#endif
- stream = pb_istream_from_buffer(buffer, msglen);
- status_proto3 = pb_decode(&stream, alltypes_proto3_static_AllTypes_fields, msg);
-
- if (status_proto2)
- {
- /* Anything decodeable as the proto2 message should be decodeable as proto3 also */
- if (!status_proto3) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
- assert(status_proto3);
- }
-
- free_with_check(msg);
- }
-
- return status_proto2;
-}
-
-static bool do_pointer_decode(const uint8_t *buffer, size_t msglen, bool assert_success)
-{
- pb_istream_t stream;
- bool status_proto2, status_proto3;
- size_t initial_alloc_count;
-
- {
- /* For proto2 syntax message */
- alltypes_pointer_AllTypes *msg;
-
- msg = malloc_with_check(sizeof(alltypes_pointer_AllTypes));
- memset(msg, 0, sizeof(alltypes_pointer_AllTypes));
- stream = pb_istream_from_buffer(buffer, msglen);
-
- initial_alloc_count = get_alloc_count();
- status_proto2 = pb_decode(&stream, alltypes_pointer_AllTypes_fields, msg);
-
- if (assert_success)
- {
- if (!status_proto2) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
- assert(status_proto2);
- }
-
- pb_release(alltypes_pointer_AllTypes_fields, msg);
- assert(get_alloc_count() == initial_alloc_count);
-
- free_with_check(msg);
- }
-
- {
- /* For proto3 syntax message */
- alltypes_proto3_pointer_AllTypes *msg;
-
- msg = malloc_with_check(sizeof(alltypes_proto3_pointer_AllTypes));
- memset(msg, 0, sizeof(alltypes_proto3_pointer_AllTypes));
- stream = pb_istream_from_buffer(buffer, msglen);
-
- initial_alloc_count = get_alloc_count();
- status_proto3 = pb_decode(&stream, alltypes_proto3_pointer_AllTypes_fields, msg);
-
- if (status_proto2)
- {
- /* Anything decodeable as the proto2 message should be decodeable as proto3 also */
- if (!status_proto3) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
- assert(status_proto3);
- }
-
- pb_release(alltypes_proto3_pointer_AllTypes_fields, msg);
- assert(get_alloc_count() == initial_alloc_count);
-
- free_with_check(msg);
- }
-
- return status_proto2;
-}
-
-/* Do a decode -> encode -> decode -> encode roundtrip */
-static void do_roundtrip(const uint8_t *buffer, size_t msglen, size_t structsize, const pb_msgdesc_t *msgtype)
-{
- bool status;
- uint8_t *buf2 = malloc_with_check(g_bufsize);
- uint8_t *buf3 = malloc_with_check(g_bufsize);
- size_t msglen2, msglen3;
- void *msg = malloc_with_check(structsize);
-
- {
- pb_istream_t stream = pb_istream_from_buffer(buffer, msglen);
- memset(msg, 0, structsize);
- status = pb_decode(&stream, msgtype, msg);
- if (!status) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
- assert(status);
-
- if (msgtype == alltypes_static_AllTypes_fields)
- {
- sanity_check_static(msg);
- }
- }
-
- {
- pb_ostream_t stream = pb_ostream_from_buffer(buf2, g_bufsize);
- status = pb_encode(&stream, msgtype, msg);
- if (!status) fprintf(stderr, "pb_encode: %s\n", PB_GET_ERROR(&stream));
- assert(status);
- msglen2 = stream.bytes_written;
- }
-
- pb_release(msgtype, msg);
-
- {
- pb_istream_t stream = pb_istream_from_buffer(buf2, msglen2);
- memset(msg, 0, structsize);
- status = pb_decode(&stream, msgtype, msg);
- if (!status) fprintf(stderr, "pb_decode: %s\n", PB_GET_ERROR(&stream));
- assert(status);
-
- if (msgtype == alltypes_static_AllTypes_fields)
- {
- sanity_check_static(msg);
- }
- }
-
- {
- pb_ostream_t stream = pb_ostream_from_buffer(buf3, g_bufsize);
- status = pb_encode(&stream, msgtype, msg);
- if (!status) fprintf(stderr, "pb_encode: %s\n", PB_GET_ERROR(&stream));
- assert(status);
- msglen3 = stream.bytes_written;
- }
-
- assert(msglen2 == msglen3);
- assert(memcmp(buf2, buf3, msglen2) == 0);
-
- pb_release(msgtype, msg);
- free_with_check(msg);
- free_with_check(buf2);
- free_with_check(buf3);
-}
-
-/* Fuzzer stub for Google OSSFuzz integration */
-int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
-{
-#ifndef __AVR__
- g_bufsize = 65536;
-#endif
-
- /* In some cases, when we re-encode input it can expand close to 2x.
- * This is because 0xFFFFFFFF in int32 is decoded as -1, but the
- * canonical encoding for it is 0xFFFFFFFFFFFFFFFF.
- * Thus we reserve 64k for the buffers but reject inputs longer than 32k.
- */
- if (size > 32767)
- return 0;
-
- if (do_static_decode(data, size, false))
- {
- do_roundtrip(data, size, sizeof(alltypes_static_AllTypes), alltypes_static_AllTypes_fields);
- do_roundtrip(data, size, sizeof(alltypes_proto3_static_AllTypes), alltypes_proto3_static_AllTypes_fields);
- }
-
- if (do_pointer_decode(data, size, false))
- {
- do_roundtrip(data, size, sizeof(alltypes_pointer_AllTypes), alltypes_pointer_AllTypes_fields);
- do_roundtrip(data, size, sizeof(alltypes_proto3_pointer_AllTypes), alltypes_proto3_pointer_AllTypes_fields);
- }
-
- return 0;
-}
-
-#ifndef LLVMFUZZER
-
/* Stand-alone fuzzer iteration, generates random data itself */
static void run_iteration()
{
uint8_t *buffer = malloc_with_check(g_bufsize);
size_t msglen;
- bool status;
+ /* Fill the whole buffer with noise, to prepare for length modifications */
rand_fill(buffer, g_bufsize);
- if (do_static_encode(buffer, &msglen))
+ if (generate_base_message(buffer, &msglen))
{
- do_protobuf_noise(buffer, &msglen);
+ rand_protobuf_noise(buffer, g_bufsize, &msglen);
- status = do_static_decode(buffer, msglen, true);
-
- if (status)
- {
- do_roundtrip(buffer, msglen, sizeof(alltypes_static_AllTypes), alltypes_static_AllTypes_fields);
- do_roundtrip(buffer, msglen, sizeof(alltypes_proto3_static_AllTypes), alltypes_proto3_static_AllTypes_fields);
- }
-
- status = do_pointer_decode(buffer, msglen, true);
-
- if (status)
- {
- do_roundtrip(buffer, msglen, sizeof(alltypes_pointer_AllTypes), alltypes_pointer_AllTypes_fields);
- do_roundtrip(buffer, msglen, sizeof(alltypes_proto3_pointer_AllTypes), alltypes_proto3_pointer_AllTypes_fields);
- }
+ /* At this point the message should always be valid */
+ do_roundtrips(buffer, msglen, true);
/* Apply randomness to the encoded data */
while (rand_bool())
@@ -498,16 +218,8 @@
if (rand_bool())
msglen = rand_int(0, g_bufsize);
- status = do_static_decode(buffer, msglen, false);
- do_pointer_decode(buffer, msglen, status);
-
- if (status)
- {
- do_roundtrip(buffer, msglen, sizeof(alltypes_static_AllTypes), alltypes_static_AllTypes_fields);
- do_roundtrip(buffer, msglen, sizeof(alltypes_proto3_static_AllTypes), alltypes_proto3_static_AllTypes_fields);
- do_roundtrip(buffer, msglen, sizeof(alltypes_pointer_AllTypes), alltypes_pointer_AllTypes_fields);
- do_roundtrip(buffer, msglen, sizeof(alltypes_proto3_pointer_AllTypes), alltypes_proto3_pointer_AllTypes_fields);
- }
+ /* In this step the message may be valid or invalid */
+ do_roundtrips(buffer, msglen, false);
}
free_with_check(buffer);
@@ -522,12 +234,12 @@
if (argc >= 2)
{
/* Run in stand-alone mode */
- random_seed = strtoul(argv[1], NULL, 0);
+ random_set_seed(strtoul(argv[1], NULL, 0));
iterations = (argc >= 3) ? atol(argv[2]) : 10000;
for (i = 0; i < iterations; i++)
{
- printf("Iteration %d/%d, seed %lu\n", i, iterations, (unsigned long)random_seed);
+ printf("Iteration %d/%d, seed %lu\n", i, iterations, (unsigned long)random_get_seed());
run_iteration();
}
}
@@ -537,10 +249,6 @@
uint8_t *buffer;
size_t msglen;
-#ifndef __AVR__
- g_bufsize = 65536;
-#endif
-
buffer = malloc_with_check(g_bufsize);
SET_BINARY_MODE(stdin);
diff --git a/tests/fuzztest/generate_message.c b/tests/fuzztest/generate_message.c
index 4c21a02..42183f5 100644
--- a/tests/fuzztest/generate_message.c
+++ b/tests/fuzztest/generate_message.c
@@ -9,27 +9,7 @@
#include <string.h>
#include <assert.h>
#include "alltypes_static.pb.h"
-
-static uint64_t random_seed;
-
-/* Uses xorshift64 here instead of rand() for both speed and
- * reproducibility across platforms. */
-static uint32_t rand_word()
-{
- random_seed ^= random_seed >> 12;
- random_seed ^= random_seed << 25;
- random_seed ^= random_seed >> 27;
- return random_seed * 2685821657736338717ULL;
-}
-
-/* Fills a buffer with random data. */
-static void rand_fill(uint8_t *buf, size_t count)
-{
- while (count--)
- {
- *buf++ = rand_word() & 0xff;
- }
-}
+#include "random_data.h"
/* Check that size/count fields do not exceed their max size.
* Otherwise we would have to loop pretty long in generate_message().
@@ -88,9 +68,9 @@
return 1;
}
- random_seed = atol(argv[1]);
+ random_set_seed(atol(argv[1]));
- fprintf(stderr, "Random seed: %u\n", (unsigned)random_seed);
+ fprintf(stderr, "Random seed: %u\n", (unsigned)random_get_seed());
generate_message();
diff --git a/tests/fuzztest/random_data.c b/tests/fuzztest/random_data.c
new file mode 100644
index 0000000..726de83
--- /dev/null
+++ b/tests/fuzztest/random_data.c
@@ -0,0 +1,196 @@
+#include "random_data.h"
+#include <string.h>
+#include <malloc_wrappers.h>
+#include <pb_encode.h>
+
+#ifndef LLVMFUZZER
+
+static uint32_t g_random_seed = 1234;
+
+void random_set_seed(uint32_t seed)
+{
+ g_random_seed = seed;
+}
+
+uint32_t random_get_seed()
+{
+ return g_random_seed;
+}
+
+/* Uses xorshift64 here instead of rand() for both speed and
+ * reproducibility across platforms. */
+uint32_t rand_word()
+{
+ g_random_seed ^= g_random_seed << 13;
+ g_random_seed ^= g_random_seed >> 17;
+ g_random_seed ^= g_random_seed << 5;
+ return g_random_seed;
+}
+
+/* Get a random integer in range, with approximately flat distribution. */
+int rand_int(int min, int max)
+{
+ return rand_word() % (max + 1 - min) + min;
+}
+
+bool rand_bool()
+{
+ return rand_word() & 1;
+}
+
+/* Get a random byte, with skewed distribution.
+ * Important corner cases like 0xFF, 0x00 and 0xFE occur more
+ * often than other values. */
+uint8_t rand_byte()
+{
+ uint32_t w = rand_word();
+ uint8_t b = w & 0xFF;
+ if (w & 0x100000)
+ b >>= (w >> 8) & 7;
+ if (w & 0x200000)
+ b <<= (w >> 12) & 7;
+ if (w & 0x400000)
+ b ^= 0xFF;
+ return b;
+}
+
+/* Get a random length, with skewed distribution.
+ * Favors the shorter lengths, but always atleast 1. */
+size_t rand_len(size_t max)
+{
+ uint32_t w = rand_word();
+ size_t s;
+ if (w & 0x800000)
+ w &= 3;
+ else if (w & 0x400000)
+ w &= 15;
+ else if (w & 0x200000)
+ w &= 255;
+
+ s = (w % max);
+ if (s == 0)
+ s = 1;
+
+ return s;
+}
+
+/* Fills a buffer with random data with skewed distribution. */
+void rand_fill(uint8_t *buf, size_t count)
+{
+ while (count--)
+ *buf++ = rand_byte();
+}
+
+/* Fill with random protobuf-like data */
+size_t rand_fill_protobuf(uint8_t *buf, size_t min_bytes, size_t max_bytes, int min_tag)
+{
+ pb_ostream_t stream = pb_ostream_from_buffer(buf, max_bytes);
+
+ while(stream.bytes_written < min_bytes)
+ {
+ pb_wire_type_t wt = rand_int(0, 3);
+ if (wt == 3) wt = 5; /* Gap in values */
+
+ if (!pb_encode_tag(&stream, wt, rand_int(min_tag, min_tag + 512)))
+ break;
+
+ if (wt == PB_WT_VARINT)
+ {
+ uint64_t value;
+ rand_fill((uint8_t*)&value, sizeof(value));
+ pb_encode_varint(&stream, value);
+ }
+ else if (wt == PB_WT_64BIT)
+ {
+ uint64_t value;
+ rand_fill((uint8_t*)&value, sizeof(value));
+ pb_encode_fixed64(&stream, &value);
+ }
+ else if (wt == PB_WT_32BIT)
+ {
+ uint32_t value;
+ rand_fill((uint8_t*)&value, sizeof(value));
+ pb_encode_fixed32(&stream, &value);
+ }
+ else if (wt == PB_WT_STRING)
+ {
+ size_t len;
+ uint8_t *buf;
+
+ if (min_bytes > stream.bytes_written)
+ len = rand_len(min_bytes - stream.bytes_written);
+ else
+ len = 0;
+
+ buf = malloc(len);
+ pb_encode_varint(&stream, len);
+ rand_fill(buf, len);
+ pb_write(&stream, buf, len);
+ free(buf);
+ }
+ }
+
+ return stream.bytes_written;
+}
+
+/* Given a buffer of data, mess it up a bit */
+void rand_mess(uint8_t *buf, size_t count)
+{
+ int m = rand_int(0, 3);
+
+ if (m == 0)
+ {
+ /* Replace random substring */
+ int s = rand_int(0, count - 1);
+ int l = rand_len(count - s);
+ rand_fill(buf + s, l);
+ }
+ else if (m == 1)
+ {
+ /* Swap random bytes */
+ int a = rand_int(0, count - 1);
+ int b = rand_int(0, count - 1);
+ int x = buf[a];
+ buf[a] = buf[b];
+ buf[b] = x;
+ }
+ else if (m == 2)
+ {
+ /* Duplicate substring */
+ int s = rand_int(0, count - 2);
+ int l = rand_len((count - s) / 2);
+ memcpy(buf + s + l, buf + s, l);
+ }
+ else if (m == 3)
+ {
+ /* Add random protobuf noise */
+ int s = rand_int(0, count - 1);
+ int l = rand_len(count - s);
+ rand_fill_protobuf(buf + s, l, count - s, 1);
+ }
+}
+
+/* Append or prepend protobuf noise */
+void rand_protobuf_noise(uint8_t *buffer, size_t bufsize, size_t *msglen)
+{
+ int m = rand_int(0, 2);
+ size_t max_size = bufsize - 32 - *msglen;
+ if (m == 1)
+ {
+ /* Prepend */
+ uint8_t *tmp = malloc_with_check(bufsize);
+ size_t s = rand_fill_protobuf(tmp, rand_len(max_size), bufsize - *msglen, 1000);
+ memmove(buffer + s, buffer, *msglen);
+ memcpy(buffer, tmp, s);
+ free_with_check(tmp);
+ *msglen += s;
+ }
+ else if (m == 2)
+ {
+ /* Append */
+ size_t s = rand_fill_protobuf(buffer + *msglen, rand_len(max_size), bufsize - *msglen, 1000);
+ *msglen += s;
+ }
+}
+
+#endif
diff --git a/tests/fuzztest/random_data.h b/tests/fuzztest/random_data.h
new file mode 100644
index 0000000..9144d49
--- /dev/null
+++ b/tests/fuzztest/random_data.h
@@ -0,0 +1,46 @@
+/* This module handles generating & modifying messages randomly for the fuzz test. */
+
+#ifndef RANDOM_DATA_H
+#define RANDOM_DATA_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+void random_set_seed(uint32_t seed);
+uint32_t random_get_seed();
+
+/* Random 32-bit integer */
+uint32_t rand_word();
+
+/* Get a random integer in range, with approximately flat distribution. */
+int rand_int(int min, int max);
+
+/* Random boolean, equal probability */
+bool rand_bool();
+
+/* Get a random byte, with skewed distribution.
+ * Important corner cases like 0xFF, 0x00 and 0xFE occur more
+ * often than other values. */
+uint8_t rand_byte();
+
+/* Get a random length, with skewed distribution.
+ * Favors the shorter lengths, but always atleast 1. */
+size_t rand_len(size_t max);
+
+/* Fills a buffer with random bytes with skewed distribution. */
+void rand_fill(uint8_t *buf, size_t count);
+
+/* Fill with random protobuf-like data */
+size_t rand_fill_protobuf(uint8_t *buf, size_t min_bytes, size_t max_bytes, int min_tag);
+
+/* Given a buffer of data, mess it up a bit by copying / swapping bytes */
+void rand_mess(uint8_t *buf, size_t count);
+
+/* Append or prepend protobuf noise, with tag values > 1000 */
+void rand_protobuf_noise(uint8_t *buffer, size_t bufsize, size_t *msglen);
+
+
+
+
+#endif
diff --git a/tests/fuzztest/validation.c b/tests/fuzztest/validation.c
new file mode 100644
index 0000000..ec3486b
--- /dev/null
+++ b/tests/fuzztest/validation.c
@@ -0,0 +1,39 @@
+#include "validation.h"
+#include "alltypes_static.pb.h"
+#include <assert.h>
+
+/* Check the invariants defined in security model on decoded structure */
+static void sanity_check_static(const alltypes_static_AllTypes *msg)
+{
+ bool truebool = true;
+ bool falsebool = false;
+
+ /* TODO: Add more checks, or rather, generate them automatically */
+ assert(strlen(msg->req_string) < sizeof(msg->req_string));
+ assert(strlen(msg->opt_string) < sizeof(msg->opt_string));
+ if (msg->rep_string_count > 0)
+ {
+ assert(strlen(msg->rep_string[0]) < sizeof(msg->rep_string[0]));
+ }
+ assert(memcmp(&msg->req_bool, &truebool, sizeof(bool)) == 0 ||
+ memcmp(&msg->req_bool, &falsebool, sizeof(bool)) == 0);
+ assert(memcmp(&msg->has_opt_bool, &truebool, sizeof(bool)) == 0 ||
+ memcmp(&msg->has_opt_bool, &falsebool, sizeof(bool)) == 0);
+ assert(memcmp(&msg->opt_bool, &truebool, sizeof(bool)) == 0 ||
+ memcmp(&msg->opt_bool, &falsebool, sizeof(bool)) == 0);
+ assert(msg->rep_bool_count <= pb_arraysize(alltypes_static_AllTypes, rep_bool));
+ if (msg->rep_bool_count > 0)
+ {
+ assert(memcmp(&msg->rep_bool[0], &truebool, sizeof(bool)) == 0 ||
+ memcmp(&msg->rep_bool[0], &falsebool, sizeof(bool)) == 0);
+ }
+}
+
+void validate_message(const void *msg, size_t structsize, const pb_msgdesc_t *msgtype)
+{
+ if (msgtype == alltypes_static_AllTypes_fields)
+ {
+ sanity_check_static(msg);
+ }
+}
+
diff --git a/tests/fuzztest/validation.h b/tests/fuzztest/validation.h
new file mode 100644
index 0000000..a5fb910
--- /dev/null
+++ b/tests/fuzztest/validation.h
@@ -0,0 +1,12 @@
+/* This module validates that the message structures are in valid state
+ * after decoding the input data. */
+
+#ifndef VALIDATION_H
+#define VALIDATION_H
+
+#include <pb.h>
+
+void validate_message(const void *msg, size_t structsize, const pb_msgdesc_t *msgtype);
+
+#endif
+