libextractor

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

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 }