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 }