libextractor

GNU libextractor
Log | Files | Refs | Submodules | README | LICENSE

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 }