Coverage Report

Created: 2026-09-01 06:27

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