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 int out;
42 int ver;
43
44 extern int LLVMFuzzerTestOneInput (const unsigned char *data, size_t size);
45
46 // libfuzzer limitation:
47 // Enforce NULL-termination of the input buffer, to avoid bogus reports. copy
48 // it. Problematic is mostly strtol(3) which also works with \n termination.
49 static int
50 enforce_null_termination (Bit_Chain *dat, bool enforce)
51 {
52 unsigned char *copy;
53 unsigned char c;
54 if (!dat->size)
55 return 0;
56 c = dat->chain[dat->size - 1];
57 // Allow \n termination without \0 in DXF? No, still crashes
58 if (!enforce && ((c == '\n' && c + 1 == '\0') || c == '\0'))
59 return 0;
60 #ifdef STANDALONE
61 fprintf (stderr,
62 "llvmfuzz_standalone: enforce libfuzzer buffer NULL termination\n");
63 #endif
64 copy = malloc (dat->size + 1);
65 memcpy (copy, dat->chain, dat->size);
66 copy[dat->size] = '\0';
67 dat->chain = copy;
68 return 1;
69 }
70
71 int
72 LLVMFuzzerTestOneInput (const unsigned char *data, size_t size)
73 {
74 Dwg_Data dwg;
75 Bit_Chain dat = { NULL, 0, 0, 0, 0, 0, 0, NULL, 0 };
76 Bit_Chain out_dat = { NULL, 0, 0, 0, 0, 0, 0, NULL, 0 };
77 int copied = 0;
78 struct ly_ctx *ctx = NULL;
79
80 static char tmp_file[256];
81 dat.chain = (unsigned char *)data;
82 dat.size = size;
83 memset (&dwg, 0, sizeof (dwg));
84
85 // Detect the input format: DWG, DXF or JSON
86 if (dat.size > 2 && dat.chain[0] == 'A' && dat.chain[1] == 'C')
87 {
88 if (dwg_decode (&dat, &dwg) >= DWG_ERR_CRITICAL)
89 {
90 dwg_free (&dwg);
91 return 0;
92 }
93 }
94 #ifndef DISABLE_JSON
95 else if (dat.size > 1 && dat.chain[0] == '{')
96 {
97 copied = enforce_null_termination (&dat, true);
98 if (dwg_read_json (&dat, &dwg) >= DWG_ERR_CRITICAL)
99 {
100 if (copied)
101 bit_chain_free (&dat);
102 dwg_free (&dwg);
103 return 0;
104 }
105 dat.opts |= DWG_OPTS_INJSON;
106 dwg.opts |= DWG_OPTS_INJSON;
107 }
108 #endif
109 #ifndef DISABLE_DXF
110 else
111 {
112 copied = enforce_null_termination (&dat, false);
113 if (dwg_read_dxf (&dat, &dwg) >= DWG_ERR_CRITICAL)
114 {
115 if (copied)
116 bit_chain_free (&dat);
117 dwg_free (&dwg);
118 return 0;
119 }
120 }
121 #else
122 else
123 return 0;
124 #endif
125
126 memset (&out_dat, 0, sizeof (out_dat));
127 bit_chain_set_version (&out_dat, &dat);
128 if (copied)
129 bit_chain_free (&dat);
130
131 #if 0
132 snprintf (tmp_file, 255, "/tmp/llvmfuzzer%d.out", getpid());
133 tmp_file[255] = '\0';
134 #elif defined _WIN32
135 strcpy (tmp_file, "NUL");
136 #else
137 strcpy (tmp_file, "/dev/null");
138 #endif
139 out_dat.fh = fopen (tmp_file, "w");
140
141 switch (out)
142 {
143 case 0:
144 {
145 switch (ver)
146 {
147 // TODO support preR13, many downconverters still missing
148 case 0:
149 out_dat.version = dwg.header.version = R_1_4;
150 break;
151 case 1:
152 out_dat.version = dwg.header.version = R_2_0;
153 break;
154 case 2:
155 out_dat.version = dwg.header.version = R_2_10;
156 break;
157 case 3:
158 out_dat.version = dwg.header.version = R_2_21;
159 break;
160 case 4:
161 out_dat.version = dwg.header.version = R_2_4;
162 break;
163 case 5:
164 out_dat.version = dwg.header.version = R_2_6;
165 break;
166 case 6:
167 out_dat.version = dwg.header.version = R_9;
168 break;
169 case 7:
170 out_dat.version = dwg.header.version = R_10;
171 break;
172 case 8:
173 out_dat.version = dwg.header.version = R_11;
174 break;
175 case 9:
176 out_dat.version = dwg.header.version = R_12;
177 break;
178 case 10:
179 out_dat.version = dwg.header.version = R_13;
180 break;
181 case 11:
182 out_dat.version = dwg.header.version = R_13c3;
183 break;
184 case 12:
185 out_dat.version = dwg.header.version = R_14;
186 break;
187 case 13:
188 out_dat.version = dwg.header.version = R_2004;
189 break;
190 default: // favor this one
191 out_dat.version = dwg.header.version = R_2000;
192 break;
193 }
194 dwg_encode (&dwg, &out_dat);
195 break;
196 }
197 #ifndef DISABLE_DXF
198 case 1:
199 dwg_write_dxf (&out_dat, &dwg);
200 break;
201 case 2: // experimental
202 dwg_write_dxfb (&out_dat, &dwg);
203 break;
204 # ifndef DISABLE_JSON
205 case 3:
206 dwg_write_json (&out_dat, &dwg);
207 break;
208 case 4:
209 dwg_write_geojson (&out_dat, &dwg);
210 break;
211 # endif
212 #endif
213 default:
214 break;
215 }
216 dwg_free (&dwg);
217 free (out_dat.chain);
218 fclose (out_dat.fh);
219 // unlink (tmp_file);
220 return 0;
221 }
222
223 #ifdef STANDALONE
224 /*
225 # ifdef __GNUC__
226 __attribute__((weak))
227 # endif
228 extern int LLVMFuzzerInitialize(int *argc, char ***argv);
229 */
230
231 static int
232 usage (void)
233 {
234 printf ("\nUsage: OUT=0 VER=3 llvmfuzz_standalone INPUT...");
235 return 1;
236 }
237 // llvmfuzz_standalone reproducer, see OUT and VER env vars
238 int
239 main (int argc, char *argv[])
240 {
241 unsigned seed;
242 const unsigned int possible_outputformats =
243 #ifdef DISABLE_DXF
244 # ifdef DISABLE_JSON
245 1;
246 # else
247 3;
248 # endif
249 #else
250 5;
251 #endif
252
253 if (argc <= 1 || !*argv[1])
254 return usage ();
255 if (getenv ("SEED"))
256 seed = (unsigned)strtol (getenv ("SEED"), NULL, 10) % 9999;
257 else
258 {
259 #ifdef HAVE_GETTIMEOFDAY
260 struct timeval tval;
261 gettimeofday (&tval, NULL);
262 seed = (unsigned)(tval.tv_sec * 1000 + tval.tv_usec) % 9999;
263 #else
264 seed = (unsigned)time (NULL) % 9999;
265 #endif
266 }
267 srand (seed);
268 /* works only on linux
269 if (LLVMFuzzerInitialize)
270 LLVMFuzzerInitialize (&argc, &argv);
271 */
272 for (int i = 1; i < argc; i++)
273 {
274 unsigned char *buf;
275 FILE *f = fopen (argv[i], "rb");
276 struct stat attrib;
277 long len;
278 size_t n_read;
279 int fd;
280 if (!f)
281 {
282 fprintf (stderr, "Illegal file argument %s\n", argv[i]);
283 continue;
284 }
285 fd = fileno (f);
286 if (fd < 0 || fstat (fd, &attrib)
287 || !(S_ISREG (attrib.st_mode)
288 # ifndef _WIN32
289 || S_ISLNK (attrib.st_mode)
290 # endif
291 ))
292 {
293 fprintf (stderr, "Illegal input file \"%s\"\n", argv[i]);
294 continue;
295 }
296 // libFuzzer design bug, not zero-terminating its text buffer
297 fseek (f, 0, SEEK_END);
298 len = ftell (f);
299 fseek (f, 0, SEEK_SET);
300 if (len <= 0)
301 continue;
302 buf = (unsigned char *)malloc (len);
303 n_read = fread (buf, 1, len, f);
304 fclose (f);
305 assert ((long)n_read == len);
306
307 out = rand () % possible_outputformats;
308 #ifdef STANDALONE
309 if (getenv ("OUT"))
310 out = strtol (getenv ("OUT"), NULL, 10);
311 // print SEED onlyu when needed (no env vars given)
312 if (!(out || getenv ("VER")))
313 fprintf (stderr, "SEED=%04u ", seed);
314 fprintf (stderr, "OUT=%d ", out);
315 #endif
316 if (out == 0)
317 {
318 ver = rand () % 20;
319 #ifdef STANDALONE
320 if (getenv ("VER"))
321 ver = strtol (getenv ("VER"), NULL, 10);
322 fprintf (stderr, "VER=%d ", ver);
323 #endif
324 }
325 fprintf (stderr, "examples/llvmfuzz_standalone %s [%" PRIuSIZE "]\n",
326 argv[i], len);
327 LLVMFuzzerTestOneInput (buf, len);
328 free (buf);
329 // Bit_Chain dat = { 0 };
330 // dat_read_file (&dat, fp, argv[i]);
331 // LLVMFuzzerTestOneInput (dat.chain, dat.size);
332 // bit_free_chain (&dat);
333 }
334 }
335 #endif