Coverage Report

Created: 2025-12-31 06:18

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