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