Add the core runtime implementation for KFuzzTest. This includes the module initialization, and the logic for receiving and processing user-provided inputs through debugfs. On module load, the framework discovers all of the simple test targets (FUZZ_TEST_SIMPLE) by iterating over the .kfuzztest_simple_target section, creating a corresponding debugfs directory with a write-only 'input_simple' file for each of them. Writing to an 'input_simple' file triggers the following fuzzing sequence: 1. The binary input is allocated and copied from userspace into a kernel buffer. 2. The buffer and its length are passed immediately to the user-defined test logic. 3. The kernel is tainted with TAINT_TEST to indicate that untrusted input has been fed directly to the internal kernel functions. This lightweight implementation relies on the caller (e.g., a fuzzer or script) to provide raw binary data that the target function can process. Signed-off-by: Ethan Graham --- PR v4: - Remove parsing, relocation, and KASAN poisoning logic to support the move to a simple-only design. - Remove the '_config' debugfs directory and associated state tracking (minimum alignment, invocation counts) to reduce complexity. - Enforce zero offset in `kfuzztest_write_cb_common` to ensure inputs are passed down as single, contiguous blocks. PR v3: - Handle FUZZ_TEST_SIMPLE targets by creating a write-only 'input_simple' under the fuzz target's directory. - Add implementation for `kfuzztest_write_input_cb`. PR v2: - Fix build issues identified by the kernel test robot . - Address some nits pointed out by Alexander Potapenko. PR v1: - Update kfuzztest/parse.c interfaces to take `unsigned char *` instead of `void *`, reducing the number of pointer casts. - Expose minimum region alignment via a new debugfs file. - Expose number of successful invocations via a new debugfs file. - Refactor module init function, add _config directory with entries containing KFuzzTest state information. - Account for kasan_poison_range() return value in input parsing logic. - Validate alignment of payload end. - Move static sizeof assertions into /lib/kfuzztest/main.c. - Remove the taint in kfuzztest/main.c. We instead taint the kernel as soon as a fuzz test is invoked for the first time, which is done in the primary FUZZ_TEST macro. RFC v2: - The module's init function now taints the kernel with TAINT_TEST. --- --- lib/Makefile | 2 + lib/kfuzztest/Makefile | 4 ++ lib/kfuzztest/input.c | 47 ++++++++++++++ lib/kfuzztest/main.c | 142 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 195 insertions(+) create mode 100644 lib/kfuzztest/Makefile create mode 100644 lib/kfuzztest/input.c create mode 100644 lib/kfuzztest/main.c diff --git a/lib/Makefile b/lib/Makefile index 392ff808c9b9..02789bf88499 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -325,6 +325,8 @@ obj-$(CONFIG_GENERIC_LIB_CMPDI2) += cmpdi2.o obj-$(CONFIG_GENERIC_LIB_UCMPDI2) += ucmpdi2.o obj-$(CONFIG_OBJAGG) += objagg.o +obj-$(CONFIG_KFUZZTEST) += kfuzztest/ + # pldmfw library obj-$(CONFIG_PLDMFW) += pldmfw/ diff --git a/lib/kfuzztest/Makefile b/lib/kfuzztest/Makefile new file mode 100644 index 000000000000..3cf5da5597a4 --- /dev/null +++ b/lib/kfuzztest/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_KFUZZTEST) += kfuzztest.o +kfuzztest-objs := main.o input.o diff --git a/lib/kfuzztest/input.c b/lib/kfuzztest/input.c new file mode 100644 index 000000000000..aae966ea76b3 --- /dev/null +++ b/lib/kfuzztest/input.c @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * KFuzzTest input handling. + * + * Copyright 2025 Google LLC + */ +#include + +int kfuzztest_write_cb_common(struct file *filp, const char __user *buf, size_t len, loff_t *off, void **test_buffer) +{ + void *buffer; + ssize_t ret; + + /* + * Enforce a zero-offset to ensure that all data is passed down in a + * single contiguous blob and not fragmented across multiple write + * system calls. + */ + if (*off) + return -EINVAL; + + /* + * Taint the kernel on the first fuzzing invocation. The debugfs + * interface provides a high-risk entry point for userspace to + * call kernel functions with untrusted input. + */ + if (!test_taint(TAINT_TEST)) + add_taint(TAINT_TEST, LOCKDEP_STILL_OK); + + if (len > KFUZZTEST_MAX_INPUT_SIZE) { + pr_warn("kfuzztest: user input of size %zu is too large", len); + return -EINVAL; + } + + buffer = kzalloc(len, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + ret = simple_write_to_buffer(buffer, len, off, buf, len); + if (ret != len) { + kfree(buffer); + return -EFAULT; + } + + *test_buffer = buffer; + return 0; +} diff --git a/lib/kfuzztest/main.c b/lib/kfuzztest/main.c new file mode 100644 index 000000000000..40a9e56c81ad --- /dev/null +++ b/lib/kfuzztest/main.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KFuzzTest core module initialization and debugfs interface. + * + * Copyright 2025 Google LLC + */ +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ethan Graham "); +MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFuzzTest)"); + +extern const struct kfuzztest_simple_target __kfuzztest_simple_targets_start[]; +extern const struct kfuzztest_simple_target __kfuzztest_simple_targets_end[]; + +struct target_fops { + struct file_operations target_simple; +}; + +/** + * struct kfuzztest_state - global state for the KFuzzTest module + * + * @kfuzztest_dir: The root debugfs directory, /sys/kernel/debug/kfuzztest/. + * @num_targets: number of registered targets. + * @target_fops: array of file operations for each registered target. + */ +struct kfuzztest_state { + struct dentry *kfuzztest_dir; + struct target_fops *target_fops; + size_t num_targets; +}; + +static struct kfuzztest_state state; + +static void cleanup_kfuzztest_state(struct kfuzztest_state *st) +{ + debugfs_remove_recursive(st->kfuzztest_dir); + st->num_targets = 0; + kfree(st->target_fops); + st->target_fops = NULL; +} + +static const umode_t KFUZZTEST_INPUT_PERMS = 0222; + +static int initialize_target_dir(struct kfuzztest_state *st, const struct kfuzztest_simple_target *targ, + struct target_fops *fops) +{ + struct dentry *dir, *input_simple; + int err = 0; + + dir = debugfs_create_dir(targ->name, st->kfuzztest_dir); + if (!dir) + err = -ENOMEM; + else if (IS_ERR(dir)) + err = PTR_ERR(dir); + if (err) { + pr_info("kfuzztest: failed to create /kfuzztest/%s dir", targ->name); + goto out; + } + + input_simple = debugfs_create_file("input_simple", KFUZZTEST_INPUT_PERMS, dir, NULL, &fops->target_simple); + if (!input_simple) + err = -ENOMEM; + else if (IS_ERR(input_simple)) + err = PTR_ERR(input_simple); + if (err) + pr_info("kfuzztest: failed to create /kfuzztest/%s/input_simple", targ->name); +out: + return err; +} + +/** + * kfuzztest_init - initializes the debug filesystem for KFuzzTest + * + * Each registered target in the ".kfuzztest_simple_target" section gets its own + * subdirectory under "/sys/kernel/debug/kfuzztest/" containing one + * write-only "input_simple" file used for receiving binary inputs from + * userspace. + * + * @return 0 on success or an error + */ +static int __init kfuzztest_init(void) +{ + const struct kfuzztest_simple_target *targ; + int err = 0; + int i = 0; + + state.num_targets = __kfuzztest_simple_targets_end - __kfuzztest_simple_targets_start; + state.target_fops = kzalloc(sizeof(struct target_fops) * state.num_targets, GFP_KERNEL); + if (!state.target_fops) + return -ENOMEM; + + /* Create the main "kfuzztest" directory in /sys/kernel/debug. */ + state.kfuzztest_dir = debugfs_create_dir("kfuzztest", NULL); + if (!state.kfuzztest_dir) { + pr_warn("kfuzztest: could not create 'kfuzztest' debugfs directory"); + return -ENOMEM; + } + if (IS_ERR(state.kfuzztest_dir)) { + pr_warn("kfuzztest: could not create 'kfuzztest' debugfs directory"); + err = PTR_ERR(state.kfuzztest_dir); + state.kfuzztest_dir = NULL; + return err; + } + + for (targ = __kfuzztest_simple_targets_start; targ < __kfuzztest_simple_targets_end; targ++, i++) { + state.target_fops[i].target_simple = (struct file_operations){ + .owner = THIS_MODULE, + .write = targ->write_input_cb, + }; + err = initialize_target_dir(&state, targ, &state.target_fops[i]); + /* + * Bail out if a single target fails to initialize. This avoids + * partial setup, and a failure here likely indicates an issue + * with debugfs. + */ + if (err) + goto cleanup_failure; + pr_info("kfuzztest: registered target %s", targ->name); + } + return 0; + +cleanup_failure: + cleanup_kfuzztest_state(&state); + return err; +} + +static void __exit kfuzztest_exit(void) +{ + pr_info("kfuzztest: exiting"); + cleanup_kfuzztest_state(&state); +} + +module_init(kfuzztest_init); +module_exit(kfuzztest_exit); -- 2.51.0