From: David Korczynski Date: Sat, 16 May 2026 15:02:56 +0000 (+0100) Subject: add config file handling logic X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7fbcc4d249b19f2b7c62c02ebdfaaa9881504536;p=thirdparty%2Ffreeradius-server.git add config file handling logic This adds coverage for missing files, e.g. cf_file.c and cf_utils.c Signed-off-by: David Korczynski --- diff --git a/src/bin/all.mk b/src/bin/all.mk index 03a57c1ff90..c70bb44c3b5 100644 --- a/src/bin/all.mk +++ b/src/bin/all.mk @@ -62,6 +62,11 @@ endef # SUBMAKEFILES += fuzzer_json.mk +# +# Enable fuzzer_cf +# +SUBMAKEFILES += fuzzer_cf.mk + $(foreach X,${FUZZER_PROTOCOLS},$(eval $(call FUZZ_PROTOCOL,${X}))) .PHONY: fuzzer.help diff --git a/src/bin/fuzzer_cf.c b/src/bin/fuzzer_cf.c new file mode 100644 index 00000000000..d335ad83535 --- /dev/null +++ b/src/bin/fuzzer_cf.c @@ -0,0 +1,145 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * + * @file src/bin/fuzzer_cf.c + * @brief Functions to fuzz the FreeRADIUS config-file parser + * + * Targets cf_file_read() and the section/pair tokenisers it drives + * (cf_file.c, cf_util.c, cf_parse.c). The full configuration grammar - + * sections, pairs, quoting, line continuation, $INCLUDE / $-INCLUDE + * resolution, operators, and xlat expansions - is exercised through + * this single entry point. + * + * The harness writes each fuzzer input to a per-process file under the + * system temporary directory because cf_file_read() is path-based and + * resolves $INCLUDE relative to the directory of the file being parsed. + * A pid-suffixed name keeps the harness safe under libFuzzer's -jobs=N. + */ +RCSID("$Id$") + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +int LLVMFuzzerInitialize(int *argc, char ***argv); +int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len); + +static char fuzz_path[64]; + +int LLVMFuzzerInitialize(UNUSED int *argc, UNUSED char ***argv) +{ + /* + * Each fuzzer worker (libFuzzer -jobs=N) gets its own pid, + * so a pid-suffixed path avoids races between workers. + */ + snprintf(fuzz_path, sizeof(fuzz_path), "/tmp/fuzzer_cf_input.%d.conf", + (int) getpid()); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + int fd; + main_config_t *config; + size_t depth = 0, max_depth = 0; + + /* + * cap input size: the parser is line-oriented and a + * pathological input can cost a great deal of time without + * exposing new states. + */ + if (size > 16 * 1024) return 0; + + /* + * Pre-filter on brace nesting depth. The config parser + * recurses via C function calls on '{'-introduced + * sub-sections (parse_subrequest, parse_foreach, + * parse_switch, etc., and cf_section_pass2 walking the + * section tree). libFuzzer trivially discovers inputs of + * the form "{{{{ ... }}}}" that exhaust the C stack without + * revealing any new parser states. Real configs nest fewer + * than ten levels deep; the cap is set generously here so + * that any legitimate nesting still reaches the parser. + * + * Quoting is intentionally ignored: a conservative count + * can only over-reject, never under-reject, and the cost of + * dropping a few well-formed inputs with '{' embedded in + * strings is negligible compared with the cost of burning + * every fuzz cycle on the same recursion failure. + */ + for (size_t i = 0; i < size; i++) { + if (data[i] == '{') { + depth++; + if (depth > max_depth) max_depth = depth; + } else if ((data[i] == '}') && (depth > 0)) { + depth--; + } + } + if (max_depth > 64) return 0; + + /* + * cf_file_read() takes a path; mirror the input to disk. + */ + fd = open(fuzz_path, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd < 0) return 0; + if (size && write(fd, data, size) != (ssize_t) size) { + close(fd); + unlink(fuzz_path); + return 0; + } + close(fd); + + config = main_config_alloc(NULL); + if (!config) { + unlink(fuzz_path); + return 0; + } + + config->root_cs = cf_section_alloc(config, NULL, "main", NULL); + if (!config->root_cs) { + talloc_free(config); + unlink(fuzz_path); + return 0; + } + cf_section_set_unlang(config->root_cs); + + (void) cf_file_read(config->root_cs, fuzz_path, true); + + talloc_free(config); + unlink(fuzz_path); + + /* + * Clear error messages from the run, keeping malloc/free + * balanced so the fuzzer's leak heuristics do not fire. + */ + fr_strerror_clear(); + + return 0; +} diff --git a/src/bin/fuzzer_cf.mk b/src/bin/fuzzer_cf.mk new file mode 100644 index 00000000000..4c2b1666137 --- /dev/null +++ b/src/bin/fuzzer_cf.mk @@ -0,0 +1,9 @@ +TARGET := fuzzer_cf$(E) +SOURCES := fuzzer_cf.c + +TGT_PREREQS := $(LIBFREERADIUS_SERVER) libfreeradius-util$(L) + +SRC_CFLAGS := -fsanitize=fuzzer + +TGT_LDFLAGS := -fsanitize=fuzzer +TGT_LDLIBS := $(LIBS) -ltalloc -ldl