/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 |