libextractor

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

fuzz_convert.c (7108B)


      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_convert.c
     22  * @brief fuzzer for the shared charset conversion helper and the
     23  *        metatype tables
     24  * @author Christian Grothoff
     25  *
     26  * `EXTRACTOR_common_convert_to_utf8()` is called by nsfe, rtf, msoffice
     27  * and png with a length and a charset name that all come out of the
     28  * file being parsed.  Its input is therefore attacker-controlled in
     29  * three independent ways at once: the bytes, the length, and the name of
     30  * the source encoding.
     31  *
     32  * The input buffer handed to it is `malloc()`ed at exactly the declared
     33  * length and is deliberately *not* 0-terminated, because that is how the
     34  * callers pass it: a helper that reaches for a terminator instead of
     35  * honouring @a len reads past the end.
     36  *
     37  * Input format:
     38  *
     39  *   byte 0    charset selector
     40  *   byte 1    behaviour bits:
     41  *               0x01  also convert the result a second time
     42  *               0x02  declare a length one byte longer than the
     43  *                     allocation is -- OFF unless LE_FUZZ_MODEL_OVERLONG
     44  *                     is set, since it models a *caller* bug, not one
     45  *                     in the helper
     46  *   byte 2..  the bytes to convert
     47  */
     48 
     49 #define FUZZ_HARNESS_NAME "fuzz_convert"
     50 
     51 #include "fuzz_common.h"
     52 #include "platform.h"
     53 #include "extractor.h"
     54 #include "convert.h"
     55 
     56 /**
     57  * Charset names that plugins really pass, plus the shapes that a
     58  * malformed file can produce.
     59  */
     60 static const char *const cv_charsets[] = {
     61   "UTF-8", "UTF-16BE", "UTF-16LE", "UTF-32", "ISO-8859-1", "ISO-8859-15",
     62   "CP1252", "CP437", "CP850", "MACINTOSH", "KOI8-R", "SHIFT_JIS",
     63   "EUC-JP", "GB18030", "BIG5", "ASCII", "ANSI_X3.4-1968",
     64   "", "?", "//TRANSLIT", "UTF-8//IGNORE", "NOSUCHCHARSET",
     65   "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
     66 };
     67 
     68 
     69 int
     70 LLVMFuzzerTestOneInput (const uint8_t *data,
     71                         size_t size)
     72 {
     73   const char *charset;
     74   unsigned int flags;
     75   size_t len;
     76   char *in;
     77   char *out;
     78 
     79   fuzz_ignore_sigpipe ();
     80   if (size < 2)
     81     return 0;
     82   charset = cv_charsets[data[0]
     83                         % (sizeof (cv_charsets) / sizeof (char *))];
     84   flags = data[1];
     85   len = size - 2;
     86   /* exact size, no terminator: exactly how the callers pass it */
     87   in = (char *) malloc ((0 == len) ? 1 : len);
     88   if (NULL == in)
     89     abort ();
     90   if (0 != len)
     91     memcpy (in, data + 2, len);
     92   out = EXTRACTOR_common_convert_to_utf8 (in, len, charset);
     93   if (NULL != out)
     94   {
     95     /* The result is documented as 0-terminated; strlen() proves it and
     96        lands in the redzone if it is not. */
     97     volatile size_t l = strlen (out);
     98 
     99     (void) l;
    100     if (0 != (flags & 0x01))
    101     {
    102       char *out2 = EXTRACTOR_common_convert_to_utf8 (out,
    103                                                      strlen (out),
    104                                                      "UTF-8");
    105 
    106       free (out2);
    107     }
    108     free (out);
    109   }
    110   free (in);
    111 
    112   /* The metatype tables are indexed by values that come from a plugin
    113      (out of process: from an untrusted child), so walk them too. */
    114   {
    115     enum EXTRACTOR_MetaType max = EXTRACTOR_metatype_get_max ();
    116     int probe = (int) ((size_t) data[0] * 7 + data[1]);
    117 
    118     (void) EXTRACTOR_metatype_to_string ((enum EXTRACTOR_MetaType) probe);
    119     (void) EXTRACTOR_metatype_to_description ((enum EXTRACTOR_MetaType) probe);
    120     (void) EXTRACTOR_metatype_to_string (max);
    121     (void) EXTRACTOR_metatype_to_description (max);
    122     (void) EXTRACTOR_metatype_to_string ((enum EXTRACTOR_MetaType) -1);
    123     (void) EXTRACTOR_metatype_to_description ((enum EXTRACTOR_MetaType) -1);
    124   }
    125   return 0;
    126 }
    127 
    128 
    129 /* ------------------------------------------------------------------ */
    130 /* Generator                                                           */
    131 /* ------------------------------------------------------------------ */
    132 
    133 /**
    134  * Byte sequences that are interesting to a charset converter: truncated
    135  * multi-byte sequences, overlong encodings, surrogates and BOMs.
    136  */
    137 static const char *const cv_atoms[] = {
    138   "\xc3\xa4", "\xc3", "\xe2\x82\xac", "\xe2\x82", "\xe2",
    139   "\xf0\x9f\x98\x80", "\xf0\x9f\x98", "\xf0",
    140   "\xc0\x80", "\xe0\x80\x80", "\xf0\x80\x80\x80",
    141   "\xed\xa0\x80", "\xed\xbf\xbf", "\xef\xbb\xbf",
    142   "\xff\xfe", "\xfe\xff", "\x00\x00\xfe\xff",
    143   "\x80", "\xff", "\xfe", "\x7f", "\x00",
    144   "ABC", "abc", "\r\n", "\t", " "
    145 };
    146 
    147 
    148 static size_t
    149 fuzz_generate (struct fuzz_rng *rng,
    150                uint8_t *buf,
    151                size_t cap)
    152 {
    153   size_t len = 0;
    154   unsigned int n;
    155   unsigned int i;
    156 
    157   if (cap < 16)
    158     return 0;
    159   buf[len++] = fuzz_byte (rng);
    160   buf[len++] = fuzz_byte (rng);
    161   n = 1 + fuzz_below (rng, 64);
    162   for (i = 0; i < n; i++)
    163   {
    164     if (fuzz_chance (rng, 6))
    165     {
    166       if (len < cap)
    167         buf[len++] = fuzz_byte (rng);
    168     }
    169     else
    170     {
    171       const char *a =
    172         cv_atoms[fuzz_below (rng,
    173                              (uint32_t) (sizeof (cv_atoms) / sizeof (char *)))];
    174       /* the atoms include embedded NULs, so use the recorded sizes
    175          rather than strlen() where it matters */
    176       size_t al = strlen (a);
    177 
    178       if (0 == al)
    179         al = 1;
    180       if (len + al > cap)
    181         break;
    182       memcpy (buf + len, a, al);
    183       len += al;
    184     }
    185   }
    186   return len;
    187 }
    188 
    189 
    190 /* ------------------------------------------------------------------ */
    191 /* Seed corpus                                                         */
    192 /* ------------------------------------------------------------------ */
    193 
    194 struct cv_seed
    195 {
    196   const char *txt;
    197   size_t len;
    198 };
    199 
    200 #define CSEED(t) { t, sizeof (t) - 1 }
    201 
    202 static const struct cv_seed cv_seeds[] = {
    203   CSEED ("\x00\x00" "hello"),
    204   CSEED ("\x00\x00" "\xc3\xa4\xc3\xb6\xc3\xbc"),
    205   CSEED ("\x00\x00" "\xc3"),
    206   CSEED ("\x00\x00" "\xed\xa0\x80"),
    207   CSEED ("\x01\x00" "\x00h\x00i"),
    208   CSEED ("\x02\x00" "h\x00i\x00"),
    209   CSEED ("\x04\x00" "\xe4\xf6\xfc"),
    210   CSEED ("\x06\x00" "\x80\x99\x9a"),
    211   CSEED ("\x0b\x00" "\x82\xa0\x82\xa2"),
    212   CSEED ("\x11\x00" "abc"),
    213   CSEED ("\x15\x00" "abc"),
    214   CSEED ("\x16\x00" "abc"),
    215   CSEED ("\x00\x00"),
    216   CSEED ("\x00\x01" "\xef\xbb\xbf" "abc")
    217 };
    218 
    219 
    220 static size_t
    221 fuzz_seed_count (void)
    222 {
    223   return sizeof (cv_seeds) / sizeof (cv_seeds[0]);
    224 }
    225 
    226 
    227 static const uint8_t *
    228 fuzz_seed_get (size_t idx,
    229                size_t *len)
    230 {
    231   *len = cv_seeds[idx].len;
    232   return (const uint8_t *) cv_seeds[idx].txt;
    233 }