Coverage Report

Created: 2025-07-11 06:13

/src/wuffs/fuzz/c/fuzzlib/fuzzlib.c
Line
Count
Source (jump to first uncovered line)
1
// Copyright 2018 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
#include <inttypes.h>
12
#include <stdio.h>
13
#include <stdlib.h>
14
#include <string.h>
15
16
#ifndef WUFFS_INCLUDE_GUARD
17
#error "Wuffs' .h files need to be included before this file"
18
#endif
19
20
void  //
21
0
intentional_segfault() {
22
0
  static volatile int* ptr = NULL;
23
0
  *ptr = 0;
24
0
}
25
26
static uint32_t  //
27
1.10k
popcount32(uint32_t x) {
28
1.10k
  static const uint8_t table[256] = {
29
1.10k
      0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,  //
30
1.10k
      1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,  //
31
1.10k
      1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,  //
32
1.10k
      2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,  //
33
1.10k
      1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,  //
34
1.10k
      2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,  //
35
1.10k
      2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,  //
36
1.10k
      3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,  //
37
1.10k
      1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,  //
38
1.10k
      2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,  //
39
1.10k
      2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,  //
40
1.10k
      3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,  //
41
1.10k
      2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,  //
42
1.10k
      3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,  //
43
1.10k
      3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,  //
44
1.10k
      4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8,  //
45
1.10k
  };
46
1.10k
  return table[0xFF & (x >> 0)] + table[0xFF & (x >> 8)] +
47
1.10k
         table[0xFF & (x >> 16)] + table[0xFF & (x >> 24)];
48
1.10k
}
49
50
// jenkins_hash_u32 implements
51
// https://en.wikipedia.org/wiki/Jenkins_hash_function
52
static uint32_t  //
53
2.20k
jenkins_hash_u32(const uint8_t* data, size_t size) {
54
2.20k
  uint32_t hash = 0;
55
2.20k
  size_t i = 0;
56
30.1M
  while (i != size) {
57
30.1M
    hash += data[i++];
58
30.1M
    hash += hash << 10;
59
30.1M
    hash ^= hash >> 6;
60
30.1M
  }
61
2.20k
  hash += hash << 3;
62
2.20k
  hash ^= hash >> 11;
63
2.20k
  hash += hash << 15;
64
2.20k
  return hash;
65
2.20k
}
66
67
// memrandomize is like memcpy or memset but it writes pseudo-random values.
68
static void*  //
69
0
memrandomize(void* dest, uint64_t seed, size_t n) {
70
0
  unsigned short xsubi[3];  // See "man 3 nrand48".
71
0
  xsubi[0] = (seed >> 0) ^ (seed >> 48);
72
0
  xsubi[1] = (seed >> 16);
73
0
  xsubi[2] = (seed >> 32);
74
0
  for (uint8_t* ptr = (uint8_t*)dest; n--;) {
75
0
    *ptr++ = nrand48(xsubi);
76
0
  }
77
0
  return dest;
78
0
}
79
80
const char*  //
81
fuzz(wuffs_base__io_buffer* src, uint64_t hash);
82
83
static const char*  //
84
1.10k
llvmFuzzerTestOneInput(const uint8_t* data, size_t size) {
85
  // Make a 64-bit hash out of two 32-bit hashes, each on half of the data.
86
1.10k
  size_t s2 = size / 2;
87
1.10k
  uint32_t hash0 = jenkins_hash_u32(data, s2);
88
1.10k
  uint32_t hash1 = jenkins_hash_u32(data + s2, size - s2);
89
1.10k
  uint64_t hash = (((uint64_t)hash0) << 32) | ((uint64_t)hash1);
90
91
  // The ! means that closed will be true for (size == 0).
92
1.10k
  const bool closed = !(1 & popcount32(hash0));
93
94
1.10k
  wuffs_base__io_buffer src =
95
1.10k
      wuffs_base__ptr_u8__reader((uint8_t*)data, size, closed);
96
97
1.10k
  const char* msg = fuzz(&src, hash);
98
1.10k
  if (msg) {
99
1.05k
    if (strnlen(msg, 2047) >= 2047) {
100
0
      msg = "fuzzlib: internal error: error message is too long";
101
0
    }
102
1.05k
    if (closed && strstr(msg, "base: short read")) {
103
0
      fprintf(stderr, "short read on a closed io_reader\n");
104
0
      intentional_segfault();
105
1.05k
    } else if (strstr(msg, "internal error:")) {
106
0
      fprintf(stderr, "internal errors shouldn't occur: \"%s\"\n", msg);
107
0
      intentional_segfault();
108
0
    }
109
1.05k
  }
110
1.10k
  return msg;
111
1.10k
}
112
113
#ifdef __cplusplus
114
extern "C" {
115
#endif
116
117
int  //
118
1.10k
LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
119
1.10k
  llvmFuzzerTestOneInput(data, size);
120
1.10k
  return 0;
121
1.10k
}
122
123
#ifdef __cplusplus
124
}  // extern "C"
125
#endif
126
127
static wuffs_base__io_buffer  //
128
0
make_limited_reader(wuffs_base__io_buffer b, uint64_t limit) {
129
0
  uint64_t n = b.meta.wi - b.meta.ri;
130
0
  bool closed = b.meta.closed;
131
0
  if (n > limit) {
132
0
    n = limit;
133
0
    closed = false;
134
0
  }
135
0
136
0
  wuffs_base__io_buffer ret;
137
0
  ret.data.ptr = b.data.ptr + b.meta.ri;
138
0
  ret.data.len = n;
139
0
  ret.meta.wi = n;
140
0
  ret.meta.ri = 0;
141
0
  ret.meta.pos = wuffs_base__u64__sat_add(b.meta.pos, b.meta.ri);
142
0
  ret.meta.closed = closed;
143
0
  return ret;
144
0
}
145
146
#ifdef WUFFS_CONFIG__FUZZLIB_MAIN
147
148
#include <dirent.h>
149
#include <errno.h>
150
#include <fcntl.h>
151
#include <stdbool.h>
152
#include <sys/mman.h>
153
#include <sys/stat.h>
154
#include <unistd.h>
155
156
struct {
157
  int remaining_argc;
158
  char** remaining_argv;
159
160
  bool color;
161
} g_flags = {0};
162
163
const char*  //
164
parse_flags(int argc, char** argv) {
165
  int c = (argc > 0) ? 1 : 0;  // Skip argv[0], the program name.
166
  for (; c < argc; c++) {
167
    char* arg = argv[c];
168
    if (*arg++ != '-') {
169
      break;
170
    }
171
172
    // A double-dash "--foo" is equivalent to a single-dash "-foo". As special
173
    // cases, a bare "-" is not a flag (some programs may interpret it as
174
    // stdin) and a bare "--" means to stop parsing flags.
175
    if (*arg == '\x00') {
176
      break;
177
    } else if (*arg == '-') {
178
      arg++;
179
      if (*arg == '\x00') {
180
        c++;
181
        break;
182
      }
183
    }
184
185
    if (!strcmp(arg, "c") || !strcmp(arg, "color")) {
186
      g_flags.color = true;
187
      continue;
188
    }
189
190
    return "main: unrecognized flag argument";
191
  }
192
193
  g_flags.remaining_argc = argc - c;
194
  g_flags.remaining_argv = argv + c;
195
  return NULL;
196
}
197
198
static int g_num_files_processed;
199
200
static struct {
201
  char buf[PATH_MAX];
202
  size_t len;
203
} g_relative_cwd;
204
205
void  //
206
errorf(const char* msg) {
207
  if (g_flags.color) {
208
    printf("\e[31m%s\e[0m\n", msg);
209
  } else {
210
    printf("%s\n", msg);
211
  }
212
}
213
214
static int  //
215
visit(char* filename);
216
217
static int  //
218
visit_dir(int fd) {
219
  int cwd_fd = open(".", O_RDONLY, 0);
220
  if (fchdir(fd)) {
221
    errorf("failed");
222
    fprintf(stderr, "FAIL: fchdir: %s\n", strerror(errno));
223
    return 1;
224
  }
225
226
  DIR* d = fdopendir(fd);
227
  if (!d) {
228
    errorf("failed");
229
    fprintf(stderr, "FAIL: fdopendir: %s\n", strerror(errno));
230
    return 1;
231
  }
232
233
  printf("dir\n");
234
  while (true) {
235
    struct dirent* e = readdir(d);
236
    if (!e) {
237
      break;
238
    }
239
    if ((e->d_name[0] == '\x00') || (e->d_name[0] == '.')) {
240
      continue;
241
    }
242
    int v = visit(e->d_name);
243
    if (v) {
244
      return v;
245
    }
246
  }
247
248
  if (closedir(d)) {
249
    fprintf(stderr, "FAIL: closedir: %s\n", strerror(errno));
250
    return 1;
251
  }
252
  if (fchdir(cwd_fd)) {
253
    fprintf(stderr, "FAIL: fchdir: %s\n", strerror(errno));
254
    return 1;
255
  }
256
  if (close(cwd_fd)) {
257
    fprintf(stderr, "FAIL: close: %s\n", strerror(errno));
258
    return 1;
259
  }
260
  return 0;
261
}
262
263
static int  //
264
visit_reg(int fd, off_t size) {
265
  if ((size < 0) || (0x7FFFFFFF < size)) {
266
    errorf("failed");
267
    fprintf(stderr, "FAIL: file size out of bounds");
268
    return 1;
269
  }
270
271
  void* data = NULL;
272
  if (size > 0) {
273
    data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
274
    if (data == MAP_FAILED) {
275
      errorf("failed");
276
      fprintf(stderr, "FAIL: mmap: %s\n", strerror(errno));
277
      return 1;
278
    }
279
  }
280
281
  const char* msg = llvmFuzzerTestOneInput((const uint8_t*)(data), size);
282
  if (msg) {
283
    errorf(msg);
284
  } else if (g_flags.color) {
285
    printf("\e[32mok\e[0m\n");
286
  } else {
287
    printf("ok\n");
288
  }
289
290
  if ((size > 0) && munmap(data, size)) {
291
    fprintf(stderr, "FAIL: mmap: %s\n", strerror(errno));
292
    return 1;
293
  }
294
  if (close(fd)) {
295
    fprintf(stderr, "FAIL: close: %s\n", strerror(errno));
296
    return 1;
297
  }
298
  return 0;
299
}
300
301
static int  //
302
visit(char* filename) {
303
  g_num_files_processed++;
304
  if (!filename || (filename[0] == '\x00')) {
305
    fprintf(stderr, "FAIL: invalid filename\n");
306
    return 1;
307
  }
308
  int n = printf("- %s%s", g_relative_cwd.buf, filename);
309
  printf("%*s", (60 > n) ? (60 - n) : 1, "");
310
  fflush(stdout);
311
312
  struct stat z;
313
  int fd = open(filename, O_RDONLY, 0);
314
  if (fd == -1) {
315
    errorf("failed");
316
    fprintf(stderr, "FAIL: open: %s\n", strerror(errno));
317
    return 1;
318
  }
319
  if (fstat(fd, &z)) {
320
    errorf("failed");
321
    fprintf(stderr, "FAIL: fstat: %s\n", strerror(errno));
322
    return 1;
323
  }
324
325
  if (S_ISREG(z.st_mode)) {
326
    return visit_reg(fd, z.st_size);
327
  } else if (!S_ISDIR(z.st_mode)) {
328
    printf("skipped\n");
329
    return 0;
330
  }
331
332
  size_t old_len = g_relative_cwd.len;
333
  size_t filename_len = strlen(filename);
334
  size_t new_len = old_len + strlen(filename);
335
  bool slash = filename[filename_len - 1] != '/';
336
  if (slash) {
337
    new_len++;
338
  }
339
  if ((filename_len >= PATH_MAX) || (new_len >= PATH_MAX)) {
340
    errorf("failed");
341
    fprintf(stderr, "FAIL: path is too long\n");
342
    return 1;
343
  }
344
  memcpy(g_relative_cwd.buf + old_len, filename, filename_len);
345
346
  if (slash) {
347
    g_relative_cwd.buf[new_len - 1] = '/';
348
  }
349
  g_relative_cwd.buf[new_len] = '\x00';
350
  g_relative_cwd.len = new_len;
351
352
  int v = visit_dir(fd);
353
354
  g_relative_cwd.buf[old_len] = '\x00';
355
  g_relative_cwd.len = old_len;
356
  return v;
357
}
358
359
int  //
360
main(int argc, char** argv) {
361
  g_num_files_processed = 0;
362
  g_relative_cwd.len = 0;
363
364
  const char* z = parse_flags(argc, argv);
365
  if (z) {
366
    fprintf(stderr, "FAIL: %s\n", z);
367
    return 1;
368
  }
369
  for (int i = 0; i < g_flags.remaining_argc; i++) {
370
    int v = visit(g_flags.remaining_argv[i]);
371
    if (v) {
372
      return v;
373
    }
374
  }
375
376
  printf("PASS: %d files processed\n", g_num_files_processed);
377
  return 0;
378
}
379
380
#endif  // WUFFS_CONFIG__FUZZLIB_MAIN