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 }