libextractor

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

fuzz_datasource.c (12468B)


      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_datasource.c
     22  * @brief fuzzer for the core datasource and its transparent decompression
     23  * @author Christian Grothoff
     24  *
     25  * `src/main/extractor_datasource.c` is the first code in the library
     26  * that touches attacker-controlled bytes, and it does so *before* any
     27  * plugin is consulted: it sniffs for gzip and bzip2 wrappers and
     28  * transparently decompresses them, maintaining its own seekable view
     29  * over a stream that is not seekable underneath.  The gzip header
     30  * walker (FEXTRA / FNAME / FCOMMENT / FHCRC) and the "seek backwards in
     31  * a decompressed stream" path are the interesting parts.
     32  *
     33  * Input format:
     34  *
     35  *   byte 0    read chunk size selector
     36  *   byte 1    behaviour bits:
     37  *               0x01  create from a file on disk instead of a buffer
     38  *                     (exercises the mmap/read path in the datasource)
     39  *               0x02  call get_size_() with force=1 up front, which is
     40  *                     what makes the datasource decompress eagerly
     41  *               0x04  perform the seek script backwards
     42  *               0x08  stop at the first read error instead of continuing
     43  *   byte 2    number of operations in the seek/read script
     44  *   byte 3    seed selecting the individual operations
     45  *   byte 4..  the file image
     46  */
     47 
     48 #define FUZZ_HARNESS_NAME "fuzz_datasource"
     49 
     50 #include "fuzz_common.h"
     51 #include "platform.h"
     52 #include "extractor.h"
     53 #include "extractor_datasource.h"
     54 
     55 #define DS_FROM_FILE   0x01
     56 #define DS_FORCE_SIZE  0x02
     57 #define DS_REVERSE     0x04
     58 #define DS_STOP_ERR    0x08
     59 
     60 /**
     61  * Same window sizes as the plugin harness; see fuzz_ec.h.
     62  */
     63 static const size_t ds_chunks[] = {
     64   16 * 1024, 1, 2, 3, 7, 16, 64, 255, 256, 1024, 4096, 8192, 16383,
     65   32768, 65536
     66 };
     67 
     68 /**
     69  * Upper bound on the number of script operations, so that a
     70  * pathological input cannot turn one execution into a fuzzing session.
     71  */
     72 #define MAX_OPS 96
     73 
     74 
     75 /**
     76  * Metadata callback; the datasource reports the compression type it
     77  * detected through it.
     78  */
     79 static int
     80 ds_proc (void *cls,
     81          const char *plugin_name,
     82          enum EXTRACTOR_MetaType type,
     83          enum EXTRACTOR_MetaFormat format,
     84          const char *data_mime_type,
     85          const char *data,
     86          size_t data_len)
     87 {
     88   volatile unsigned int sink = 0;
     89   size_t i;
     90 
     91   (void) cls;
     92   (void) type;
     93   (void) format;
     94   if (NULL == plugin_name)
     95     fuzz_report_finding ("datasource reported a NULL plugin name");
     96   sink += (unsigned int) strlen (plugin_name);
     97   if (NULL != data_mime_type)
     98     sink += (unsigned int) strlen (data_mime_type);
     99   for (i = 0; (NULL != data) && (i < data_len); i++)
    100     sink += (unsigned char) data[i];
    101   (void) sink;
    102   return 0;
    103 }
    104 
    105 
    106 int
    107 LLVMFuzzerTestOneInput (const uint8_t *data,
    108                         size_t size)
    109 {
    110   struct EXTRACTOR_Datasource *ds;
    111   struct fuzz_rng rng;
    112   const uint8_t *img;
    113   size_t img_len;
    114   size_t chunk;
    115   unsigned int flags;
    116   unsigned int ops;
    117   unsigned int i;
    118   int64_t fsize;
    119   char tmpl[] = "/tmp/le-fuzz-ds-XXXXXX";
    120   int fd = -1;
    121 
    122   fuzz_ignore_sigpipe ();
    123   if (size < 4)
    124     return 0;
    125   chunk = ds_chunks[data[0] % (sizeof (ds_chunks) / sizeof (ds_chunks[0]))];
    126   flags = data[1];
    127   ops = data[2] % MAX_OPS;
    128   fuzz_rng_seed (&rng, data[3]);
    129   img = data + 4;
    130   img_len = size - 4;
    131 
    132   if (0 != (flags & DS_FROM_FILE))
    133   {
    134     fd = mkstemp (tmpl);
    135     if (-1 == fd)
    136       return 0;
    137     if ( (0 != img_len) &&
    138          (img_len != (size_t) write (fd, img, img_len)) )
    139     {
    140       (void) close (fd);
    141       (void) unlink (tmpl);
    142       return 0;
    143     }
    144     (void) close (fd);
    145     ds = EXTRACTOR_datasource_create_from_file_ (tmpl,
    146                                                  &ds_proc,
    147                                                  NULL);
    148   }
    149   else
    150   {
    151     ds = EXTRACTOR_datasource_create_from_buffer_ ((const char *) img,
    152                                                    img_len,
    153                                                    &ds_proc,
    154                                                    NULL);
    155   }
    156   if (NULL == ds)
    157   {
    158     if (0 != (flags & DS_FROM_FILE))
    159       (void) unlink (tmpl);
    160     return 0;
    161   }
    162   fsize = EXTRACTOR_datasource_get_size_ (ds,
    163                                           (0 != (flags & DS_FORCE_SIZE)));
    164   for (i = 0; i < ops; i++)
    165   {
    166     uint32_t op = fuzz_below (&rng, 10);
    167 
    168     if (op < 5)
    169     {
    170       /* Exact-size destination: the datasource is told how many bytes
    171          it may write, so writing more is an overflow in the caller's
    172          buffer -- which in production is InProcessContext::buf. */
    173       size_t want = (op < 4) ? chunk : (1 + fuzz_below (&rng, 4096));
    174       unsigned char *buf = (unsigned char *) malloc ((0 == want) ? 1 : want);
    175       ssize_t got;
    176 
    177       if (NULL == buf)
    178         abort ();
    179       got = EXTRACTOR_datasource_read_ (ds, buf, want);
    180       if (got > (ssize_t) want)
    181       {
    182         free (buf);
    183         fuzz_report_finding ("EXTRACTOR_datasource_read_() reported more "
    184                              "bytes than the buffer size it was given");
    185       }
    186       free (buf);
    187       if ( (0 > got) &&
    188            (0 != (flags & DS_STOP_ERR)) )
    189         break;
    190     }
    191     else if (op < 9)
    192     {
    193       static const int whences[] = { SEEK_SET, SEEK_CUR, SEEK_END };
    194       int whence = whences[fuzz_below (&rng, 3)];
    195       int64_t pos;
    196 
    197       switch (fuzz_below (&rng, 6))
    198       {
    199       case 0:
    200         pos = 0;
    201         break;
    202       case 1:
    203         pos = (int64_t) fuzz_below (&rng, 4096);
    204         break;
    205       case 2:
    206         pos = -(int64_t) fuzz_below (&rng, 4096);
    207         break;
    208       case 3:
    209         pos = (0 > fsize) ? 0 : fsize;
    210         break;
    211       case 4:
    212         pos = INT64_MAX;
    213         break;
    214       default:
    215         pos = INT64_MIN;
    216         break;
    217       }
    218       if ( (0 != (flags & DS_REVERSE)) &&
    219            (INT64_MIN != pos) )
    220         pos = -pos;
    221       (void) EXTRACTOR_datasource_seek_ (ds, pos, whence);
    222     }
    223     else
    224     {
    225       (void) EXTRACTOR_datasource_get_size_ (ds, (int) fuzz_below (&rng, 2));
    226     }
    227   }
    228   EXTRACTOR_datasource_destroy_ (ds);
    229   if (0 != (flags & DS_FROM_FILE))
    230     (void) unlink (tmpl);
    231   return 0;
    232 }
    233 
    234 
    235 /* ------------------------------------------------------------------ */
    236 /* Generator                                                           */
    237 /* ------------------------------------------------------------------ */
    238 
    239 /**
    240  * Build a gzip member by hand.  The point is the *header*: the optional
    241  * FEXTRA / FNAME / FCOMMENT / FHCRC fields are variable length and are
    242  * skipped by a hand-written walker in extractor_datasource.c, which is
    243  * where a length that runs off the end of the buffer bites.
    244  */
    245 static size_t
    246 gen_gzip (struct fuzz_rng *rng,
    247           uint8_t *buf,
    248           size_t len,
    249           size_t cap)
    250 {
    251   uint8_t flg = 0;
    252   unsigned int k;
    253 
    254   if (fuzz_chance (rng, 2))
    255     flg |= 0x04;                /* FEXTRA */
    256   if (fuzz_chance (rng, 2))
    257     flg |= 0x08;                /* FNAME */
    258   if (fuzz_chance (rng, 2))
    259     flg |= 0x10;                /* FCOMMENT */
    260   if (fuzz_chance (rng, 3))
    261     flg |= 0x02;                /* FHCRC */
    262   fuzz_put_mem (buf, &len, cap, "\x1f\x8b\x08", 3);
    263   fuzz_put_le (buf, &len, cap, flg, 1);
    264   fuzz_put_le (buf, &len, cap, 0, 4);                   /* mtime */
    265   fuzz_put_le (buf, &len, cap, 0, 1);                   /* xfl */
    266   fuzz_put_le (buf, &len, cap, 3, 1);                   /* os */
    267   if (0 != (flg & 0x04))
    268   {
    269     /* the declared extra length is what matters; usually make it lie */
    270     uint16_t xlen = fuzz_chance (rng, 2)
    271                     ? (uint16_t) fuzz_below (rng, 0x10000)
    272                     : (uint16_t) fuzz_below (rng, 32);
    273 
    274     fuzz_put_le (buf, &len, cap, xlen, 2);
    275     if (! fuzz_chance (rng, 3))
    276       for (k = 0; (k < xlen) && (len < cap); k++)
    277         buf[len++] = fuzz_byte (rng);
    278   }
    279   if (0 != (flg & 0x08))
    280   {
    281     unsigned int n = fuzz_below (rng, 64);
    282 
    283     for (k = 0; (k < n) && (len < cap); k++)
    284       buf[len++] = (uint8_t) (1 + fuzz_below (rng, 254));
    285     /* half the time forget the terminator */
    286     if ( (! fuzz_chance (rng, 2)) && (len < cap) )
    287       buf[len++] = 0;
    288   }
    289   if (0 != (flg & 0x10))
    290   {
    291     unsigned int n = fuzz_below (rng, 64);
    292 
    293     for (k = 0; (k < n) && (len < cap); k++)
    294       buf[len++] = (uint8_t) (1 + fuzz_below (rng, 254));
    295     if ( (! fuzz_chance (rng, 2)) && (len < cap) )
    296       buf[len++] = 0;
    297   }
    298   if (0 != (flg & 0x02))
    299     fuzz_put_le (buf, &len, cap, fuzz_next (rng), 2);
    300   /* deflate payload: mostly garbage, occasionally a valid stored block */
    301   if (fuzz_chance (rng, 3))
    302   {
    303     uint16_t n = (uint16_t) fuzz_below (rng, 64);
    304 
    305     fuzz_put_le (buf, &len, cap, 0x01, 1);              /* final, stored */
    306     fuzz_put_le (buf, &len, cap, n, 2);
    307     fuzz_put_le (buf, &len, cap, (uint16_t) ~n, 2);
    308     for (k = 0; (k < n) && (len < cap); k++)
    309       buf[len++] = fuzz_byte (rng);
    310   }
    311   else
    312   {
    313     unsigned int n = fuzz_below (rng, 256);
    314 
    315     for (k = 0; (k < n) && (len < cap); k++)
    316       buf[len++] = fuzz_byte (rng);
    317   }
    318   fuzz_put_le (buf, &len, cap, fuzz_next (rng), 4);     /* crc32 */
    319   fuzz_put_le (buf, &len, cap,
    320                fuzz_chance (rng, 2) ? fuzz_next (rng) : 0, 4); /* isize */
    321   return len;
    322 }
    323 
    324 
    325 static size_t
    326 fuzz_generate (struct fuzz_rng *rng,
    327                uint8_t *buf,
    328                size_t cap)
    329 {
    330   size_t len = 0;
    331   uint32_t kind;
    332 
    333   if (cap < 128)
    334     return 0;
    335   buf[len++] = fuzz_byte (rng);
    336   buf[len++] = (uint8_t) fuzz_below (rng, 16);
    337   buf[len++] = fuzz_byte (rng);
    338   buf[len++] = fuzz_byte (rng);
    339 
    340   kind = fuzz_below (rng, 10);
    341   if (kind < 5)
    342   {
    343     len = gen_gzip (rng, buf, len, cap);
    344   }
    345   else if (kind < 8)
    346   {
    347     /* bzip2: the library rejects almost everything, so the value here is
    348        in the header-sniffing code rather than in libbz2 */
    349     unsigned int n = fuzz_below (rng, 512);
    350     unsigned int k;
    351 
    352     fuzz_put_mem (buf, &len, cap, "BZh", 3);
    353     fuzz_put_le (buf, &len, cap,
    354                  (uint8_t) ('0' + 1 + fuzz_below (rng, 9)), 1);
    355     fuzz_put_mem (buf, &len, cap, "\x31\x41\x59\x26\x53\x59", 6);
    356     for (k = 0; (k < n) && (len < cap); k++)
    357       buf[len++] = fuzz_byte (rng);
    358   }
    359   else
    360   {
    361     /* plain data, sometimes with a truncated compression magic so that
    362        the sniffing code has to decide on very few bytes */
    363     unsigned int n = fuzz_below (rng, 1024);
    364     unsigned int k;
    365 
    366     if (fuzz_chance (rng, 2))
    367       fuzz_put_mem (buf, &len, cap,
    368                     fuzz_chance (rng, 2) ? "\x1f\x8b" : "BZ", 2);
    369     for (k = 0; (k < n) && (len < cap); k++)
    370       buf[len++] = fuzz_byte (rng);
    371   }
    372   return len;
    373 }
    374 
    375 
    376 /* ------------------------------------------------------------------ */
    377 /* Seed corpus                                                         */
    378 /* ------------------------------------------------------------------ */
    379 
    380 #define DS_NSEEDS 10
    381 
    382 static uint8_t ds_seed_buf[DS_NSEEDS][1024];
    383 static size_t ds_seed_len[DS_NSEEDS];
    384 static int ds_seeds_ready;
    385 
    386 
    387 static void
    388 ds_build_seeds (void)
    389 {
    390   struct fuzz_rng rng;
    391   unsigned int i;
    392 
    393   if (ds_seeds_ready)
    394     return;
    395   ds_seeds_ready = 1;
    396   for (i = 0; i < DS_NSEEDS; i++)
    397   {
    398     fuzz_rng_seed (&rng, 0xD5000u + i);
    399     ds_seed_len[i] = fuzz_generate (&rng,
    400                                     ds_seed_buf[i],
    401                                     sizeof (ds_seed_buf[i]));
    402     if (ds_seed_len[i] >= 4)
    403     {
    404       ds_seed_buf[i][0] = (uint8_t) i;
    405       ds_seed_buf[i][1] = (uint8_t) (i % 16u);
    406       ds_seed_buf[i][2] = 32;
    407     }
    408   }
    409 }
    410 
    411 
    412 static size_t
    413 fuzz_seed_count (void)
    414 {
    415   ds_build_seeds ();
    416   return DS_NSEEDS;
    417 }
    418 
    419 
    420 static const uint8_t *
    421 fuzz_seed_get (size_t idx,
    422                size_t *len)
    423 {
    424   ds_build_seeds ();
    425   *len = ds_seed_len[idx];
    426   return ds_seed_buf[idx];
    427 }