Coverage Report

Created: 2026-09-20 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wuffs/fuzz/c/std/cbor_fuzzer.c
Line
Count
Source
1
// Copyright 2020 The Wuffs Authors.
2
//
3
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6
// option. This file may not be copied, modified, or distributed
7
// except according to those terms.
8
//
9
// SPDX-License-Identifier: Apache-2.0 OR MIT
10
11
// ----------------
12
13
// Silence the nested slash-star warning for the next comment's command line.
14
#pragma clang diagnostic push
15
#pragma clang diagnostic ignored "-Wcomment"
16
17
/*
18
This fuzzer (the fuzz function) is typically run indirectly, by a framework
19
such as https://github.com/google/oss-fuzz calling LLVMFuzzerTestOneInput.
20
21
When working on the fuzz implementation, or as a coherence check, defining
22
WUFFS_CONFIG__FUZZLIB_MAIN will let you manually run fuzz over a set of files:
23
24
gcc -DWUFFS_CONFIG__FUZZLIB_MAIN cbor_fuzzer.c
25
./a.out ../../../test/data/*.cbor
26
rm -f ./a.out
27
28
It should print "PASS", amongst other information, and exit(0).
29
*/
30
31
#pragma clang diagnostic pop
32
33
// Wuffs ships as a "single file C library" or "header file library" as per
34
// https://github.com/nothings/stb/blob/master/docs/stb_howto.txt
35
//
36
// To use that single file as a "foo.c"-like implementation, instead of a
37
// "foo.h"-like header, #define WUFFS_IMPLEMENTATION before #include'ing or
38
// compiling it.
39
#define WUFFS_IMPLEMENTATION
40
41
#if defined(WUFFS_CONFIG__FUZZLIB_MAIN)
42
// Defining the WUFFS_CONFIG__STATIC_FUNCTIONS macro is optional, but when
43
// combined with WUFFS_IMPLEMENTATION, it demonstrates making all of Wuffs'
44
// functions have static storage.
45
//
46
// This can help the compiler ignore or discard unused code, which can produce
47
// faster compiles and smaller binaries. Other motivations are discussed in the
48
// "ALLOW STATIC IMPLEMENTATION" section of
49
// https://raw.githubusercontent.com/nothings/stb/master/docs/stb_howto.txt
50
#define WUFFS_CONFIG__STATIC_FUNCTIONS
51
#endif  // defined(WUFFS_CONFIG__FUZZLIB_MAIN)
52
53
// Defining the WUFFS_CONFIG__MODULE* macros are optional, but it lets users of
54
// release/c/etc.c choose which parts of Wuffs to build. That file contains the
55
// entire Wuffs standard library, implementing a variety of codecs and file
56
// formats. Without this macro definition, an optimizing compiler or linker may
57
// very well discard Wuffs code for unused codecs, but listing the Wuffs
58
// modules we use makes that process explicit. Preprocessing means that such
59
// code simply isn't compiled.
60
#define WUFFS_CONFIG__MODULES
61
#define WUFFS_CONFIG__MODULE__BASE
62
#define WUFFS_CONFIG__MODULE__CBOR
63
64
// If building this program in an environment that doesn't easily accommodate
65
// relative includes, you can use the script/inline-c-relative-includes.go
66
// program to generate a stand-alone C file.
67
#include "../../../release/c/wuffs-unsupported-snapshot.c"
68
#include "../fuzzlib/fuzzlib.c"
69
70
4.25k
#define TOK_BUFFER_ARRAY_SIZE 4096
71
122k
#define STACK_SIZE (WUFFS_CBOR__DECODER_DEPTH_MAX_INCL + 1)
72
73
// Wuffs allows either statically or dynamically allocated work buffers. This
74
// program exercises static allocation.
75
#define WORK_BUFFER_ARRAY_SIZE \
76
948k
  WUFFS_CBOR__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE
