libextractor

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

fuzz_ipc.c (8096B)


      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_ipc.c
     22  * @brief fuzzer for the core's parser of plugin replies
     23  * @author Christian Grothoff
     24  *
     25  * The whole point of libextractor's out-of-process design is that a
     26  * plugin -- and the third-party parser it wraps -- is *untrusted*: it
     27  * runs in a child process precisely so that a memory-safety bug in it
     28  * cannot reach the host application.  That makes
     29  * `EXTRACTOR_IPC_process_reply_()` the security boundary of the design:
     30  * it is the code in the trusted parent that parses a byte stream
     31  * produced by the untrusted child.  A bug here defeats the entire
     32  * sandbox, so it is worth fuzzing on its own even though the function
     33  * is short.
     34  *
     35  * The harness feeds the message stream directly.  The buffer handed in
     36  * is `malloc()`ed at exactly the input length, so any read past the
     37  * declared `size` -- the classic way a length-prefixed parser goes
     38  * wrong -- is an ASAN report rather than a silent read of adjacent
     39  * stack.
     40  *
     41  * Input format: raw message stream (no configuration prefix).
     42  */
     43 
     44 #define FUZZ_HARNESS_NAME "fuzz_ipc"
     45 
     46 #include "fuzz_common.h"
     47 #include "platform.h"
     48 #include "extractor.h"
     49 #include "extractor_plugins.h"
     50 #include "extractor_ipc.h"
     51 
     52 
     53 /**
     54  * Message processor; touches exactly what the parser promises.
     55  */
     56 static void
     57 ipc_proc (void *cls,
     58           struct EXTRACTOR_PluginList *plugin,
     59           enum EXTRACTOR_MetaType meta_type,
     60           enum EXTRACTOR_MetaFormat meta_format,
     61           const char *mime,
     62           const void *value,
     63           size_t value_len)
     64 {
     65   volatile unsigned int sink = 0;
     66   const unsigned char *v = value;
     67   size_t i;
     68 
     69   (void) cls;
     70   (void) plugin;
     71   (void) meta_format;
     72   if (meta_type >= EXTRACTOR_metatype_get_max ())
     73     fuzz_report_finding ("EXTRACTOR_IPC_process_reply_() passed on a meta "
     74                          "type outside [0, EXTRACTOR_metatype_get_max())");
     75   if (NULL != mime)
     76     sink += (unsigned int) strlen (mime);
     77   if ( (NULL == value) &&
     78        (0 != value_len) )
     79     fuzz_report_finding ("EXTRACTOR_IPC_process_reply_() passed on NULL "
     80                          "data with a non-zero length");
     81   for (i = 0; (NULL != v) && (i < value_len); i++)
     82     sink += v[i];
     83   (void) sink;
     84 }
     85 
     86 
     87 int
     88 LLVMFuzzerTestOneInput (const uint8_t *data,
     89                         size_t size)
     90 {
     91   struct EXTRACTOR_PluginList plugin;
     92   uint8_t *buf;
     93   ssize_t ret;
     94 
     95   fuzz_ignore_sigpipe ();
     96   if (0 == size)
     97     return 0;
     98   memset (&plugin, 0, sizeof (plugin));
     99   plugin.short_libname = (char *) "fuzz";
    100   plugin.libname = (char *) "libextractor_fuzz.so";
    101   /* exact-size copy: the redzone starts right after the last byte the
    102      parser was told about */
    103   buf = (uint8_t *) malloc (size);
    104   if (NULL == buf)
    105     abort ();
    106   memcpy (buf, data, size);
    107   ret = EXTRACTOR_IPC_process_reply_ (&plugin,
    108                                       buf,
    109                                       size,
    110                                       &ipc_proc,
    111                                       NULL);
    112   if (ret > (ssize_t) size)
    113     fuzz_report_finding ("EXTRACTOR_IPC_process_reply_() consumed more "
    114                          "bytes than it was given");
    115   free (buf);
    116   return 0;
    117 }
    118 
    119 
    120 /* ------------------------------------------------------------------ */
    121 /* Generator                                                           */
    122 /* ------------------------------------------------------------------ */
    123 
    124 static size_t
    125 fuzz_generate (struct fuzz_rng *rng,
    126                uint8_t *buf,
    127                size_t cap)
    128 {
    129   size_t len = 0;
    130   unsigned int n = 1 + fuzz_below (rng, 12);
    131   unsigned int i;
    132 
    133   if (cap < 128)
    134     return 0;
    135   for (i = 0; i < n; i++)
    136   {
    137     uint32_t kind = fuzz_below (rng, 10);
    138 
    139     if (kind < 2)
    140     {
    141       fuzz_put_le (buf, &len, cap, MESSAGE_DONE, 1);
    142     }
    143     else if (kind < 4)
    144     {
    145       /* struct SeekRequestMessage */
    146       fuzz_put_le (buf, &len, cap, MESSAGE_SEEK, 1);
    147       fuzz_put_le (buf, &len, cap, fuzz_byte (rng), 1);     /* reserved */
    148       fuzz_put_le (buf, &len, cap, fuzz_next (rng), 2);     /* whence */
    149       fuzz_put_le (buf, &len, cap, fuzz_next (rng), 4);     /* req bytes */
    150       fuzz_put_le (buf, &len, cap, fuzz_next (rng), 8);     /* offset */
    151     }
    152     else if (kind < 9)
    153     {
    154       /* struct MetaMessage, with the length fields frequently lying
    155          about how many bytes really follow */
    156       uint16_t mime_len;
    157       uint32_t val_len;
    158       uint16_t decl_mime;
    159       uint32_t decl_val;
    160       unsigned int k;
    161 
    162       mime_len = (uint16_t) fuzz_below (rng, 24);
    163       val_len = fuzz_below (rng, 96);
    164       decl_mime = fuzz_chance (rng, 3)
    165                   ? (uint16_t) fuzz_below (rng, 0x10000)
    166                   : mime_len;
    167       decl_val = fuzz_chance (rng, 3)
    168                  ? (uint32_t) fuzz_next (rng)
    169                  : val_len;
    170       fuzz_put_le (buf, &len, cap, MESSAGE_META, 1);
    171       fuzz_put_le (buf, &len, cap, 0, 1);
    172       fuzz_put_le (buf, &len, cap, fuzz_below (rng, 6), 2);  /* format */
    173       fuzz_put_le (buf, &len, cap,
    174                    fuzz_chance (rng, 3)
    175                    ? fuzz_next (rng)
    176                    : fuzz_below (rng, 200), 2);              /* type */
    177       fuzz_put_le (buf, &len, cap, decl_mime, 2);
    178       fuzz_put_le (buf, &len, cap, decl_val, 4);
    179       for (k = 0; (k < mime_len) && (len < cap); k++)
    180         buf[len++] = (0 == k + 1 - mime_len) && (! fuzz_chance (rng, 3))
    181                      ? 0
    182                      : (uint8_t) (1 + fuzz_below (rng, 254));
    183       for (k = 0; (k < val_len) && (len < cap); k++)
    184         buf[len++] = fuzz_byte (rng);
    185     }
    186     else
    187     {
    188       fuzz_put_le (buf, &len, cap, fuzz_byte (rng), 1);
    189     }
    190   }
    191   return len;
    192 }
    193 
    194 
    195 /* ------------------------------------------------------------------ */
    196 /* Seed corpus                                                         */
    197 /* ------------------------------------------------------------------ */
    198 
    199 struct ipc_seed
    200 {
    201   const char *txt;
    202   size_t len;
    203 };
    204 
    205 #define ISEED(t) { t, sizeof (t) - 1 }
    206 
    207 static const struct ipc_seed ipc_seeds[] = {
    208   /* a bare DONE */
    209   ISEED ("\x03"),
    210   /* SEEK, whence 0, 16 KiB requested, offset 0 */
    211   ISEED ("\x04\x00\x00\x00\x00\x40\x00\x00"
    212          "\x00\x00\x00\x00\x00\x00\x00\x00"),
    213   /* META with a 0-terminated mime type and a 4 byte value */
    214   ISEED ("\x05\x00\x01\x00\x01\x00\x0a\x00\x04\x00\x00\x00"
    215          "text/plain" "abcd"),
    216   /* META with a mime type that is NOT 0-terminated: must be rejected */
    217   ISEED ("\x05\x00\x01\x00\x01\x00\x04\x00\x00\x00\x00\x00" "abcd"),
    218   /* META declaring more value bytes than are present */
    219   ISEED ("\x05\x00\x01\x00\x01\x00\x00\x00\xff\xff\x00\x00" "ab"),
    220   /* META declaring a value larger than MAX_META_DATA */
    221   ISEED ("\x05\x00\x01\x00\x01\x00\x00\x00\x00\x00\x01\x00"),
    222   /* META with an out-of-range meta type */
    223   ISEED ("\x05\x00\x01\x00\xff\xff\x00\x00\x01\x00\x00\x00" "x"),
    224   /* truncated META header */
    225   ISEED ("\x05\x00\x01\x00"),
    226   /* unknown opcode */
    227   ISEED ("\x7f"),
    228   /* DONE followed by a truncated SEEK */
    229   ISEED ("\x03\x04\x00\x00")
    230 };
    231 
    232 
    233 static size_t
    234 fuzz_seed_count (void)
    235 {
    236   return sizeof (ipc_seeds) / sizeof (ipc_seeds[0]);
    237 }
    238 
    239 
    240 static const uint8_t *
    241 fuzz_seed_get (size_t idx,
    242                size_t *len)
    243 {
    244   *len = ipc_seeds[idx].len;
    245   return (const uint8_t *) ipc_seeds[idx].txt;
    246 }