fuzz_plugin.c (14956B)
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 70 /* ------------------------------------------------------------------ */ 71 /* The fuzz target */ 72 /* ------------------------------------------------------------------ */ 73 74 int 75 LLVMFuzzerTestOneInput (const uint8_t *data, 76 size_t size) 77 { 78 struct EXTRACTOR_ExtractContext ec; 79 struct fuzz_ec_state st; 80 81 fuzz_ignore_sigpipe (); 82 if (! le_fuzz_ec_setup (&ec, &st, data, size)) 83 return 0; 84 if (NULL != LE_FUZZ_OPTIONS_METHOD) 85 ec.config = LE_FUZZ_OPTIONS_METHOD (); 86 LE_FUZZ_EXTRACT_METHOD (&ec); 87 if (0 != (st.faults & LE_FUZZ_RUN_TWICE)) 88 { 89 /* A plugin must not carry state from one file to the next: the same 90 process is reused for every file in a directory walk. */ 91 le_fuzz_ec_rewind (&st); 92 LE_FUZZ_EXTRACT_METHOD (&ec); 93 } 94 le_fuzz_ec_cleanup (&st); 95 return 0; 96 } 97 98 99 /* ------------------------------------------------------------------ */ 100 /* Structure-aware generator */ 101 /* ------------------------------------------------------------------ */ 102 103 /* The body shapes -- LE_SHAPE_RAW (magic plus random bytes), 104 LE_SHAPE_CHUNK_BE (tag + big-endian length + payload, as in PNG and 105 QuickTime), LE_SHAPE_CHUNK_LE (RIFF/WAV/AVI), LE_SHAPE_TEXT 106 (line-oriented) and LE_SHAPE_ZIP -- are #defined in 107 fuzz_plugin_name.h, because that header has to select one before this 108 translation unit could declare an enum. */ 109 110 /** 111 * Magic bytes and body shape of the format this target parses. Chosen 112 * per plugin at compile time by fuzz_plugin_name.h. 113 */ 114 static const char le_fuzz_magic[] = LE_FUZZ_MAGIC; 115 116 /** 117 * Number of bytes in #le_fuzz_magic (the trailing NUL is not part of it 118 * unless the format really has one). 119 */ 120 #define LE_FUZZ_MAGIC_LEN (sizeof (le_fuzz_magic) - 1) 121 122 /** 123 * Four-character tags that appear in the chunked formats. 124 */ 125 static const char *const le_fuzz_tags[] = { 126 "IHDR", "tEXt", "zTXt", "iTXt", "pHYs", "tIME", "IEND", "PLTE", 127 "LIST", "INFO", "fmt ", "data", "INAM", "IART", "ICMT", "ICRD", 128 "moov", "mvhd", "trak", "udta", "meta", "ilst", "\xa9nam", "cmov", 129 "avih", "strh", "strf", "movi", "JUNK", "AAAA", "\x00\x00\x00\x00" 130 }; 131 132 /** 133 * Text fragments that appear in the line-oriented formats. 134 */ 135 static const char *const le_fuzz_lines[] = { 136 "%%Title: fuzz\n", "%%Creator: fuzz\n", "%%Pages: 1\n", 137 "%%BoundingBox: 0 0 1 1\n", "%%EOF\n", "%!PS-Adobe-3.0\n", 138 ".TH FUZZ 1 \"2026\" \"le\" \"fuzz\"\n", ".SH NAME\n", ".SH SYNOPSIS\n", 139 "{\\rtf1\\ansi\\deff0", "{\\info{\\title fuzz}}", "{\\*\\generator x}", 140 "\\u1234?", "\\'ff", "\\par ", "}", "{", "\\\\", 141 "<html><head><title>fuzz</title>", "<meta name=\"author\" content=\"x\">", 142 "\n", "\r\n", "\t", " " 143 }; 144 145 146 /** 147 * Append a chunk-structured body to @a buf. 148 * 149 * @param rng PRNG state 150 * @param buf output buffer 151 * @param[in,out] len current length 152 * @param cap capacity 153 * @param big non-zero for big-endian lengths 154 */ 155 static void 156 le_fuzz_gen_chunks (struct fuzz_rng *rng, 157 uint8_t *buf, 158 size_t *len, 159 size_t cap, 160 int big) 161 { 162 unsigned int n = 1 + fuzz_below (rng, 12); 163 unsigned int i; 164 165 for (i = 0; i < n; i++) 166 { 167 const char *tag = 168 le_fuzz_tags[fuzz_below (rng, 169 (uint32_t) (sizeof (le_fuzz_tags) 170 / sizeof (le_fuzz_tags[0])))]; 171 uint32_t plen = fuzz_below (rng, 96); 172 uint32_t decl; 173 unsigned int k; 174 175 /* Every so often declare a length that has nothing to do with the 176 number of bytes actually present. This is the single most 177 productive mutation for these parsers. */ 178 if (fuzz_chance (rng, 3)) 179 { 180 static const uint32_t bogus[] = { 181 0, 1, 0x7FFFFFFF, 0x80000000, 0xFFFFFFFF, 0xFFFF, 0x10000 182 }; 183 184 decl = bogus[fuzz_below (rng, 185 (uint32_t) (sizeof (bogus) 186 / sizeof (bogus[0])))]; 187 } 188 else 189 { 190 decl = plen; 191 } 192 if (big) 193 { 194 fuzz_put_mem (buf, len, cap, tag, 4); 195 fuzz_put_be (buf, len, cap, decl, 4); 196 } 197 else 198 { 199 fuzz_put_be (buf, len, cap, decl, 4); 200 fuzz_put_mem (buf, len, cap, tag, 4); 201 } 202 if (*len + plen > cap) 203 return; 204 for (k = 0; k < plen; k++) 205 buf[(*len)++] = fuzz_byte (rng); 206 } 207 } 208 209 210 /** 211 * Append a ZIP skeleton to @a buf: one local file header, one central 212 * directory entry and an end-of-central-directory record, with 213 * fuzzer-chosen (often inconsistent) offsets and sizes. 214 */ 215 static void 216 le_fuzz_gen_zip (struct fuzz_rng *rng, 217 uint8_t *buf, 218 size_t *len, 219 size_t cap) 220 { 221 static const char *const names[] = { 222 "mimetype", "meta.xml", "content.xml", "docProps/core.xml", 223 "word/document.xml", "xl/workbook.xml", "ppt/presentation.xml", 224 "META-INF/manifest.xml", "a", "" 225 }; 226 const char *name = 227 names[fuzz_below (rng, (uint32_t) (sizeof (names) / sizeof (names[0])))]; 228 size_t nlen = strlen (name); 229 size_t lfh_off = *len; 230 uint32_t payload = fuzz_below (rng, 64); 231 unsigned int k; 232 233 fuzz_put_mem (buf, len, cap, "PK\x03\x04", 4); 234 fuzz_put_le (buf, len, cap, 20, 2); /* version */ 235 fuzz_put_le (buf, len, cap, fuzz_byte (rng), 2); /* flags */ 236 fuzz_put_le (buf, len, cap, fuzz_chance (rng, 2) ? 0 : 8, 2); /* method */ 237 fuzz_put_le (buf, len, cap, 0, 4); /* time/date */ 238 fuzz_put_le (buf, len, cap, fuzz_next (rng), 4); /* crc */ 239 fuzz_put_le (buf, len, cap, 240 fuzz_chance (rng, 3) ? fuzz_next (rng) : payload, 4); 241 fuzz_put_le (buf, len, cap, 242 fuzz_chance (rng, 3) ? fuzz_next (rng) : payload, 4); 243 fuzz_put_le (buf, len, cap, nlen, 2); 244 fuzz_put_le (buf, len, cap, fuzz_chance (rng, 4) ? fuzz_below (rng, 64) : 0, 245 2); 246 fuzz_put_mem (buf, len, cap, name, nlen); 247 if (*len + payload > cap) 248 return; 249 for (k = 0; k < payload; k++) 250 buf[(*len)++] = fuzz_byte (rng); 251 252 { 253 size_t cd_off = *len; 254 255 fuzz_put_mem (buf, len, cap, "PK\x01\x02", 4); 256 fuzz_put_le (buf, len, cap, 20, 2); 257 fuzz_put_le (buf, len, cap, 20, 2); 258 fuzz_put_le (buf, len, cap, 0, 2); 259 fuzz_put_le (buf, len, cap, 0, 2); 260 fuzz_put_le (buf, len, cap, 0, 4); 261 fuzz_put_le (buf, len, cap, 0, 4); 262 fuzz_put_le (buf, len, cap, payload, 4); 263 fuzz_put_le (buf, len, cap, payload, 4); 264 fuzz_put_le (buf, len, cap, nlen, 2); 265 fuzz_put_le (buf, len, cap, 0, 2); /* extra */ 266 fuzz_put_le (buf, len, cap, 267 fuzz_chance (rng, 4) ? fuzz_next (rng) : 0, 2); /* comment */ 268 fuzz_put_le (buf, len, cap, 0, 2); 269 fuzz_put_le (buf, len, cap, 0, 2); 270 fuzz_put_le (buf, len, cap, 0, 4); 271 fuzz_put_le (buf, len, cap, 272 fuzz_chance (rng, 4) ? fuzz_next (rng) : lfh_off, 4); 273 fuzz_put_mem (buf, len, cap, name, nlen); 274 275 fuzz_put_mem (buf, len, cap, "PK\x05\x06", 4); 276 fuzz_put_le (buf, len, cap, 0, 2); 277 fuzz_put_le (buf, len, cap, 0, 2); 278 fuzz_put_le (buf, len, cap, 279 fuzz_chance (rng, 4) ? fuzz_next (rng) : 1, 2); 280 fuzz_put_le (buf, len, cap, 281 fuzz_chance (rng, 4) ? fuzz_next (rng) : 1, 2); 282 fuzz_put_le (buf, len, cap, *len - cd_off, 4); 283 fuzz_put_le (buf, len, cap, 284 fuzz_chance (rng, 4) ? fuzz_next (rng) : cd_off, 4); 285 fuzz_put_le (buf, len, cap, 0, 2); 286 } 287 } 288 289 290 static size_t 291 fuzz_generate (struct fuzz_rng *rng, 292 uint8_t *buf, 293 size_t cap) 294 { 295 size_t len = 0; 296 unsigned int shape = LE_FUZZ_SHAPE; 297 298 if (cap < 64) 299 return 0; 300 /* configuration prefix */ 301 buf[len++] = fuzz_chance (rng, 2) ? 0 : fuzz_byte (rng); 302 buf[len++] = fuzz_chance (rng, 3) ? 0 : (uint8_t) fuzz_below (rng, 0x40); 303 buf[len++] = fuzz_byte (rng); 304 buf[len++] = fuzz_byte (rng); 305 306 /* magic; occasionally corrupted so that the reject path is covered 307 too, but usually intact so that the parser is actually entered */ 308 fuzz_put_mem (buf, &len, cap, le_fuzz_magic, LE_FUZZ_MAGIC_LEN); 309 if ( (LE_FUZZ_MAGIC_LEN > 0) && 310 fuzz_chance (rng, 12) ) 311 buf[LE_FUZZ_EC_PREFIX + fuzz_below (rng, (uint32_t) LE_FUZZ_MAGIC_LEN)] = 312 fuzz_byte (rng); 313 314 switch (shape) 315 { 316 case LE_SHAPE_CHUNK_BE: 317 le_fuzz_gen_chunks (rng, buf, &len, cap, 1); 318 break; 319 case LE_SHAPE_CHUNK_LE: 320 /* RIFF: overall size field, form type, then LE chunks */ 321 fuzz_put_le (buf, &len, cap, 322 fuzz_chance (rng, 3) ? fuzz_next (rng) : cap, 4); 323 fuzz_put_mem (buf, &len, cap, 324 fuzz_chance (rng, 2) ? "WAVE" : "AVI ", 4); 325 le_fuzz_gen_chunks (rng, buf, &len, cap, 0); 326 break; 327 case LE_SHAPE_TEXT: 328 { 329 unsigned int n = 1 + fuzz_below (rng, 40); 330 unsigned int i; 331 332 for (i = 0; i < n; i++) 333 { 334 if (fuzz_chance (rng, 6)) 335 { 336 unsigned int k; 337 unsigned int r = 1 + fuzz_below (rng, 200); 338 339 for (k = 0; (k < r) && (len < cap); k++) 340 buf[len++] = fuzz_byte (rng); 341 } 342 else 343 { 344 fuzz_put_str (buf, &len, cap, 345 le_fuzz_lines[fuzz_below (rng, 346 (uint32_t) 347 (sizeof (le_fuzz_lines) 348 / sizeof (char *)))]); 349 } 350 } 351 break; 352 } 353 case LE_SHAPE_ZIP: 354 le_fuzz_gen_zip (rng, buf, &len, cap); 355 break; 356 case LE_SHAPE_RAW: 357 default: 358 { 359 unsigned int n = fuzz_below (rng, 1024); 360 unsigned int i; 361 362 for (i = 0; (i < n) && (len < cap); i++) 363 { 364 if (fuzz_chance (rng, 16)) 365 { 366 /* a run of one value: length fields and counters love these */ 367 unsigned int k; 368 unsigned int r = 1 + fuzz_below (rng, 64); 369 uint8_t v = fuzz_chance (rng, 2) ? 0xFF : fuzz_byte (rng); 370 371 for (k = 0; (k < r) && (len < cap); k++) 372 buf[len++] = v; 373 } 374 else 375 { 376 buf[len++] = fuzz_byte (rng); 377 } 378 } 379 break; 380 } 381 } 382 return len; 383 } 384 385 386 /* ------------------------------------------------------------------ */ 387 /* Built-in seed corpus */ 388 /* ------------------------------------------------------------------ */ 389 390 /** 391 * The built-in seeds are deliberately minimal: the interesting seeds are 392 * the real files under src/plugins/testdata/, which 393 * contrib/oss-fuzz/make_seed_corpus.sh turns into corpus entries. These 394 * exist so that a bare `make -C src/fuzz check` still starts from 395 * something that gets past the magic-number test. 396 */ 397 #define LE_FUZZ_NSEEDS 6 398 399 static uint8_t le_fuzz_seed_buf[LE_FUZZ_NSEEDS][512]; 400 static size_t le_fuzz_seed_len[LE_FUZZ_NSEEDS]; 401 static int le_fuzz_seeds_ready; 402 403 404 static void 405 le_fuzz_build_seeds (void) 406 { 407 struct fuzz_rng rng; 408 unsigned int i; 409 410 if (le_fuzz_seeds_ready) 411 return; 412 le_fuzz_seeds_ready = 1; 413 for (i = 0; i < LE_FUZZ_NSEEDS; i++) 414 { 415 /* Seed 0 is the bare magic with the production configuration; the 416 others vary the window size, which is what most reliably changes 417 which code path a parser takes. */ 418 size_t len = 0; 419 uint8_t *b = le_fuzz_seed_buf[i]; 420 421 fuzz_rng_seed (&rng, 0x5EED0000u + i); 422 if (0 == i) 423 { 424 /* the bare magic under the production configuration */ 425 len = LE_FUZZ_EC_PREFIX; 426 memset (b, 0, len); 427 fuzz_put_mem (b, &len, sizeof (le_fuzz_seed_buf[i]), 428 le_fuzz_magic, LE_FUZZ_MAGIC_LEN); 429 } 430 else 431 { 432 len = fuzz_generate (&rng, b, sizeof (le_fuzz_seed_buf[i])); 433 if (len < LE_FUZZ_EC_PREFIX) 434 len = LE_FUZZ_EC_PREFIX; 435 /* pin the configuration so that the seeds cover the knobs that 436 matter rather than whatever the PRNG happened to pick */ 437 b[0] = (uint8_t) (i + 1); /* window */ 438 b[1] = (uint8_t) ((4 == i) ? LE_FUZZ_RUN_TWICE : 0); /* faults */ 439 b[2] = (uint8_t) ((5 == i) ? 1 : 0); 440 b[3] = 0; 441 } 442 le_fuzz_seed_len[i] = len; 443 } 444 } 445 446 447 static size_t 448 fuzz_seed_count (void) 449 { 450 le_fuzz_build_seeds (); 451 return LE_FUZZ_NSEEDS; 452 } 453 454 455 static const uint8_t * 456 fuzz_seed_get (size_t idx, 457 size_t *len) 458 { 459 le_fuzz_build_seeds (); 460 *len = le_fuzz_seed_len[idx]; 461 return le_fuzz_seed_buf[idx]; 462 }