The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
fuzzer_xlat.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
13/**
14 * @file src/fuzzer/fuzzer_xlat.c
15 * @brief Fuzz the xlat tokenize -> resolve -> eval pipeline.
16 *
17 * Drives the three public xlat tokenisers (xlat_tokenize,
18 * xlat_tokenize_expression, xlat_tokenize_condition) and, on success,
19 * follows through xlat_resolve() and xlat_aeval_compiled() against a
20 * synthetic request built from the test dictionary. This exercises:
21 *
22 * src/lib/unlang/xlat_tokenize.c
23 * src/lib/unlang/xlat_expr.c
24 * src/lib/unlang/xlat_eval.c
25 * src/lib/unlang/xlat_builtin.c
26 *
27 * All of which are at 0% coverage under the existing protocol-decoder
28 * fuzzers despite being on the network-attacker-reachable path: xlat
29 * expansions interpolate attribute values that originate from RADIUS,
30 * DHCP, DNS etc. packets at request time.
31 *
32 * Input layout:
33 * byte[0] - low 2 bits select the tokeniser variant
34 * byte[1..] - the xlat expression text (not NUL-terminated)
35 */
36RCSID("$Id: 0ca005614a9b26954ddc1030856b909a59df7af6 $")
37
38#include <freeradius-devel/build.h>
39#include <freeradius-devel/util/atexit.h>
40#include <freeradius-devel/util/dict.h>
41#include <freeradius-devel/util/dl.h>
42#include <freeradius-devel/util/lsan.h>
43#include <freeradius-devel/util/sbuff.h>
44#include <freeradius-devel/util/strerror.h>
45#include <freeradius-devel/util/syserror.h>
46#include <freeradius-devel/util/talloc.h>
47#include <freeradius-devel/util/types.h>
48#include <freeradius-devel/util/value.h>
49#include <freeradius-devel/server/base.h>
50#include <freeradius-devel/server/request.h>
51#include <freeradius-devel/server/tmpl.h>
52#include <freeradius-devel/unlang/base.h>
53#include <freeradius-devel/unlang/xlat.h>
54
55int LLVMFuzzerInitialize(int *argc, char ***argv);
56int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len);
57
58static bool init_done = false;
59static fr_dict_t *dict_internal = NULL;
60
61int LLVMFuzzerInitialize(int *argc, char ***argv)
62{
63 char const *dict_dir = NULL;
64 char const *lib_dir = NULL;
65 char *dict_buf = NULL, *lib_buf = NULL;
66 char const *p;
67
68 if (init_done) return 0;
69 if (!argc || !argv || !*argv) return -1;
70
73 fr_strerror_const("fuzz");
75
77 error:
78 fr_perror("fuzzer_xlat");
79 return -1;
80 }
81
82 dict_dir = getenv("FR_DICTIONARY_DIR");
83 lib_dir = getenv("FR_LIBRARY_PATH");
84
85 p = strrchr((*argv)[0], '/');
86 if (p) {
87 if (!dict_dir) {
88 dict_buf = talloc_asprintf(NULL, "%.*s/dict",
89 (int)(p - (*argv)[0]), (*argv)[0]);
90 if (!dict_buf) goto error;
91 dict_dir = dict_buf;
92 }
93 if (!lib_dir) {
94 lib_buf = talloc_asprintf(NULL, "%.*s/lib",
95 (int)(p - (*argv)[0]), (*argv)[0]);
96 if (!lib_buf) goto error;
97 lib_dir = lib_buf;
98 }
99 }
100
101 if (lib_dir && dl_search_global_path_set(lib_dir) < 0) goto error;
102
103 if (dict_dir) (void) setenv("FR_DICTIONARY_DIR", dict_dir, 1);
104
105 if (!fr_dict_global_ctx_init(NULL, true, dict_dir ? dict_dir : "share/dictionary")) goto error;
106
108
109 if (request_global_init() < 0) goto error;
110
111 /*
112 * Bootstraps xlat_func tree, registers builtins, prepares
113 * the unlang interpreter. Required before xlat_tokenize().
114 */
115 if (unlang_global_init() < 0) goto error;
116
117 talloc_free(dict_buf);
118 talloc_free(lib_buf);
119
120 init_done = true;
121 return 0;
122}
123
124/*
125 * Poison gutters either side of the fmt buffer so ASan flags any
126 * path inside xlat_tokenize* / xlat_resolve that walks past the
127 * declared sbuff bounds. __asan_poison_memory_region rounds to
128 * 8-byte granules, so reads more than ~7 bytes past either end will
129 * be reported; tight over-reads may slip through but the pattern
130 * mirrors fuzzer_value.c and src/bin/unit_test_attribute.c.
131 */
132#define POISON_START 64
133#define POISON_END 64
134
135int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
136{
137 TALLOC_CTX *ctx;
138 xlat_exp_head_t *head = NULL;
139 fr_sbuff_t sbuff;
140 tmpl_rules_t t_rules;
141 uint8_t mode;
142 uint8_t *raw_fmt = NULL;
143 char *fmt = NULL;
144 size_t fmt_len;
145 fr_slen_t slen;
146
147 if (!init_done) return 0;
148 if (size < 2) return 0;
149 if (size > 4096) return 0; /* keep iterations fast */
150
151 mode = data[0] & 0x03;
152 fmt_len = size - 1;
153
154 ctx = talloc_init_const("fuzzer_xlat");
155 if (!ctx) return 0;
156
157 /*
158 * Tokenisers consume an sbuff with an explicit length, so we
159 * deliberately do NOT NUL-terminate. Any code path that does
160 * strlen/strchr/memchr on the underlying buffer (rather than
161 * respecting the sbuff end) will read into the poisoned gutter
162 * and trip ASan, which is the bug we want to surface.
163 */
164 raw_fmt = talloc_array(ctx, uint8_t, POISON_START + fmt_len + POISON_END);
165 if (!raw_fmt) goto done;
166 fmt = (char *)(raw_fmt + POISON_START);
167 if (fmt_len) memcpy(fmt, data + 1, fmt_len);
170
171 /*
172 * Tighten tmpl_rules: refuse unresolved attribute references
173 * at tokenize-time so we never reach eval with a half-resolved
174 * tree (which trips the per-node tmpl_needs_resolving assert
175 * in xlat_frame_eval, see xlat_eval.c:1475).
176 */
177 t_rules = (tmpl_rules_t) {
180 .list_def = request_attr_request,
181 .allow_unresolved = false,
182 .allow_unknown = false,
183 .allow_wildcard = true,
184 },
185 };
186
187 sbuff = FR_SBUFF_IN(fmt, fmt_len);
188
189 switch (mode) {
190 case 0:
191 slen = xlat_tokenize(ctx, &head, &sbuff, NULL, &t_rules);
192 break;
193 case 1:
194 slen = xlat_tokenize_expression(ctx, &head, &sbuff, NULL, &t_rules);
195 break;
196 case 2:
197 slen = xlat_tokenize_condition(ctx, &head, &sbuff, NULL, &t_rules);
198 break;
199 default:
200 /*
201 * Argv-style takes an arg parser table; pass NULL
202 * args - the tokeniser tolerates this by treating
203 * every arg as STRING/required.
204 */
205 slen = xlat_tokenize_argv(ctx, &head, &sbuff, NULL, NULL, &t_rules, false);
206 break;
207 }
208
209 if (slen <= 0 || !head) goto done;
210
211 /*
212 * Resolve unknown function / attribute references. The
213 * resolve pass is itself a meaningful target - it walks
214 * the whole AST. Tolerant of failure: many fuzzer inputs
215 * will leave dangling references that won't resolve, but
216 * the walk still exercises code.
217 */
218 {
219 xlat_res_rules_t const xr_rules = {
221 .allow_unresolved = false,
222 };
223 (void) xlat_resolve(head, &xr_rules);
224 }
225
226 /*
227 * xlat_print round-trip - exercises the unparse path
228 * (xlat_tokenize.c) and validates the tree is well-formed
229 * enough to be serialised back to text.
230 *
231 * We deliberately do NOT call xlat_aeval_compiled / xlat_eval
232 * here: eval requires invariants that are normally established
233 * by xlat_compile() / xlat_purify() (e.g. quote != T_BARE_WORD,
234 * all per-node tmpls fully resolved). Fuzzer-generated trees
235 * skip those passes and trip dev-asserts in xlat_frame_eval.
236 * Tokenize + resolve already cover the bulk of the previously
237 * 0%-covered xlat code.
238 */
239 {
240 fr_sbuff_t print_sb;
241 char print_buf[1024];
242 print_sb = FR_SBUFF_OUT(print_buf, sizeof(print_buf));
243 (void) xlat_print(&print_sb, head, NULL);
244 }
245
246done:
247 /*
248 * Unpoison before talloc_free walks the chunk headers.
249 */
250 if (raw_fmt) {
252 }
253
254 talloc_free(ctx);
256 return 0;
257}
static int const char * fmt
Definition acutest.h:573
int fr_atexit_global_setup(void)
Setup the atexit handler, should be called at the start of a program's execution.
Definition atexit.c:179
#define RCSID(id)
Definition build.h:512
void fr_talloc_fault_setup(void)
Register talloc fault handlers.
Definition debug.c:1051
int fr_dict_internal_afrom_file(fr_dict_t **out, char const *dict_subdir, char const *dependent))
(Re-)Initialize the special internal dictionary
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:4707
int dl_search_global_path_set(char const *lib_dir)
Set the global library path.
Definition dl.c:783
#define POISON_START
#define POISON_END
static fr_dict_t * dict_internal
Definition fuzzer_xlat.c:59
int LLVMFuzzerInitialize(int *argc, char ***argv)
Definition fuzzer_xlat.c:61
static bool init_done
Definition fuzzer_xlat.c:58
int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len)
talloc_free(hp)
int unlang_global_init(void)
Definition base.c:158
#define ASAN_POISON_MEMORY_REGION(_start, _size)
Definition lsan.h:62
#define ASAN_UNPOISON_MEMORY_REGION(_start, _size)
Definition lsan.h:63
unsigned char uint8_t
ssize_t fr_slen_t
static bool done
Definition radclient.c:80
fr_dict_attr_t const * request_attr_request
Definition request.c:43
int request_global_init(void)
Definition request.c:596
#define FR_SBUFF_IN(_start, _len_or_end)
#define FR_SBUFF_OUT(_start, _len_or_end)
fr_dict_t const * dict_def
Alternative default dictionary to use if vpt->rules->dict_def is NULL.
Definition tmpl.h:369
tmpl_attr_rules_t attr
Rules/data for parsing attribute references.
Definition tmpl.h:339
struct tmpl_res_rules_s tmpl_res_rules_t
Definition tmpl.h:237
struct tmpl_rules_s tmpl_rules_t
Definition tmpl.h:233
struct tmpl_attr_rules_s tmpl_attr_rules_t
Definition tmpl.h:234
Optional arguments passed to vp_tmpl functions.
Definition tmpl.h:336
fr_dict_t const * dict_def
Default dictionary to use with unqualified attribute references.
Definition tmpl.h:273
static TALLOC_CTX * talloc_init_const(char const *name)
Allocate a top level chunk with a constant name.
Definition talloc.h:127
#define talloc_asprintf
Definition talloc.h:151
tmpl_res_rules_t const * tr_rules
tmpl resolution rules.
Definition xlat.h:165
fr_slen_t xlat_tokenize_condition(TALLOC_CTX *ctx, xlat_exp_head_t **head, fr_sbuff_t *in, fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules))
Definition xlat_expr.c:3191
fr_slen_t xlat_print(fr_sbuff_t *in, xlat_exp_head_t const *node, fr_sbuff_escape_rules_t const *e_rules)
Reconstitute an xlat expression from its constituent nodes.
fr_slen_t xlat_tokenize(TALLOC_CTX *ctx, xlat_exp_head_t **head, fr_sbuff_t *in, fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules)
Tokenize an xlat expansion.
static fr_slen_t head
Definition xlat.h:420
int xlat_resolve(xlat_exp_head_t *head, xlat_res_rules_t const *xr_rules)
Walk over an xlat tree recursively, resolving any unresolved functions or references.
fr_slen_t xlat_tokenize_argv(TALLOC_CTX *ctx, xlat_exp_head_t **head, fr_sbuff_t *in, xlat_arg_parser_t const *xlat_args, fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules, bool spaces))
Tokenize an xlat expansion into a series of XLAT_TYPE_CHILD arguments.
fr_slen_t xlat_tokenize_expression(TALLOC_CTX *ctx, xlat_exp_head_t **head, fr_sbuff_t *in, fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules))
Definition xlat_expr.c:3163
#define FR_DICTIONARY_INTERNAL_DIR
Definition conf.h:8
void fr_perror(char const *fmt,...)
Print the current error to stderr with a prefix.
Definition strerror.c:737
void fr_strerror_clear(void)
Clears all pending messages from the talloc pools.
Definition strerror.c:581
#define fr_strerror_const(_msg)
Definition strerror.h:223
int fr_check_lib_magic(uint64_t magic)
Check if the application linking to the library has the correct magic number.
Definition version.c:40
#define RADIUSD_MAGIC_NUMBER
Definition version.h:81
static fr_slen_t data
Definition value.h:1340