The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
fuzzer.c
Go to the documentation of this file.
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;
45 
46 static fr_dict_t *dict = NULL;
47 
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 {
55  if (dl_proto && dl_proto->free) dl_proto->free();
56 
57  fr_dict_free(&dict, __FILE__);
58 
59  if (dl && dl->handle) {
60  dlclose(dl->handle);
61  dl->handle = NULL;
62  }
64 
65  /*
66  * Ensure our atexit handlers run before any other
67  * atexit handlers registered by third party libraries.
68  */
70 }
71 
72 static inline
73 fr_dict_protocol_t *fuzzer_dict_init(void *dl_handle, char const *proto)
74 {
75  char buffer[256];
76  fr_dict_protocol_t *our_dl_proto;
77 
78  snprintf(buffer, sizeof(buffer), "libfreeradius_%s_dict_protocol", proto);
79 
80  our_dl_proto = dlsym(dl_handle, buffer);
81  if (our_dl_proto && our_dl_proto->init() && (our_dl_proto->init() < 0)) {
82  fr_perror("fuzzer: Failed initializing library %s", buffer);
83  fr_exit_now(EXIT_FAILURE);
84  }
85 
86  return our_dl_proto;
87 }
88 
89 int LLVMFuzzerInitialize(int *argc, char ***argv)
90 {
91  char const *lib_dir = getenv("FR_LIBRARY_PATH");
92  char const *proto = getenv("FR_LIBRARY_FUZZ_PROTOCOL");
93  char const *dict_dir = getenv("FR_DICTIONARY_DIR");
94  char const *debug_lvl_str = getenv("FR_DEBUG_LVL");
95  char const *p;
96 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
97  char *dict_dir_to_free = NULL;
98  char *lib_dir_to_free = NULL;
99 #endif
100 
101  if (!argc || !argv || !*argv) return -1; /* shut up clang scan */
102 
103  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  */
110 
111  /*
112  * Initialise the talloc fault handlers.
113  */
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  fr_strerror_const("fuzz"); /* allocate the pools */
123  fr_strerror_clear(); /* clears the message, leaves the pools */
124 
125  /*
126  * Setup our own internal atexit handler
127  */
128  if (atexit(exitHandler)) {
129  fr_perror("fuzzer: Failed to register exit handler: %s", fr_syserror(errno));
130  fr_exit_now(EXIT_FAILURE);
131  }
132 
133  /*
134  * Get the name from the binary name of fuzzer_foo
135  */
136  if (!proto) {
137  proto = strrchr((*argv)[0], '_');
138  if (proto) proto++;
139  }
140 
141  /*
142  * Look for -D dir
143  *
144  * If found, nuke it from the argument list.
145  */
146  if (!dict_dir) {
147  int i, j;
148 
149  for (i = 0; i < *argc - 1; i++) {
150  p = (*argv)[i];
151 
152  if ((p[0] == '-') && (p[1] == 'D')) {
153  dict_dir = (*argv)[i + 1];
154 
155  for (j = i + 2; j < *argc; i++, j++) {
156  (*argv)[i] = (*argv)[j];
157  }
158 
159  *argc -= 2;
160  break;
161  }
162  }
163  }
164 
165 #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  p = strrchr((*argv)[0], '/');
172  if (p) {
173  if (!dict_dir) {
174  dict_dir = dict_dir_to_free = talloc_asprintf(NULL, "%.*s/dict", (int) (p - (*argv)[0]), (*argv)[0]);
175  if (!dict_dir_to_free) fr_exit_now(EXIT_FAILURE);
176  }
177 
178  if (!lib_dir) {
179  lib_dir = lib_dir_to_free = talloc_asprintf(NULL, "%.*s/lib", (int) (p - (*argv)[0]), (*argv)[0]);
180  if (!lib_dir_to_free) fr_exit_now(EXIT_FAILURE);
181  }
182  }
183 #endif
184 
185  if (!dict_dir) dict_dir = DICTDIR;
186  if (!lib_dir) lib_dir = LIBDIR;
187 
188  /*
189  * Set the global search path for all dynamic libraries we load.
190  */
191  if (dl_search_global_path_set(lib_dir) < 0) {
192  fr_perror("fuzzer: Failed setting library path");
193  fr_exit_now(EXIT_FAILURE);
194  }
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  if (setenv("FR_DICTIONARY_DIR", dict_dir, 1)) {
202  fprintf(stderr, "Failed to set FR_DICTIONARY_DIR env variable\n");
203  fr_exit_now(EXIT_FAILURE);
204  }
205 
206  if (!fr_dict_global_ctx_init(NULL, true, dict_dir)) {
207  fr_perror("dict_global");
208  fr_exit_now(EXIT_FAILURE);
209  }
210 
212  fr_perror("fuzzer: Failed initializing internal dictionary");
213  fr_exit_now(EXIT_FAILURE);
214  }
215 
216  if (!proto) {
217  fr_perror("Failed to find protocol for fuzzer");
218  fr_exit_now(EXIT_FAILURE);
219  }
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  */
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  dl_proto = fuzzer_dict_init(RTLD_DEFAULT, proto);
233 
234  init = true;
235 
236 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
237  talloc_free(dict_dir_to_free);
238  talloc_free(lib_dir_to_free);
239 #endif
240 
241  return 1;
242 }
243 
244 int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len)
245 {
246  TALLOC_CTX * ctx = talloc_init_const("fuzzer");
247  fr_pair_list_t vps;
248  void *decode_ctx = NULL;
250 
251  fr_pair_list_init(&vps);
252  if (!init) LLVMFuzzerInitialize(NULL, NULL);
253 
254  if (tp->test_ctx && (tp->test_ctx(&decode_ctx, NULL) < 0)) {
255  fr_perror("fuzzer: Failed initializing test point decode_ctx");
256  fr_exit_now(EXIT_FAILURE);
257  }
258 
259  tp->func(ctx, &vps, buf, len, decode_ctx);
260  if (fr_debug_lvl > 3) fr_pair_list_debug(&vps);
261 
262  talloc_free(decode_ctx);
263  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  */
271 
272  return 0;
273 }
static int const char char buffer[256]
Definition: acutest.h:574
int fr_atexit_global_setup(void)
Setup the atexit handler, should be called at the start of a program's execution.
Definition: atexit.c:160
int fr_atexit_global_trigger_all(void)
Cause all global free triggers to fire.
Definition: atexit.c:286
static dl_loader_t * dl_loader
Definition: fuzzer.c:43
fr_test_point_proto_decode_t XX_PROTOCOL_XX_tp_decode_proto
static dl_t * dl
Definition: fuzzer.c:42
static fr_dict_t * dict
Definition: fuzzer.c:46
static fr_dict_protocol_t * fuzzer_dict_init(void *dl_handle, char const *proto)
Definition: fuzzer.c:73
static fr_dict_protocol_t * dl_proto
Definition: fuzzer.c:44
static bool init
Definition: fuzzer.c:41
int LLVMFuzzerInitialize(int *argc, char ***argv)
Definition: fuzzer.c:89
static void exitHandler(void)
Definition: fuzzer.c:53
int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len)
Definition: fuzzer.c:244
#define RCSID(id)
Definition: build.h:444
void fr_talloc_fault_setup(void)
Register talloc fault handlers.
Definition: debug.c:1196
#define fr_exit_now(_x)
Exit without calling atexit() handlers, producing a log message in debug builds.
Definition: debug.h:232
fr_dict_protocol_free_t free
free the library
Definition: dict.h:350
int fr_dict_internal_afrom_file(fr_dict_t **out, char const *internal_name, char const *dependent)
(Re-)Initialize the special internal dictionary
fr_dict_protocol_init_t init
initialize the library
Definition: dict.h:349
int fr_dict_free(fr_dict_t **dict, char const *dependent)
Decrement the reference count on a previously loaded dictionary.
Definition: dict_util.c:3581
fr_dict_gctx_t * fr_dict_global_ctx_init(TALLOC_CTX *ctx, bool free_at_exit, char const *dict_dir)
Initialise the global protocol hashes.
Definition: dict_util.c:3984
Protocol-specific callbacks in libfreeradius-PROTOCOL.
Definition: dict.h:341
int dl_search_global_path_set(char const *lib_dir)
Set the global library path.
Definition: dl.c:768
A dynamic loader.
Definition: dl.c:81
void * handle
Handle returned by dlopen.
Definition: dl.h:62
Module handle.
Definition: dl.h:58
bool fr_hostname_lookups
hostname -> IP lookups?
Definition: inet.c:52
bool fr_reverse_lookups
IP -> hostname lookups?
Definition: inet.c:51
talloc_free(reap)
int fr_debug_lvl
Definition: log.c:42
unsigned char uint8_t
Definition: merged_model.c:30
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition: pair.c:46
static char const * proto(int id, int porttype)
Definition: radwho.c:85
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition: snprintf.c:689
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition: syserror.c:243
static TALLOC_CTX * talloc_init_const(char const *name)
Allocate a top level chunk with a constant name.
Definition: talloc.h:112
fr_tp_proto_decode_t func
Decoder for proto layer.
Definition: test_point.h:67
fr_test_point_ctx_alloc_t test_ctx
Allocate a test ctx for the encoder.
Definition: test_point.h:66
Entry point for protocol decoders.
Definition: test_point.h:65
#define FR_DICTIONARY_INTERNAL_DIR
Definition: conf.h:8
void fr_pair_list_debug(fr_pair_list_t const *list)
Dumps a list to the default logging destination - Useful for calling from debuggers.
Definition: pair_print.c:296
void fr_perror(char const *fmt,...)
Print the current error to stderr with a prefix.
Definition: strerror.c:733
void fr_strerror_clear(void)
Clears all pending messages from the talloc pools.
Definition: strerror.c:577
#define fr_strerror_const(_msg)
Definition: strerror.h:223