Coverage Report

Created: 2026-03-31 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}