Coverage Report

Created: 2026-08-12 07:12

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
17.7k
#    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
2.65k
{
64
2.65k
  unsigned char *copy;
65
2.65k
  unsigned char c;
66
2.65k
  if (!dat->size)
67
0
    return 0;
68
2.65k
  c = dat->chain[dat->size - 1];
69
  // Allow \n termination without \0 in DXF? No, still crashes
70
2.65k
  if (!enforce && ((c == '\n' && c + 1 == '\0') || c == '\0'))
71
145
    return 0;
72
#ifdef STANDALONE
73
  fprintf (stderr,
74
           "llvmfuzz_standalone: enforce libfuzzer buffer NULL termination\n");
75
#endif
76
2.50k
  copy = malloc (dat->size + 1);
77
2.50k
  memcpy (copy, dat->chain, dat->size);
78
2.50k
  copy[dat->size] = '\0';
79
2.50k
  dat->chain = copy;
80
2.50k
  return 1;
81
2.65k
}
82
83
int
84
LLVMFuzzerTestOneInput (const unsigned char *data, size_t size)
85
17.7k
{
86
17.7k
  Dwg_Data dwg;
87
17.7k
  Bit_Chain dat = { NULL, 0, 0, 0, 0, 0, 0, NULL, 0 };
88
17.7k
  Bit_Chain out_dat = { NULL, 0, 0, 0, 0, 0, 0, NULL, 0 };
89
17.7k
  int copied = 0;
90
17.7k
  struct ly_ctx *ctx = NULL;
91
92
17.7k
  static char tmp_file[256];
93
94
17.7k
#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
17.7k
  out = 0;
99
17.7k
  ver = 0;
100
17.7k
  if (size)
101
17.7k
    {
102
17.7k
      unsigned int h = 2166136261u;
103
17.7k
      const size_t n = size > 4096 ? 4096 : size;
104
35.8M
      for (size_t i = 0; i < n; i++)
105
35.8M
        h = (h ^ data[i]) * 16777619u;
106
17.7k
      out = (int)(h % LLVMFUZZ_NUM_OUTPUTS);
107
17.7k
      ver = (int)((h >> 8) % 20);
108
17.7k
    }
109
17.7k
#endif
110
111
17.7k
  dat.chain = (unsigned char *)data;
112
17.7k
  dat.size = size;
113
17.7k
  memset (&dwg, 0, sizeof (dwg));
114
115
  /* Cap input size to prevent OOM/timeout in fuzzing */
116
17.7k
  if (size > 10 * 1024 * 1024)
117
0
    return 0;
118
  // Detect the input format: DWG, DXF or JSON
119
17.7k
  if (dat.size > 2 && dat.chain[0] == 'A' && dat.chain[1] == 'C')
120
15.0k
    {
121
15.0k
      if (dwg_decode (&dat, &dwg) >= DWG_ERR_CRITICAL)
122
4.46k
        {
123
4.46k
          dwg_free (&dwg);
124
4.46k
          return 0;
125
4.46k
        }
126
15.0k
    }
127
2.65k
#ifndef DISABLE_JSON
128
2.65k
  else if (dat.size > 1 && dat.chain[0] == '{')
129
1.29k
    {
130
1.29k
      copied = enforce_null_termination (&dat, true);
131
1.29k
      if (dwg_read_json (&dat, &dwg) >= DWG_ERR_CRITICAL)
132
361
        {
133
361
          if (copied)
134
361
            bit_chain_free (&dat);
135
361
          dwg_free (&dwg);
136
361
          return 0;
137
361
        }
138
930
      dat.opts |= DWG_OPTS_INJSON;
139
930
      dwg.opts |= DWG_OPTS_INJSON;
140
930
    }
141
1.36k
#endif
142
1.36k
#ifndef DISABLE_DXF
143
1.36k
  else
144
1.36k
    {
145
1.36k
      copied = enforce_null_termination (&dat, false);
146
1.36k
      if (dwg_read_dxf (&dat, &dwg) >= DWG_ERR_CRITICAL)
147
1.31k
        {
148
1.31k
          if (copied)
149
1.18k
            bit_chain_free (&dat);
150
1.31k
          dwg_free (&dwg);
151
1.31k
          return 0;
152
1.31k
        }
153
1.36k
    }
154
#else
155
  else
156
    return 0;
157
#endif
158
159
11.5k
  memset (&out_dat, 0, sizeof (out_dat));
160
11.5k
  bit_chain_set_version (&out_dat, &dat);
161
11.5k
  if (copied)
162
967
    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
11.5k
  strcpy (tmp_file, "/dev/null");
171
11.5k
#endif
172
11.5k
  out_dat.fh = fopen (tmp_file, "w");
173
174
11.5k
  switch (out)
175
11.5k
    {
176
3.25k
    case 0:
177
3.25k
      {
178
3.25k
        switch (ver)
179
3.25k
          {
180
          // TODO support preR13, many downconverters still missing
181
95
          case 0:
182
95
            out_dat.version = dwg.header.version = R_1_4;
183
95
            break;
184
70
          case 1:
185
70
            out_dat.version = dwg.header.version = R_2_0;
186
70
            break;
187
99
          case 2:
188
99
            out_dat.version = dwg.header.version = R_2_10;
189
99
            break;
190
82
          case 3:
191
82
            out_dat.version = dwg.header.version = R_2_21;
192
82
            break;
193
87
          case 4:
194
87
            out_dat.version = dwg.header.version = R_2_4;
195
87
            break;
196
84
          case 5:
197
84
            out_dat.version = dwg.header.version = R_2_6;
198
84
            break;
199
72
          case 6:
200
72
            out_dat.version = dwg.header.version = R_9;
201
72
            break;
202
127
          case 7:
203
127
            out_dat.version = dwg.header.version = R_10;
204
127
            break;
205
170
          case 8:
206
170
            out_dat.version = dwg.header.version = R_11;
207
170
            break;
208
142
          case 9:
209
142
            out_dat.version = dwg.header.version = R_12;
210
142
            break;
211
230
          case 10:
212
230
            out_dat.version = dwg.header.version = R_13;
213
230
            break;
214
193
          case 11:
215
193
            out_dat.version = dwg.header.version = R_13c3;
216
193
            break;
217
226
          case 12:
218
226
            out_dat.version = dwg.header.version = R_14;
219
226
            break;
220
217
          case 13:
221
217
            out_dat.version = dwg.header.version = R_2000;
222
217
            break;
223
210
          case 14:
224
210
            out_dat.version = dwg.header.version = R_2004;
225
210
            break;
226
262
          case 15:
227
262
            out_dat.version = dwg.header.version = R_2010;
228
262
            break;
229
267
          case 16:
230
267
            out_dat.version = dwg.header.version = R_2013;
231
267
            break;
232
275
          case 17:
233
275
            out_dat.version = dwg.header.version = R_2018;
234
275
            break;
235
347
          default: // favor this one
236
347
            out_dat.version = dwg.header.version = R_2000;
237
347
            break;
238
3.25k
          }
239
3.25k
        dwg_encode (&dwg, &out_dat);
240
3.25k
        break;
241
3.25k
      }
242
0
#ifndef DISABLE_DXF
243
1.04k
    case 1:
244
1.04k
      dwg_write_dxf (&out_dat, &dwg);
245
1.04k
      break;
246
1.20k
    case 2: // experimental
247
1.20k
      dwg_write_dxfb (&out_dat, &dwg);
248
1.20k
      break;
249
0
#  ifndef DISABLE_JSON
250
5.55k
    case 3:
251
5.55k
      dwg_write_json (&out_dat, &dwg);
252
5.55k
      break;
253
538
    case 4:
254
538
      dwg_write_geojson (&out_dat, &dwg);
255
538
      break;
256
0
#  endif
257
0
#endif
258
0
    default:
259
0
      break;
260
11.5k
    }
261
11.5k
  dwg_free (&dwg);
262
11.5k
  free (out_dat.chain);
263
11.5k
  fclose (out_dat.fh);
264
  // unlink (tmp_file);
265
11.5k
  return 0;
266
11.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