/src/freeradius-server/src/fuzzer/fuzzer_cf.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: e74e4bb7eb1f5d1997db8032c515663fbc37e1e4 $ |
19 | | * |
20 | | * @file src/bin/fuzzer_cf.c |
21 | | * @brief Functions to fuzz the FreeRADIUS config-file parser |
22 | | * |
23 | | * Targets cf_file_read() and the section/pair tokenisers it drives |
24 | | * (cf_file.c, cf_util.c, cf_parse.c). The full configuration grammar - |
25 | | * sections, pairs, quoting, line continuation, $INCLUDE / $-INCLUDE |
26 | | * resolution, operators, and xlat expansions - is exercised through |
27 | | * this single entry point. |
28 | | * |
29 | | * The harness writes each fuzzer input to a per-process file under the |
30 | | * system temporary directory because cf_file_read() is path-based and |
31 | | * resolves $INCLUDE relative to the directory of the file being parsed. |
32 | | * A pid-suffixed name keeps the harness safe under libFuzzer's -jobs=N. |
33 | | */ |
34 | | RCSID("$Id: e74e4bb7eb1f5d1997db8032c515663fbc37e1e4 $") |
35 | | |
36 | | #include <freeradius-devel/build.h> |
37 | | #include <freeradius-devel/server/cf_file.h> |
38 | | #include <freeradius-devel/server/cf_util.h> |
39 | | #include <freeradius-devel/server/main_config.h> |
40 | | #include <freeradius-devel/util/strerror.h> |
41 | | |
42 | | extern char const *__lsan_default_suppressions(void); |
43 | | |
44 | | int LLVMFuzzerInitialize(int *argc, char ***argv); |
45 | | int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len); |
46 | | |
47 | | int LLVMFuzzerInitialize(UNUSED int *argc, UNUSED char ***argv) |
48 | 4 | { |
49 | | /* |
50 | | * Don't put output anywhere. Otherwise we will have reams of log messages. |
51 | | */ |
52 | 4 | default_log.dst = L_DST_NULL; |
53 | 4 | default_log.fd = -1; |
54 | 4 | default_log.print_level = true; |
55 | 4 | default_log.suppress_secrets = true; |
56 | | |
57 | 4 | return 0; |
58 | 4 | } |
59 | | |
60 | | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) |
61 | 121 | { |
62 | 121 | main_config_t *config; |
63 | 121 | size_t depth = 0, max_depth = 0; |
64 | | |
65 | | /* |
66 | | * cap input size: the parser is line-oriented and a |
67 | | * pathological input can cost a great deal of time without |
68 | | * exposing new states. |
69 | | */ |
70 | 121 | if (size > 16 * 1024) return 0; |
71 | | |
72 | | /* |
73 | | * Pre-filter on brace nesting depth. The config parser |
74 | | * recurses via C function calls on '{'-introduced |
75 | | * sub-sections (parse_subrequest, parse_foreach, |
76 | | * parse_switch, etc., and cf_section_pass2 walking the |
77 | | * section tree). libFuzzer trivially discovers inputs of |
78 | | * the form "{{{{ ... }}}}" that exhaust the C stack without |
79 | | * revealing any new parser states. Real configs nest fewer |
80 | | * than ten levels deep; the cap is set generously here so |
81 | | * that any legitimate nesting still reaches the parser. |
82 | | * |
83 | | * Quoting is intentionally ignored: a conservative count |
84 | | * can only over-reject, never under-reject, and the cost of |
85 | | * dropping a few well-formed inputs with '{' embedded in |
86 | | * strings is negligible compared with the cost of burning |
87 | | * every fuzz cycle on the same recursion failure. |
88 | | */ |
89 | 536k | for (size_t i = 0; i < size; i++) { |
90 | 536k | if (!data[i]) { |
91 | 10 | size = i; |
92 | 10 | break; |
93 | 10 | } |
94 | | |
95 | 536k | if (data[i] == '{') { |
96 | 25.0k | depth++; |
97 | 25.0k | if (depth > max_depth) max_depth = depth; |
98 | 511k | } else if ((data[i] == '}') && (depth > 0)) { |
99 | 3.81k | depth--; |
100 | 3.81k | } |
101 | 536k | } |
102 | 114 | if (max_depth > 64) return 0; |
103 | | |
104 | 110 | config = main_config_alloc(NULL); |
105 | 110 | if (!config) return 0; |
106 | | |
107 | 110 | config->root_cs = cf_section_alloc(config, NULL, "main", NULL); |
108 | 110 | if (!config->root_cs) { |
109 | 0 | talloc_free(config); |
110 | 0 | return 0; |
111 | 0 | } |
112 | 110 | cf_section_set_unlang(config->root_cs); |
113 | | |
114 | 110 | (void) cf_file_read_buffer(config->root_cs, (char const *) data, size, "/"); |
115 | | |
116 | 110 | talloc_free(config); |
117 | | |
118 | | /* |
119 | | * Clear error messages from the run, keeping malloc/free |
120 | | * balanced so the fuzzer's leak heuristics do not fire. |
121 | | */ |
122 | 110 | fr_strerror_clear(); |
123 | | |
124 | 110 | return 0; |
125 | 110 | } |