The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
fuzzer_tmpl.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_tmpl.c
15 * @brief Fuzz the tmpl tokenize -> resolve pipeline.
16 *
17 * Drives the two public tmpl parsers:
18 *
19 * tmpl_afrom_substr() - the general parser, dispatches by quote.
20 * Reads from an fr_sbuff_t with explicit length,
21 * i.e. the input is NOT required to be
22 * NUL-terminated. Network-attacker-reachable
23 * via every place a config string or xlat
24 * operand is turned into a tmpl.
25 *
26 * tmpl_afrom_attr_str() - the attribute-only convenience wrapper.
27 * Takes a NUL-terminated C string. Used by
28 * callers that already have a flat name
29 * (e.g. legacy callers, some unit tests).
30 *
31 * The APIs are called based on mode (see below), and then
32 * tmpl_resolve() and tmpl_print() are called to fully exercise the
33 * tmpl code.
34 *
35 * Input layout:
36 * byte[0] - mode selector, used mod the number of variants.
37 * byte[1..] - the tmpl text. For tmpl_afrom_substr() the bytes are
38 * used verbatim and NOT NUL-terminated; for
39 * tmpl_afrom_attr_str() they are copied into a separate
40 * NUL-terminated scratch buffer first (the function's
41 * contract requires that).
42 */
43RCSID("$Id: 8f24196d07b27777f324a3119890ff1dbf9c4488 $")
44
45#include <freeradius-devel/fuzzer/common.h>
46#include <freeradius-devel/server/base.h>
47#include <freeradius-devel/server/tmpl.h>
48#include <freeradius-devel/unlang/base.h>
49
50int LLVMFuzzerInitialize(int *argc, char ***argv);
51int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len);
52
54
55int LLVMFuzzerInitialize(int *argc, char ***argv)
56{
57 if (dict) return 0;
58
59 if (fuzzer_common_init(argc, argv, false) < 0) fr_exit_now(EXIT_FAILURE);
60
62 error:
63 fr_perror("fuzzer_tmpl");
64 fr_exit_now(EXIT_FAILURE);
65 }
66
68
69 /*
70 * tmpl_global_init() sets up tmpl_attr_unspec which the
71 * [<filter>]-only path of tmpl_attr_afrom_attr_substr()
72 * dereferences unconditionally - without it, an input like
73 * "[0]" SIGSEGVs on a NULL ar_da. See tmpl_eval.c:1378.
74 */
75 if (tmpl_global_init() < 0) goto error;
76
77 /*
78 * request_attr_request and friends are read out of the
79 * tmpl_rules .list_def below, so request_global_init() has to
80 * have populated them before we tokenise anything.
81 */
82 if (request_global_init() < 0) goto error;
83
84 /*
85 * Pull in the xlat infrastructure too: tmpl_afrom_substr()
86 * hands a TMPL_TYPE_XLAT subtree off to xlat_tokenize_*, and
87 * xlat_tmpl_normalize() is called from inside that path.
88 * Without unlang_global_init() the xlat function tree is
89 * empty and every "%foo(...)" tail-call short-circuits to
90 * "unknown function" before reaching the parser surface we
91 * want to fuzz.
92 */
93 if (unlang_global_init() < 0) goto error;
94
95 return 0;
96}
97
98/*
99 * Use the same poison scheme as fuzzer_xlat.c: ASan poisons
100 * either side of the live fmt region so any code path that walks
101 * past the declared sbuff bounds (instead of respecting the
102 * explicit length) trips a report. fr_sbuff lengths are honest,
103 * but a bug in one of the dispatched parsers -
104 * tmpl_request_ref_list_from_substr, the OID branch in
105 * tmpl_attr_afrom_attr_substr, etc. - that does strlen() or
106 * memchr() over the underlying buffer will get caught here.
107 */
108#define POISON_START 64
109#define POISON_END 64
110
111/*
112 * The four tmpl_afrom_substr() quote variants, plus
113 * tmpl_afrom_attr_str(). Keep these contiguous so that a single
114 * mode selects between them.
115 */
116#define MODE_SUBSTR_BARE 0
117#define MODE_SUBSTR_DOUBLE 1
118#define MODE_SUBSTR_SINGLE 2
119#define MODE_SUBSTR_BACK 3
120#define MODE_ATTR_STR 4
121#define MODE_COUNT 5
122
123int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
124{
125 TALLOC_CTX *ctx;
126 tmpl_t *vpt = NULL;
127 tmpl_rules_t t_rules;
129 uint8_t mode;
130 uint8_t *raw_fmt = NULL;
131 char *fmt = NULL;
132 size_t fmt_len;
133 fr_slen_t slen = -1;
134
135 if (!dict) return 0;
136 if (size < 2) return 0;
137 if (size > 4096) return 0; /* keep iterations fast */
138
139 mode = data[0] % MODE_COUNT;
140 fmt_len = size - 1;
141
142 ctx = talloc_init_const("fuzzer_tmpl");
143 if (!ctx) return 0;
144
145 /*
146 * Tokenisers consume an sbuff with an explicit length, so we
147 * deliberately do NOT NUL-terminate. Any code path that does
148 * strlen/strchr/memchr on the underlying buffer (rather than
149 * respecting the sbuff end) will read into the poisoned gutter
150 * and trip ASan, which is the bug we want to surface.
151 *
152 * We allocate an extra byte to NUL terminate the input
153 * for functions which need that.
154 */
155 raw_fmt = talloc_array(ctx, uint8_t, POISON_START + fmt_len + 1 + POISON_END);
156 if (!raw_fmt) goto done;
157 fmt = (char *)(raw_fmt + POISON_START);
158 if (fmt_len) memcpy(fmt, data + 1, fmt_len);
159 fmt[fmt_len] = '\0'; /* always present, _str needs it, substr ignores it */
160
162 ASAN_POISON_MEMORY_REGION(raw_fmt + POISON_START + fmt_len + 1, POISON_END);
163
164 /*
165 * Tighten tmpl_rules: refuse unresolved attribute references
166 * at tokenize-time so we never reach eval with a half-resolved
167 * tree (which trips the per-node tmpl_needs_resolving assert
168 * in xlat_frame_eval, see xlat_eval.c:1475).
169 */
170 t_rules = (tmpl_rules_t) {
172 .dict_def = dict,
173 .list_def = request_attr_request,
174 .allow_unresolved = false,
175 .allow_unknown = false,
176 .allow_wildcard = true,
177 },
178 };
179
180 switch (mode) {
181 case MODE_SUBSTR_BARE:
184 case MODE_SUBSTR_BACK:
185 {
186 fr_token_t quote;
187 fr_sbuff_t sbuff;
188
189 switch (mode) {
190 default:
191 case MODE_SUBSTR_BARE:
192 quote = T_BARE_WORD;
193 break;
194
197 break;
198
201 break;
202
203 case MODE_SUBSTR_BACK:
204 quote = T_BACK_QUOTED_STRING;
205 break;
206 }
207
208 sbuff = FR_SBUFF_IN(fmt, fmt_len);
209 slen = tmpl_afrom_substr(ctx, &vpt, &sbuff, quote, NULL, &t_rules);
210 break;
211 }
212
213 case MODE_ATTR_STR:
214 /*
215 * The _str signature takes a length too, but its body
216 * uses strlen() internally for the substr it builds.
217 * Pass fmt_len so the explicit-length API stays
218 * honest, but the NUL we wrote at fmt[fmt_len] is what
219 * the function actually relies on.
220 */
221 slen = tmpl_afrom_attr_str(ctx, &attr_err, &vpt, fmt, &t_rules);
222 break;
223 }
224
225 if (slen <= 0 || !vpt) goto done;
226
227 /*
228 * Resolve walks the whole tmpl AST: request refs, attr
229 * refs, nested xlats inside TMPL_TYPE_XLAT. Most fuzzer
230 * inputs leave unresolved refs, but the resolution walk
231 * still exercises the code.
232 */
233 (void) tmpl_resolve(vpt, &tr_rules);
234
235 /*
236 * Write the tmpl out. This check exercises every
237 * per-type print branch in tmpl_tokenize.c (attr OID
238 * rendering, request-ref prefixing, xlat unparse via
239 * xlat_print, escape rules, etc.) and validates the tree
240 * is well-formed enough to serialise back to text.
241 *
242 * We deliberately do not chase further (tmpl_eval / map_proc)
243 * for the same reason fuzzer_xlat doesn't: eval requires
244 * invariants that only a full compile pass establishes, and
245 * fuzzer-generated trees skip those.
246 */
247 {
248 fr_sbuff_t print_sb;
249 char print_buf[1024];
250
251 print_sb = FR_SBUFF_OUT(print_buf, sizeof(print_buf));
252 (void) tmpl_print(&print_sb, vpt, NULL);
253 }
254
255done:
256 if (raw_fmt) {
257 ASAN_UNPOISON_MEMORY_REGION(raw_fmt, POISON_START + fmt_len + 1 + POISON_END);
258 }
259
260 talloc_free(ctx);
262 return 0;
263}
static int const char * fmt
Definition acutest.h:573
#define RCSID(id)
Definition build.h:512
fr_dict_t * dict
Definition common.c:31
int fuzzer_common_init(int *argc, char ***argv, bool load_proto)
Perform all bootstrapping for the fuzzer.
Definition common.c:55
#define fr_exit_now(_x)
Exit without calling atexit() handlers, producing a log message in debug builds.
Definition debug.h:226
#define MODE_SUBSTR_DOUBLE
#define POISON_START
#define MODE_COUNT
#define POISON_END
#define MODE_SUBSTR_SINGLE
#define MODE_ATTR_STR
#define MODE_SUBSTR_BACK
int LLVMFuzzerInitialize(int *argc, char ***argv)
Definition fuzzer_tmpl.c:55
#define MODE_SUBSTR_BARE
static tmpl_res_rules_t tr_rules
Definition fuzzer_tmpl.c:53
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
fr_slen_t tmpl_print(fr_sbuff_t *out, tmpl_t const *vpt, fr_sbuff_escape_rules_t const *e_rules)
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)
int tmpl_resolve(tmpl_t *vpt, tmpl_res_rules_t const *tr_rules))
Attempt to resolve functions and attributes in xlats and attribute references.
ssize_t tmpl_afrom_attr_str(TALLOC_CTX *ctx, tmpl_attr_error_t *err, tmpl_t **out, char const *name, tmpl_rules_t const *rules))
Parse a string into a TMPL_TYPE_ATTR_* type tmpl_t.
ssize_t tmpl_afrom_substr(TALLOC_CTX *ctx, tmpl_t **out, fr_sbuff_t *in, fr_token_t quote, fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules))
Convert an arbitrary string into a tmpl_t.
int tmpl_global_init(void)
Definition tmpl_eval.c:1383
static fr_slen_t vpt
Definition tmpl.h:1269
fr_dict_t const * dict_def
Alternative default dictionary to use if vpt->rules->dict_def is NULL.
Definition tmpl.h:369
tmpl_attr_error_t
Definition tmpl.h:1004
@ TMPL_ATTR_ERROR_NONE
No error.
Definition tmpl.h:1005
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
Similar to tmpl_rules_t, but used to specify parameters that may change during subsequent resolution ...
Definition tmpl.h:368
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
enum fr_token fr_token_t
@ T_SINGLE_QUOTED_STRING
Definition token.h:120
@ T_BARE_WORD
Definition token.h:118
@ T_BACK_QUOTED_STRING
Definition token.h:121
@ T_DOUBLE_QUOTED_STRING
Definition token.h:119
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
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