LCOV - code coverage report
Current view: top level - fuzztest - fuzztest.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 208 211 98.6 %
Date: 2023-02-14 20:10:26 Functions: 12 12 100.0 %

          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

Generated by: LCOV version 1.14