fuzz_plugin.c (16366B)
1 /* 2 This file is part of libextractor. 3 Copyright (C) 2026 Christian Grothoff 4 5 libextractor is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published 7 by the Free Software Foundation; either version 3, or (at your 8 option) any later version. 9 10 libextractor is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with libextractor; see the file COPYING. If not, write to the 17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 Boston, MA 02110-1301, USA. 19 */ 20 /** 21 * @file fuzz/fuzz_plugin.c 22 * @brief generic in-process fuzzer for a single libextractor plugin 23 * @author Christian Grothoff 24 * 25 * This one source is compiled once per plugin. `-DLE_FUZZ_PLUGIN=gif` 26 * produces the `fuzz_gif` target, which calls 27 * `EXTRACTOR_gif_extract_method()` directly -- the plugin's own object 28 * files are linked in, so every line of the parser is instrumented, and 29 * no `dlopen()`, no `fork()` and no IPC is involved. 30 * 31 * The extraction context is the contract-exact model in fuzz_ec.h; read 32 * its header comment first, it is where the interesting properties are. 33 * 34 * Input format: 35 * 36 * byte 0 read window size selector (0 = the production 16 KiB) 37 * byte 1 fault-injection bitmask, see LE_FUZZ_FAULT_* in fuzz_ec.h 38 * byte 2 call index at which the injected fault fires 39 * byte 3 reserved, must be present 40 * byte 4.. the file image handed to the plugin 41 * 42 * An all-zero prefix is exactly what production does, so a corpus entry 43 * is just four zero bytes followed by a real file of the format; that is 44 * what contrib/oss-fuzz/make_seed_corpus.sh generates from 45 * src/plugins/testdata/. 46 */ 47 48 #include "fuzz_plugin_name.h" 49 50 #define FUZZ_HARNESS_NAME "fuzz_" LE_FUZZ_PLUGIN_STR 51 52 #include "fuzz_common.h" 53 #include "fuzz_ec.h" 54 55 /** 56 * The plugin's extract method, resolved at link time rather than through 57 * `lt_dlsym()`. 58 */ 59 extern void 60 LE_FUZZ_EXTRACT_METHOD (struct EXTRACTOR_ExtractContext *ec); 61 62 /** 63 * The plugin's optional configuration hook. Declared weak: most plugins 64 * do not have one. 65 */ 66 extern const char * 67 LE_FUZZ_OPTIONS_METHOD (void) __attribute__ ((weak)); 68 69 #ifdef LE_FUZZ_LSAN_SUPPRESSIONS 70 /** 71 * Leak suppressions for plugins whose library keeps global state that 72 * is never released (see fuzz_plugin_name.h). 73 * 74 * @return suppression list in LSan syntax 75 */ 76 const char * 77 __lsan_default_suppressions (void); 78 79 const char * 80 __lsan_default_suppressions (void) 81 { 82 return LE_FUZZ_LSAN_SUPPRESSIONS; 83 } 84 85 86 /** 87 * The suppressions match on library frames; the default frame-pointer 88 * unwinder loses those in libraries built without frame pointers, so 89 * use the slow unwinder for allocations. 90 * 91 * @return default options for LeakSanitizer 92 */ 93 const char * 94 __lsan_default_options (void); 95 96 const char * 97 __lsan_default_options (void) 98 { 99 return "fast_unwind_on_malloc=0"; 100 } 101 102 103 #endif 104 105 #ifndef LE_FUZZ_CONFIGS 106 /** 107 * Plugin configurations to fuzz. fuzz_plugin_name.h may define 108 * LE_FUZZ_CONFIGS as a comma-separated list of configuration strings 109 * for plugins that have options; entry 0 is always NULL, i.e. the 110 * configuration used when the user gives no options. 111 */ 112 #define LE_FUZZ_CONFIGS NULL 113 #endif 114 115 /** 116 * Configurations selected by the fourth prefix byte. 117 */ 118 static const char *const le_fuzz_configs[] = { LE_FUZZ_CONFIGS }; 119 120 /** 121 * Number of entries in #le_fuzz_configs. 122 */ 123 #define LE_FUZZ_NUM_CONFIGS (sizeof (le_fuzz_configs) \ 124 / sizeof (le_fuzz_configs[0])) 125 126 127 /* ------------------------------------------------------------------ */ 128 /* The fuzz target */ 129 /* ------------------------------------------------------------------ */ 130 131 int 132 LLVMFuzzerTestOneInput (const uint8_t *data, 133 size_t size) 134 { 135 struct EXTRACTOR_ExtractContext ec; 136 struct fuzz_ec_state st; 137 138 fuzz_ignore_sigpipe (); 139 if (! le_fuzz_ec_setup (&ec, &st, data, size)) 140 return 0; 141 ec.config = le_fuzz_configs[data[3] % LE_FUZZ_NUM_CONFIGS]; 142 LE_FUZZ_EXTRACT_METHOD (&ec); 143 if (0 != (st.faults & LE_FUZZ_RUN_TWICE)) 144 { 145 /* A plugin must not carry state from one file to the next: the same 146 process is reused for every file in a directory walk. */ 147 le_fuzz_ec_rewind (&st); 148 LE_FUZZ_EXTRACT_METHOD (&ec); 149 } 150 le_fuzz_ec_cleanup (&st); 151 return 0; 152 } 153 154 155 /* ------------------------------------------------------------------ */ 156 /* Structure-aware generator */ 157 /* ------------------------------------------------------------------ */ 158 159 /* The body shapes -- LE_SHAPE_RAW (magic plus random bytes), 160 LE_SHAPE_CHUNK_BE (tag + big-endian length + payload, as in PNG and 161 QuickTime), LE_SHAPE_CHUNK_LE (RIFF/WAV/AVI), LE_SHAPE_TEXT 162 (line-oriented) and LE_SHAPE_ZIP -- are #defined in 163 fuzz_plugin_name.h, because that header has to select one before this 164 translation unit could declare an enum. */ 165 166 /** 167 * Magic bytes and body shape of the format this target parses. Chosen 168 * per plugin at compile time by fuzz_plugin_name.h. 169 */ 170 static const char le_fuzz_magic[] = LE_FUZZ_MAGIC; 171 172 /** 173 * Number of bytes in #le_fuzz_magic (the trailing NUL is not part of it 174 * unless the format really has one). 175 */ 176 #define LE_FUZZ_MAGIC_LEN (sizeof (le_fuzz_magic) - 1) 177 178 /** 179 * Four-character tags that appear in the chunked formats. 180 */ 181 static const char *const le_fuzz_tags[] = { 182 "IHDR", "tEXt", "zTXt", "iTXt", "pHYs", "tIME", "IEND", "PLTE", 183 "LIST", "INFO", "fmt ", "data", "INAM", "IART", "ICMT", "ICRD", 184 "moov", "mvhd", "trak", "udta", "meta", "ilst", "\xa9nam", "cmov", 185 "avih", "strh", "strf", "movi", "JUNK", "AAAA", "\x00\x00\x00\x00" 186 }; 187 188 /** 189 * Text fragments that appear in the line-oriented formats. 190 */ 191 static const char *const le_fuzz_lines[] = { 192 "%%Title: fuzz\n", "%%Creator: fuzz\n", "%%Pages: 1\n", 193 "%%BoundingBox: 0 0 1 1\n", "%%EOF\n", "%!PS-Adobe-3.0\n", 194 ".TH FUZZ 1 \"2026\" \"le\" \"fuzz\"\n", ".SH NAME\n", ".SH SYNOPSIS\n", 195 "{\\rtf1\\ansi\\deff0", "{\\info{\\title fuzz}}", "{\\*\\generator x}", 196 "\\u1234?", "\\'ff", "\\par ", "}", "{", "\\\\", 197 "<html><head><title>fuzz</title>", "<meta name=\"author\" content=\"x\">", 198 "\n", "\r\n", "\t", " " 199 }; 200 201 202 /** 203 * Append a chunk-structured body to @a buf. 204 * 205 * @param rng PRNG state 206 * @param buf output buffer 207 * @param[in,out] len current length 208 * @param cap capacity 209 * @param big non-zero for big-endian lengths 210 */ 211 static void 212 le_fuzz_gen_chunks (struct fuzz_rng *rng, 213 uint8_t *buf, 214 size_t *len, 215 size_t cap, 216 int big) 217 { 218 unsigned int n = 1 + fuzz_below (rng, 12); 219 unsigned int i; 220 221 for (i = 0; i < n; i++) 222 { 223 const char *tag = 224 le_fuzz_tags[fuzz_below (rng, 225 (uint32_t) (sizeof (le_fuzz_tags) 226 / sizeof (le_fuzz_tags[0])))]; 227 uint32_t plen = fuzz_below (rng, 96); 228 uint32_t decl; 229 unsigned int k; 230 231 /* Every so often declare a length that has nothing to do with the 232 number of bytes actually present. This is the single most 233 productive mutation for these parsers. */ 234 if (fuzz_chance (rng, 3)) 235 { 236 static const uint32_t bogus[] = { 237 0, 1, 0x7FFFFFFF, 0x80000000, 0xFFFFFFFF, 0xFFFF, 0x10000 238 }; 239 240 decl = bogus[fuzz_below (rng, 241 (uint32_t) (sizeof (bogus) 242 / sizeof (bogus[0])))]; 243 } 244 else 245 { 246 decl = plen; 247 } 248 if (big) 249 { 250 fuzz_put_mem (buf, len, cap, tag, 4); 251 fuzz_put_be (buf, len, cap, decl, 4); 252 } 253 else 254 { 255 fuzz_put_be (buf, len, cap, decl, 4); 256 fuzz_put_mem (buf, len, cap, tag, 4); 257 } 258 if (*len + plen > cap) 259 return; 260 for (k = 0; k < plen; k++) 261 buf[(*len)++] = fuzz_byte (rng); 262 } 263 } 264 265 266 /** 267 * Append a ZIP skeleton to @a buf: one local file header, one central 268 * directory entry and an end-of-central-directory record, with 269 * fuzzer-chosen (often inconsistent) offsets and sizes. 270 */ 271 static void 272 le_fuzz_gen_zip (struct fuzz_rng *rng, 273 uint8_t *buf, 274 size_t *len, 275 size_t cap) 276 { 277 static const char *const names[] = { 278 "mimetype", "meta.xml", "content.xml", "docProps/core.xml", 279 "word/document.xml", "xl/workbook.xml", "ppt/presentation.xml", 280 "META-INF/manifest.xml", "a", "" 281 }; 282 const char *name = 283 names[fuzz_below (rng, (uint32_t) (sizeof (names) / sizeof (names[0])))]; 284 size_t nlen = strlen (name); 285 size_t lfh_off = *len; 286 uint32_t payload = fuzz_below (rng, 64); 287 unsigned int k; 288 289 fuzz_put_mem (buf, len, cap, "PK\x03\x04", 4); 290 fuzz_put_le (buf, len, cap, 20, 2); /* version */ 291 fuzz_put_le (buf, len, cap, fuzz_byte (rng), 2); /* flags */ 292 fuzz_put_le (buf, len, cap, fuzz_chance (rng, 2) ? 0 : 8, 2); /* method */ 293 fuzz_put_le (buf, len, cap, 0, 4); /* time/date */ 294 fuzz_put_le (buf, len, cap, fuzz_next (rng), 4); /* crc */ 295 fuzz_put_le (buf, len, cap, 296 fuzz_chance (rng, 3) ? fuzz_next (rng) : payload, 4); 297 fuzz_put_le (buf, len, cap, 298 fuzz_chance (rng, 3) ? fuzz_next (rng) : payload, 4); 299 fuzz_put_le (buf, len, cap, nlen, 2); 300 fuzz_put_le (buf, len, cap, fuzz_chance (rng, 4) ? fuzz_below (rng, 64) : 0, 301 2); 302 fuzz_put_mem (buf, len, cap, name, nlen); 303 if (*len + payload > cap) 304 return; 305 for (k = 0; k < payload; k++) 306 buf[(*len)++] = fuzz_byte (rng); 307 308 { 309 size_t cd_off = *len; 310 311 fuzz_put_mem (buf, len, cap, "PK\x01\x02", 4); 312 fuzz_put_le (buf, len, cap, 20, 2); 313 fuzz_put_le (buf, len, cap, 20, 2); 314 fuzz_put_le (buf, len, cap, 0, 2); 315 fuzz_put_le (buf, len, cap, 0, 2); 316 fuzz_put_le (buf, len, cap, 0, 4); 317 fuzz_put_le (buf, len, cap, 0, 4); 318 fuzz_put_le (buf, len, cap, payload, 4); 319 fuzz_put_le (buf, len, cap, payload, 4); 320 fuzz_put_le (buf, len, cap, nlen, 2); 321 fuzz_put_le (buf, len, cap, 0, 2); /* extra */ 322 fuzz_put_le (buf, len, cap, 323 fuzz_chance (rng, 4) ? fuzz_next (rng) : 0, 2); /* comment */ 324 fuzz_put_le (buf, len, cap, 0, 2); 325 fuzz_put_le (buf, len, cap, 0, 2); 326 fuzz_put_le (buf, len, cap, 0, 4); 327 fuzz_put_le (buf, len, cap, 328 fuzz_chance (rng, 4) ? fuzz_next (rng) : lfh_off, 4); 329 fuzz_put_mem (buf, len, cap, name, nlen); 330 331 fuzz_put_mem (buf, len, cap, "PK\x05\x06", 4); 332 fuzz_put_le (buf, len, cap, 0, 2); 333 fuzz_put_le (buf, len, cap, 0, 2); 334 fuzz_put_le (buf, len, cap, 335 fuzz_chance (rng, 4) ? fuzz_next (rng) : 1, 2); 336 fuzz_put_le (buf, len, cap, 337 fuzz_chance (rng, 4) ? fuzz_next (rng) : 1, 2); 338 fuzz_put_le (buf, len, cap, *len - cd_off, 4); 339 fuzz_put_le (buf, len, cap, 340 fuzz_chance (rng, 4) ? fuzz_next (rng) : cd_off, 4); 341 fuzz_put_le (buf, len, cap, 0, 2); 342 } 343 } 344 345 346 static size_t 347 fuzz_generate (struct fuzz_rng *rng, 348 uint8_t *buf, 349 size_t cap) 350 { 351 size_t len = 0; 352 unsigned int shape = LE_FUZZ_SHAPE; 353 354 if (cap < 64) 355 return 0; 356 /* configuration prefix */ 357 buf[len++] = fuzz_chance (rng, 2) ? 0 : fuzz_byte (rng); 358 buf[len++] = fuzz_chance (rng, 3) ? 0 : (uint8_t) fuzz_below (rng, 0x40); 359 buf[len++] = fuzz_byte (rng); 360 buf[len++] = fuzz_byte (rng); 361 362 /* magic; occasionally corrupted so that the reject path is covered 363 too, but usually intact so that the parser is actually entered */ 364 fuzz_put_mem (buf, &len, cap, le_fuzz_magic, LE_FUZZ_MAGIC_LEN); 365 if ( (LE_FUZZ_MAGIC_LEN > 0) && 366 fuzz_chance (rng, 12) ) 367 buf[LE_FUZZ_EC_PREFIX + fuzz_below (rng, (uint32_t) LE_FUZZ_MAGIC_LEN)] = 368 fuzz_byte (rng); 369 370 switch (shape) 371 { 372 case LE_SHAPE_CHUNK_BE: 373 le_fuzz_gen_chunks (rng, buf, &len, cap, 1); 374 break; 375 case LE_SHAPE_CHUNK_LE: 376 /* RIFF: overall size field, form type, then LE chunks */ 377 fuzz_put_le (buf, &len, cap, 378 fuzz_chance (rng, 3) ? fuzz_next (rng) : cap, 4); 379 fuzz_put_mem (buf, &len, cap, 380 fuzz_chance (rng, 2) ? "WAVE" : "AVI ", 4); 381 le_fuzz_gen_chunks (rng, buf, &len, cap, 0); 382 break; 383 case LE_SHAPE_TEXT: 384 { 385 unsigned int n = 1 + fuzz_below (rng, 40); 386 unsigned int i; 387 388 for (i = 0; i < n; i++) 389 { 390 if (fuzz_chance (rng, 6)) 391 { 392 unsigned int k; 393 unsigned int r = 1 + fuzz_below (rng, 200); 394 395 for (k = 0; (k < r) && (len < cap); k++) 396 buf[len++] = fuzz_byte (rng); 397 } 398 else 399 { 400 fuzz_put_str (buf, &len, cap, 401 le_fuzz_lines[fuzz_below (rng, 402 (uint32_t) 403 (sizeof (le_fuzz_lines) 404 / sizeof (char *)))]); 405 } 406 } 407 break; 408 } 409 case LE_SHAPE_ZIP: 410 le_fuzz_gen_zip (rng, buf, &len, cap); 411 break; 412 case LE_SHAPE_RAW: 413 default: 414 { 415 unsigned int n = fuzz_below (rng, 1024); 416 unsigned int i; 417 418 for (i = 0; (i < n) && (len < cap); i++) 419 { 420 if (fuzz_chance (rng, 16)) 421 { 422 /* a run of one value: length fields and counters love these */ 423 unsigned int k; 424 unsigned int r = 1 + fuzz_below (rng, 64); 425 uint8_t v = fuzz_chance (rng, 2) ? 0xFF : fuzz_byte (rng); 426 427 for (k = 0; (k < r) && (len < cap); k++) 428 buf[len++] = v; 429 } 430 else 431 { 432 buf[len++] = fuzz_byte (rng); 433 } 434 } 435 break; 436 } 437 } 438 return len; 439 } 440 441 442 /* ------------------------------------------------------------------ */ 443 /* Built-in seed corpus */ 444 /* ------------------------------------------------------------------ */ 445 446 /** 447 * The built-in seeds are deliberately minimal: the interesting seeds are 448 * the real files under src/plugins/testdata/, which 449 * contrib/oss-fuzz/make_seed_corpus.sh turns into corpus entries. These 450 * exist so that a bare `make -C src/fuzz check` still starts from 451 * something that gets past the magic-number test. 452 */ 453 #define LE_FUZZ_NSEEDS 6 454 455 static uint8_t le_fuzz_seed_buf[LE_FUZZ_NSEEDS][512]; 456 static size_t le_fuzz_seed_len[LE_FUZZ_NSEEDS]; 457 static int le_fuzz_seeds_ready; 458 459 460 static void 461 le_fuzz_build_seeds (void) 462 { 463 struct fuzz_rng rng; 464 unsigned int i; 465 466 if (le_fuzz_seeds_ready) 467 return; 468 le_fuzz_seeds_ready = 1; 469 for (i = 0; i < LE_FUZZ_NSEEDS; i++) 470 { 471 /* Seed 0 is the bare magic with the production configuration; the 472 others vary the window size, which is what most reliably changes 473 which code path a parser takes. */ 474 size_t len = 0; 475 uint8_t *b = le_fuzz_seed_buf[i]; 476 477 fuzz_rng_seed (&rng, 0x5EED0000u + i); 478 if (0 == i) 479 { 480 /* the bare magic under the production configuration */ 481 len = LE_FUZZ_EC_PREFIX; 482 memset (b, 0, len); 483 fuzz_put_mem (b, &len, sizeof (le_fuzz_seed_buf[i]), 484 le_fuzz_magic, LE_FUZZ_MAGIC_LEN); 485 } 486 else 487 { 488 len = fuzz_generate (&rng, b, sizeof (le_fuzz_seed_buf[i])); 489 if (len < LE_FUZZ_EC_PREFIX) 490 len = LE_FUZZ_EC_PREFIX; 491 /* pin the configuration so that the seeds cover the knobs that 492 matter rather than whatever the PRNG happened to pick */ 493 b[0] = (uint8_t) (i + 1); /* window */ 494 b[1] = (uint8_t) ((4 == i) ? LE_FUZZ_RUN_TWICE : 0); /* faults */ 495 b[2] = (uint8_t) ((5 == i) ? 1 : 0); 496 /* cycle through the plugin configurations */ 497 b[3] = (uint8_t) (i % LE_FUZZ_NUM_CONFIGS); 498 } 499 le_fuzz_seed_len[i] = len; 500 } 501 } 502 503 504 static size_t 505 fuzz_seed_count (void) 506 { 507 le_fuzz_build_seeds (); 508 return LE_FUZZ_NSEEDS; 509 } 510 511 512 static const uint8_t * 513 fuzz_seed_get (size_t idx, 514 size_t *len) 515 { 516 le_fuzz_build_seeds (); 517 *len = le_fuzz_seed_len[idx]; 518 return le_fuzz_seed_buf[idx]; 519 }