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