fuzz_extract.c (10381B)
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_extract.c 22 * @brief end-to-end fuzzer for the public EXTRACTOR_extract() entry point 23 * @author Christian Grothoff 24 * 25 * Where the per-plugin harnesses each drive one parser through a 26 * fabricated context, this one drives the *whole* library the way an 27 * application does: `EXTRACTOR_plugin_add_defaults()` followed by 28 * `EXTRACTOR_extract()`. That covers everything the per-plugin targets 29 * cannot reach -- 30 * 31 * - the datasource and its transparent gzip/bzip2 decompression, 32 * - plugin discovery, loading and the per-plugin option strings, 33 * - the dispatch loop in extractor.c, including the "plugin asked to 34 * stop" and "application asked to stop" paths, 35 * - the plugins that wrap a third-party library (libjpeg, libtiff, 36 * libgif, exiv2, ...), which the static per-plugin targets do not 37 * link, 38 * 39 * and it does so with the real plugin *set*, so one input is handed to 40 * every plugin that claims it. 41 * 42 * Plugins run in-process (#EXTRACTOR_OPTION_IN_PROCESS): a fuzzer that 43 * forks a child per plugin per input is not a fuzzer, and a crash in a 44 * child would be invisible to the engine. The out-of-process machinery 45 * itself is covered by fuzz_ipc. 46 * 47 * The plugin list is built once and reused: loading ~30 shared objects 48 * costs milliseconds and would otherwise dominate every execution. 49 * 50 * Input format: 51 * 52 * byte 0 behaviour bits: 53 * 0x01 extract from a file on disk rather than from memory 54 * 0x02 stop after the first metadata item 55 * 0x04 run the same input twice through the same plugin 56 * list (plugins must not carry state between files) 57 * byte 1 number of metadata items to accept before asking to stop 58 * byte 2.. the file image 59 */ 60 61 #define FUZZ_HARNESS_NAME "fuzz_extract" 62 63 #include "fuzz_common.h" 64 #include "platform.h" 65 #include "extractor.h" 66 67 #define EX_FROM_FILE 0x01 68 #define EX_STOP_FIRST 0x02 69 #define EX_RUN_TWICE 0x04 70 71 /** 72 * The plugin list, built on first use. 73 */ 74 static struct EXTRACTOR_PluginList *plugins; 75 76 /** 77 * Set once the (possibly failed) attempt to build #plugins was made. 78 */ 79 static int plugins_tried; 80 81 /** 82 * Number of metadata items to accept before returning 1 from the 83 * processor. 84 */ 85 static unsigned int stop_after; 86 87 /** 88 * Number of metadata items seen for the current input. 89 */ 90 static unsigned int seen; 91 92 93 /** 94 * Metadata callback. Touches exactly the bytes that the library 95 * promises are there, so that an over-long length or an unterminated 96 * mime type is an ASAN report rather than a silent pass. 97 */ 98 static int 99 ex_proc (void *cls, 100 const char *plugin_name, 101 enum EXTRACTOR_MetaType type, 102 enum EXTRACTOR_MetaFormat format, 103 const char *data_mime_type, 104 const char *data, 105 size_t data_len) 106 { 107 volatile unsigned int sink = 0; 108 size_t i; 109 110 (void) cls; 111 (void) type; 112 (void) format; 113 if (NULL == plugin_name) 114 fuzz_report_finding ("EXTRACTOR_extract() reported a NULL plugin name"); 115 sink += (unsigned int) strlen (plugin_name); 116 if (NULL != data_mime_type) 117 sink += (unsigned int) strlen (data_mime_type); 118 if ( (NULL == data) && 119 (0 != data_len) ) 120 fuzz_report_finding ("EXTRACTOR_extract() reported NULL data with a " 121 "non-zero length"); 122 for (i = 0; (NULL != data) && (i < data_len); i++) 123 sink += (unsigned char) data[i]; 124 (void) sink; 125 seen++; 126 return (seen > stop_after) ? 1 : 0; 127 } 128 129 130 /** 131 * Build the plugin list once. 132 * 133 * @return the list, NULL if no plugin could be found 134 */ 135 static struct EXTRACTOR_PluginList * 136 get_plugins (void) 137 { 138 if (plugins_tried) 139 return plugins; 140 plugins_tried = 1; 141 plugins = EXTRACTOR_plugin_add_defaults (EXTRACTOR_OPTION_IN_PROCESS); 142 if (NULL == plugins) 143 fprintf (stderr, 144 "%s: no plugins found; set LIBEXTRACTOR_PREFIX to the " 145 "directory holding the built plugins (typically " 146 "src/plugins/.libs)\n", 147 FUZZ_HARNESS_NAME); 148 return plugins; 149 } 150 151 152 int 153 LLVMFuzzerTestOneInput (const uint8_t *data, 154 size_t size) 155 { 156 struct EXTRACTOR_PluginList *pl; 157 unsigned int flags; 158 const char *img; 159 size_t img_len; 160 char tmpl[] = "/tmp/le-fuzz-ex-XXXXXX"; 161 int fd = -1; 162 163 fuzz_ignore_sigpipe (); 164 if (size < 2) 165 return 0; 166 pl = get_plugins (); 167 if (NULL == pl) 168 return 0; 169 flags = data[0]; 170 stop_after = (0 != (flags & EX_STOP_FIRST)) ? 0 : data[1]; 171 img = (const char *) (data + 2); 172 img_len = size - 2; 173 174 if (0 != (flags & EX_FROM_FILE)) 175 { 176 fd = mkstemp (tmpl); 177 if (-1 == fd) 178 return 0; 179 if ( (0 != img_len) && 180 (img_len != (size_t) write (fd, img, img_len)) ) 181 { 182 (void) close (fd); 183 (void) unlink (tmpl); 184 return 0; 185 } 186 (void) close (fd); 187 seen = 0; 188 EXTRACTOR_extract (pl, 189 tmpl, 190 NULL, 0, 191 &ex_proc, NULL); 192 if (0 != (flags & EX_RUN_TWICE)) 193 { 194 seen = 0; 195 EXTRACTOR_extract (pl, 196 tmpl, 197 NULL, 0, 198 &ex_proc, NULL); 199 } 200 (void) unlink (tmpl); 201 } 202 else 203 { 204 seen = 0; 205 EXTRACTOR_extract (pl, 206 NULL, 207 img, img_len, 208 &ex_proc, NULL); 209 if (0 != (flags & EX_RUN_TWICE)) 210 { 211 seen = 0; 212 EXTRACTOR_extract (pl, 213 NULL, 214 img, img_len, 215 &ex_proc, NULL); 216 } 217 } 218 return 0; 219 } 220 221 222 /* ------------------------------------------------------------------ */ 223 /* Generator */ 224 /* ------------------------------------------------------------------ */ 225 226 /** 227 * Magic numbers of every format the shipped plugins claim. The 228 * generator picks one and appends noise: without a recognised magic the 229 * input is rejected by every plugin in microseconds and nothing is 230 * learned. 231 */ 232 static const struct 233 { 234 const char *magic; 235 size_t len; 236 } ex_magics[] = { 237 #define M(s) { s, sizeof (s) - 1 } 238 M ("\x89PNG\r\n\x1a\n"), 239 M ("GIF89a"), 240 M ("GIF87a"), 241 M ("\xff\xd8\xff\xe0\x00\x10JFIF\x00"), 242 M ("\xff\xd8\xff\xe1"), 243 M ("II\x2a\x00\x08\x00\x00\x00"), 244 M ("MM\x00\x2a\x00\x00\x00\x08"), 245 M ("RIFF....WAVE"), 246 M ("RIFF....AVI "), 247 M ("\x7f" "ELF\x02\x01\x01\x00"), 248 M ("%!PS-Adobe-3.0\n"), 249 M ("{\\rtf1\\ansi"), 250 M ("NESM\x1a\x01"), 251 M ("NSFE"), 252 M ("PSID\x00\x02"), 253 M ("RSID\x00\x02"), 254 M ("SCRM"), 255 M ("IMPM"), 256 M ("Extended Module: "), 257 M ("MThd\x00\x00\x00\x06"), 258 M ("fLaC"), 259 M ("OggS\x00\x02"), 260 M ("PK\x03\x04"), 261 M ("\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1"), 262 M ("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03"), 263 M ("BZh9\x31\x41\x59\x26\x53\x59"), 264 M ("!<arch>\ndebian-binary "), 265 M ("\xed\xab\xee\xdb"), 266 M ("\x00\x05\x16\x00\x00\x02\x00\x00"), 267 M ("\xf7\x02"), 268 M ("\x00\x00\x00\x08moov"), 269 M (".RMF\x00\x00\x00\x12"), 270 M (".TH FUZZ 1\n.SH NAME\n"), 271 M ("<!DOCTYPE html><html><head><title>x</title>"), 272 M ("\x00\x00\x01\xba") 273 #undef M 274 }; 275 276 277 static size_t 278 fuzz_generate (struct fuzz_rng *rng, 279 uint8_t *buf, 280 size_t cap) 281 { 282 size_t len = 0; 283 unsigned int idx; 284 unsigned int n; 285 unsigned int i; 286 287 if (cap < 64) 288 return 0; 289 buf[len++] = fuzz_chance (rng, 3) 290 ? (uint8_t) fuzz_below (rng, 8) 291 : 0; 292 buf[len++] = fuzz_byte (rng); 293 idx = fuzz_below (rng, 294 (uint32_t) (sizeof (ex_magics) / sizeof (ex_magics[0]))); 295 fuzz_put_mem (buf, &len, cap, ex_magics[idx].magic, ex_magics[idx].len); 296 n = fuzz_below (rng, 2048); 297 for (i = 0; (i < n) && (len < cap); i++) 298 { 299 if (fuzz_chance (rng, 24)) 300 { 301 unsigned int k; 302 unsigned int r = 1 + fuzz_below (rng, 64); 303 uint8_t v = fuzz_chance (rng, 2) ? 0xFF : fuzz_byte (rng); 304 305 for (k = 0; (k < r) && (len < cap); k++) 306 buf[len++] = v; 307 } 308 else if (fuzz_chance (rng, 16)) 309 { 310 unsigned int j = 311 fuzz_below (rng, (uint32_t) (sizeof (ex_magics) 312 / sizeof (ex_magics[0]))); 313 314 fuzz_put_mem (buf, &len, cap, ex_magics[j].magic, ex_magics[j].len); 315 } 316 else 317 { 318 buf[len++] = fuzz_byte (rng); 319 } 320 } 321 return len; 322 } 323 324 325 /* ------------------------------------------------------------------ */ 326 /* Seed corpus */ 327 /* ------------------------------------------------------------------ */ 328 329 /** 330 * One seed per magic number: the bare header with the default 331 * configuration. The substantial seeds come from 332 * src/plugins/testdata/ via contrib/oss-fuzz/make_seed_corpus.sh. 333 */ 334 #define EX_NSEEDS (sizeof (ex_magics) / sizeof (ex_magics[0])) 335 336 static uint8_t ex_seed_buf[EX_NSEEDS][64]; 337 static size_t ex_seed_len[EX_NSEEDS]; 338 static int ex_seeds_ready; 339 340 341 static void 342 ex_build_seeds (void) 343 { 344 size_t i; 345 346 if (ex_seeds_ready) 347 return; 348 ex_seeds_ready = 1; 349 for (i = 0; i < EX_NSEEDS; i++) 350 { 351 size_t len = 0; 352 353 ex_seed_buf[i][len++] = 0; 354 ex_seed_buf[i][len++] = 0xFF; 355 fuzz_put_mem (ex_seed_buf[i], &len, sizeof (ex_seed_buf[i]), 356 ex_magics[i].magic, ex_magics[i].len); 357 ex_seed_len[i] = len; 358 } 359 } 360 361 362 static size_t 363 fuzz_seed_count (void) 364 { 365 ex_build_seeds (); 366 return EX_NSEEDS; 367 } 368 369 370 static const uint8_t * 371 fuzz_seed_get (size_t idx, 372 size_t *len) 373 { 374 ex_build_seeds (); 375 *len = ex_seed_len[idx]; 376 return ex_seed_buf[idx]; 377 }