/src/freeradius-server/src/bin/fuzzer_der.c
Line | Count | Source |
1 | | /* |
2 | | * This program is free software; you can redistribute it and/or modify |
3 | | * it under the terms of the GNU General Public License as published by |
4 | | * the Free Software Foundation; either version 2 of the License, or |
5 | | * (at your option) any later version. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** |
18 | | * $Id: deb784557e8a64bdd034971ce6afe9f8783f0042 $ |
19 | | * |
20 | | * @file src/bin/fuzzer.c |
21 | | * @brief Functions to fuzz protocol decoding |
22 | | * |
23 | | * @copyright 2019 Network RADIUS SAS (legal@networkradius.com) |
24 | | */ |
25 | | RCSID("$Id: deb784557e8a64bdd034971ce6afe9f8783f0042 $") |
26 | | |
27 | | #include <freeradius-devel/util/dl.h> |
28 | | #include <freeradius-devel/util/conf.h> |
29 | | #include <freeradius-devel/util/dict.h> |
30 | | #include <freeradius-devel/util/proto.h> |
31 | | #include <freeradius-devel/util/atexit.h> |
32 | | #include <freeradius-devel/util/syserror.h> |
33 | | #include <freeradius-devel/util/strerror.h> |
34 | | #include <freeradius-devel/io/test_point.h> |
35 | | |
36 | | /* |
37 | | * Run from the source directory via: |
38 | | * |
39 | | * ./build/make/jlibtool --mode=execute ./build/bin/local/fuzzer_radius -D share/dictionary /path/to/corpus/directory/ |
40 | | */ |
41 | | |
42 | | static bool init = false; |
43 | | static dl_t *dl = NULL; |
44 | | static dl_loader_t *dl_loader; |
45 | | static fr_dict_protocol_t *dl_proto; |
46 | | static TALLOC_CTX *autofree = NULL; |
47 | | static bool do_encode = false; |
48 | | |
49 | | static fr_dict_t *dict = NULL; |
50 | | |
51 | | extern fr_test_point_proto_decode_t der_tp_decode_proto; |
52 | | extern fr_test_point_proto_encode_t der_tp_encode_proto; |
53 | | |
54 | | int LLVMFuzzerInitialize(int *argc, char ***argv); |
55 | | int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len); |
56 | | |
57 | | static void exitHandler(void) |
58 | 4 | { |
59 | 4 | if (dl_proto && dl_proto->free) dl_proto->free(); |
60 | | |
61 | 4 | fr_dict_free(&dict, __FILE__); |
62 | | |
63 | 4 | if (dl && dl->handle) { |
64 | 0 | dlclose(dl->handle); |
65 | 0 | dl->handle = NULL; |
66 | 0 | } |
67 | 4 | talloc_free(dl_loader); |
68 | | |
69 | 4 | talloc_free(autofree); |
70 | | |
71 | | /* |
72 | | * Ensure our atexit handlers run before any other |
73 | | * atexit handlers registered by third party libraries. |
74 | | */ |
75 | 4 | fr_atexit_global_trigger_all(); |
76 | 4 | } |
77 | | |
78 | | static inline |
79 | | fr_dict_protocol_t *fuzzer_dict_init(void *dl_handle, char const *proto) |
80 | 2 | { |
81 | 2 | char buffer[256]; |
82 | 2 | fr_dict_protocol_t *our_dl_proto; |
83 | | |
84 | 2 | snprintf(buffer, sizeof(buffer), "libfreeradius_%s_dict_protocol", proto); |
85 | | |
86 | 2 | our_dl_proto = dlsym(dl_handle, buffer); |
87 | 2 | if (our_dl_proto && our_dl_proto->init() && (our_dl_proto->init() < 0)) { |
88 | 0 | fr_perror("fuzzer: Failed initializing library %s", buffer); |
89 | 0 | fr_exit_now(EXIT_FAILURE); |
90 | 0 | } |
91 | | |
92 | 2 | return our_dl_proto; |
93 | 2 | } |
94 | | |
95 | | int LLVMFuzzerInitialize(int *argc, char ***argv) |
96 | 38 | { |
97 | 38 | char const *lib_dir = getenv("FR_LIBRARY_PATH"); |
98 | 38 | char const *proto = getenv("FR_LIBRARY_FUZZ_PROTOCOL"); |
99 | 38 | char const *dict_dir = getenv("FR_DICTIONARY_DIR"); |
100 | 38 | char const *debug_lvl_str = getenv("FR_DEBUG_LVL"); |
101 | 38 | char const *panic_action = getenv("PANIC_ACTION"); |
102 | 38 | char const *p; |
103 | 38 | #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
104 | 38 | char *dict_dir_to_free = NULL; |
105 | 38 | char *lib_dir_to_free = NULL; |
106 | 38 | #endif |
107 | | |
108 | 38 | if (!argc || !argv || !*argv) return -1; /* shut up clang scan */ |
109 | | |
110 | 38 | if (debug_lvl_str) { |
111 | 0 | fr_debug_lvl = atoi(debug_lvl_str); |
112 | |
|
113 | 0 | if (fr_debug_lvl) fr_time_start(); |
114 | 0 | } |
115 | | |
116 | | /* |
117 | | * Setup atexit handlers to free any thread local |
118 | | * memory on exit |
119 | | */ |
120 | 38 | fr_atexit_global_setup(); |
121 | | |
122 | | /* |
123 | | * Initialise the talloc fault handlers. |
124 | | */ |
125 | 38 | fr_talloc_fault_setup(); |
126 | | |
127 | | /* |
128 | | * Initialise the error stack _before_ we run any |
129 | | * tests so there's no chance of the memory |
130 | | * appearing as a leak the first time an error |
131 | | * is generated. |
132 | | */ |
133 | 38 | fr_strerror_const("fuzz"); /* allocate the pools */ |
134 | 38 | fr_strerror_clear(); /* clears the message, leaves the pools */ |
135 | | |
136 | | /* |
137 | | * Setup our own internal atexit handler |
138 | | */ |
139 | 38 | if (atexit(exitHandler)) { |
140 | 0 | fr_perror("fuzzer: Failed to register exit handler: %s", fr_syserror(errno)); |
141 | 0 | fr_exit_now(EXIT_FAILURE); |
142 | 0 | } |
143 | | |
144 | | /* |
145 | | * Get the name from the binary name of fuzzer_foo |
146 | | */ |
147 | 38 | if (!proto) { |
148 | 38 | proto = strrchr((*argv)[0], '_'); |
149 | 38 | if (proto) proto++; |
150 | 38 | } |
151 | | |
152 | | /* |
153 | | * Look for -D dir |
154 | | * |
155 | | * If found, nuke it from the argument list. |
156 | | */ |
157 | 38 | if (!dict_dir) { |
158 | 19 | int i, j; |
159 | | |
160 | 95 | for (i = 0; i < *argc - 1; i++) { |
161 | 76 | p = (*argv)[i]; |
162 | | |
163 | 76 | if ((p[0] == '-') && (p[1] == 'D')) { |
164 | 0 | dict_dir = (*argv)[i + 1]; |
165 | |
|
166 | 0 | for (j = i + 2; j < *argc; i++, j++) { |
167 | 0 | (*argv)[i] = (*argv)[j]; |
168 | 0 | } |
169 | |
|
170 | 0 | *argc -= 2; |
171 | 0 | break; |
172 | 0 | } |
173 | 76 | } |
174 | 19 | } |
175 | | |
176 | 38 | #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
177 | | /* |
178 | | * oss-fuzz puts the dictionaries, etc. into subdirectories named after the location of the |
179 | | * binary. So we find the directory of the binary, and append "/dict" or "/lib" to find |
180 | | * dictionaries and libraries. |
181 | | */ |
182 | 38 | p = strrchr((*argv)[0], '/'); |
183 | 38 | if (p) { |
184 | 38 | if (!dict_dir) { |
185 | 19 | dict_dir = dict_dir_to_free = talloc_asprintf(NULL, "%.*s/dict", (int) (p - (*argv)[0]), (*argv)[0]); |
186 | 19 | if (!dict_dir_to_free) fr_exit_now(EXIT_FAILURE); |
187 | 19 | } |
188 | | |
189 | 38 | if (!lib_dir) { |
190 | 38 | lib_dir = lib_dir_to_free = talloc_asprintf(NULL, "%.*s/lib", (int) (p - (*argv)[0]), (*argv)[0]); |
191 | 38 | if (!lib_dir_to_free) fr_exit_now(EXIT_FAILURE); |
192 | 38 | } |
193 | 38 | } |
194 | 38 | #endif |
195 | | |
196 | 38 | if (!dict_dir) dict_dir = DICTDIR; |
197 | 38 | if (!lib_dir) lib_dir = LIBDIR; |
198 | | |
199 | | /* |
200 | | * Set the global search path for all dynamic libraries we load. |
201 | | */ |
202 | 38 | if (dl_search_global_path_set(lib_dir) < 0) { |
203 | 0 | fr_perror("fuzzer: Failed setting library path"); |
204 | 0 | fr_exit_now(EXIT_FAILURE); |
205 | 0 | } |
206 | | |
207 | | /* |
208 | | * When jobs=N is specified the fuzzer spawns worker processes via |
209 | | * a shell. We have removed any -D dictdir argument that were |
210 | | * supplied, so we pass it to our children via the environment. |
211 | | */ |
212 | 38 | if (setenv("FR_DICTIONARY_DIR", dict_dir, 1)) { |
213 | 0 | fprintf(stderr, "Failed to set FR_DICTIONARY_DIR env variable\n"); |
214 | 0 | fr_exit_now(EXIT_FAILURE); |
215 | 0 | } |
216 | | |
217 | 38 | if (!fr_dict_global_ctx_init(NULL, true, dict_dir)) { |
218 | 0 | fr_perror("dict_global"); |
219 | 0 | fr_exit_now(EXIT_FAILURE); |
220 | 0 | } |
221 | | |
222 | 38 | if (fr_dict_internal_afrom_file(&dict, FR_DICTIONARY_INTERNAL_DIR, __FILE__) < 0) { |
223 | 0 | fr_perror("fuzzer: Failed initializing internal dictionary"); |
224 | 0 | fr_exit_now(EXIT_FAILURE); |
225 | 0 | } |
226 | | |
227 | 38 | if (!proto) { |
228 | 0 | fr_perror("Failed to find protocol for fuzzer"); |
229 | 0 | fr_exit_now(EXIT_FAILURE); |
230 | 0 | } |
231 | | |
232 | | /* |
233 | | * Disable hostname lookups, so we don't produce spurious DNS |
234 | | * queries, and there's no chance of spurious failures if |
235 | | * it takes a long time to get a response. |
236 | | */ |
237 | 38 | fr_hostname_lookups = fr_reverse_lookups = false; |
238 | | |
239 | | /* |
240 | | * Search in our symbol space first. We may have been dynamically |
241 | | * or statically linked to the library we're fuzzing... |
242 | | */ |
243 | 38 | dl_proto = fuzzer_dict_init(RTLD_DEFAULT, proto); |
244 | | |
245 | 38 | autofree = talloc_autofree_context(); |
246 | 38 | if (fr_fault_setup(autofree, panic_action, (*argv)[0]) < 0) { |
247 | 0 | fr_perror("Failed initializing fault handler"); |
248 | 0 | fr_exit_now(EXIT_FAILURE); |
249 | 0 | } |
250 | | |
251 | 38 | init = true; |
252 | | |
253 | 38 | #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
254 | 38 | talloc_free(dict_dir_to_free); |
255 | 38 | talloc_free(lib_dir_to_free); |
256 | 38 | #endif |
257 | | |
258 | 38 | return 1; |
259 | 38 | } |
260 | | |
261 | | static uint8_t encoded_data[65536]; |
262 | | |
263 | | int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) |
264 | 20.2k | { |
265 | 20.2k | TALLOC_CTX * ctx = talloc_init_const("fuzzer"); |
266 | 20.2k | fr_pair_list_t vps; |
267 | 20.2k | void *decode_ctx = NULL; |
268 | 20.2k | void *encode_ctx = NULL; |
269 | 20.2k | fr_test_point_proto_decode_t *tp_decode = &der_tp_decode_proto; |
270 | 20.2k | fr_test_point_proto_encode_t *tp_encode = &der_tp_encode_proto; |
271 | | |
272 | 20.2k | fr_pair_list_init(&vps); |
273 | 20.2k | if (!init) LLVMFuzzerInitialize(NULL, NULL); |
274 | | |
275 | 20.2k | if (tp_decode->test_ctx && (tp_decode->test_ctx(&decode_ctx, NULL, dict, NULL) < 0)) { |
276 | 0 | fr_perror("fuzzer: Failed initializing test point decode_ctx"); |
277 | 0 | fr_exit_now(EXIT_FAILURE); |
278 | 0 | } |
279 | | |
280 | 20.2k | if (tp_encode->test_ctx && (tp_encode->test_ctx(&encode_ctx, NULL, dict, NULL) < 0)) { |
281 | 0 | fr_perror("fuzzer: Failed initializing test point encode_ctx"); |
282 | 0 | fr_exit_now(EXIT_FAILURE); |
283 | 0 | } |
284 | | |
285 | 20.2k | if (fr_debug_lvl > 3) { |
286 | 0 | FR_PROTO_TRACE("Fuzzer input"); |
287 | |
|
288 | 0 | FR_PROTO_HEX_DUMP(buf, len, ""); |
289 | 0 | } |
290 | | |
291 | | /* |
292 | | * Decode the input, and print the resulting data if we |
293 | | * decoded it successfully. |
294 | | * |
295 | | * If we have successfully decoded the data, then encode |
296 | | * it again, too. |
297 | | */ |
298 | 20.2k | if (tp_decode->func(ctx, &vps, buf, len, decode_ctx) > 0) { |
299 | 14.0k | PAIR_LIST_VERIFY_WITH_CTX(ctx, &vps); |
300 | | |
301 | 14.0k | if (fr_debug_lvl > 3) fr_pair_list_debug(stderr, &vps); |
302 | | |
303 | 14.0k | if (do_encode) (void) tp_encode->func(ctx, &vps, encoded_data, sizeof(encoded_data), encode_ctx); |
304 | 14.0k | } |
305 | | |
306 | 20.2k | talloc_free(decode_ctx); |
307 | 20.2k | talloc_free(encode_ctx); |
308 | 20.2k | talloc_free(ctx); |
309 | | |
310 | | /* |
311 | | * Clear error messages from the run. Clearing these |
312 | | * keeps malloc/free balanced, which helps to avoid the |
313 | | * fuzzers leak heuristics from firing. |
314 | | */ |
315 | 20.2k | fr_strerror_clear(); |
316 | | |
317 | 20.2k | return 0; |
318 | 20.2k | } |