From 38e2cd18372f918fd2a4115725713d9cd558c279 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Mon, 12 Jan 2026 19:13:55 +0100 Subject: [PATCH 1/6] kfuzztest: add user-facing API and data structures Add the foundational user-facing components for the KFuzzTest framework. This includes the main API header , the Kconfig option to enable the feature, and the required linker script changes which introduce a new ELF section in vmlinux. Note that KFuzzTest is intended strictly for debug builds only, and should never be enabled in a production build. The fact that it exposes internal kernel functions and state directly to userspace may constitute a serious security vulnerability if used for any reason other than testing. The header defines: - The FUZZ_TEST_SIMPLE() macro for creating test targets. - The `struct kfuzztest_simple_target` structure used to register tests. - The linker section (.kfuzztest_simple_target) where test metadata is stored for discovery by the framework. This patch only adds the public interface and build integration; no runtime logic is included. Signed-off-by: Ethan Graham --- PR v4: - Remove the complex FUZZ_TEST macro and associated dependencies, including domain constraints, annotations, and de-serialization, dramatically simplifying the flow. - Drop unused ELF sections (.kfuzztest_constraint, etc...) from the linker script, keeping only .kfuzztest_simple_target. PR v3: - Reorder definitions in kfuzztest.h for better flow and readability. - Introduce __KFUZZTEST_CONSTRAINT macro in preparation for the introduction of the FUZZ_TEST_SIMPLE macro in the following patch, which uses it for manually emitting constraint metadata. PR v1: - Move KFuzzTest metadata definitions to generic vmlinux linkage so that the framework isn't bound to x86_64. - Return -EFAULT when simple_write_to_buffer returns a value not equal to the input length in the main FUZZ_TEST macro. - Enforce a maximum input size of 64KiB in the main FUZZ_TEST macro, returning -EINVAL when it isn't respected. - Refactor KFUZZTEST_ANNOTATION_* macros. - Taint the kernel with TAINT_TEST inside the FUZZ_TEST macro when a fuzz target is invoked for the first time. --- --- include/asm-generic/vmlinux.lds.h | 14 ++++- include/linux/kfuzztest.h | 88 +++++++++++++++++++++++++++++++ lib/Kconfig.debug | 1 + lib/kfuzztest/Kconfig | 16 ++++++ 4 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 include/linux/kfuzztest.h create mode 100644 lib/kfuzztest/Kconfig diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index 8ca130af301fc5..a99084c6475427 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -391,7 +391,8 @@ TRACE_PRINTKS() \ BPF_RAW_TP() \ TRACEPOINT_STR() \ - KUNIT_TABLE() + KUNIT_TABLE() \ + KFUZZTEST_TABLE() /* * Data section helpers @@ -985,6 +986,17 @@ BOUNDED_SECTION_POST_LABEL(.kunit_init_test_suites, \ __kunit_init_suites, _start, _end) +#ifdef CONFIG_KFUZZTEST +#define KFUZZTEST_TABLE() \ + . = ALIGN(PAGE_SIZE); \ + __kfuzztest_simple_targets_start = .; \ + KEEP(*(.kfuzztest_simple_target)); \ + __kfuzztest_simple_targets_end = .; \ + +#else /* CONFIG_KFUZZTEST */ +#define KFUZZTEST_TABLE() +#endif /* CONFIG_KFUZZTEST */ + #ifdef CONFIG_BLK_DEV_INITRD #define INIT_RAM_FS \ . = ALIGN(4); \ diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h new file mode 100644 index 00000000000000..62fce926776142 --- /dev/null +++ b/include/linux/kfuzztest.h @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * The Kernel Fuzz Testing Framework (KFuzzTest) API for defining fuzz targets + * for internal kernel functions. + * + * Copyright 2025 Google LLC + */ +#ifndef KFUZZTEST_H +#define KFUZZTEST_H + +#include +#include +#include + +#define KFUZZTEST_MAX_INPUT_SIZE (PAGE_SIZE * 16) + +/* Common code for receiving inputs from userspace. */ +int kfuzztest_write_cb_common(struct file *filp, const char __user *buf, size_t len, loff_t *off, void **test_buffer); + +struct kfuzztest_simple_target { + const char *name; + ssize_t (*write_input_cb)(struct file *filp, const char __user *buf, size_t len, loff_t *off); +}; + +/** + * FUZZ_TEST_SIMPLE - defines a KFuzzTest target + * + * @test_name: the unique identifier for the fuzz test, which is used to name + * the debugfs entry. + * + * This macro defines a fuzz target entry point that accepts raw byte buffers + * from userspace. It registers a struct kfuzztest_simple_target which the + * framework exposes via debugfs. + * + * When userspace writes to the corresponding debugfs file, the framework + * allocates a kernel buffer, copies the user data, and passes it to the + * logic defined in the macro body. + * + * User-provided Logic: + * The developer must provide the body of the fuzz test logic within the curly + * braces following the macro invocation. Within this scope, the framework + * implicitly defines the following variables: + * + * - `char *data`: A pointer to the raw input data. + * - `size_t datalen`: The length of the input data. + * + * Example Usage: + * + * // 1. The kernel function that we want to fuzz. + * int process_data(const char *data, size_t datalen); + * + * // 2. Define a fuzz target using the FUZZ_TEST_SIMPLE macro. + * FUZZ_TEST_SIMPLE(test_process_data) + * { + * // Call the function under test using the `data` and `datalen` + * // variables. + * process_data(data, datalen); + * } + * + */ +#define FUZZ_TEST_SIMPLE(test_name) \ + static ssize_t kfuzztest_simple_write_cb_##test_name(struct file *filp, const char __user *buf, size_t len, \ + loff_t *off); \ + static ssize_t kfuzztest_simple_logic_##test_name(char *data, size_t datalen); \ + static const struct kfuzztest_simple_target __fuzz_test_simple__##test_name __section( \ + ".kfuzztest_simple_target") __used = { \ + .name = #test_name, \ + .write_input_cb = kfuzztest_simple_write_cb_##test_name, \ + }; \ + static ssize_t kfuzztest_simple_write_cb_##test_name(struct file *filp, const char __user *buf, size_t len, \ + loff_t *off) \ + { \ + void *buffer; \ + int ret; \ + \ + ret = kfuzztest_write_cb_common(filp, buf, len, off, &buffer); \ + if (ret < 0) \ + goto out; \ + ret = kfuzztest_simple_logic_##test_name(buffer, len); \ + if (ret == 0) \ + ret = len; \ + kfree(buffer); \ +out: \ + return ret; \ + } \ + static ssize_t kfuzztest_simple_logic_##test_name(char *data, size_t datalen) + +#endif /* KFUZZTEST_H */ diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index ba36939fda79bf..ad3257301b131b 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1943,6 +1943,7 @@ endmenu menu "Kernel Testing and Coverage" source "lib/kunit/Kconfig" +source "lib/kfuzztest/Kconfig" config NOTIFIER_ERROR_INJECTION tristate "Notifier error injection" diff --git a/lib/kfuzztest/Kconfig b/lib/kfuzztest/Kconfig new file mode 100644 index 00000000000000..d8e9caaac1088a --- /dev/null +++ b/lib/kfuzztest/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config KFUZZTEST + bool "KFuzzTest - enable support for internal fuzz targets" + depends on DEBUG_FS && DEBUG_KERNEL + help + Enables support for the kernel fuzz testing framework (KFuzzTest), an + interface for exposing internal kernel functions to a userspace fuzzing + engine. KFuzzTest targets are exposed via a debugfs interface that + accepts raw binary inputs from userspace, and is designed to make it + easier to fuzz deeply nested kernel code that is hard to reach from + the system call boundary. Using a simple macro-based API, developers + can add a new fuzz target with minimal boilerplate code. + + WARNING: This exposes internal kernel functions directly to userspace + and must NEVER be enabled in production builds. From aeb7608a214ef4b14838300c76db550f12c67b3d Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Mon, 12 Jan 2026 19:18:26 +0100 Subject: [PATCH 2/6] kfuzztest: implement core module and input processing 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 aaf677cf4527e8..3acdf76ca979e8 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -324,6 +324,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 00000000000000..3cf5da5597a478 --- /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 00000000000000..aae966ea76b33c --- /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 00000000000000..40a9e56c81ada4 --- /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); From 8db4ec60eaae5c0fd5ae7dfaf814e6d4df517e18 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Tue, 6 Jan 2026 16:32:29 +0100 Subject: [PATCH 3/6] kfuzztest: add ReST documentation Add Documentation/dev-tools/kfuzztest.rst and reference it in the dev-tools index. Signed-off-by: Ethan Graham --- PR v4: - Rework documentation to focus exclusively on the `FUZZ_TEST_SIMPLE` macro, removing all references to the legacy complex targets and serialization format. - Remove obsolete sections describing DWARF constraints, annotations, and the userspace bridge tool. - Add examples demonstrating basic usage with standard command-line tools. --- --- Documentation/dev-tools/index.rst | 1 + Documentation/dev-tools/kfuzztest.rst | 152 ++++++++++++++++++++++++++ include/linux/kfuzztest.h | 2 + 3 files changed, 155 insertions(+) create mode 100644 Documentation/dev-tools/kfuzztest.rst diff --git a/Documentation/dev-tools/index.rst b/Documentation/dev-tools/index.rst index 4b8425e348abd1..32b634ebea918e 100644 --- a/Documentation/dev-tools/index.rst +++ b/Documentation/dev-tools/index.rst @@ -33,6 +33,7 @@ Documentation/process/debugging/index.rst kfence kselftest kunit/index + kfuzztest ktap checkuapi gpio-sloppy-logic-analyzer diff --git a/Documentation/dev-tools/kfuzztest.rst b/Documentation/dev-tools/kfuzztest.rst new file mode 100644 index 00000000000000..f5ccf545d45d8b --- /dev/null +++ b/Documentation/dev-tools/kfuzztest.rst @@ -0,0 +1,152 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. Copyright 2025 Google LLC + +========================================= +Kernel Fuzz Testing Framework (KFuzzTest) +========================================= + +Overview +======== + +The Kernel Fuzz Testing Framework (KFuzzTest) is a framework designed to expose +internal kernel functions to a userspace fuzzing engine. + +It is intended for testing stateless or low-state functions that are difficult +to reach from the system call interface, such as routines involved in file +format parsing or complex data transformations. This provides a method for +in-situ fuzzing of kernel code without requiring that it be built as a separate +userspace library or that its dependencies be stubbed out. + +The framework consists of two main components: + +1. An API, based on the ``FUZZ_TEST_SIMPLE`` macro, for defining test targets + directly in the kernel tree. +2. A ``debugfs`` interface through which a userspace fuzzer submits raw + binary test inputs. + +.. warning:: + KFuzzTest is a debugging and testing tool. It exposes internal kernel + functions to userspace with minimal sanitization and is designed for + use in controlled test environments only. It must **NEVER** be enabled + in production kernels. + +Supported Architectures +======================= + +KFuzzTest is designed for generic architecture support. It has only been +explicitly tested on x86_64. + +Usage +===== + +To enable KFuzzTest, configure the kernel with:: + + CONFIG_KFUZZTEST=y + +which depends on ``CONFIG_DEBUGFS`` for receiving userspace inputs, and +``CONFIG_DEBUG_KERNEL`` as an additional guardrail for preventing KFuzzTest +from finding its way into a production build accidentally. + +The KFuzzTest sample fuzz targets can be built in with +``CONFIG_SAMPLE_KFUZZTEST``. + +KFuzzTest currently only supports targets that are built into the kernel, as the +core module's startup process discovers fuzz targets from a dedicated ELF +section during startup. + +Defining a KFuzzTest target +--------------------------- + +A fuzz target should be defined in a .c file. The recommended place to define +this is under the subsystem's ``/tests`` directory in a ``_kfuzz.c`` +file, following the convention used by KUnit. The only strict requirement is +that the function being fuzzed is visible to the fuzz target. + +Use the ``FUZZ_TEST_SIMPLE`` macro to define a fuzz target. This macro is +designed for functions that accept a buffer and its length (e.g., +``(const char *data, size_t datalen)``). + +This macro provides ``data`` and ``datalen`` variables implicitly to the test +body. + +.. code-block:: c + + /* 1. The kernel function that we want to fuzz. */ + int process_data(const char *data, size_t len); + + /* 2. Define the fuzz target with the FUZZ_TEST_SIMPLE macro. */ + FUZZ_TEST_SIMPLE(test_process_data) + { + /* 3. Call the kernel function with the provided input. */ + process_data(data, datalen); + } + +A ``FUZZ_TEST_SIMPLE`` target creates a debugfs directory +(``/sys/kernel/debug/kfuzztest/``) containing a single write-only +file ``input_simple``: writing a raw blob to this file will invoke the fuzz +target, passing the blob as ``(data, datalen)``. + +Basic Usage +^^^^^^^^^^^ + +Because the interface accepts raw binary data, targets can be smoke-tested or +fuzzed naively using standard command-line tools without any external +dependencies. + +For example, to feed 128 bytes of random data to the target defined above: + +.. code-block:: sh + + head -c 128 /dev/urandom > \ + /sys/kernel/debug/kfuzztest/test_process_data/input_simple + +Integration with Fuzzers +^^^^^^^^^^^^^^^^^^^^^^^^ + +The simple interface makes it easy to integrate with userspace fuzzers (e.g., +LibFuzzer, AFL++, honggfuzz). A LibFuzzer, for example, harness may look like +so: + +.. code-block:: c + + /* Path to the simple target's input file */ + const char *filepath = "/sys/kernel/debug/kfuzztest/test_process_data/input_simple"; + + extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + FILE *f = fopen(filepath, "w"); + if (!f) { + return 0; /* Fuzzer should not stop. */ + } + /* Write the raw fuzzer input directly. */ + fwrite(Data, 1, Size, f); + fclose(f); + return 0; + } + +Note that while it is simple to feed inputs to KFuzzTest targets, kernel +coverage collection is key for the effectiveness of a coverage-guided fuzzer; +setup of KCOV or other coverage mechanisms is outside of KFuzzTest's scope. + +Metadata +-------- + +The ``FUZZ_TEST_SIMPLE`` macro embeds metadata into a dedicated section within +the main ``.data`` section of the final ``vmlinux`` binary: +``.kfuzztest_simple_target``, delimited by ``__kfuzztest_simple_targets_start`` +and ``__kfuzztest_simple_targets_end``. + +The metadata serves two purposes: + +1. The core module uses the ``.kfuzztest_simple_target`` section at boot to + discover every test instance and create its ``debugfs`` directory and + ``input_simple`` file. +2. Tooling can use this section for offline discovery. While available fuzz + targets can be trivially enumerated at runtime by listing the directories + under ``/sys/kernel/debug/kfuzztest``, the metadata allows fuzzing + orchestrators to index available fuzz targets directly from the ``vmlinux`` + binary without needing to boot the kernel. + +This metadata consists of an array of ``struct kfuzztest_simple_target``. The +``name`` field within this struct references data in other locations of the +``vmlinux`` binary, and therefore a userspace tool that parses the ELF must +resolve these pointers to read the underlying data. diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index 62fce926776142..4f210c5ec9199a 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -3,6 +3,8 @@ * The Kernel Fuzz Testing Framework (KFuzzTest) API for defining fuzz targets * for internal kernel functions. * + * For more information please see Documentation/dev-tools/kfuzztest.rst. + * * Copyright 2025 Google LLC */ #ifndef KFUZZTEST_H From 1a67b07275a817129e5cd79f56a6896001030cc2 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Mon, 12 Jan 2026 19:22:47 +0100 Subject: [PATCH 4/6] kfuzztest: add KFuzzTest sample fuzz targets Add two simple fuzz target samples to demonstrate the KFuzzTest API and provide basic self-tests for the framework. These examples showcase how a developer can define a fuzz target using the FUZZ_TEST_SIMPLE() macro. It also serves as a runtime sanity check, ensuring that the framework correctly passes the input buffer and that KASAN correctly detects out-of-bounds memory accesses (in this case, a buffer underflow) on the allocated test data. This target can be fuzzed naively by writing random data into the debugfs 'input_simple' file and verifying that the KASAN report is triggered. Signed-off-by: Ethan Graham Acked-by: Alexander Potapenko --- PR v4: - Remove the `test_underflow_on_nested_buffer` sample target which relied on the now removed `FUZZ_TEST` macro. - Update the sample comment to demonstrate naive fuzzing (using `head`) instead of the removed bridge tool. - Fix stale comments referencing internal layout structures. PR v3: - Use the FUZZ_TEST_SIMPLE macro in the `underflow_on_buffer` sample fuzz target instead of FUZZ_TEST. PR v2: - Fix build issues pointed out by the kernel test robot . --- --- samples/Kconfig | 7 ++++ samples/Makefile | 1 + samples/kfuzztest/Makefile | 3 ++ samples/kfuzztest/underflow_on_buffer.c | 52 +++++++++++++++++++++++++ 4 files changed, 63 insertions(+) create mode 100644 samples/kfuzztest/Makefile create mode 100644 samples/kfuzztest/underflow_on_buffer.c diff --git a/samples/Kconfig b/samples/Kconfig index 5bc7c9e5a59e0a..ff5cf3d07102a3 100644 --- a/samples/Kconfig +++ b/samples/Kconfig @@ -320,6 +320,13 @@ config SAMPLE_HUNG_TASK Reading these files with multiple processes triggers hung task detection by holding locks for a long time (256 seconds). +config SAMPLE_KFUZZTEST + bool "Build KFuzzTest sample targets" + depends on KFUZZTEST + help + Build KFuzzTest sample targets that serve as selftests for raw input + delivery and KASAN out-of-bounds detection. + source "samples/rust/Kconfig" source "samples/damon/Kconfig" diff --git a/samples/Makefile b/samples/Makefile index 07641e177bd8bb..3a0e7f744f445d 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -44,4 +44,5 @@ obj-$(CONFIG_SAMPLE_DAMON_WSSE) += damon/ obj-$(CONFIG_SAMPLE_DAMON_PRCL) += damon/ obj-$(CONFIG_SAMPLE_DAMON_MTIER) += damon/ obj-$(CONFIG_SAMPLE_HUNG_TASK) += hung_task/ +obj-$(CONFIG_SAMPLE_KFUZZTEST) += kfuzztest/ obj-$(CONFIG_SAMPLE_TSM_MR) += tsm-mr/ diff --git a/samples/kfuzztest/Makefile b/samples/kfuzztest/Makefile new file mode 100644 index 00000000000000..2dc5d424824d63 --- /dev/null +++ b/samples/kfuzztest/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_SAMPLE_KFUZZTEST) += underflow_on_buffer.o diff --git a/samples/kfuzztest/underflow_on_buffer.c b/samples/kfuzztest/underflow_on_buffer.c new file mode 100644 index 00000000000000..5568c5e6be7adb --- /dev/null +++ b/samples/kfuzztest/underflow_on_buffer.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file contains a KFuzzTest example target that ensures that a buffer + * underflow on a region triggers a KASAN OOB access report. + * + * Copyright 2025 Google LLC + */ + +/** + * test_underflow_on_buffer - a sample fuzz target + * + * This sample fuzz target serves to illustrate the usage of the + * FUZZ_TEST_SIMPLE macro, as well as provide a sort of self-test that KFuzzTest + * functions correctly for trivial fuzz targets. In KASAN builds, fuzzing this + * harness should trigger a report for every input (provided that its length is + * greater than 0 and less than KFUZZTEST_MAX_INPUT_SIZE). + * + * This harness can be invoked (naively) like so: + * head -c 128 /dev/urandom > \ + * /sys/kernel/debug/kfuzztest/test_underflow_on_buffer/input_simple + */ +#include + +static void underflow_on_buffer(char *buf, size_t buflen) +{ + size_t i; + + /* + * Print the address range of `buf` to allow correlation with the + * subsequent KASAN report. + */ + pr_info("buf = [%px, %px)", buf, buf + buflen); + + /* First ensure that all bytes in `buf` are accessible. */ + for (i = 0; i < buflen; i++) + READ_ONCE(buf[i]); + /* + * Provoke a buffer underflow on the first byte preceding `buf`, + * triggering a KASAN report. + */ + READ_ONCE(*((char *)buf - 1)); +} + +/** + * Define the fuzz target. This wrapper ensures that the `underflow_on_buffer` + * function is invoked with the data provided from userspace. + */ +FUZZ_TEST_SIMPLE(test_underflow_on_buffer) +{ + underflow_on_buffer(data, datalen); + return 0; +} From 727713dc10e7fc9a6c590f1d7a5d0a6de42e521c Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Mon, 12 Jan 2026 19:24:34 +0100 Subject: [PATCH 5/6] crypto: implement KFuzzTest targets for PKCS7 and RSA parsing Add KFuzzTest targets for pkcs7_parse_message, rsa_parse_pub_key, and rsa_parse_priv_key to serve as real-world examples of how the framework is used. These functions are ideal candidates for KFuzzTest as they perform complex parsing of user-controlled data but are not directly exposed at the syscall boundary. This makes them difficult to exercise with traditional fuzzing tools and showcases the primary strength of the KFuzzTest framework: providing an interface to fuzz internal functions. To validate the effectiveness of the framework on these new targets, we injected two artificial bugs and let syzkaller fuzz the targets in an attempt to catch them. The first of these was calling the asn1 decoder with an incorrect input from pkcs7_parse_message, like so: - ret = asn1_ber_decoder(&pkcs7_decoder, ctx, data, datalen); + ret = asn1_ber_decoder(&pkcs7_decoder, ctx, data, datalen + 1); The second was bug deeper inside of asn1_ber_decoder itself, like so: - for (len = 0; n > 0; n--) + for (len = 0; n >= 0; n--) syzkaller was able to trigger these bugs, and the associated KASAN slab-out-of-bounds reports, within seconds. The targets are defined within crypto/asymmetric-keys/tests. Signed-off-by: Ethan Graham --- PR v4: - Use pkcs7_free_message() instead of kfree() on the success path of the pkcs7_parse_message fuzz target. - Dropped Ignat Korchagin's reviewed-by due to the functional change in switching from kfree to pkcs7_free_message. - Restrict introduced fuzz targets to build only when their dependencies (CONFIG_PKCS7_MESSAGE_PARSER and CONFIG_CRYPTO_RSA) are built-in. This prevents linker errors when they are configured as modules, as KFuzzTest symbols are not exported. PR v3: - Use the FUZZ_TEST_SIMPLE macro for all introduced fuzz targets as they each take `(data, datalen)` pairs. This also removes the need for explicit constraints and annotations which become implicit. PR v2: - Make fuzz targets also depend on the KConfig options needed for the functions they are fuzzing, CONFIG_PKCS7_MESSAGE_PARSER and CONFIG_CRYPTO_RSA respectively. - Fix build issues pointed out by the kernel test robot . - Account for return value of pkcs7_parse_message, and free resources if the function call succeeds. PR v1: - Change the fuzz target build to depend on CONFIG_KFUZZTEST=y, eliminating the need for a separate config option for each individual file as suggested by Ignat Korchagin. - Remove KFUZZTEST_EXPECT_LE on the length of the `key` field inside of the fuzz targets. A maximum length is now set inside of the core input parsing logic. RFC v2: - Move KFuzzTest targets outside of the source files into dedicated _kfuzz.c files under /crypto/asymmetric_keys/tests/ as suggested by Ignat Korchagin and Eric Biggers. --- --- crypto/asymmetric_keys/Makefile | 2 ++ crypto/asymmetric_keys/tests/Makefile | 4 ++++ crypto/asymmetric_keys/tests/pkcs7_kfuzz.c | 18 ++++++++++++++ .../asymmetric_keys/tests/rsa_helper_kfuzz.c | 24 +++++++++++++++++++ 4 files changed, 48 insertions(+) create mode 100644 crypto/asymmetric_keys/tests/Makefile create mode 100644 crypto/asymmetric_keys/tests/pkcs7_kfuzz.c create mode 100644 crypto/asymmetric_keys/tests/rsa_helper_kfuzz.c diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile index bc65d3b98dcbfa..77b825aee6b24f 100644 --- a/crypto/asymmetric_keys/Makefile +++ b/crypto/asymmetric_keys/Makefile @@ -67,6 +67,8 @@ obj-$(CONFIG_PKCS7_TEST_KEY) += pkcs7_test_key.o pkcs7_test_key-y := \ pkcs7_key_type.o +obj-y += tests/ + # # Signed PE binary-wrapped key handling # diff --git a/crypto/asymmetric_keys/tests/Makefile b/crypto/asymmetric_keys/tests/Makefile new file mode 100644 index 00000000000000..b43aa769e2ce65 --- /dev/null +++ b/crypto/asymmetric_keys/tests/Makefile @@ -0,0 +1,4 @@ +pkcs7-kfuzz-y := $(and $(CONFIG_KFUZZTEST),$(filter y, $(CONFIG_PKCS7_MESSAGE_PARSER))) +rsa-helper-kfuzz-y := $(and $(CONFIG_KFUZZTEST),$(filter y, $(CONFIG_CRYPTO_RSA))) +obj-$(pkcs7-kfuzz-y) += pkcs7_kfuzz.o +obj-$(rsa-helper-kfuzz-y) += rsa_helper_kfuzz.o diff --git a/crypto/asymmetric_keys/tests/pkcs7_kfuzz.c b/crypto/asymmetric_keys/tests/pkcs7_kfuzz.c new file mode 100644 index 00000000000000..2e1a59fb60357d --- /dev/null +++ b/crypto/asymmetric_keys/tests/pkcs7_kfuzz.c @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PKCS#7 parser KFuzzTest target. + * + * Copyright 2025 Google LLC + */ +#include +#include + +FUZZ_TEST_SIMPLE(test_pkcs7_parse_message) +{ + struct pkcs7_message *msg; + + msg = pkcs7_parse_message(data, datalen); + if (msg && !IS_ERR(msg)) + pkcs7_free_message(msg); + return 0; +} diff --git a/crypto/asymmetric_keys/tests/rsa_helper_kfuzz.c b/crypto/asymmetric_keys/tests/rsa_helper_kfuzz.c new file mode 100644 index 00000000000000..e45e8fa5319005 --- /dev/null +++ b/crypto/asymmetric_keys/tests/rsa_helper_kfuzz.c @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * RSA key extract helper KFuzzTest targets. + * + * Copyright 2025 Google LLC + */ +#include +#include + +FUZZ_TEST_SIMPLE(test_rsa_parse_pub_key) +{ + struct rsa_key out; + + rsa_parse_pub_key(&out, data, datalen); + return 0; +} + +FUZZ_TEST_SIMPLE(test_rsa_parse_priv_key) +{ + struct rsa_key out; + + rsa_parse_priv_key(&out, data, datalen); + return 0; +} From a8fa42fae4f2c27bdcab7f437d5aaf2af7e424f1 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Mon, 12 Jan 2026 19:24:59 +0100 Subject: [PATCH 6/6] MAINTAINERS: add maintainer information for KFuzzTest Add myself as maintainer and Alexander Potapenko as reviewer for KFuzzTest. Signed-off-by: Ethan Graham Acked-by: Alexander Potapenko --- PR v4: - Remove reference to the kfuzztest-bridge tool that has been removed PR v3: - Update MAINTAINERS to reflect the correct location of kfuzztest-bridge under tools/testing as pointed out by SeongJae Park. --- --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 0d044a58cbfe0f..465ba325e34c4c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14057,6 +14057,13 @@ F: include/linux/kfifo.h F: lib/kfifo.c F: samples/kfifo/ +KFUZZTEST +M: Ethan Graham +R: Alexander Potapenko +F: include/linux/kfuzztest.h +F: lib/kfuzztest/ +F: Documentation/dev-tools/kfuzztest.rst + KGDB / KDB /debug_core M: Jason Wessel M: Daniel Thompson