Coverage Report

Created: 2026-10-05 07:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libredwg/examples/llvmfuzz.c
Line
Count
Source
1
/*****************************************************************************/
2
/*  LibreDWG - free implementation of the DWG file format                    */
3
/*                                                                           */
4
/*  Copyright (C) 2021, 2023 Free Software Foundation, Inc.                  */
5
/*                                                                           */
6
/*  This library is free software, licensed under the terms of the GNU       */
7
/*  General Public License as published by the Free Software Foundation,     */
8
/*  either version 3 of the License, or (at your option) any later version.  */
9
/*  You should have received a copy of the GNU General Public License        */
10
/*  along with this program.  If not, see <http://www.gnu.org/licenses/>.    */
11
/*****************************************************************************/
12
13
/*
14
 * llvmfuzz.c: libfuzzer testing, esp. for oss-fuzz. with libfuzzer or
15
 * standalone written by Reini Urban
16
 */
17
18
#include <stdio.h>
19
#include <stdlib.h>
20
#include <assert.h>
21
// #include <unistd.h>
22
#include <sys/stat.h>
23
24
#include "common.h"
25
#include <dwg.h>
26
#ifdef HAVE_SYS_TIME_H
27
#  include <sys/time.h>
28
#endif
29
#include "decode.h"
30
#include "encode.h"
31
#include "bits.h"
32
#ifndef DISABLE_DXF
33
#  include "out_dxf.h"
34
#  ifndef DISABLE_JSON
35
#    include "in_json.h"
36
#    include "out_json.h"
37
#  endif
38
#  include "in_dxf.h"
39
#endif
40
41
// Number of output converters selectable via out: 0 encode, 1 dxf, 2 dxfb, 3
42
// json, 4 geojson
43
#ifdef DISABLE_DXF
44
#  define LLVMFUZZ_NUM_OUTPUTS 1
45
#else
46
#  ifdef DISABLE_JSON
47
#    define LLVMFUZZ_NUM_OUTPUTS 3
48
#  else
49
23.3k
#    define LLVMFUZZ_NUM_OUTPUTS 5
50
#  endif
51
#endif
52
53
int out;
54
int ver;
55
56
extern int LLVMFuzzerTestOneInput (const unsigned char *data, size_t size);
57
58
// libfuzzer limitation:
59
// Enforce NULL-termination of the input buffer, to avoid bogus reports. copy
60
// it. Problematic is mostly strtol(3) which also works with \n termination.
61
static int
62
enforce_null_termination (Bit_Chain *dat, bool enforce)
63
886
{
64
886
  unsigned char *copy;
65
886
  unsigned char c;
66
886
  if (!dat->size)
67
0
    return 0;
68
886
  c = dat->chain[dat->size - 1];
69
  // Allow \n termination without \0 in DXF? No, still crashes
70
886
  if (!enforce && ((c == '\n' && c + 1 == '\0') || c == '\0'))
71
146
    return 0;
72
#ifdef STANDALONE
73
  fprintf (stderr,
74
           "llvmfuzz_standalone: enforce libfuzzer buffer NULL termination\n");
75
#endif
76
740
  copy = malloc (dat->size + 1);
77
740
  memcpy (copy, dat->chain, dat->size);
78
740
  copy[dat->size] = '\0';
79
740
  dat->chain = copy;
80
740
  return 1;
81
886
}
82
83
int
84
LLVMFuzzerTestOneInput (const unsigned char *data, size_t size)
85
23.3k
{
86
23.3k
  Dwg_Data dwg;
87
23.3k
  Bit_Chain dat = { NULL, 0, 0, 0, 0, 0, 0, NULL, 0 };
88
23.3k
  Bit_Chain out_dat = { NULL, 0, 0, 0, 0, 0, 0, NULL, 0 };
89
23.3k
  int copied = 0;
90
23.3k
  struct ly_ctx *ctx = NULL;
91
92
23.3k
  static char tmp_file[256];
93
94
23.3k
#ifndef STANDALONE
95
  /* The libfuzzer path otherwise leaves out/ver at 0, so only dwg_encode runs.
96
     Derive them from the input (without consuming it, so the existing corpus
97
     keeps decoding) to also drive the out_dxf/out_dxfb/out_json encoders. */
98
23.3k
  out = 0;
99
23.3k
  ver = 0;
100
23.3k
  if (size)
101
23.3k
    {
102
23.3k
      unsigned int h = 2166136261u;
103
23.3k
      const size_t n = size > 4096 ? 4096 : size;
104
76.7M
      for (size_t i = 0; i < n; i++)
105
76.6M
        h = (h ^ data[i]) * 16777619u;
106
23.3k
      out = (int)(h % LLVMFUZZ_NUM_OUTPUTS);
107
23.3k
      ver = (int)((h >> 8) % 20);
108
23.3k
    }
109
23.3k
#endif
110
111
23.3k
  dat.chain = (unsigned char *)data;
112
23.3k
  dat.size = size;
113
23.3k
  memset (&dwg, 0, sizeof (dwg));
114
115
  /* Cap input size to prevent OOM/timeout in fuzzing */
116
23.3k
  if (size > 10 * 1024 * 1024)
117
0
    return 0;
118
  // Detect the input format: DWG, DXF or JSON
119
23.3k
  if (dat.size > 2 && dat.chain[0] == 'A' && dat.chain[1] == 'C')
120
22.4k
    {
121
22.4k
      if (dwg_decode (&dat, &dwg) >= DWG_ERR_CRITICAL)
122
2.18k
        {
123
2.18k
          dwg_free (&dwg);
124
2.18k
          return 0;
125
2.18k
        }
126
22.4k
    }
127
886
#ifndef DISABLE_JSON
128
886
  else if (dat.size > 1 && dat.chain[0] == '{')
129
54
    {
130
54
      copied = enforce_null_termination (&dat, true);
131
54
      if (dwg_read_json (&dat, &dwg) >= DWG_ERR_CRITICAL)
132
33
        {
133
33
          if (copied)
134
33
            bit_chain_free (&dat);
135
33
          dwg_free (&dwg);
136
33
          return 0;
137
33
        }
138
21
      dat.opts |= DWG_OPTS_INJSON;
139
21
      dwg.opts |= DWG_OPTS_INJSON;
140
21
    }
141
832
#endif
142
832
#ifndef DISABLE_DXF
143
832
  else
144
832
    {
145
832
      copied = enforce_null_termination (&dat, false);
146
832
      if (dwg_read_dxf (&dat, &dwg) >= DWG_ERR_CRITICAL)
147
608
        {
148
608
          if (copied)
149
498
            bit_chain_free (&dat);
150
608
          dwg_free (&dwg);
151
608
          return 0;
152
608
        }
153
832
    }
154
#else
155
  else
156
    return 0;
157
#endif
158
159
20.5k
  memset (&out_dat, 0, sizeof (out_dat));
160
20.5k
  bit_chain_set_version (&out_dat, &dat);
161
20.5k
  if (copied)
162
209
    bit_chain_free (&dat);
163
164
#if 0
165
    snprintf (tmp_file, 255, "/tmp/llvmfuzzer%d.out", getpid());
166
    tmp_file[255] = '\0';
167
#elif defined _WIN32
168
  strcpy (tmp_file, "NUL");
169
#else
170
20.5k
  strcpy (tmp_file, "/dev/null");
171
20.5k
#endif
172
20.5k
  out_dat.fh = fopen (tmp_file, "w");
173
174
20.5k
  switch (out)
175
20.5k
    {
176
5.44k
    case 0:
177
5.44k
      {
178
5.44k
        switch (ver)
179
5.44k
          {
180
          // TODO support preR13, many downconverters still missing
181
117
          case 0:
182
117
            out_dat.version = dwg.header.version = R_1_4;
183
117
            break;
184
116
          case 1:
185
116
            out_dat.version = dwg.header.version = R_2_0;
186
116
            break;
187
146
          case 2:
188
146
            out_dat.version = dwg.header.version = R_2_10;
189
146
            break;
190
127
          case 3:
191
127
            out_dat.version = dwg.header.version = R_2_21;
192
127
            break;
193
139
          case 4:
194
139
            out_dat.version = dwg.header.version = R_2_4;
195
139
            break;
196
112
          case 5:
197
112
            out_dat.version = dwg.header.version = R_2_6;
198
112
            break;
199
160
          case 6:
200
160
            out_dat.version = dwg.header.version = R_9;
201
160
            break;
202
155
          case 7:
203
155
            out_dat.version = dwg.header.version = R_10;
204
155
            break;
205
219
          case 8:
206
219
            out_dat.version = dwg.header.version = R_11;
207
219
            break;
208
197
          case 9:
209
197
            out_dat.version = dwg.header.version = R_12;
210
197
            break;
211
349
          case 10:
212
349
            out_dat.version = dwg.header.version = R_13;
213
349
            break;
214
379
          case 11:
215
379
            out_dat.version = dwg.header.version = R_13c3;
216
379
            break;
217
343
          case 12:
218
343
            out_dat.version = dwg.header.version = R_14;
219
343
            break;
220
365
          case 13:
221
365
            out_dat.version = dwg.header.version = R_2000;
222
365
            break;
223
379
          case 14:
224
379
            out_dat.version = dwg.header.version = R_2004;
225
379
            break;
226
443
          case 15:
227
443
            out_dat.version = dwg.header.version = R_2010;
228
443
            break;
229
480
          case 16:
230
480
            out_dat.version = dwg.header.version = R_2013;
231
480
            break;
232
539
          case 17:
233
539
            out_dat.version = dwg.header.version = R_2018;
234
539
            break;
235
682
          default: // favor this one
236
682
            out_dat.version = dwg.header.version = R_2000;
237
682
            break;
238
5.44k
          }
239
5.44k
        dwg_encode (&dwg, &out_dat);
240
5.44k
        break;
241
5.44k
      }
242
0
#ifndef DISABLE_DXF
243
2.12k
    case 1:
244
2.12k
      dwg_write_dxf (&out_dat, &dwg);
245
2.12k
      break;
246
2.56k
    case 2: // experimental
247
2.56k
      dwg_write_dxfb (&out_dat, &dwg);
248
2.56k
      break;
249
0
#  ifndef DISABLE_JSON
250
9.63k
    case 3:
251
9.63k
      dwg_write_json (&out_dat, &dwg);
252
9.63k
      break;
253
778
    case 4:
254
778
      dwg_write_geojson (&out_dat, &dwg);
255
778
      break;
256
0
#  endif
257
0
#endif
258
0
    default:
259
0
      break;
260
20.5k
    }
261
20.5k
  dwg_free (&dwg);
262
20.5k
  free (out_dat.chain);
263
20.5k
  fclose (out_dat.fh);
264
  // unlink (tmp_file);
265
20.5k
  return 0;
266
20.5k
}
267
268
#ifdef STANDALONE
269
/*
270
# ifdef __GNUC__
271
__attribute__((weak))
272
# endif
273
extern int LLVMFuzzerInitialize(int *argc, char ***argv);
274
*/
275
276
static int
277
usage (void)
278
{
279
  printf ("\nUsage: OUT=0 VER=3 llvmfuzz_standalone INPUT...\n");
280
  printf ("VER: 0=r1.4, 1=r2.0, 2=r2.10, 3=r2.10, 4=r2.4, 5=r2.6, 6=r9, 7=r10, 8=r11,\n"          "     9=r12, 10=r13, 11=r13c3, 12=r14, 13=r2000, 14=r2004, 15=r2010, 16=r2013, 17=r2018. default r2000\n");
281
  printf ("OUT: 0=encode to dwg, 1=dxf, 2=json, 3=dxfb\n");
282
  return 1;
283
}
284
// llvmfuzz_standalone reproducer, see OUT and VER env vars
285
int
286
main (int argc, char *argv[])
287
{
288
  unsigned seed;
289
  const unsigned int possible_outputformats = LLVMFUZZ_NUM_OUTPUTS;
290
291
  if (argc <= 1 || !*argv[1])
292
    return usage ();
293
  if (getenv ("SEED"))
294
    seed = (unsigned)strtol (getenv ("SEED"), NULL, 10) % 9999;
295
  else
296
    {
297
#  ifdef HAVE_GETTIMEOFDAY
298
      struct timeval tval;
299
      gettimeofday (&tval, NULL);
300
      seed = (unsigned)(tval.tv_sec * 1000 + tval.tv_usec) % 9999;
301
#  else
302
      seed = (unsigned)time (NULL) % 9999;
303
#  endif
304
    }
305
  srand (seed);
306
  /* works only on linux
307
  if (LLVMFuzzerInitialize)
308
    LLVMFuzzerInitialize (&argc, &argv);
309
  */
310
  for (int i = 1; i < argc; i++)
311
    {
312
      unsigned char *buf;
313
      FILE *f = fopen (argv[i], "rb");
314
      struct stat attrib;
315
      long len;
316
      size_t n_read;
317
      int fd;
318
      if (!f)
319
        {
320
          fprintf (stderr, "Illegal file argument %s\n", argv[i]);
321
          continue;
322
        }
323
      fd = fileno (f);
324
      if (fd < 0 || fstat (fd, &attrib)
325
          || !(S_ISREG (attrib.st_mode)
326
#  ifndef _WIN32
327
               || S_ISLNK (attrib.st_mode)
328
#  endif
329
                   ))
330
        {
331
          fprintf (stderr, "Illegal input file \"%s\"\n", argv[i]);
332
          continue;
333
        }
334
      // libFuzzer design bug, not zero-terminating its text buffer
335
      fseek (f, 0, SEEK_END);
336
      len = ftell (f);
337
      fseek (f, 0, SEEK_SET);
338
      if (len <= 0)
339
        continue;
340
      buf = (unsigned char *)malloc (len);
341
      n_read = fread (buf, 1, len, f);
342
      fclose (f);
343
      assert ((long)n_read == len);
344
345
      out = rand () % possible_outputformats;
346
#  ifdef STANDALONE
347
      if (getenv ("OUT"))
348
        out = strtol (getenv ("OUT"), NULL, 10);
349
      // print SEED onlyu when needed (no env vars given)
350
      if (!(out || getenv ("VER")))
351
        fprintf (stderr, "SEED=%04u ", seed);
352
      fprintf (stderr, "OUT=%d ", out);
353
#  endif
354
      if (1)
355
        {
356
          ver = rand () % 20;
357
#  ifdef STANDALONE
358
          if (getenv ("VER"))
359
            ver = strtol (getenv ("VER"), NULL, 10);
360
          fprintf (stderr, "VER=%d ", ver);
361
#  endif
362
        }
363
      fprintf (stderr, "examples/llvmfuzz_standalone %s [%" PRIuSIZE "]\n",
364
               argv[i], len);
365
      LLVMFuzzerTestOneInput (buf, len);
366
      free (buf);
367
      // Bit_Chain dat = { 0 };
368
      // dat_read_file (&dat, fp, argv[i]);
369
      // LLVMFuzzerTestOneInput (dat.chain, dat.size);
370
      // bit_free_chain (&dat);
371
    }
372
}
373
#endif