77
#if WORK_BUFFER_ARRAY_SIZE > 0
78
uint8_t g_work_buffer_array[WORK_BUFFER_ARRAY_SIZE];
79
#else
80
// Not all C/C++ compilers support 0-length arrays.
81
uint8_t g_work_buffer_array[1];
82
#endif
83
84
// Each stack element is 1 byte. The low 7 bits denote the container:
85
//  - 0x01 means no container: we are at the top level.
86
//  - 0x02 means a [] list.
87
//  - 0x04 means a {} dictionary.
88
//
89
// The high 0x80 bit holds the even/odd-ness of the number of elements in that
90
// container. A valid dictionary contains key-value pairs and should therefore
91
// contain an even number of elements.
92
typedef uint8_t stack_element;
93
94
bool  //
95
2.45M
token_is_cbor_tag(wuffs_base__token t) {
96
2.45M
  return (wuffs_base__token__value_major(&t) ==
97
2.45M
          WUFFS_CBOR__TOKEN_VALUE_MAJOR) &&
98
6.89k
         (wuffs_base__token__value_minor(&t) &
99
6.89k
          WUFFS_CBOR__TOKEN_VALUE_MINOR__TAG);
100
2.45M
}
101
102
const char*  //
103
fuzz_one_token(wuffs_base__token t,
104
               wuffs_base__token prev_token,
105
               wuffs_base__io_buffer* src,
106
               size_t* ti,
107
               stack_element* stack,
108
2.44M
               size_t* depth) {
109
2.44M
  uint64_t len = wuffs_base__token__length(&t);
110
2.44M
  if (len > 0xFFFF) {
111
0
    return "fuzz: internal error: length too long (vs 0xFFFF)";
112
2.44M
  } else if (len > (src->meta.wi - *ti)) {
113
0
    return "fuzz: internal error: length too long (vs wi - ti)";
114
0
  }
115
2.44M
  *ti += len;
116
117
2.44M
  bool is_cbor_tag = token_is_cbor_tag(t);
118
2.44M
  if (wuffs_base__token__value_extension(&t) >= 0) {
119
2.44k
    if (!wuffs_base__token__continued(&prev_token)) {
120
0
      return "fuzz: internal error: extended token not after continued token";
121
0
    }
122
2.44k
    is_cbor_tag = token_is_cbor_tag(prev_token);
123
2.44k
  }
124
125
2.44M
  int64_t vbc = wuffs_base__token__value_base_category(&t);
126
2.44M
  uint64_t vbd = wuffs_base__token__value_base_detail(&t);
127
128
2.44M
  switch (vbc) {
129
198k
    case WUFFS_BASE__TOKEN__VBC__STRUCTURE: {
130
198k
      bool from_consistent = false;
131
198k
      if (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__FROM_NONE) {
132
791
        from_consistent = stack[*depth] & 0x01;
133
197k
      } else if (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__FROM_LIST) {
134
143k
        from_consistent = stack[*depth] & 0x02;
135
143k
      } else if (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__FROM_DICT) {
136
54.5k
        from_consistent = stack[*depth] & 0x04;
137
54.5k
      }
138
198k
      if (!from_consistent) {
139
0
        return "fuzz: internal error: inconsistent VBD__STRUCTURE__FROM_ETC";
140
0
      }
141
142
198k
      if (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__PUSH) {
143
122k
        (*depth)++;
144
122k
        if ((*depth >= STACK_SIZE) || (*depth == 0)) {
145
0
          return "fuzz: internal error: depth too large";
146
0
        }
147
148
122k
        if (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_NONE) {
149
0
          return "fuzz: internal error: push to the 'none' container";
150
122k
        } else if (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_LIST) {
151
104k
          stack[*depth] = 0x02;
152
104k
        } else if (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_DICT) {
153
17.6k
          stack[*depth] = 0x04;
154
17.6k
        } else {
155
0
          return "fuzz: internal error: unrecognized VBD__STRUCTURE__TO_ETC";
156
0
        }
157
158
122k
      } else if (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__POP) {
159
76.4k
        if ((vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__FROM_DICT) &&
160
9.34k
            (0 != (0x80 & stack[*depth]))) {
161
0
          return "fuzz: internal error: dictionary had an incomplete key/value "
162
0
                 "pair";
163
0
        }
164
165
76.4k
        if (*depth <= 0) {
166
0
          return "fuzz: internal error: depth too small";
167
0
        }
168
76.4k
        (*depth)--;
169
170
76.4k
        bool to_consistent = false;
171
76.4k
        if (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_NONE) {
172
65
          to_consistent = stack[*depth] & 0x01;
173
76.3k
        } else if (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_LIST) {
174
39.2k
          to_consistent = stack[*depth] & 0x02;
175
39.2k
        } else if (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_DICT) {
176
37.1k
          to_consistent = stack[*depth] & 0x04;
177
37.1k
        }
178
76.4k
        if (!to_consistent) {
179
0
          return "fuzz: internal error: inconsistent VBD__STRUCTURE__TO_ETC";
180
0
        }
181
182
76.4k
      } else {
183
0
        return "fuzz: internal error: unrecognized VBC__STRUCTURE";
184
0
      }
185
198k
      break;
186
198k
    }
187
188
261k
    case WUFFS_BASE__TOKEN__VBC__STRING: {
189
261k
      if (vbd & WUFFS_BASE__TOKEN__VBD__STRING__CONVERT_1_DST_1_SRC_COPY) {
190
195k
        wuffs_base__slice_u8 s =
191
195k
            wuffs_base__make_slice_u8(src->data.ptr + *ti - len, len);
192
195k
        if ((vbd & WUFFS_BASE__TOKEN__VBD__STRING__DEFINITELY_UTF_8) &&
193
58.6k
            (s.len != wuffs_base__utf_8__longest_valid_prefix(s.ptr, s.len))) {
194
0
          return "fuzz: internal error: invalid UTF-8";
195
0
        }
196
195k
        if ((vbd & WUFFS_BASE__TOKEN__VBD__STRING__DEFINITELY_ASCII) &&
197
0
            (s.len != wuffs_base__ascii__longest_valid_prefix(s.ptr, s.len))) {
198
0
          return "fuzz: internal error: invalid ASCII";
199
0
        }
200
195k
      }
201
261k
      break;
202
261k
    }
203
204
261k
    case WUFFS_BASE__TOKEN__VBC__UNICODE_CODE_POINT: {
205
0
      if ((WUFFS_BASE__UNICODE_SURROGATE__MIN_INCL <= vbd) &&
206
0
          (vbd <= WUFFS_BASE__UNICODE_SURROGATE__MAX_INCL)) {
207
0
        return "fuzz: internal error: invalid Unicode surrogate";
208
0
      } else if (WUFFS_BASE__UNICODE_CODE_POINT__MAX_INCL < vbd) {
209
0
        return "fuzz: internal error: invalid Unicode code point";
210
0
      }
211
0
      break;
212
0
    }
213
214
1.98M
    default:
215
1.98M
      break;
216
2.44M
  }
217
218
  // After a complete CBOR value, update the parity (even/odd count) of the
219
  // container.
220
2.44M
  if (!wuffs_base__token__continued(&t) &&
221
2.24M
      (vbc != WUFFS_BASE__TOKEN__VBC__FILLER) &&
222
2.24M
      ((vbc != WUFFS_BASE__TOKEN__VBC__STRUCTURE) ||
223
198k
       (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__POP)) &&
224
2.12M
      !is_cbor_tag) {
225
2.11M
    stack[*depth] ^= 0x80;
226
2.11M
  }
227
228
2.44M
  return NULL;
229
2.44M
}
230
231
uint64_t  //
232
4.04k
buffer_limit(uint64_t hash, uint64_t min, uint64_t max) {
233
4.04k
  hash &= 0x3F;
234
4.04k
  uint64_t n;
235
4.04k
  if (hash < 0x20) {
236
2.05k
    n = min + hash;
237
2.05k
  } else {
238
1.99k
    n = max - (0x3F - hash);
239
1.99k
  }
240
4.04k
  if (n < min) {
241
0
    return min;
242
4.04k
  } else if (n > max) {
243
0
    return max;
244
0
  }
245
4.04k
  return n;
246
4.04k
}
247
248
const char*  //
249
2.02k
fuzz_complex(wuffs_base__io_buffer* full_src, uint64_t hash) {
250
2.02k
  uint64_t tok_limit = buffer_limit(
251
2.02k
      hash & 0x3F, WUFFS_CBOR__DECODER_DST_TOKEN_BUFFER_LENGTH_MIN_INCL,
252
2.02k
      TOK_BUFFER_ARRAY_SIZE);
253
2.02k
  hash = wuffs_base__u64__rotate_right(hash, 6);
254
255
2.02k
  uint64_t src_limit = buffer_limit(
256
2.02k
      hash & 0x3F, WUFFS_CBOR__DECODER_SRC_IO_BUFFER_LENGTH_MIN_INCL, 4096);
257
2.02k
  hash = wuffs_base__u64__rotate_right(hash, 6);
258
259
  // ----
260
261
2.02k
  wuffs_cbor__decoder dec;
262
2.02k
  wuffs_base__status status = wuffs_cbor__decoder__initialize(
263
2.02k
      &dec, sizeof dec, WUFFS_VERSION,
264
2.02k
      WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED);
265
2.02k
  if (!wuffs_base__status__is_ok(&status)) {
266
0
    return wuffs_base__status__message(&status);
267
0
  }
268
269
2.02k
  wuffs_base__token tok_array[TOK_BUFFER_ARRAY_SIZE];
270
2.02k
  wuffs_base__token_buffer tok = ((wuffs_base__token_buffer){
271
2.02k
      .data = ((wuffs_base__slice_token){
272
2.02k
          .ptr = tok_array,
273
2.02k
          .len = (size_t)((tok_limit < TOK_BUFFER_ARRAY_SIZE)
274
2.02k
                              ? tok_limit
275
2.02k
                              : TOK_BUFFER_ARRAY_SIZE),
276
2.02k
      }),
277
2.02k
  });
278
279
2.02k
  wuffs_base__token prev_token = wuffs_base__make_token(0);
280
2.02k
  uint32_t no_progress_count = 0;
281
282
2.02k
  stack_element stack[STACK_SIZE];
283
2.02k
  stack[0] = 0x01;  // We start in the 'none' container.
284
2.02k
  size_t depth = 0;
285
286
  // ----
287
288
948k
  while (true) {  // Outer loop.
289
948k
    wuffs_base__io_buffer src = make_limited_reader(*full_src, src_limit);
290
291
948k
    size_t old_tok_wi = tok.meta.wi;
292
948k
    size_t old_tok_ri = tok.meta.ri;
293
948k
    size_t old_src_wi = src.meta.wi;
294
948k
    size_t old_src_ri = src.meta.ri;
295
948k
    size_t ti = old_src_ri;
296
297
948k
    status = wuffs_cbor__decoder__decode_tokens(
298
948k
        &dec, &tok, &src,
299
948k
        wuffs_base__make_slice_u8(g_work_buffer_array, WORK_BUFFER_ARRAY_SIZE));
300
948k
    if ((tok.data.len < tok.meta.wi) ||  //
301
948k
        (tok.meta.wi < tok.meta.ri) ||   //
302
948k
        (tok.meta.ri != old_tok_ri)) {
303
0
      return "fuzz: internal error: inconsistent tok indexes";
304
948k
    } else if ((src.data.len < src.meta.wi) ||  //
305
948k
               (src.meta.wi < src.meta.ri) ||   //
306
948k
               (src.meta.wi != old_src_wi)) {
307
0
      return "fuzz: internal error: inconsistent src indexes";
308
0
    }
309
948k
    full_src->meta.ri += src.meta.ri - old_src_ri;
310
311
948k
    if ((tok.meta.wi > old_tok_wi) || (src.meta.ri > old_src_ri) ||
312
581k
        !wuffs_base__status__is_suspension(&status)) {
313
367k
      no_progress_count = 0;
314
581k
    } else if (no_progress_count < 999) {
315
580k
      no_progress_count++;
316
580k
    } else if (!full_src->meta.closed &&
317
581
               (status.repr == wuffs_base__suspension__short_read)) {
318
581
      return wuffs_base__status__message(&status);
319
581
    } else {
320
0
      return "fuzz: internal error: no progress";
321
0
    }
322
323
    // ----
324
325
3.39M
    while (tok.meta.ri < tok.meta.wi) {  // Inner loop.
326
2.44M
      wuffs_base__token t = tok.data.ptr[tok.meta.ri++];
327
2.44M
      const char* z =
328
2.44M
          fuzz_one_token(t, prev_token, &src, &ti, &stack[0], &depth);
329
2.44M
      if (z != NULL) {
330
0
        return z;
331
0
      }
332
2.44M
      prev_token = t;
333
2.44M
    }  // Inner loop.
334
335
    // ----
336
337
    // Check that, starting from old_src_ri, summing the token lengths brings
338
    // us to the new src.meta.ri.
339
947k
    if (ti != src.meta.ri) {
340
0
      return "fuzz: internal error: ti != ri";
341
0
    }
342
343
947k
    if (status.repr == NULL) {
344
287
      break;
345
346
947k
    } else if (status.repr == wuffs_base__suspension__short_read) {
347
733k
      if (src.meta.closed) {
348
0
        return "fuzz: internal error: short read on a closed io_reader";
349
0
      }
350
      // We don't compact full_src as it may be mmap'ed read-only.
351
733k
      continue;
352
353
733k
    } else if (status.repr == wuffs_base__suspension__short_write) {
354
212k
      wuffs_base__token_buffer__compact(&tok);
355
212k
      continue;
356
212k
    }
357
358
1.15k
    return wuffs_base__status__message(&status);
359
947k
  }  // Outer loop.
360
361
  // ----
362
363
287
  if (depth != 0) {
364
0
    return "fuzz: internal error: decoded OK but final depth was not zero";
365
287
  } else if (wuffs_base__token__continued(&prev_token)) {
366
0
    return "fuzz: internal error: decoded OK but final token was continued";
367
0
  }
368
287
  return NULL;
369
287
}
370
371
const char*  //
372
163
fuzz_simple(wuffs_base__io_buffer* full_src) {
373
163
  wuffs_cbor__decoder dec;
374
163
  wuffs_base__status status =
375
163
      wuffs_cbor__decoder__initialize(&dec, sizeof dec, WUFFS_VERSION, 0);
376
163
  if (!wuffs_base__status__is_ok(&status)) {
377
0
    return wuffs_base__status__message(&status);
378
0
  }
379
380
163
  wuffs_base__token tok_array[TOK_BUFFER_ARRAY_SIZE];
381
163
  wuffs_base__token_buffer tok = ((wuffs_base__token_buffer){
382
163
      .data = ((wuffs_base__slice_token){
383
163
          .ptr = tok_array,
384
163
          .len = TOK_BUFFER_ARRAY_SIZE,
385
163
      }),
386
163
  });
387
388
481
  while (true) {
389
481
    status = wuffs_cbor__decoder__decode_tokens(
390
481
        &dec, &tok, full_src,
391
481
        wuffs_base__make_slice_u8(g_work_buffer_array, WORK_BUFFER_ARRAY_SIZE));
392
481
    if (status.repr == NULL) {
393
19
      break;
394
395
462
    } else if (status.repr == wuffs_base__suspension__short_write) {
396
318
      tok.meta.ri = tok.meta.wi;
397
318
      wuffs_base__token_buffer__compact(&tok);
398
318
      continue;
399
318
    }
400
401
144
    return wuffs_base__status__message(&status);
402
481
  }
403
404
19
  return NULL;
405
163
}
406
407
const char*  //
408
2.18k
fuzz(wuffs_base__io_buffer* full_src, uint64_t hash) {
409
  // Send 99.6% of inputs to fuzz_complex and the remainder to fuzz_simple. The
410
  // 0xA5 constant is arbitrary but non-zero. If the hash function maps the
411
  // empty input to 0, this still sends the empty input to fuzz_complex.
412
  //
413
  // The fuzz_simple implementation shows how easy decoding with Wuffs is when
414
  // all you want is to run LLVMFuzzerTestOneInput's built-in (Wuffs API
415
  // independent) checks (e.g. the ASan address sanitizer) and you don't really
416
  // care what the output is, just that it doesn't crash.
417
  //
418
  // The fuzz_complex implementation adds many more Wuffs API specific checks
419
  // (e.g. that the sum of the tokens' lengths do not exceed the input length).
420
2.18k
  if ((hash & 0xFF) != 0xA5) {
421
2.02k
    return fuzz_complex(full_src, wuffs_base__u64__rotate_right(hash, 8));
422
2.02k
  }
423
163
  return fuzz_simple(full_src);
424
2.18k
}