libextractor

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

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 }