From 1e0587e1d36211d7ea3ba8bb9eb1305bc74bd013 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Wed, 18 Jun 2025 10:02:55 +0000 Subject: [PATCH 01/32] Add simple configuration for kftf module that is built with kernel into a .ko file. --- include/linux/kftf.h | 17 +++++++++++++++++ lib/Kconfig.debug | 1 + lib/Kconfig.kftf | 4 ++++ lib/Makefile | 2 ++ lib/kftf/Makefile | 4 ++++ lib/kftf/kftf_main.c | 11 +++++++++++ 6 files changed, 39 insertions(+) create mode 100644 include/linux/kftf.h create mode 100644 lib/Kconfig.kftf create mode 100644 lib/kftf/Makefile create mode 100644 lib/kftf/kftf_main.c diff --git a/include/linux/kftf.h b/include/linux/kftf.h new file mode 100644 index 00000000000000..ece1860d1ce393 --- /dev/null +++ b/include/linux/kftf.h @@ -0,0 +1,17 @@ +#ifndef KFTF_H +#define KFTF_H + +#include + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ethan Graham "); +MODULE_DESCRIPTION("Kernel Fuzz Testing Framework"); + +struct foo { + int size; //< size of struct foo + int access; +}; + +void kftf_fuzzable(struct foo *foo); + +#endif /* KFTF_H */ diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index ebe33181b6e6e0..42cd345f93ad81 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1040,6 +1040,7 @@ config MEM_ALLOC_PROFILING_DEBUG source "lib/Kconfig.kasan" source "lib/Kconfig.kfence" source "lib/Kconfig.kmsan" +source "lib/Kconfig.kftf" endmenu # "Memory Debugging" diff --git a/lib/Kconfig.kftf b/lib/Kconfig.kftf new file mode 100644 index 00000000000000..c948ba08ec3fe4 --- /dev/null +++ b/lib/Kconfig.kftf @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config KFTF + tristate "Enable Kernel Fuzz Testing Framework (KFTF)" diff --git a/lib/Makefile b/lib/Makefile index c38582f187dd81..33e75b3682018e 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -354,6 +354,8 @@ obj-$(CONFIG_GENERIC_LIB_CMPDI2) += cmpdi2.o obj-$(CONFIG_GENERIC_LIB_UCMPDI2) += ucmpdi2.o obj-$(CONFIG_OBJAGG) += objagg.o +obj-$(CONFIG_KFTF) += kftf/ + # pldmfw library obj-$(CONFIG_PLDMFW) += pldmfw/ diff --git a/lib/kftf/Makefile b/lib/kftf/Makefile new file mode 100644 index 00000000000000..6e1df924ce37ae --- /dev/null +++ b/lib/kftf/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_KFTF) += kftf.o +kftf-objs := kftf_main.o diff --git a/lib/kftf/kftf_main.c b/lib/kftf/kftf_main.c new file mode 100644 index 00000000000000..dc9dc84f2a7518 --- /dev/null +++ b/lib/kftf/kftf_main.c @@ -0,0 +1,11 @@ +#include +#include + +// contains a bug! +void kftf_fuzzable(struct foo *foo) +{ + // can buffer overflow or underflow, or cause a null pointer dereference + // crashing the kernel + char *data = ((char *)foo) + foo->access; + printk("%s: data at foo[%d] = %x\n", __FUNCTION__, foo->access, *data); +} From 43dc89375f3c8b01b55552a9ac00d588a6b756c3 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Wed, 18 Jun 2025 10:18:06 +0000 Subject: [PATCH 02/32] kftf: Add init and exit functions to module --- lib/kftf/kftf_main.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/kftf/kftf_main.c b/lib/kftf/kftf_main.c index dc9dc84f2a7518..e6bce52a2e0657 100644 --- a/lib/kftf/kftf_main.c +++ b/lib/kftf/kftf_main.c @@ -7,5 +7,25 @@ void kftf_fuzzable(struct foo *foo) // can buffer overflow or underflow, or cause a null pointer dereference // crashing the kernel char *data = ((char *)foo) + foo->access; - printk("%s: data at foo[%d] = %x\n", __FUNCTION__, foo->access, *data); + pr_info("%s: data at foo[%d] = %x\n", __FUNCTION__, foo->access, *data); } + +static int __init kftf_init(void) +{ + struct foo foo = { + .access = -1, + .size = 0, + }; + + pr_info("%s: enter\n", __FUNCTION__); + kftf_fuzzable(&foo); + return 0; +} + +static void __exit kftf_exit(void) +{ + pr_info("%s: exiting\n", __FUNCTION__); +} + +module_init(kftf_init); +module_exit(kftf_exit); From 42d6acc17d47167db836e88a7d2363dd21d40a92 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Wed, 18 Jun 2025 13:12:28 +0000 Subject: [PATCH 03/32] kftf: simple sysfs that accepts a struct for a single function and the binary representation down. Correctly causes a KASAN repot when passing a buggy input to it. --- include/linux/kftf.h | 6 ++-- lib/kftf/kftf_main.c | 18 +++++----- lib/kftf/kftf_simple_fuzzer.h | 66 +++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 13 deletions(-) create mode 100644 lib/kftf/kftf_simple_fuzzer.h diff --git a/include/linux/kftf.h b/include/linux/kftf.h index ece1860d1ce393..ddf8b8fd53a60e 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -7,11 +7,11 @@ MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ethan Graham "); MODULE_DESCRIPTION("Kernel Fuzz Testing Framework"); -struct foo { - int size; //< size of struct foo +struct kftf_simple_arg { + int data; int access; }; -void kftf_fuzzable(struct foo *foo); +void kftf_fuzzable(struct kftf_simple_arg *foo); #endif /* KFTF_H */ diff --git a/lib/kftf/kftf_main.c b/lib/kftf/kftf_main.c index e6bce52a2e0657..5de45c5fd4d887 100644 --- a/lib/kftf/kftf_main.c +++ b/lib/kftf/kftf_main.c @@ -1,30 +1,28 @@ +#include +#include #include #include +#include "kftf_simple_fuzzer.h" + // contains a bug! -void kftf_fuzzable(struct foo *foo) +void kftf_fuzzable(struct kftf_simple_arg *foo) { // can buffer overflow or underflow, or cause a null pointer dereference // crashing the kernel char *data = ((char *)foo) + foo->access; + pr_info("%s: foo->data = %d\n", __FUNCTION__, foo->data); pr_info("%s: data at foo[%d] = %x\n", __FUNCTION__, foo->access, *data); } static int __init kftf_init(void) { - struct foo foo = { - .access = -1, - .size = 0, - }; - - pr_info("%s: enter\n", __FUNCTION__); - kftf_fuzzable(&foo); - return 0; + return kftf_simple_fuzzer_init(); } static void __exit kftf_exit(void) { - pr_info("%s: exiting\n", __FUNCTION__); + kftf_simple_fuzzer_cleanup(); } module_init(kftf_init); diff --git a/lib/kftf/kftf_simple_fuzzer.h b/lib/kftf/kftf_simple_fuzzer.h new file mode 100644 index 00000000000000..da4fb3ab78bb36 --- /dev/null +++ b/lib/kftf/kftf_simple_fuzzer.h @@ -0,0 +1,66 @@ +#ifndef KFTF_SIMPLE_FUZZER_H +#define KFTF_SIMPLE_FUZZER_H + +#include +#include +#include + +struct kftf_simple_fuzzer_state { + struct file_operations fops; + struct dentry *kftf_dir; + struct dentry *input_file; + char buffer[128]; //< buffer for user input +}; + +static struct kftf_simple_fuzzer_state st; + +/** + * write callback for the simple fuzzer + */ +static ssize_t kftf_fuzz_write(struct file *filp, const char __user *buf, + size_t len, loff_t *off) +{ + if (len >= sizeof(st.buffer)) + return -EINVAL; + + if (simple_write_to_buffer(st.buffer, sizeof(st.buffer) - 1, off, buf, + len) < 0) + return -EFAULT; + + if (len != sizeof(struct kftf_simple_arg)) { + pr_warn("incorrect data size\n"); + return -EINVAL; + } + + // intrepret the binary contents of the buffer + struct kftf_simple_arg *fuzz_arg = (void *)st.buffer; + kftf_fuzzable(fuzz_arg); + return len; +} + +static int kftf_simple_fuzzer_init(void) +{ + st.kftf_dir = debugfs_create_dir("kftf", NULL); + if (!st.kftf_dir) + return 1; // TODO: proper errors + + st.fops = (struct file_operations){ + .owner = THIS_MODULE, + .write = kftf_fuzz_write, + }; + + st.input_file = + debugfs_create_file("input", 0222, st.kftf_dir, NULL, &st.fops); + + if (!st.input_file) + return 1; // TODO: proper errors + + return 0; +} + +static void kftf_simple_fuzzer_cleanup(void) +{ + debugfs_remove(st.kftf_dir); +} + +#endif /* KFTF_SIMPLE_FUZZER_H */ From 4a35a644674623580d70a58f8c2392d4342ffe5f Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 20 Jun 2025 09:55:20 +0000 Subject: [PATCH 04/32] kftf: allow tests with macro definitions in .kftf section of binary. Added support for this for x86 arch, but it is probably fragile because we aren't guarding with a #ifdef yet --- arch/x86/kernel/vmlinux.lds.S | 9 ++++ include/linux/kftf.h | 56 ++++++++++++++++++++-- lib/Kconfig.kftf | 1 + lib/kftf/kftf_main.c | 90 ++++++++++++++++++++++++++++++----- lib/kftf/kftf_simple_fuzzer.h | 26 ---------- lib/kftf/kftf_tests.h | 37 ++++++++++++++ lib/math/int_sqrt.c | 7 ++- 7 files changed, 182 insertions(+), 44 deletions(-) create mode 100644 lib/kftf/kftf_tests.h diff --git a/arch/x86/kernel/vmlinux.lds.S b/arch/x86/kernel/vmlinux.lds.S index 4fa0be732af10f..3750ad14f26473 100644 --- a/arch/x86/kernel/vmlinux.lds.S +++ b/arch/x86/kernel/vmlinux.lds.S @@ -112,6 +112,13 @@ ASSERT(__relocate_kernel_end - __relocate_kernel_start <= KEXEC_CONTROL_CODE_MAX #else #define KEXEC_RELOCATE_KERNEL #endif + +#define KFTF_TABLE \ + . = ALIGN(0X100); \ + __kftf_start = .; \ + KEEP(*(.kftf)); /* keep everything referencing section */ \ + __kftf_end = .; + PHDRS { text PT_LOAD FLAGS(5); /* R_E */ data PT_LOAD FLAGS(6); /* RW_ */ @@ -199,6 +206,8 @@ SECTIONS CONSTRUCTORS KEXEC_RELOCATE_KERNEL + KFTF_TABLE + /* rarely changed data like cpu maps */ READ_MOSTLY_DATA(INTERNODE_CACHE_BYTES) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index ddf8b8fd53a60e..320c4a3b54f036 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -5,13 +5,59 @@ MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ethan Graham "); -MODULE_DESCRIPTION("Kernel Fuzz Testing Framework"); +MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFTF)"); -struct kftf_simple_arg { - int data; - int access; +struct kftf_test_case { + const char *name; + ssize_t (*write_callback)(struct file *filp, const char __user *buf, + size_t len, loff_t *off); }; -void kftf_fuzzable(struct kftf_simple_arg *foo); +/** + * Shared buffer used for copying data out of userspace + */ +static char kftf_input_buf[128]; + +/** + * Expected usage: + * + * ``` + * FUZZ_TEST(func, func_arg_type) + * ret = func(arg.arg1, arg.arg2, ..., arg.argn); + * validate(ret); + * } + * ``` + * + * The created structure will be registered by the kftf module, which creates + * a debugfs entry for it. The write callback is such that it accepts valid + * struct instances as input. + */ +#define FUZZ_TEST(func, func_arg_type) \ + /* forward decl */ \ + static ssize_t _write_callback_##func(struct file *filp, \ + const char __user *buf, \ + size_t len, loff_t *off); \ + const struct kftf_test_case __fuzz_test__##func \ + __attribute__((__section__(".kftf"), __used__)) = { \ + .name = #func, \ + .write_callback = _write_callback_##func, \ + }; \ + /* user-defined write callback */ \ + static ssize_t _write_callback_##func(struct file *filp, \ + const char __user *buf, \ + size_t len, loff_t *off) \ + { \ + pr_info("invoke %s", __FUNCTION__); \ + if (len >= sizeof(kftf_input_buf)) \ + return -EINVAL; \ + if (simple_write_to_buffer((void *)kftf_input_buf, \ + sizeof(kftf_input_buf) - 1, off, \ + buf, len) < 0) \ + return -EFAULT; \ + if (len != sizeof(struct kftf_simple_arg)) { \ + pr_warn("incorrect data size\n"); \ + return -EINVAL; \ + } \ + func_arg_type *arg = (void *)kftf_input_buf; #endif /* KFTF_H */ diff --git a/lib/Kconfig.kftf b/lib/Kconfig.kftf index c948ba08ec3fe4..6a7d35a25b5f50 100644 --- a/lib/Kconfig.kftf +++ b/lib/Kconfig.kftf @@ -2,3 +2,4 @@ config KFTF tristate "Enable Kernel Fuzz Testing Framework (KFTF)" + depends on DEBUG_FS diff --git a/lib/kftf/kftf_main.c b/lib/kftf/kftf_main.c index 5de45c5fd4d887..6ad6259dca2af3 100644 --- a/lib/kftf/kftf_main.c +++ b/lib/kftf/kftf_main.c @@ -1,28 +1,94 @@ +#include "kftf_tests.h" #include #include #include #include -#include "kftf_simple_fuzzer.h" +extern const struct kftf_test_case __kftf_start[]; +extern const struct kftf_test_case __kftf_end[]; -// contains a bug! -void kftf_fuzzable(struct kftf_simple_arg *foo) -{ - // can buffer overflow or underflow, or cause a null pointer dereference - // crashing the kernel - char *data = ((char *)foo) + foo->access; - pr_info("%s: foo->data = %d\n", __FUNCTION__, foo->data); - pr_info("%s: data at foo[%d] = %x\n", __FUNCTION__, foo->access, *data); -} +struct kftf_debugfs_state { + struct file_operations fops; + struct dentry *test_dir; + struct dentry *input_file; +}; + +struct kftf_simple_fuzzer_state { + struct file_operations fops; + struct dentry *kftf_dir; + struct kftf_debugfs_state debugfs_state[1024]; // FIXME: fine for WIP +}; + +static struct kftf_simple_fuzzer_state st; + +// XXX: allows everyone to write - correct flags? +const umode_t kftf_debug_fs_flags = 0222; static int __init kftf_init(void) { - return kftf_simple_fuzzer_init(); + int ret = 0; + + st.kftf_dir = debugfs_create_dir("kftf", NULL); + if (!st.kftf_dir) { + pr_warn("kftf: could not create debugfs"); + return -ENODEV; + } + + if (IS_ERR(st.kftf_dir)) { + st.kftf_dir = NULL; + return PTR_ERR(st.kftf_dir); + } + + const struct kftf_test_case *test; + int i = 0; // XXX: find better way of doing this + for (test = __kftf_start; test < __kftf_end; test++) { + st.debugfs_state[i].test_dir = + debugfs_create_dir(test->name, st.kftf_dir); + + if (!st.debugfs_state[i].test_dir) { + ret = -ENOMEM; + goto cleanup_failure; + } else if (IS_ERR(st.debugfs_state[i].test_dir)) { + ret = PTR_ERR(st.debugfs_state[i].test_dir); + goto cleanup_failure; + } + + st.debugfs_state[i].fops = (struct file_operations){ + .owner = THIS_MODULE, + .write = test->write_callback, + }; + + st.debugfs_state[i].input_file = + debugfs_create_file("input", kftf_debug_fs_flags, + st.debugfs_state[i].test_dir, NULL, + &st.debugfs_state[i].fops); + + if (!st.debugfs_state[i].input_file) { + ret = -ENOMEM; + goto cleanup_failure; + } else if (IS_ERR(st.debugfs_state[i].input_file)) { + ret = PTR_ERR(st.debugfs_state[i].input_file); + goto cleanup_failure; + } + + i++; + pr_info("kftf: registered %s\n", test->name); + } + + return 0; + +cleanup_failure: + debugfs_remove_recursive(st.kftf_dir); + return ret; } static void __exit kftf_exit(void) { - kftf_simple_fuzzer_cleanup(); + if (!st.kftf_dir) + return; + + debugfs_remove_recursive(st.kftf_dir); + st.kftf_dir = NULL; } module_init(kftf_init); diff --git a/lib/kftf/kftf_simple_fuzzer.h b/lib/kftf/kftf_simple_fuzzer.h index da4fb3ab78bb36..ae99e6227c317f 100644 --- a/lib/kftf/kftf_simple_fuzzer.h +++ b/lib/kftf/kftf_simple_fuzzer.h @@ -8,7 +8,6 @@ struct kftf_simple_fuzzer_state { struct file_operations fops; struct dentry *kftf_dir; - struct dentry *input_file; char buffer[128]; //< buffer for user input }; @@ -38,29 +37,4 @@ static ssize_t kftf_fuzz_write(struct file *filp, const char __user *buf, return len; } -static int kftf_simple_fuzzer_init(void) -{ - st.kftf_dir = debugfs_create_dir("kftf", NULL); - if (!st.kftf_dir) - return 1; // TODO: proper errors - - st.fops = (struct file_operations){ - .owner = THIS_MODULE, - .write = kftf_fuzz_write, - }; - - st.input_file = - debugfs_create_file("input", 0222, st.kftf_dir, NULL, &st.fops); - - if (!st.input_file) - return 1; // TODO: proper errors - - return 0; -} - -static void kftf_simple_fuzzer_cleanup(void) -{ - debugfs_remove(st.kftf_dir); -} - #endif /* KFTF_SIMPLE_FUZZER_H */ diff --git a/lib/kftf/kftf_tests.h b/lib/kftf/kftf_tests.h new file mode 100644 index 00000000000000..c8e57677185254 --- /dev/null +++ b/lib/kftf/kftf_tests.h @@ -0,0 +1,37 @@ +#ifndef KFTF_TESTS_H +#define KFTF_TESTS_H + +#include +#include + +struct kftf_simple_arg { + int data; + int access; +}; + +// contains a bug! +static void kftf_fuzzable(struct kftf_simple_arg *foo) +{ + // can buffer overflow or underflow, or cause a null pointer dereference + // crashing the kernel + char *data = ((char *)foo) + foo->access; + pr_info("%s: foo->data = %d\n", __FUNCTION__, foo->data); + pr_info("%s: data at foo[%d] = %x\n", __FUNCTION__, foo->access, *data); +} + +FUZZ_TEST(kftf_fuzzable, struct kftf_simple_arg) +kftf_fuzzable(arg); +return len; +} + +struct int_sqrt_arg { + unsigned long x; +}; + +FUZZ_TEST(int_sqrt, struct int_sqrt_arg) +unsigned long res = int_sqrt(arg->x); +pr_info("fuzz_arg.x = %lu, res = %lu", arg->x, res); +return len; +} + +#endif /* KFTF_TESTS_H */ diff --git a/lib/math/int_sqrt.c b/lib/math/int_sqrt.c index a8170bb9142f39..1ce96d3792a40c 100644 --- a/lib/math/int_sqrt.c +++ b/lib/math/int_sqrt.c @@ -21,6 +21,11 @@ unsigned long int_sqrt(unsigned long x) { unsigned long b, m, y = 0; + // deliberate bug + if (x == 0) { + char c = *(char *)x; + pr_info("should crash here! %c\n", c); + } if (x <= 1) return x; @@ -51,7 +56,7 @@ u32 int_sqrt64(u64 x) u64 b, m, y = 0; if (x <= ULONG_MAX) - return int_sqrt((unsigned long) x); + return int_sqrt((unsigned long)x); m = 1ULL << ((fls64(x) - 1) & ~1ULL); while (m != 0) { From e8992cb9d4c89b46957cb5d8503730805806cfd4 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Mon, 23 Jun 2025 12:31:45 +0000 Subject: [PATCH 05/32] kftf: fixx sizeof check so that it checks against the provided type. --- include/linux/kftf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 320c4a3b54f036..37844322813e69 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -54,7 +54,7 @@ static char kftf_input_buf[128]; sizeof(kftf_input_buf) - 1, off, \ buf, len) < 0) \ return -EFAULT; \ - if (len != sizeof(struct kftf_simple_arg)) { \ + if (len != sizeof(func_arg_type)) { \ pr_warn("incorrect data size\n"); \ return -EINVAL; \ } \ From d7adb902c22e0245ac3d737858b2c2b74e0e7f18 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Mon, 23 Jun 2025 13:45:01 +0000 Subject: [PATCH 06/32] kftf: add metadata file per fuzzed function - this contains the name of the argument type that the function takes so that the user can use tools like pahole to generate a syzkaller definition of the argument and thus fuzz the function more easily. --- include/linux/kftf.h | 83 ++++++++++++++++++++++++++++++-------------- lib/kftf/kftf_main.c | 70 ++++++++++++++++++++++++++++--------- 2 files changed, 110 insertions(+), 43 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 37844322813e69..951f531d5e6370 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -9,8 +9,11 @@ MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFTF)"); struct kftf_test_case { const char *name; - ssize_t (*write_callback)(struct file *filp, const char __user *buf, + const char *arg_type_name; + ssize_t (*write_input_cb)(struct file *filp, const char __user *buf, size_t len, loff_t *off); + ssize_t (*read_metadata_cb)(struct file *, char __user *, size_t, + loff_t *); }; /** @@ -32,32 +35,58 @@ static char kftf_input_buf[128]; * a debugfs entry for it. The write callback is such that it accepts valid * struct instances as input. */ -#define FUZZ_TEST(func, func_arg_type) \ - /* forward decl */ \ - static ssize_t _write_callback_##func(struct file *filp, \ - const char __user *buf, \ - size_t len, loff_t *off); \ - const struct kftf_test_case __fuzz_test__##func \ - __attribute__((__section__(".kftf"), __used__)) = { \ - .name = #func, \ - .write_callback = _write_callback_##func, \ - }; \ - /* user-defined write callback */ \ - static ssize_t _write_callback_##func(struct file *filp, \ - const char __user *buf, \ - size_t len, loff_t *off) \ - { \ - pr_info("invoke %s", __FUNCTION__); \ - if (len >= sizeof(kftf_input_buf)) \ - return -EINVAL; \ - if (simple_write_to_buffer((void *)kftf_input_buf, \ - sizeof(kftf_input_buf) - 1, off, \ - buf, len) < 0) \ - return -EFAULT; \ - if (len != sizeof(func_arg_type)) { \ - pr_warn("incorrect data size\n"); \ - return -EINVAL; \ - } \ +#define FUZZ_TEST(func, func_arg_type) \ + /* forward decls */ \ + static ssize_t _write_callback_##func(struct file *filp, \ + const char __user *buf, \ + size_t len, loff_t *off); \ + static ssize_t _read_metadata_callback_##func( \ + struct file *filp, char __user *buf, size_t len, loff_t *off); \ + /* test case struct initialization */ \ + const struct kftf_test_case __fuzz_test__##func \ + __attribute__((__section__(".kftf"), __used__)) = { \ + .name = #func, \ + .arg_type_name = #func_arg_type, \ + .write_input_cb = _write_callback_##func, \ + .read_metadata_cb = _read_metadata_callback_##func \ + }; \ + /* callback that simply returns the type name to the user */ \ + static ssize_t _read_metadata_callback_##func( \ + struct file *filp, char __user *buf, size_t len, loff_t *off) \ + { \ + int bytes_to_copy; \ + int message_len = strlen(__fuzz_test__##func.arg_type_name); \ + if (*off >= message_len) { \ + return -EINVAL; \ + } \ + bytes_to_copy = message_len - *off; \ + if (bytes_to_copy > len) { \ + bytes_to_copy = len; \ + } \ + if (copy_to_user(buf, \ + __fuzz_test__##func.arg_type_name + *off, \ + bytes_to_copy) != 0) { \ + return -EFAULT; \ + } \ + *off += bytes_to_copy; \ + return bytes_to_copy; \ + } \ + /* user-defined write callback */ \ + static ssize_t _write_callback_##func(struct file *filp, \ + const char __user *buf, \ + size_t len, loff_t *off) \ + { \ + pr_info("invoke %s", __FUNCTION__); \ + if (len >= sizeof(kftf_input_buf)) \ + return -EINVAL; \ + if (simple_write_to_buffer((void *)kftf_input_buf, \ + sizeof(kftf_input_buf) - 1, off, \ + buf, len) < 0) \ + return -EFAULT; \ + if (len != sizeof(func_arg_type)) { \ + pr_warn("incorrect data size\n"); \ + return -EINVAL; \ + } \ func_arg_type *arg = (void *)kftf_input_buf; #endif /* KFTF_H */ diff --git a/lib/kftf/kftf_main.c b/lib/kftf/kftf_main.c index 6ad6259dca2af3..df9f82de734be9 100644 --- a/lib/kftf/kftf_main.c +++ b/lib/kftf/kftf_main.c @@ -7,27 +7,46 @@ extern const struct kftf_test_case __kftf_start[]; extern const struct kftf_test_case __kftf_end[]; -struct kftf_debugfs_state { +#define KFTF_MAX_TEST_CASES 1024 + +/// defines a dentry with file-operations +struct kftf_dentry { + struct dentry *dentry; struct file_operations fops; +}; + +/** + * Wraps teh state of the created + */ +struct kftf_debugfs_state { struct dentry *test_dir; - struct dentry *input_file; + struct kftf_dentry input_dentry; + struct kftf_dentry metadata_dentry; }; struct kftf_simple_fuzzer_state { struct file_operations fops; struct dentry *kftf_dir; - struct kftf_debugfs_state debugfs_state[1024]; // FIXME: fine for WIP + struct kftf_debugfs_state + debugfs_state[KFTF_MAX_TEST_CASES]; // FIXME: fine for WIP }; static struct kftf_simple_fuzzer_state st; -// XXX: allows everyone to write - correct flags? -const umode_t kftf_debug_fs_flags = 0222; +// XXX: be careful of flags here +const umode_t kftf_flags_w = 0666; +const umode_t kftf_flags_r = 0444; static int __init kftf_init(void) { int ret = 0; + // To avoid kmalloc entirely, we enforce a maximum number of fuzz tests + // that can be defined inside the kernel. + size_t num_test_cases = __kftf_end - __kftf_start; + if (num_test_cases > KFTF_MAX_TEST_CASES) + return -EINVAL; + st.kftf_dir = debugfs_create_dir("kftf", NULL); if (!st.kftf_dir) { pr_warn("kftf: could not create debugfs"); @@ -53,21 +72,40 @@ static int __init kftf_init(void) goto cleanup_failure; } - st.debugfs_state[i].fops = (struct file_operations){ - .owner = THIS_MODULE, - .write = test->write_callback, - }; + st.debugfs_state[i].input_dentry.fops = + (struct file_operations){ + .owner = THIS_MODULE, + .write = test->write_input_cb, + }; - st.debugfs_state[i].input_file = - debugfs_create_file("input", kftf_debug_fs_flags, - st.debugfs_state[i].test_dir, NULL, - &st.debugfs_state[i].fops); + st.debugfs_state[i].input_dentry.dentry = debugfs_create_file( + "input", kftf_flags_w, st.debugfs_state[i].test_dir, + NULL, &st.debugfs_state[i].input_dentry.fops); + if (!st.debugfs_state[i].input_dentry.dentry) { + ret = -ENOMEM; + goto cleanup_failure; + } else if (IS_ERR(st.debugfs_state[i].input_dentry.dentry)) { + ret = PTR_ERR(st.debugfs_state[i].input_dentry.dentry); + goto cleanup_failure; + } - if (!st.debugfs_state[i].input_file) { + st.debugfs_state[i].metadata_dentry.fops = + (struct file_operations){ + .owner = THIS_MODULE, + .read = test->read_metadata_cb, + }; + + st.debugfs_state[i].metadata_dentry.dentry = + debugfs_create_file( + "metadata", kftf_flags_r, + st.debugfs_state[i].test_dir, NULL, + &st.debugfs_state[i].metadata_dentry.fops); + if (!st.debugfs_state[i].metadata_dentry.dentry) { ret = -ENOMEM; goto cleanup_failure; - } else if (IS_ERR(st.debugfs_state[i].input_file)) { - ret = PTR_ERR(st.debugfs_state[i].input_file); + } else if (IS_ERR(st.debugfs_state[i].metadata_dentry.dentry)) { + ret = PTR_ERR( + st.debugfs_state[i].metadata_dentry.dentry); goto cleanup_failure; } From ca5ba483c7e4be469ae2743bec2464b5d9f0d6cd Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Mon, 23 Jun 2025 13:51:00 +0000 Subject: [PATCH 07/32] move int_sqrt test out of kftf_tests file and into the math source files. It works, and the function is exposed correctly! --- lib/kftf/kftf_tests.h | 21 ++++++--------------- lib/math/int_sqrt.c | 11 +++++++++++ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/lib/kftf/kftf_tests.h b/lib/kftf/kftf_tests.h index c8e57677185254..b8121d04f9c191 100644 --- a/lib/kftf/kftf_tests.h +++ b/lib/kftf/kftf_tests.h @@ -10,27 +10,18 @@ struct kftf_simple_arg { }; // contains a bug! -static void kftf_fuzzable(struct kftf_simple_arg *foo) +static void kftf_fuzzable(int access, int data, void *ptr) { // can buffer overflow or underflow, or cause a null pointer dereference // crashing the kernel - char *data = ((char *)foo) + foo->access; - pr_info("%s: foo->data = %d\n", __FUNCTION__, foo->data); - pr_info("%s: data at foo[%d] = %x\n", __FUNCTION__, foo->access, *data); + char access_data = *(((char *)ptr) + access); + pr_info("%s: data = %0x\n", __FUNCTION__, data); + pr_info("%s: data at ptr[%d] = %x\n", __FUNCTION__, access, + access_data); } FUZZ_TEST(kftf_fuzzable, struct kftf_simple_arg) -kftf_fuzzable(arg); -return len; -} - -struct int_sqrt_arg { - unsigned long x; -}; - -FUZZ_TEST(int_sqrt, struct int_sqrt_arg) -unsigned long res = int_sqrt(arg->x); -pr_info("fuzz_arg.x = %lu, res = %lu", arg->x, res); +kftf_fuzzable(arg->access, arg->data, &arg); return len; } diff --git a/lib/math/int_sqrt.c b/lib/math/int_sqrt.c index 1ce96d3792a40c..cc9c59b655d2d0 100644 --- a/lib/math/int_sqrt.c +++ b/lib/math/int_sqrt.c @@ -10,6 +10,17 @@ #include #include #include +#include + +struct int_sqrt_arg { + unsigned long x; +}; + +FUZZ_TEST(int_sqrt, struct int_sqrt_arg) +unsigned long res = int_sqrt(arg->x); +pr_info("fuzz_arg.x = %lu, res = %lu", arg->x, res); +return len; +} /** * int_sqrt - computes the integer square root From 96ae8d557b734d05025e410c2db09c2fb4af1c57 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Mon, 23 Jun 2025 16:25:20 +0000 Subject: [PATCH 08/32] kftf: address PR comments and make the FUZZ_TEST interface a lot more user friendly. --- include/linux/kftf.h | 16 +++++++++++----- lib/kftf/kftf_main.c | 33 +++++++++++++++++++-------------- lib/kftf/kftf_tests.h | 27 ++++++++++++++++++--------- lib/math/int_sqrt.c | 6 +++--- 4 files changed, 51 insertions(+), 31 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 951f531d5e6370..13ba5d52b4c6cb 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -25,9 +25,9 @@ static char kftf_input_buf[128]; * Expected usage: * * ``` - * FUZZ_TEST(func, func_arg_type) - * ret = func(arg.arg1, arg.arg2, ..., arg.argn); - * validate(ret); + * FUZZ_TEST(func, func_arg_type) { + * ret = func(arg.arg1, arg.arg2, ..., arg.argn); + * validate(ret); * } * ``` * @@ -42,6 +42,7 @@ static char kftf_input_buf[128]; size_t len, loff_t *off); \ static ssize_t _read_metadata_callback_##func( \ struct file *filp, char __user *buf, size_t len, loff_t *off); \ + static void _fuzz_test_logic_##func(func_arg_type arg); \ /* test case struct initialization */ \ const struct kftf_test_case __fuzz_test__##func \ __attribute__((__section__(".kftf"), __used__)) = { \ @@ -76,7 +77,6 @@ static char kftf_input_buf[128]; const char __user *buf, \ size_t len, loff_t *off) \ { \ - pr_info("invoke %s", __FUNCTION__); \ if (len >= sizeof(kftf_input_buf)) \ return -EINVAL; \ if (simple_write_to_buffer((void *)kftf_input_buf, \ @@ -87,6 +87,12 @@ static char kftf_input_buf[128]; pr_warn("incorrect data size\n"); \ return -EINVAL; \ } \ - func_arg_type *arg = (void *)kftf_input_buf; + func_arg_type arg = *(func_arg_type *)kftf_input_buf; \ + /* call the user's logic on the provided arg. */ \ + /* NOTE: define some success/failure return types? */ \ + _fuzz_test_logic_##func(arg); \ + return len; \ + } \ + static void _fuzz_test_logic_##func(func_arg_type arg) #endif /* KFTF_H */ diff --git a/lib/kftf/kftf_main.c b/lib/kftf/kftf_main.c index df9f82de734be9..965370aed50dcf 100644 --- a/lib/kftf/kftf_main.c +++ b/lib/kftf/kftf_main.c @@ -1,15 +1,17 @@ -#include "kftf_tests.h" -#include #include -#include +#include #include +#include +#include "kftf_tests.h" extern const struct kftf_test_case __kftf_start[]; extern const struct kftf_test_case __kftf_end[]; #define KFTF_MAX_TEST_CASES 1024 -/// defines a dentry with file-operations +/** + * defines a struct dentry with file-operations + */ struct kftf_dentry { struct dentry *dentry; struct file_operations fops; @@ -33,17 +35,22 @@ struct kftf_simple_fuzzer_state { static struct kftf_simple_fuzzer_state st; -// XXX: be careful of flags here +/* XXX: Be careful of flags here. Should formally define what we want */ const umode_t kftf_flags_w = 0666; const umode_t kftf_flags_r = 0444; static int __init kftf_init(void) { + const struct kftf_test_case *test; int ret = 0; - - // To avoid kmalloc entirely, we enforce a maximum number of fuzz tests - // that can be defined inside the kernel. - size_t num_test_cases = __kftf_end - __kftf_start; + int i = 0; + size_t num_test_cases; + + /* + * To avoid kmalloc entirely, we enforce a maximum number of fuzz tests + * that can be defined inside the kernel. + */ + num_test_cases = __kftf_end - __kftf_start; if (num_test_cases > KFTF_MAX_TEST_CASES) return -EINVAL; @@ -58,9 +65,7 @@ static int __init kftf_init(void) return PTR_ERR(st.kftf_dir); } - const struct kftf_test_case *test; - int i = 0; // XXX: find better way of doing this - for (test = __kftf_start; test < __kftf_end; test++) { + for (test = __kftf_start; test < __kftf_end; test++, i++) { st.debugfs_state[i].test_dir = debugfs_create_dir(test->name, st.kftf_dir); @@ -72,12 +77,12 @@ static int __init kftf_init(void) goto cleanup_failure; } + /* create "input" file for fuzz test */ st.debugfs_state[i].input_dentry.fops = (struct file_operations){ .owner = THIS_MODULE, .write = test->write_input_cb, }; - st.debugfs_state[i].input_dentry.dentry = debugfs_create_file( "input", kftf_flags_w, st.debugfs_state[i].test_dir, NULL, &st.debugfs_state[i].input_dentry.fops); @@ -95,6 +100,7 @@ static int __init kftf_init(void) .read = test->read_metadata_cb, }; + /* create "metadata" file for fuzz test */ st.debugfs_state[i].metadata_dentry.dentry = debugfs_create_file( "metadata", kftf_flags_r, @@ -109,7 +115,6 @@ static int __init kftf_init(void) goto cleanup_failure; } - i++; pr_info("kftf: registered %s\n", test->name); } diff --git a/lib/kftf/kftf_tests.h b/lib/kftf/kftf_tests.h index b8121d04f9c191..b3895eebe5e7e3 100644 --- a/lib/kftf/kftf_tests.h +++ b/lib/kftf/kftf_tests.h @@ -5,24 +5,33 @@ #include struct kftf_simple_arg { - int data; - int access; + char first; + char second; + char third; }; // contains a bug! -static void kftf_fuzzable(int access, int data, void *ptr) +static void kftf_fuzzable(char first, char second, char third) { // can buffer overflow or underflow, or cause a null pointer dereference // crashing the kernel - char access_data = *(((char *)ptr) + access); - pr_info("%s: data = %0x\n", __FUNCTION__, data); - pr_info("%s: data at ptr[%d] = %x\n", __FUNCTION__, access, - access_data); + if (first == 'a') { + pr_info("first was a"); + if (second == 'b') { + pr_info("second was b"); + if (third == 'c') { + pr_info("third was c"); + /* do some weird access */ + char value = *(char *)&first + (first * 10 + 1); + pr_info("dumping ptr %c\b", value); + } + } + } } FUZZ_TEST(kftf_fuzzable, struct kftf_simple_arg) -kftf_fuzzable(arg->access, arg->data, &arg); -return len; +{ + kftf_fuzzable(arg.first, arg.second, arg.third); } #endif /* KFTF_TESTS_H */ diff --git a/lib/math/int_sqrt.c b/lib/math/int_sqrt.c index cc9c59b655d2d0..a52b16f8045393 100644 --- a/lib/math/int_sqrt.c +++ b/lib/math/int_sqrt.c @@ -17,9 +17,9 @@ struct int_sqrt_arg { }; FUZZ_TEST(int_sqrt, struct int_sqrt_arg) -unsigned long res = int_sqrt(arg->x); -pr_info("fuzz_arg.x = %lu, res = %lu", arg->x, res); -return len; +{ + unsigned long res = int_sqrt(arg.x); + pr_info("fuzz_arg.x = %lu, res = %lu", arg.x, res); } /** From ece3042da74cac1b97db823dfd0e729f9877b4e5 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Mon, 23 Jun 2025 16:32:33 +0000 Subject: [PATCH 09/32] kftf: update include block so that the relative include (kftf_tests.h) has a line separator between it and the common linux includes, as per https://elixir.bootlin.com/linux/v6.12.5/source/mm/kfence/core.c --- lib/kftf/kftf_main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/kftf/kftf_main.c b/lib/kftf/kftf_main.c index 965370aed50dcf..e83382e7be6ce0 100644 --- a/lib/kftf/kftf_main.c +++ b/lib/kftf/kftf_main.c @@ -2,6 +2,7 @@ #include #include #include + #include "kftf_tests.h" extern const struct kftf_test_case __kftf_start[]; From 07bfafa4942a764d7b1d10f0a257f83f7c05cbfc Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Mon, 23 Jun 2025 17:11:38 +0000 Subject: [PATCH 10/32] delete unused file kftf_simple_fuzzer --- lib/kftf/kftf_simple_fuzzer.h | 40 ----------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 lib/kftf/kftf_simple_fuzzer.h diff --git a/lib/kftf/kftf_simple_fuzzer.h b/lib/kftf/kftf_simple_fuzzer.h deleted file mode 100644 index ae99e6227c317f..00000000000000 --- a/lib/kftf/kftf_simple_fuzzer.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef KFTF_SIMPLE_FUZZER_H -#define KFTF_SIMPLE_FUZZER_H - -#include -#include -#include - -struct kftf_simple_fuzzer_state { - struct file_operations fops; - struct dentry *kftf_dir; - char buffer[128]; //< buffer for user input -}; - -static struct kftf_simple_fuzzer_state st; - -/** - * write callback for the simple fuzzer - */ -static ssize_t kftf_fuzz_write(struct file *filp, const char __user *buf, - size_t len, loff_t *off) -{ - if (len >= sizeof(st.buffer)) - return -EINVAL; - - if (simple_write_to_buffer(st.buffer, sizeof(st.buffer) - 1, off, buf, - len) < 0) - return -EFAULT; - - if (len != sizeof(struct kftf_simple_arg)) { - pr_warn("incorrect data size\n"); - return -EINVAL; - } - - // intrepret the binary contents of the buffer - struct kftf_simple_arg *fuzz_arg = (void *)st.buffer; - kftf_fuzzable(fuzz_arg); - return len; -} - -#endif /* KFTF_SIMPLE_FUZZER_H */ From 0c5cd7efce248fe929cf68c4466db88fd8e02132 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Mon, 23 Jun 2025 17:12:05 +0000 Subject: [PATCH 11/32] kftf: per test-case input buffer with mutex --- include/linux/kftf.h | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 13ba5d52b4c6cb..80ede23d275d53 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -16,11 +16,6 @@ struct kftf_test_case { loff_t *); }; -/** - * Shared buffer used for copying data out of userspace - */ -static char kftf_input_buf[128]; - /** * Expected usage: * @@ -36,6 +31,10 @@ static char kftf_input_buf[128]; * struct instances as input. */ #define FUZZ_TEST(func, func_arg_type) \ + /* input buffer. Size 1 for now, but we may support batching */ \ + static func_arg_type input_buf_##func[2]; \ + /* guard the buffer as concurrent processes could race */ \ + DEFINE_MUTEX(input_mutex_##func); \ /* forward decls */ \ static ssize_t _write_callback_##func(struct file *filp, \ const char __user *buf, \ @@ -77,20 +76,30 @@ static char kftf_input_buf[128]; const char __user *buf, \ size_t len, loff_t *off) \ { \ - if (len >= sizeof(kftf_input_buf)) \ + pr_info("invoke %s\n", __FUNCTION__); \ + if (len >= sizeof(input_buf_##func)) { \ + mutex_unlock(&input_mutex_##func); \ return -EINVAL; \ - if (simple_write_to_buffer((void *)kftf_input_buf, \ - sizeof(kftf_input_buf) - 1, off, \ - buf, len) < 0) \ + } \ + if (simple_write_to_buffer((void *)input_buf_##func, \ + sizeof(input_buf_##func) - 1, off, \ + buf, len) < 0) { \ + pr_info("unable to read from buffer!\n"); \ + mutex_unlock(&input_mutex_##func); \ return -EFAULT; \ + } \ if (len != sizeof(func_arg_type)) { \ - pr_warn("incorrect data size\n"); \ + pr_info("incorrect data size\n"); \ + mutex_unlock(&input_mutex_##func); \ return -EINVAL; \ } \ - func_arg_type arg = *(func_arg_type *)kftf_input_buf; \ + /* XXX: no batching support, so just take the only elem */ \ + func_arg_type arg = input_buf_##func[0]; \ /* call the user's logic on the provided arg. */ \ /* NOTE: define some success/failure return types? */ \ + pr_info("invoking fuzz logic\n"); \ _fuzz_test_logic_##func(arg); \ + mutex_unlock(&input_mutex_##func); \ return len; \ } \ static void _fuzz_test_logic_##func(func_arg_type arg) From a4bb0025de9fb22b2291aec8c7a9bc69b203f432 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Mon, 23 Jun 2025 17:12:26 +0000 Subject: [PATCH 12/32] kftf: update kftf_fuzzable test --- lib/kftf/kftf_tests.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/kftf/kftf_tests.h b/lib/kftf/kftf_tests.h index b3895eebe5e7e3..7cc0a4676d0ec8 100644 --- a/lib/kftf/kftf_tests.h +++ b/lib/kftf/kftf_tests.h @@ -21,9 +21,8 @@ static void kftf_fuzzable(char first, char second, char third) pr_info("second was b"); if (third == 'c') { pr_info("third was c"); - /* do some weird access */ - char value = *(char *)&first + (first * 10 + 1); - pr_info("dumping ptr %c\b", value); + volatile char *ptr = (void *)0xBEEF; + pr_info("reading %p: 0x%x", ptr, *(uint *)ptr); } } } From 0bddc3ff15c75a18d4c811390cd4ec0d3e29c8f5 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Wed, 25 Jun 2025 07:54:15 +0000 Subject: [PATCH 13/32] kftf: add some comments to module main file and kftf header file --- include/linux/kftf.h | 188 ++++++++++++++++++++++++------------------- lib/kftf/kftf_main.c | 77 +++++++++++++++--- 2 files changed, 174 insertions(+), 91 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 80ede23d275d53..36c5f6775d788d 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -7,6 +7,21 @@ MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ethan Graham "); MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFTF)"); +/** + * struct kftf_test case defines a single fuzz test case. These should not + * be created manually. Instead the user should use the FUZZ_TEST macro defined + * below. + * @name: The name of the test case, generally the function being fuzzed. + * @arg_type_name: string representation of the type of argument being fuzzed, + * for example "struct func_arg_type" + * @write_input_cb: Callback invoked when a write's to the test case's debugfs + * input file. This is the entry point for fuzzing data. It is responsible + * for parsing any data written to the input file and invoking the fuzzing + * logic. It should return the number of bytes consumed from `buf` + * @read_metadata_cb: Callback invoked when a user reads from the test case's + * "metadata" debugfs file. It should simply return whatever is contained + * in the `arg_type_name` field. + */ struct kftf_test_case { const char *name; const char *arg_type_name; @@ -17,91 +32,102 @@ struct kftf_test_case { }; /** - * Expected usage: + * FUZZ_TEST - defines a fuzz test case for a function. + * @func: the function to be fuzzed. This is used to name the test case and + * create associated debufs entries. + * @func_arg_type: the input type of func. If func takes multiple arguments, + * then one should wrap that inside of a multi-fielded struct. See usage + * example below. + * + * + * This macro generates all of the necessary boilerplate for a KFTF test case, + * which is placed in a dedicated ".kftf" section so that the dedicated KFTF + * module can discover all defined tests at runtime. * - * ``` - * FUZZ_TEST(func, func_arg_type) { - * ret = func(arg.arg1, arg.arg2, ..., arg.argn); + * For each test, this macro generates + * - A buffer to receive input through the debugfs entry + * - A mutex to protect the input buffer + * - A `struct kftf_test_case` instance + * + * Example usagea: + * + * Assume some function `func(T1 param1, ... TN paramN)` + * // Define input type of the target function + * struct func_arg_type { + * T1 arg1; + * ... + * TN argn; + * }; + * + * // Define the test case + * FUZZ_TEST(func, struct func_arg_type) + * { + * // arg is provided by the macro, and is of type `struct func_arg_type` + * ret = func(arg.arg1, ..., arg.argn); * validate(ret); * } - * ``` - * - * The created structure will be registered by the kftf module, which creates - * a debugfs entry for it. The write callback is such that it accepts valid - * struct instances as input. */ -#define FUZZ_TEST(func, func_arg_type) \ - /* input buffer. Size 1 for now, but we may support batching */ \ - static func_arg_type input_buf_##func[2]; \ - /* guard the buffer as concurrent processes could race */ \ - DEFINE_MUTEX(input_mutex_##func); \ - /* forward decls */ \ - static ssize_t _write_callback_##func(struct file *filp, \ - const char __user *buf, \ - size_t len, loff_t *off); \ - static ssize_t _read_metadata_callback_##func( \ - struct file *filp, char __user *buf, size_t len, loff_t *off); \ - static void _fuzz_test_logic_##func(func_arg_type arg); \ - /* test case struct initialization */ \ - const struct kftf_test_case __fuzz_test__##func \ - __attribute__((__section__(".kftf"), __used__)) = { \ - .name = #func, \ - .arg_type_name = #func_arg_type, \ - .write_input_cb = _write_callback_##func, \ - .read_metadata_cb = _read_metadata_callback_##func \ - }; \ - /* callback that simply returns the type name to the user */ \ - static ssize_t _read_metadata_callback_##func( \ - struct file *filp, char __user *buf, size_t len, loff_t *off) \ - { \ - int bytes_to_copy; \ - int message_len = strlen(__fuzz_test__##func.arg_type_name); \ - if (*off >= message_len) { \ - return -EINVAL; \ - } \ - bytes_to_copy = message_len - *off; \ - if (bytes_to_copy > len) { \ - bytes_to_copy = len; \ - } \ - if (copy_to_user(buf, \ - __fuzz_test__##func.arg_type_name + *off, \ - bytes_to_copy) != 0) { \ - return -EFAULT; \ - } \ - *off += bytes_to_copy; \ - return bytes_to_copy; \ - } \ - /* user-defined write callback */ \ - static ssize_t _write_callback_##func(struct file *filp, \ - const char __user *buf, \ - size_t len, loff_t *off) \ - { \ - pr_info("invoke %s\n", __FUNCTION__); \ - if (len >= sizeof(input_buf_##func)) { \ - mutex_unlock(&input_mutex_##func); \ - return -EINVAL; \ - } \ - if (simple_write_to_buffer((void *)input_buf_##func, \ - sizeof(input_buf_##func) - 1, off, \ - buf, len) < 0) { \ - pr_info("unable to read from buffer!\n"); \ - mutex_unlock(&input_mutex_##func); \ - return -EFAULT; \ - } \ - if (len != sizeof(func_arg_type)) { \ - pr_info("incorrect data size\n"); \ - mutex_unlock(&input_mutex_##func); \ - return -EINVAL; \ - } \ - /* XXX: no batching support, so just take the only elem */ \ - func_arg_type arg = input_buf_##func[0]; \ - /* call the user's logic on the provided arg. */ \ - /* NOTE: define some success/failure return types? */ \ - pr_info("invoking fuzz logic\n"); \ - _fuzz_test_logic_##func(arg); \ - mutex_unlock(&input_mutex_##func); \ - return len; \ - } \ +#define FUZZ_TEST(func, func_arg_type) \ + /* The input buffer holds data written from userspace. Size 2 to \ + * support batching in the future, but currently only the first \ + * element is used. \ + */ \ + static func_arg_type input_buf_##func[2]; \ + /* guard the buffer as concurrent processes could race */ \ + DEFINE_MUTEX(input_mutex_##func); \ + /* forward decls */ \ + static ssize_t _write_callback_##func(struct file *filp, \ + const char __user *buf, \ + size_t len, loff_t *off); \ + static ssize_t _read_metadata_callback_##func( \ + struct file *filp, char __user *buf, size_t len, loff_t *off); \ + static void _fuzz_test_logic_##func(func_arg_type arg); \ + /* test case struct initialization */ \ + const struct kftf_test_case __fuzz_test__##func \ + __attribute__((__section__(".kftf"), __used__)) = { \ + .name = #func, \ + .arg_type_name = #func_arg_type, \ + .write_input_cb = _write_callback_##func, \ + .read_metadata_cb = _read_metadata_callback_##func \ + }; \ + /* callback that simply returns the type name to the user */ \ + static ssize_t _read_metadata_callback_##func( \ + struct file *filp, char __user *buf, size_t len, loff_t *off) \ + { \ + const char *message = __fuzz_test__##func.arg_type_name; \ + int message_len = strlen(message); \ + return simple_read_from_buffer(buf, len, off, message, \ + message_len); \ + } \ + /* user-defined write callback */ \ + static ssize_t _write_callback_##func(struct file *filp, \ + const char __user *buf, \ + size_t len, loff_t *off) \ + { \ + if (len >= sizeof(input_buf_##func)) { \ + mutex_unlock(&input_mutex_##func); \ + return -EINVAL; \ + } \ + if (simple_write_to_buffer((void *)input_buf_##func, \ + sizeof(input_buf_##func) - 1, off, \ + buf, len) < 0) { \ + pr_warn("unable to read from buffer!\n"); \ + mutex_unlock(&input_mutex_##func); \ + return -EFAULT; \ + } \ + if (len != sizeof(func_arg_type)) { \ + pr_warn("incorrect data size\n"); \ + mutex_unlock(&input_mutex_##func); \ + return -EINVAL; \ + } \ + /* XXX: no batching support, so just take the only elem */ \ + func_arg_type arg = input_buf_##func[0]; \ + /* call the user's logic on the provided arg. */ \ + /* NOTE: define some success/failure return types? */ \ + _fuzz_test_logic_##func(arg); \ + mutex_unlock(&input_mutex_##func); \ + return len; \ + } \ static void _fuzz_test_logic_##func(func_arg_type arg) #endif /* KFTF_H */ diff --git a/lib/kftf/kftf_main.c b/lib/kftf/kftf_main.c index e83382e7be6ce0..9beb74f2b23d23 100644 --- a/lib/kftf/kftf_main.c +++ b/lib/kftf/kftf_main.c @@ -1,17 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Kernel Fuzz Testing Framework (KFTF) - Core Module + * + * This module is responsible for discovering and initializing all fuzz test + * cases defined using the FUZZ_TEST() macro. It creates a debugfs interface +* under /sys/kernel/debug/kftf/ for userspace to interact with each test. +*/ #include #include #include #include -#include "kftf_tests.h" - extern const struct kftf_test_case __kftf_start[]; extern const struct kftf_test_case __kftf_end[]; +/* + * KFTF_MAX_TEST_CASES - the hardcoded maximum number of test cases. + * + * To avoid kmalloc in this module, we use a statically allocated array to hold + * the state for each test case. This defines the upper limit on the size of + * that array. + */ #define KFTF_MAX_TEST_CASES 1024 /** - * defines a struct dentry with file-operations + * struct kftf_dentry - A container for a debugfs dentry and its fops. + * @dentry: Pointer to the created debugfs dentry. + * @fops: The file_operations struct associated with this dentry. + * + * This simplifies state management by keeping a file's dentry and its + * operations bundled together. */ struct kftf_dentry { struct dentry *dentry; @@ -19,7 +37,13 @@ struct kftf_dentry { }; /** - * Wraps teh state of the created + * struct kftf_debugfs_state - Per-test-case debugfs state. + * @test_dir: The top-level debugfs directory for a single test case, e.g., + * /sys/kernel/debug/kftf//. + * @input_dentry: The state for the "input" file, which is write-only. + * @metadata_dentry: The state for the "metadata" file, which is read-only. + * + * Wraps all debugfs components created for a single test case. */ struct kftf_debugfs_state { struct dentry *test_dir; @@ -27,6 +51,12 @@ struct kftf_debugfs_state { struct kftf_dentry metadata_dentry; }; +/** + * struct kftf_simple_fuzzer_state - Global state for the KFTF module. + * @kftf_dir: The root debugfs directory, /sys/kernel/debug/kftf/. + * @debugfs_state: A statically sized array holding the state for each + * registered test case. + */ struct kftf_simple_fuzzer_state { struct file_operations fops; struct dentry *kftf_dir; @@ -34,12 +64,36 @@ struct kftf_simple_fuzzer_state { debugfs_state[KFTF_MAX_TEST_CASES]; // FIXME: fine for WIP }; +/* Global static variable to hold all state for the module. */ static struct kftf_simple_fuzzer_state st; -/* XXX: Be careful of flags here. Should formally define what we want */ -const umode_t kftf_flags_w = 0666; +/* + * Default file permissions for the debugfs entries. + * 0222: World-writable for the 'input' file. + * 0444: World-readable for the 'metadata' file. + * + * XXX: should formally define what the permissions should be on these files + */ +const umode_t kftf_flags_w = 0222; const umode_t kftf_flags_r = 0444; +/** + * kftf_init - Initializes the debug filesystem for KFTF. + * + * This function is the entry point for the KFTF module, populating the debugfs + * that is used for IO interaction between the individual fuzzing drivers and + * a userspace fuzzing tool like syzkaller. + * + * Each registered test in the ".kftf" section gets its own subdirectory + * under "/sys/kernel/debug/kftf/" with two files: + * - input: write-only file to send input to the fuzz driver + * - metadata: used to read the type name that the fuzz driver expects + * + * Returns: + * 0 on success. + * -EINVAL if the number of tests exceeds KFTF_MAX_TEST_CASES + * -ENODEV or other error codes if debugfs creation fails. + */ static int __init kftf_init(void) { const struct kftf_test_case *test; @@ -47,14 +101,11 @@ static int __init kftf_init(void) int i = 0; size_t num_test_cases; - /* - * To avoid kmalloc entirely, we enforce a maximum number of fuzz tests - * that can be defined inside the kernel. - */ num_test_cases = __kftf_end - __kftf_start; if (num_test_cases > KFTF_MAX_TEST_CASES) return -EINVAL; + /* create the main "kftf" directory in `/sys/kernel/debug` */ st.kftf_dir = debugfs_create_dir("kftf", NULL); if (!st.kftf_dir) { pr_warn("kftf: could not create debugfs"); @@ -66,7 +117,9 @@ static int __init kftf_init(void) return PTR_ERR(st.kftf_dir); } + /* iterate over all discovered test cases and set up debugfs entries */ for (test = __kftf_start; test < __kftf_end; test++, i++) { + /* create a directory for the discovered test case */ st.debugfs_state[i].test_dir = debugfs_create_dir(test->name, st.kftf_dir); @@ -126,8 +179,12 @@ static int __init kftf_init(void) return ret; } +/** + * kftf_exit - Cleans up the module. + */ static void __exit kftf_exit(void) { + pr_info("kftf: shutting down\n"); if (!st.kftf_dir) return; From 597146743b10089cf0bd3875d9ab28e08fcb7199 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Wed, 25 Jun 2025 14:13:43 +0000 Subject: [PATCH 14/32] kftf: replace static test cases buffer with kmalloc'd one. --- lib/kftf/kftf_main.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/lib/kftf/kftf_main.c b/lib/kftf/kftf_main.c index 9beb74f2b23d23..b151164d327802 100644 --- a/lib/kftf/kftf_main.c +++ b/lib/kftf/kftf_main.c @@ -14,15 +14,6 @@ extern const struct kftf_test_case __kftf_start[]; extern const struct kftf_test_case __kftf_end[]; -/* - * KFTF_MAX_TEST_CASES - the hardcoded maximum number of test cases. - * - * To avoid kmalloc in this module, we use a statically allocated array to hold - * the state for each test case. This defines the upper limit on the size of - * that array. - */ -#define KFTF_MAX_TEST_CASES 1024 - /** * struct kftf_dentry - A container for a debugfs dentry and its fops. * @dentry: Pointer to the created debugfs dentry. @@ -60,8 +51,7 @@ struct kftf_debugfs_state { struct kftf_simple_fuzzer_state { struct file_operations fops; struct dentry *kftf_dir; - struct kftf_debugfs_state - debugfs_state[KFTF_MAX_TEST_CASES]; // FIXME: fine for WIP + struct kftf_debugfs_state *debugfs_state; }; /* Global static variable to hold all state for the module. */ @@ -102,8 +92,11 @@ static int __init kftf_init(void) size_t num_test_cases; num_test_cases = __kftf_end - __kftf_start; - if (num_test_cases > KFTF_MAX_TEST_CASES) - return -EINVAL; + + st.debugfs_state = kmalloc( + num_test_cases * sizeof(struct kftf_debugfs_state), GFP_KERNEL); + if (!st.debugfs_state) + return -ENOMEM; /* create the main "kftf" directory in `/sys/kernel/debug` */ st.kftf_dir = debugfs_create_dir("kftf", NULL); From d799f5c2d00cb1e21ad7c66624cfaef7f164af4f Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Tue, 1 Jul 2025 06:35:00 +0000 Subject: [PATCH 15/32] kftf: replace input buffers for callback with dynamically allocated pointer to function argument type. This should remove the need for a mutex guarding a shared buffer entirely, and ultimately will likely perform better. --- include/linux/kftf.h | 119 +++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 61 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 36c5f6775d788d..918352115cc3f2 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -67,67 +67,64 @@ struct kftf_test_case { * validate(ret); * } */ -#define FUZZ_TEST(func, func_arg_type) \ - /* The input buffer holds data written from userspace. Size 2 to \ - * support batching in the future, but currently only the first \ - * element is used. \ - */ \ - static func_arg_type input_buf_##func[2]; \ - /* guard the buffer as concurrent processes could race */ \ - DEFINE_MUTEX(input_mutex_##func); \ - /* forward decls */ \ - static ssize_t _write_callback_##func(struct file *filp, \ - const char __user *buf, \ - size_t len, loff_t *off); \ - static ssize_t _read_metadata_callback_##func( \ - struct file *filp, char __user *buf, size_t len, loff_t *off); \ - static void _fuzz_test_logic_##func(func_arg_type arg); \ - /* test case struct initialization */ \ - const struct kftf_test_case __fuzz_test__##func \ - __attribute__((__section__(".kftf"), __used__)) = { \ - .name = #func, \ - .arg_type_name = #func_arg_type, \ - .write_input_cb = _write_callback_##func, \ - .read_metadata_cb = _read_metadata_callback_##func \ - }; \ - /* callback that simply returns the type name to the user */ \ - static ssize_t _read_metadata_callback_##func( \ - struct file *filp, char __user *buf, size_t len, loff_t *off) \ - { \ - const char *message = __fuzz_test__##func.arg_type_name; \ - int message_len = strlen(message); \ - return simple_read_from_buffer(buf, len, off, message, \ - message_len); \ - } \ - /* user-defined write callback */ \ - static ssize_t _write_callback_##func(struct file *filp, \ - const char __user *buf, \ - size_t len, loff_t *off) \ - { \ - if (len >= sizeof(input_buf_##func)) { \ - mutex_unlock(&input_mutex_##func); \ - return -EINVAL; \ - } \ - if (simple_write_to_buffer((void *)input_buf_##func, \ - sizeof(input_buf_##func) - 1, off, \ - buf, len) < 0) { \ - pr_warn("unable to read from buffer!\n"); \ - mutex_unlock(&input_mutex_##func); \ - return -EFAULT; \ - } \ - if (len != sizeof(func_arg_type)) { \ - pr_warn("incorrect data size\n"); \ - mutex_unlock(&input_mutex_##func); \ - return -EINVAL; \ - } \ - /* XXX: no batching support, so just take the only elem */ \ - func_arg_type arg = input_buf_##func[0]; \ - /* call the user's logic on the provided arg. */ \ - /* NOTE: define some success/failure return types? */ \ - _fuzz_test_logic_##func(arg); \ - mutex_unlock(&input_mutex_##func); \ - return len; \ - } \ +#define FUZZ_TEST(func, func_arg_type) \ + /* forward decls */ \ + static ssize_t _write_callback_##func(struct file *filp, \ + const char __user *buf, \ + size_t len, loff_t *off); \ + static ssize_t _read_metadata_callback_##func( \ + struct file *filp, char __user *buf, size_t len, loff_t *off); \ + static void _fuzz_test_logic_##func(func_arg_type arg); \ + /* test case struct initialization */ \ + const struct kftf_test_case __fuzz_test__##func \ + __attribute__((__section__(".kftf"), __used__)) = { \ + .name = #func, \ + .arg_type_name = #func_arg_type, \ + .write_input_cb = _write_callback_##func, \ + .read_metadata_cb = _read_metadata_callback_##func \ + }; \ + /* callback that simply returns the type name to the user */ \ + static ssize_t _read_metadata_callback_##func( \ + struct file *filp, char __user *buf, size_t len, loff_t *off) \ + { \ + const char *message = __fuzz_test__##func.arg_type_name; \ + int message_len = strlen(message); \ + return simple_read_from_buffer(buf, len, off, message, \ + message_len); \ + } \ + /* user-defined write callback */ \ + static ssize_t _write_callback_##func(struct file *filp, \ + const char __user *buf, \ + size_t len, loff_t *off) \ + { \ + func_arg_type *input_buf = \ + kmalloc(sizeof(func_arg_type), GFP_KERNEL); \ + if (!input_buf) \ + return -ENOMEM; \ + if (len >= sizeof(*input_buf)) { \ + kfree(input_buf); \ + return -EINVAL; \ + } \ + if (simple_write_to_buffer((void *)input_buf, \ + sizeof(*input_buf) - 1, off, buf, \ + len) < 0) { \ + pr_warn("unable to read from buffer!\n"); \ + kfree(input_buf); \ + return -EFAULT; \ + } \ + if (len != sizeof(func_arg_type)) { \ + pr_warn("incorrect data size\n"); \ + kfree(input_buf); \ + return -EINVAL; \ + } \ + /* XXX: no batching support yet */ \ + func_arg_type arg = *input_buf; \ + /* call the user's logic on the provided arg. */ \ + /* NOTE: define some success/failure return types? */ \ + _fuzz_test_logic_##func(arg); \ + kfree(input_buf); \ + return len; \ + } \ static void _fuzz_test_logic_##func(func_arg_type arg) #endif /* KFTF_H */ From aae72ff0d0fae79b86f32d4b6bbbb85347ad9f4a Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Tue, 1 Jul 2025 09:27:29 +0000 Subject: [PATCH 16/32] kftf: replace heap allocated input buffer with function-local stack variable, and extract common input reading functionality into a separate function. --- include/linux/kftf.h | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 918352115cc3f2..219baaee3b4ab6 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -31,6 +31,20 @@ struct kftf_test_case { loff_t *); }; +static int write_input_cb_common(struct file *filp, const char __user *buf, + size_t len, loff_t *off, void *arg, + size_t arg_size) +{ + if (len != arg_size) { + return -EINVAL; + } + if (simple_write_to_buffer(arg, sizeof(arg_size) - 1, off, buf, len) < + 0) { + return -EFAULT; + } + return 0; +} + /** * FUZZ_TEST - defines a fuzz test case for a function. * @func: the function to be fuzzed. This is used to name the test case and @@ -97,32 +111,17 @@ struct kftf_test_case { const char __user *buf, \ size_t len, loff_t *off) \ { \ - func_arg_type *input_buf = \ - kmalloc(sizeof(func_arg_type), GFP_KERNEL); \ - if (!input_buf) \ - return -ENOMEM; \ - if (len >= sizeof(*input_buf)) { \ - kfree(input_buf); \ - return -EINVAL; \ - } \ - if (simple_write_to_buffer((void *)input_buf, \ - sizeof(*input_buf) - 1, off, buf, \ - len) < 0) { \ - pr_warn("unable to read from buffer!\n"); \ - kfree(input_buf); \ - return -EFAULT; \ - } \ - if (len != sizeof(func_arg_type)) { \ - pr_warn("incorrect data size\n"); \ - kfree(input_buf); \ - return -EINVAL; \ + int err; \ + func_arg_type arg; \ + err = write_input_cb_common(filp, buf, len, off, &arg, \ + sizeof(arg)); \ + if (err != 0) { \ + return err; \ } \ - /* XXX: no batching support yet */ \ - func_arg_type arg = *input_buf; \ /* call the user's logic on the provided arg. */ \ /* NOTE: define some success/failure return types? */ \ + pr_info("invoking fuzz logic for %s\n", #func); \ _fuzz_test_logic_##func(arg); \ - kfree(input_buf); \ return len; \ } \ static void _fuzz_test_logic_##func(func_arg_type arg) From 72e3dec7208c9032ffb451799f6e08c3067420ce Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Tue, 1 Jul 2025 16:17:32 +0000 Subject: [PATCH 17/32] kftf: fix bug in input reading logic. --- include/linux/kftf.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 219baaee3b4ab6..ecfb3cb1c3695b 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -38,8 +38,7 @@ static int write_input_cb_common(struct file *filp, const char __user *buf, if (len != arg_size) { return -EINVAL; } - if (simple_write_to_buffer(arg, sizeof(arg_size) - 1, off, buf, len) < - 0) { + if (simple_write_to_buffer((void *)arg, arg_size, off, buf, len) < 0) { return -EFAULT; } return 0; From 6673d6fd0d655ce221c8bf6db36a82cce9a03d44 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Thu, 3 Jul 2025 19:32:19 +0000 Subject: [PATCH 18/32] kftf: work in progress on domain constraints. Need to see about exposing these via sysfs, and how they can be parsed from the vmlinux binary as well. --- arch/x86/kernel/vmlinux.lds.S | 8 +++-- include/linux/kftf.h | 65 +++++++++++++++++++++++++++++++++-- lib/kftf/kftf_main.c | 21 ++++++++--- lib/kftf/kftf_tests.h | 3 ++ 4 files changed, 88 insertions(+), 9 deletions(-) diff --git a/arch/x86/kernel/vmlinux.lds.S b/arch/x86/kernel/vmlinux.lds.S index 3750ad14f26473..a70e56d3c70582 100644 --- a/arch/x86/kernel/vmlinux.lds.S +++ b/arch/x86/kernel/vmlinux.lds.S @@ -115,9 +115,13 @@ ASSERT(__relocate_kernel_end - __relocate_kernel_start <= KEXEC_CONTROL_CODE_MAX #define KFTF_TABLE \ . = ALIGN(0X100); \ - __kftf_start = .; \ + __kftf_test_case_start = .; \ KEEP(*(.kftf)); /* keep everything referencing section */ \ - __kftf_end = .; + __kftf_test_case_end = .; \ + . = ALIGN(0x100); \ + __kftf_constraint_start = .; \ + KEEP(*(.kftf.constraint)); \ + __kftf_constraint_end = .; \ PHDRS { text PT_LOAD FLAGS(5); /* R_E */ diff --git a/include/linux/kftf.h b/include/linux/kftf.h index ecfb3cb1c3695b..869351c5f9a682 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -31,9 +31,10 @@ struct kftf_test_case { loff_t *); }; -static int write_input_cb_common(struct file *filp, const char __user *buf, - size_t len, loff_t *off, void *arg, - size_t arg_size) +// XXX: why can't we use without the attribute unused anymore?? +__attribute__((unused)) static int +write_input_cb_common(struct file *filp, const char __user *buf, size_t len, + loff_t *off, void *arg, size_t arg_size) { if (len != arg_size) { return -EINVAL; @@ -125,4 +126,62 @@ static int write_input_cb_common(struct file *filp, const char __user *buf, } \ static void _fuzz_test_logic_##func(func_arg_type arg) +/** + * struct kftf_constraint_type defines a type of constraint. The fuzzing driver + * should be aware of these. + */ +enum kftf_constraint_type : uint8_t { + EXPECT_NE = 0, + EXPECT_LE, + EXPECT_GT, +}; + +struct kftf_constraint { + const char *input_type; + const char *field_name; + uintptr_t value; + enum kftf_constraint_type type; +}; + +/** + * __KFTF_DEFINE_CONSTRAINT - defines a fuzz test constraint linked to a given + * argument type belonging to a fuzz test. See FUZZ_TEST above. + * + * @arg_type: the type of argument (a struct) without the leading "struct" in + * its name, which will be prepended. + * @field: the field on which the constraint is defined. + * @val: used for comparison constraints such as KFTF_EXPECT_NE + * @tpe: the type of constaint that this defines + * + * This macro is intended for internal use. A user should opt for + * KFTF_EXPECT_* instead when defining fuzz test constraints. + */ +#define __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, tpe) \ + static struct kftf_constraint __constraint_##arg_type##_##field \ + __attribute__((__section__(".kftf.constraint"))) = { \ + .input_type = "struct " #arg_type, \ + .field_name = #field, \ + .value = (uintptr_t)val, \ + .type = tpe, \ + }; \ + (void)__constraint_##arg_type##_##field; + +#define KFTF_EXPECT_NE(arg_type, field, val) \ + if (arg.field == val) \ + return; \ + __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, EXPECT_NE) + +#define KFTF_EXPECT_LE(arg_type, field, val) \ + if (arg.field >= val) \ + return; \ + __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, EXPECT_LE); + +#define KFTF_EXPECT_GT(arg_type, field, val) \ + if (arg.field > val) \ + return; \ + __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, EXPECT_GT); + +#define KFTF_EXPECT_NOT_NULL(arg_type, field) \ + KFTF_EXPECT_NE(arg_type, field, 0x0) + #endif /* KFTF_H */ diff --git a/lib/kftf/kftf_main.c b/lib/kftf/kftf_main.c index b151164d327802..2f9e2a8537d58c 100644 --- a/lib/kftf/kftf_main.c +++ b/lib/kftf/kftf_main.c @@ -11,8 +11,10 @@ #include #include -extern const struct kftf_test_case __kftf_start[]; -extern const struct kftf_test_case __kftf_end[]; +extern const struct kftf_test_case __kftf_test_case_start[]; +extern const struct kftf_test_case __kftf_test_case_end[]; +extern const struct kftf_constraint __kftf_constraint_start[]; +extern const struct kftf_constraint __kftf_constraint_end[]; /** * struct kftf_dentry - A container for a debugfs dentry and its fops. @@ -87,11 +89,12 @@ const umode_t kftf_flags_r = 0444; static int __init kftf_init(void) { const struct kftf_test_case *test; + const struct kftf_constraint *constraint; int ret = 0; int i = 0; size_t num_test_cases; - num_test_cases = __kftf_end - __kftf_start; + num_test_cases = __kftf_test_case_end - __kftf_test_case_start; st.debugfs_state = kmalloc( num_test_cases * sizeof(struct kftf_debugfs_state), GFP_KERNEL); @@ -111,7 +114,8 @@ static int __init kftf_init(void) } /* iterate over all discovered test cases and set up debugfs entries */ - for (test = __kftf_start; test < __kftf_end; test++, i++) { + for (test = __kftf_test_case_start; test < __kftf_test_case_end; + test++, i++) { /* create a directory for the discovered test case */ st.debugfs_state[i].test_dir = debugfs_create_dir(test->name, st.kftf_dir); @@ -165,6 +169,15 @@ static int __init kftf_init(void) pr_info("kftf: registered %s\n", test->name); } + pr_info("kftf dumping constraints...\n"); + for (constraint = __kftf_constraint_start; + constraint < __kftf_constraint_end; constraint++) { + pr_info("input type: %s\n", constraint->input_type); + pr_info("field name: %s\n", constraint->field_name); + pr_info("value: %lx\n", constraint->value); + pr_info("type: %d\n", constraint->type); + } + return 0; cleanup_failure: diff --git a/lib/kftf/kftf_tests.h b/lib/kftf/kftf_tests.h index 7cc0a4676d0ec8..350be2595d1d80 100644 --- a/lib/kftf/kftf_tests.h +++ b/lib/kftf/kftf_tests.h @@ -30,6 +30,9 @@ static void kftf_fuzzable(char first, char second, char third) FUZZ_TEST(kftf_fuzzable, struct kftf_simple_arg) { + KFTF_EXPECT_NOT_NULL(kftf_simple_arg, first); + KFTF_EXPECT_LE(kftf_simple_arg, second, 'b'); + KFTF_EXPECT_GT(kftf_simple_arg, third, 'b'); kftf_fuzzable(arg.first, arg.second, arg.third); } From 95a0a016cc3f7637bdd912855b39315f29b37397 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Sun, 6 Jul 2025 15:12:24 +0000 Subject: [PATCH 19/32] kftf: allow for range constraints by expanding constraint struct. --- include/linux/kftf.h | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 869351c5f9a682..08d99cbb76ea6a 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -126,20 +126,29 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, } \ static void _fuzz_test_logic_##func(func_arg_type arg) +/** + * Reports a bug with a predictable prefix so that it can be parsed by a + * fuzzing driver. + */ +#define KFTF_REPORT_BUG(msg, fmt) pr_warn("bug: " #msg, fmt) + /** * struct kftf_constraint_type defines a type of constraint. The fuzzing driver * should be aware of these. */ enum kftf_constraint_type : uint8_t { - EXPECT_NE = 0, + EXPECT_EQ = 0, + EXPECT_NE, EXPECT_LE, EXPECT_GT, + EXPECT_IN_RANGE, }; struct kftf_constraint { const char *input_type; const char *field_name; - uintptr_t value; + uintptr_t value1; + uintptr_t value2; enum kftf_constraint_type type; }; @@ -156,32 +165,44 @@ struct kftf_constraint { * This macro is intended for internal use. A user should opt for * KFTF_EXPECT_* instead when defining fuzz test constraints. */ -#define __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, tpe) \ +#define __KFTF_DEFINE_CONSTRAINT(arg_type, field, val1, val2, tpe) \ static struct kftf_constraint __constraint_##arg_type##_##field \ __attribute__((__section__(".kftf.constraint"))) = { \ .input_type = "struct " #arg_type, \ .field_name = #field, \ - .value = (uintptr_t)val, \ + .value1 = (uintptr_t)val1, \ + .value2 = (uintptr_t)val2, \ .type = tpe, \ }; \ (void)__constraint_##arg_type##_##field; +#define KFTF_EXPECT_EQ(arg_type, field, val) \ + if (arg.field != val) \ + return; \ + __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_EQ) + #define KFTF_EXPECT_NE(arg_type, field, val) \ if (arg.field == val) \ return; \ - __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, EXPECT_NE) + __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_NE) #define KFTF_EXPECT_LE(arg_type, field, val) \ - if (arg.field >= val) \ + if (arg.field > val) \ return; \ - __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, EXPECT_LE); + __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_LE); #define KFTF_EXPECT_GT(arg_type, field, val) \ - if (arg.field > val) \ + if (arg.field <= val) \ return; \ - __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, EXPECT_GT); + __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_GT); #define KFTF_EXPECT_NOT_NULL(arg_type, field) \ KFTF_EXPECT_NE(arg_type, field, 0x0) +#define KFTF_EXPECT_IN_RANGE(arg_type, field, lower_bound, upper_bound) \ + if (arg.field < lower_bound || arg.field > upper_bound) \ + return; \ + __KFTF_DEFINE_CONSTRAINT(arg_type, field, lower_bound, upper_bound, \ + EXPECT_IN_RANGE) + #endif /* KFTF_H */ From 581f240635278eb46acc74d6be9e09e7d66800c8 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Sun, 6 Jul 2025 18:11:05 +0000 Subject: [PATCH 20/32] kftf: move some stuff around and enforce 64 byte alignment of KFuzzTest constraints to avoid weird errors occurring during parsing. --- arch/x86/kernel/vmlinux.lds.S | 4 +-- include/linux/kftf.h | 46 ++++++++++++++++++++++++----------- lib/kftf/kftf_main.c | 10 ++++++-- lib/kftf/kftf_tests.h | 4 +-- 4 files changed, 44 insertions(+), 20 deletions(-) diff --git a/arch/x86/kernel/vmlinux.lds.S b/arch/x86/kernel/vmlinux.lds.S index a70e56d3c70582..523132f08557cb 100644 --- a/arch/x86/kernel/vmlinux.lds.S +++ b/arch/x86/kernel/vmlinux.lds.S @@ -116,11 +116,11 @@ ASSERT(__relocate_kernel_end - __relocate_kernel_start <= KEXEC_CONTROL_CODE_MAX #define KFTF_TABLE \ . = ALIGN(0X100); \ __kftf_test_case_start = .; \ - KEEP(*(.kftf)); /* keep everything referencing section */ \ + KEEP(*(.kftf_test)); \ __kftf_test_case_end = .; \ . = ALIGN(0x100); \ __kftf_constraint_start = .; \ - KEEP(*(.kftf.constraint)); \ + KEEP(*(.kftf_constraint)); \ __kftf_constraint_end = .; \ PHDRS { diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 08d99cbb76ea6a..e96e1f9d834e55 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -91,7 +91,7 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, static void _fuzz_test_logic_##func(func_arg_type arg); \ /* test case struct initialization */ \ const struct kftf_test_case __fuzz_test__##func \ - __attribute__((__section__(".kftf"), __used__)) = { \ + __attribute__((__section__(".kftf_test"), __used__)) = { \ .name = #func, \ .arg_type_name = #func_arg_type, \ .write_input_cb = _write_callback_##func, \ @@ -144,13 +144,32 @@ enum kftf_constraint_type : uint8_t { EXPECT_IN_RANGE, }; +/** + * ktft_constraint defines a domain constraint for a struct variable that is + * taken as input for a FUZZ_TEST + * + * @input_type: the name of the input (a struct name) + * @field_name: the name of the field that this domain constraint applies to + * @value1: used in all comparisons + * @value2: only used in comparisons that require multiple values, e.g. range + * constraints + * @type: the type of the constraint, enumerated above + * + * Note: if this struct is not a multiple of 64 bytes, everything breaks and + * we get corrupted data and occasional kernel panics. To avoid this happening, + * we enforce 64 Byte alignment and statically assert that this struct has size + * 64 Bytes. + */ struct kftf_constraint { const char *input_type; const char *field_name; uintptr_t value1; uintptr_t value2; enum kftf_constraint_type type; -}; +} __attribute__((aligned(64))); + +static_assert(sizeof(struct kftf_constraint) == 64, + "struct kftf_constraint should have size 64"); /** * __KFTF_DEFINE_CONSTRAINT - defines a fuzz test constraint linked to a given @@ -165,16 +184,15 @@ struct kftf_constraint { * This macro is intended for internal use. A user should opt for * KFTF_EXPECT_* instead when defining fuzz test constraints. */ -#define __KFTF_DEFINE_CONSTRAINT(arg_type, field, val1, val2, tpe) \ - static struct kftf_constraint __constraint_##arg_type##_##field \ - __attribute__((__section__(".kftf.constraint"))) = { \ - .input_type = "struct " #arg_type, \ - .field_name = #field, \ - .value1 = (uintptr_t)val1, \ - .value2 = (uintptr_t)val2, \ - .type = tpe, \ - }; \ - (void)__constraint_##arg_type##_##field; +#define __KFTF_DEFINE_CONSTRAINT(arg_type, field, val1, val2, tpe) \ + static struct kftf_constraint __constraint_##arg_type##_##field \ + __attribute__((__section__(".kftf_constraint"), __used__)) = { \ + .input_type = "struct " #arg_type, \ + .field_name = #field, \ + .value1 = (uintptr_t)val1, \ + .value2 = (uintptr_t)val2, \ + .type = tpe, \ + }; #define KFTF_EXPECT_EQ(arg_type, field, val) \ if (arg.field != val) \ @@ -189,12 +207,12 @@ struct kftf_constraint { #define KFTF_EXPECT_LE(arg_type, field, val) \ if (arg.field > val) \ return; \ - __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_LE); + __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_LE) #define KFTF_EXPECT_GT(arg_type, field, val) \ if (arg.field <= val) \ return; \ - __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_GT); + __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_GT) #define KFTF_EXPECT_NOT_NULL(arg_type, field) \ KFTF_EXPECT_NE(arg_type, field, 0x0) diff --git a/lib/kftf/kftf_main.c b/lib/kftf/kftf_main.c index 2f9e2a8537d58c..133ee92b33ab31 100644 --- a/lib/kftf/kftf_main.c +++ b/lib/kftf/kftf_main.c @@ -11,6 +11,8 @@ #include #include +#include "kftf_tests.h" + extern const struct kftf_test_case __kftf_test_case_start[]; extern const struct kftf_test_case __kftf_test_case_end[]; extern const struct kftf_constraint __kftf_constraint_start[]; @@ -169,13 +171,17 @@ static int __init kftf_init(void) pr_info("kftf: registered %s\n", test->name); } - pr_info("kftf dumping constraints...\n"); + // TODO: make debugfs entries for these constraints + size_t num_constraints = 0; for (constraint = __kftf_constraint_start; constraint < __kftf_constraint_end; constraint++) { + pr_info("kftf: addr = 0x%lX\n", (size_t)constraint); pr_info("input type: %s\n", constraint->input_type); pr_info("field name: %s\n", constraint->field_name); - pr_info("value: %lx\n", constraint->value); + pr_info("value1: %lx\n", constraint->value1); + pr_info("value2: %lx\n", constraint->value2); pr_info("type: %d\n", constraint->type); + num_constraints++; } return 0; diff --git a/lib/kftf/kftf_tests.h b/lib/kftf/kftf_tests.h index 350be2595d1d80..2f59bfbd71b556 100644 --- a/lib/kftf/kftf_tests.h +++ b/lib/kftf/kftf_tests.h @@ -31,8 +31,8 @@ static void kftf_fuzzable(char first, char second, char third) FUZZ_TEST(kftf_fuzzable, struct kftf_simple_arg) { KFTF_EXPECT_NOT_NULL(kftf_simple_arg, first); - KFTF_EXPECT_LE(kftf_simple_arg, second, 'b'); - KFTF_EXPECT_GT(kftf_simple_arg, third, 'b'); + KFTF_EXPECT_IN_RANGE(kftf_simple_arg, second, 'a', 'z'); + KFTF_EXPECT_IN_RANGE(kftf_simple_arg, third, 'a', 'z'); kftf_fuzzable(arg.first, arg.second, arg.third); } From a83dee743958828f5e8912a5bf3d8cdb1bc31aae Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Tue, 8 Jul 2025 18:27:52 +0000 Subject: [PATCH 21/32] kftf: add annotations macros to express field types (for example strings) and a field being the length of another. --- arch/x86/kernel/vmlinux.lds.S | 8 ++++++-- include/linux/kftf.h | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/arch/x86/kernel/vmlinux.lds.S b/arch/x86/kernel/vmlinux.lds.S index 523132f08557cb..d7f403f6432ad0 100644 --- a/arch/x86/kernel/vmlinux.lds.S +++ b/arch/x86/kernel/vmlinux.lds.S @@ -114,14 +114,18 @@ ASSERT(__relocate_kernel_end - __relocate_kernel_start <= KEXEC_CONTROL_CODE_MAX #endif #define KFTF_TABLE \ - . = ALIGN(0X100); \ + . = ALIGN(PAGE_SIZE); \ __kftf_test_case_start = .; \ KEEP(*(.kftf_test)); \ __kftf_test_case_end = .; \ - . = ALIGN(0x100); \ + . = ALIGN(PAGE_SIZE); \ __kftf_constraint_start = .; \ KEEP(*(.kftf_constraint)); \ __kftf_constraint_end = .; \ + . = ALIGN(PAGE_SIZE); \ + __kftf_annotation_start = .; \ + KEEP(*(.kftf_annotation)); \ + __kftf_annotation_end = .; \ PHDRS { text PT_LOAD FLAGS(5); /* R_E */ diff --git a/include/linux/kftf.h b/include/linux/kftf.h index e96e1f9d834e55..5a7cc1a9c1ae93 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -223,4 +223,37 @@ static_assert(sizeof(struct kftf_constraint) == 64, __KFTF_DEFINE_CONSTRAINT(arg_type, field, lower_bound, upper_bound, \ EXPECT_IN_RANGE) +enum kftf_annotation_attribute : uint8_t { + ATTRIBUTE_LEN = 0, + ATTRIBUTE_STRING, +}; + +struct kftf_annotation { + const char *input_type; + const char *field_name; + const char *linked_field_name; + enum kftf_annotation_attribute attrib; +} __attribute__((aligned(32))); + +#define __KFTF_ANNOTATE(arg_type, field, linked_field, attribute) \ + static struct kftf_annotation __annotation_##arg_type##_##field \ + __attribute__((__section__(".kftf_annotation"), __used__)) = { \ + .input_type = "struct " #arg_type, \ + .field_name = #field, \ + .linked_field_name = #linked_field, \ + .attrib = attribute, \ + }; + +/** + * Annotates arg_type.field as a string + */ +#define KFTF_ANNOTATE_STRING(arg_type, field) \ + __KFTF_ANNOTATE(arg_type, field, , ATTRIBUTE_STRING) + +/** + * Annotates arg_type.field as the length of arg_type.linked_field + */ +#define KFTF_ANNOTATE_LEN(arg_type, field, linked_field) \ + __KFTF_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) + #endif /* KFTF_H */ From 8e1f787df5a8484e28e56598057464091752ef78 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 18 Jul 2025 10:40:41 +0000 Subject: [PATCH 22/32] kfuzztest: add array annotation There was not any mechanism in place in the KFuzzTest for distinguishing between value pointers and array pointers in fuzz test inputs. This information is difficult to parse without additional semantic information on the context. We introduce a new KFTF_ANNOTATE_ARRAY macro that annotates a field in a KFuzzTest input struct as an array, removing this ambiguity. --- include/linux/kftf.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 5a7cc1a9c1ae93..865526109bf5fc 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -226,6 +226,7 @@ static_assert(sizeof(struct kftf_constraint) == 64, enum kftf_annotation_attribute : uint8_t { ATTRIBUTE_LEN = 0, ATTRIBUTE_STRING, + ATTRIBUTE_ARRAY, }; struct kftf_annotation { @@ -250,6 +251,15 @@ struct kftf_annotation { #define KFTF_ANNOTATE_STRING(arg_type, field) \ __KFTF_ANNOTATE(arg_type, field, , ATTRIBUTE_STRING) +/** + * Annotates arg_type.field as an arrray. For example, assume that + * arg_type.field is of type `unsigned long *`, which could either represent + * a pointer to a single value or an array. Using this annotation removes that + * ambiguity. + */ +#define KFTF_ANNOTEATE_ARRAY(arg_type, field) \ + __KFTF_ANNOTATE(arg_type, field, , ATTRIBUTE_ARRAY) + /** * Annotates arg_type.field as the length of arg_type.linked_field */ From c74d33aa8f444187eef26e789ca73ebc9df97d63 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Wed, 9 Jul 2025 14:56:57 +0000 Subject: [PATCH 23/32] kftf: remove int_sqrt test which is no longer really needed, added relocation parsing code. --- include/linux/kftf.h | 83 +++++++++++++++++++++++++++++++++++++------ lib/kftf/kftf_tests.h | 2 +- lib/math/int_sqrt.c | 18 +--------- 3 files changed, 75 insertions(+), 28 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 865526109bf5fc..1679e6475f072e 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -7,6 +7,9 @@ MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ethan Graham "); MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFTF)"); +/* forward decl */ +static void *kftf_parse_input(void *input, size_t input_size); + /** * struct kftf_test case defines a single fuzz test case. These should not * be created manually. Instead the user should use the FUZZ_TEST macro defined @@ -88,7 +91,7 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, size_t len, loff_t *off); \ static ssize_t _read_metadata_callback_##func( \ struct file *filp, char __user *buf, size_t len, loff_t *off); \ - static void _fuzz_test_logic_##func(func_arg_type arg); \ + static void _fuzz_test_logic_##func(func_arg_type *arg); \ /* test case struct initialization */ \ const struct kftf_test_case __fuzz_test__##func \ __attribute__((__section__(".kftf_test"), __used__)) = { \ @@ -111,20 +114,33 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, const char __user *buf, \ size_t len, loff_t *off) \ { \ + pr_info("[ENTER] %s\n", __FUNCTION__); \ int err; \ - func_arg_type arg; \ - err = write_input_cb_common(filp, buf, len, off, &arg, \ - sizeof(arg)); \ + void *buffer = kmalloc(len, GFP_KERNEL); \ + if (!buffer || IS_ERR(buffer)) \ + return PTR_ERR(buffer); \ + err = write_input_cb_common(filp, buf, len, off, buffer, \ + sizeof(buffer)); \ if (err != 0) { \ + pr_info("%s: failed to read data, len = %zu\n", \ + __FUNCTION__, len); \ + kfree(buffer); \ return err; \ } \ + void *payload = kftf_parse_input(buffer, len); \ + if (!payload) { \ + kfree(buffer); \ + return -1; \ + } \ + func_arg_type *arg = payload; \ /* call the user's logic on the provided arg. */ \ /* NOTE: define some success/failure return types? */ \ pr_info("invoking fuzz logic for %s\n", #func); \ _fuzz_test_logic_##func(arg); \ + kfree(buffer); \ return len; \ } \ - static void _fuzz_test_logic_##func(func_arg_type arg) + static void _fuzz_test_logic_##func(func_arg_type *arg) /** * Reports a bug with a predictable prefix so that it can be parsed by a @@ -195,22 +211,22 @@ static_assert(sizeof(struct kftf_constraint) == 64, }; #define KFTF_EXPECT_EQ(arg_type, field, val) \ - if (arg.field != val) \ + if (arg->field != val) \ return; \ __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_EQ) #define KFTF_EXPECT_NE(arg_type, field, val) \ - if (arg.field == val) \ + if (arg->field == val) \ return; \ __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_NE) #define KFTF_EXPECT_LE(arg_type, field, val) \ - if (arg.field > val) \ + if (arg->field > val) \ return; \ __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_LE) #define KFTF_EXPECT_GT(arg_type, field, val) \ - if (arg.field <= val) \ + if (arg->field <= val) \ return; \ __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_GT) @@ -218,7 +234,7 @@ static_assert(sizeof(struct kftf_constraint) == 64, KFTF_EXPECT_NE(arg_type, field, 0x0) #define KFTF_EXPECT_IN_RANGE(arg_type, field, lower_bound, upper_bound) \ - if (arg.field < lower_bound || arg.field > upper_bound) \ + if (arg->field < lower_bound || arg->field > upper_bound) \ return; \ __KFTF_DEFINE_CONSTRAINT(arg_type, field, lower_bound, upper_bound, \ EXPECT_IN_RANGE) @@ -266,4 +282,51 @@ struct kftf_annotation { #define KFTF_ANNOTATE_LEN(arg_type, field, linked_field) \ __KFTF_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) +struct reloc_entry { + off_t pointer; + off_t value; +}; + +struct reloc_table { + int num_entries; + int padding[3]; // why? + struct reloc_entry entries[]; +}; +static_assert(offsetof(struct reloc_table, entries) % + sizeof(struct reloc_entry) == + 0); + +static void *kftf_parse_input(void *input, size_t input_size) +{ + pr_info("[ENTER]%s\n", __FUNCTION__); + size_t i; + void *payload_start; + uintptr_t *ptr_location; + struct reloc_entry re; + + if (input_size < sizeof(struct reloc_table)) { + pr_warn("got misformed input in %s\n", __FUNCTION__); + } + struct reloc_table *rt = input; + pr_info("%s: num_entries = %d\n", __FUNCTION__, rt->num_entries); + + payload_start = input + offsetof(struct reloc_table, entries) + + rt->num_entries * sizeof(struct reloc_entry); + + if (payload_start >= input + input_size) + return NULL; + + for (i = 0; i < rt->num_entries; i++) { + re = rt->entries[i]; + ptr_location = (uintptr_t *)(payload_start + re.pointer); + if ((void *)ptr_location >= input + input_size) + return NULL; + + *ptr_location = (uintptr_t)ptr_location + re.value; + } + + pr_info("%s: parsed %d entries\n", __FUNCTION__, rt->num_entries); + return payload_start; +} + #endif /* KFTF_H */ diff --git a/lib/kftf/kftf_tests.h b/lib/kftf/kftf_tests.h index 2f59bfbd71b556..3c2bed01093846 100644 --- a/lib/kftf/kftf_tests.h +++ b/lib/kftf/kftf_tests.h @@ -33,7 +33,7 @@ FUZZ_TEST(kftf_fuzzable, struct kftf_simple_arg) KFTF_EXPECT_NOT_NULL(kftf_simple_arg, first); KFTF_EXPECT_IN_RANGE(kftf_simple_arg, second, 'a', 'z'); KFTF_EXPECT_IN_RANGE(kftf_simple_arg, third, 'a', 'z'); - kftf_fuzzable(arg.first, arg.second, arg.third); + kftf_fuzzable(arg->first, arg->second, arg->third); } #endif /* KFTF_TESTS_H */ diff --git a/lib/math/int_sqrt.c b/lib/math/int_sqrt.c index a52b16f8045393..a8170bb9142f39 100644 --- a/lib/math/int_sqrt.c +++ b/lib/math/int_sqrt.c @@ -10,17 +10,6 @@ #include #include #include -#include - -struct int_sqrt_arg { - unsigned long x; -}; - -FUZZ_TEST(int_sqrt, struct int_sqrt_arg) -{ - unsigned long res = int_sqrt(arg.x); - pr_info("fuzz_arg.x = %lu, res = %lu", arg.x, res); -} /** * int_sqrt - computes the integer square root @@ -32,11 +21,6 @@ unsigned long int_sqrt(unsigned long x) { unsigned long b, m, y = 0; - // deliberate bug - if (x == 0) { - char c = *(char *)x; - pr_info("should crash here! %c\n", c); - } if (x <= 1) return x; @@ -67,7 +51,7 @@ u32 int_sqrt64(u64 x) u64 b, m, y = 0; if (x <= ULONG_MAX) - return int_sqrt((unsigned long)x); + return int_sqrt((unsigned long) x); m = 1ULL << ((fls64(x) - 1) & ~1ULL); while (m != 0) { From 6b03486d28877adf8ec084ef6eb75798d1630c7c Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 11 Jul 2025 14:00:03 +0000 Subject: [PATCH 24/32] kftf: remove some prints from write_input_callback, update test case to remove copying from userspace --- include/linux/kftf.h | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 1679e6475f072e..807fcf9221958b 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -119,11 +119,8 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, void *buffer = kmalloc(len, GFP_KERNEL); \ if (!buffer || IS_ERR(buffer)) \ return PTR_ERR(buffer); \ - err = write_input_cb_common(filp, buf, len, off, buffer, \ - sizeof(buffer)); \ + err = write_input_cb_common(filp, buf, len, off, buffer, len); \ if (err != 0) { \ - pr_info("%s: failed to read data, len = %zu\n", \ - __FUNCTION__, len); \ kfree(buffer); \ return err; \ } \ @@ -283,49 +280,54 @@ struct kftf_annotation { __KFTF_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) struct reloc_entry { - off_t pointer; - off_t value; + uintptr_t pointer; /* offset from the beginning of the payload */ + uintptr_t value; /* difference between the pointed to address and the address itself */ }; struct reloc_table { int num_entries; - int padding[3]; // why? + int padding[3]; struct reloc_entry entries[]; }; static_assert(offsetof(struct reloc_table, entries) % sizeof(struct reloc_entry) == 0); +static const uintptr_t nullPtr = (uintptr_t)-1; + static void *kftf_parse_input(void *input, size_t input_size) { - pr_info("[ENTER]%s\n", __FUNCTION__); size_t i; void *payload_start; uintptr_t *ptr_location; struct reloc_entry re; + pr_info("%s: input_size = %zu\n", __FUNCTION__, input_size); if (input_size < sizeof(struct reloc_table)) { pr_warn("got misformed input in %s\n", __FUNCTION__); + return NULL; } struct reloc_table *rt = input; - pr_info("%s: num_entries = %d\n", __FUNCTION__, rt->num_entries); - payload_start = input + offsetof(struct reloc_table, entries) + + payload_start = (char *)input + offsetof(struct reloc_table, entries) + rt->num_entries * sizeof(struct reloc_entry); - if (payload_start >= input + input_size) return NULL; for (i = 0; i < rt->num_entries; i++) { re = rt->entries[i]; ptr_location = (uintptr_t *)(payload_start + re.pointer); - if ((void *)ptr_location >= input + input_size) + if ((void *)ptr_location + sizeof(uintptr_t) >= + input + input_size) return NULL; - *ptr_location = (uintptr_t)ptr_location + re.value; + if (re.value == nullPtr) { + *ptr_location = (uintptr_t)NULL; + } else { + *ptr_location = (uintptr_t)ptr_location + re.value; + } } - pr_info("%s: parsed %d entries\n", __FUNCTION__, rt->num_entries); return payload_start; } From ba24eb3f5cc6522d8d43ffdc51851fe670bf56ee Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Mon, 14 Jul 2025 09:45:27 +0000 Subject: [PATCH 25/32] kftf: introduce new buffer for payload that provides alignment guarantees for encoded structures. --- include/linux/kftf.h | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 807fcf9221958b..3bf79bbc319f3d 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -135,6 +135,7 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, pr_info("invoking fuzz logic for %s\n", #func); \ _fuzz_test_logic_##func(arg); \ kfree(buffer); \ + kfree(payload); \ return len; \ } \ static void _fuzz_test_logic_##func(func_arg_type *arg) @@ -284,9 +285,16 @@ struct reloc_entry { uintptr_t value; /* difference between the pointed to address and the address itself */ }; +/* + * How many integers of padding in the relocation table between the header + * information and the relocation entries + */ +#define RELOC_TABLE_PADDING 2 + struct reloc_table { int num_entries; - int padding[3]; + uint32_t max_alignment; + int padding[RELOC_TABLE_PADDING]; struct reloc_entry entries[]; }; static_assert(offsetof(struct reloc_table, entries) % @@ -298,8 +306,10 @@ static const uintptr_t nullPtr = (uintptr_t)-1; static void *kftf_parse_input(void *input, size_t input_size) { size_t i; - void *payload_start; + void *payload_start, *out; uintptr_t *ptr_location; + size_t payload_len, alloc_size; + struct reloc_table *rt; struct reloc_entry re; pr_info("%s: input_size = %zu\n", __FUNCTION__, input_size); @@ -307,16 +317,35 @@ static void *kftf_parse_input(void *input, size_t input_size) pr_warn("got misformed input in %s\n", __FUNCTION__); return NULL; } - struct reloc_table *rt = input; + rt = input; payload_start = (char *)input + offsetof(struct reloc_table, entries) + rt->num_entries * sizeof(struct reloc_entry); if (payload_start >= input + input_size) return NULL; + /* + * To guarantee correct alignment of structures within the payload, we + * allocate a new property that is aligned to the next power of two + * greater than either the size of the payload or the maximum alignment + * of the nested structures. + */ + payload_len = input_size - (payload_start - input); + alloc_size = MAX(roundup_pow_of_two(payload_len), + roundup_pow_of_two(rt->max_alignment)); + out = kmalloc(alloc_size, GFP_KERNEL); + if (!out) { + return NULL; + } + memcpy(out, payload_start, payload_len); + + /* + * Iterate through entries in the relocation table and patch the + * pointers. + */ for (i = 0; i < rt->num_entries; i++) { re = rt->entries[i]; - ptr_location = (uintptr_t *)(payload_start + re.pointer); + ptr_location = (uintptr_t *)(out + re.pointer); if ((void *)ptr_location + sizeof(uintptr_t) >= input + input_size) return NULL; @@ -328,7 +357,7 @@ static void *kftf_parse_input(void *input, size_t input_size) } } - return payload_start; + return out; } #endif /* KFTF_H */ From 799cdcdd85baf8a9407669d23691bc73a0d2af4a Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Tue, 15 Jul 2025 17:48:48 +0000 Subject: [PATCH 26/32] kftf: harden binary input parsing --- include/linux/kftf.h | 82 +++++++++++++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 3bf79bbc319f3d..b0bbf2e7b1db9a 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -237,6 +237,10 @@ static_assert(sizeof(struct kftf_constraint) == 64, __KFTF_DEFINE_CONSTRAINT(arg_type, field, lower_bound, upper_bound, \ EXPECT_IN_RANGE) +#define KFTF_EXPECT_LEN(expected_len, actual_len) \ + if ((expected_len) != (actual_len)) \ + return; + enum kftf_annotation_attribute : uint8_t { ATTRIBUTE_LEN = 0, ATTRIBUTE_STRING, @@ -301,17 +305,24 @@ static_assert(offsetof(struct reloc_table, entries) % sizeof(struct reloc_entry) == 0); +/** + * This value should be known the fuzz engine. + */ static const uintptr_t nullPtr = (uintptr_t)-1; -static void *kftf_parse_input(void *input, size_t input_size) +/* XXX: wasn't building before without attribute unused, but it is used in + * several locations - weird... */ +__attribute__((unused)) static void *kftf_parse_input(void *input, + size_t input_size) { size_t i; void *payload_start, *out; uintptr_t *ptr_location; - size_t payload_len, alloc_size; + size_t payload_len, alloc_size, entries_size, header_size; struct reloc_table *rt; struct reloc_entry re; - pr_info("%s: input_size = %zu\n", __FUNCTION__, input_size); + if (input_size > KMALLOC_MAX_SIZE) + return NULL; if (input_size < sizeof(struct reloc_table)) { pr_warn("got misformed input in %s\n", __FUNCTION__); @@ -319,24 +330,59 @@ static void *kftf_parse_input(void *input, size_t input_size) } rt = input; - payload_start = (char *)input + offsetof(struct reloc_table, entries) + - rt->num_entries * sizeof(struct reloc_entry); + if (check_mul_overflow(rt->num_entries, sizeof(struct reloc_entry), + &entries_size)) + return NULL; + header_size = offsetof(struct reloc_table, entries) + entries_size; + if (header_size > input_size) + return NULL; + + payload_start = (char *)input + header_size; if (payload_start >= input + input_size) return NULL; - /* - * To guarantee correct alignment of structures within the payload, we - * allocate a new property that is aligned to the next power of two - * greater than either the size of the payload or the maximum alignment - * of the nested structures. - */ - payload_len = input_size - (payload_start - input); - alloc_size = MAX(roundup_pow_of_two(payload_len), - roundup_pow_of_two(rt->max_alignment)); - out = kmalloc(alloc_size, GFP_KERNEL); - if (!out) { + if (!is_power_of_2(rt->max_alignment) || rt->max_alignment > PAGE_SIZE) return NULL; + + payload_len = input_size - (payload_start - input); + + /* + * Check input for out-of-bounds pointers before before allocating + * aligned output buffer. + */ + for (i = 0; i < rt->num_entries; i++) { + re = rt->entries[i]; + if (re.pointer > payload_len || + re.pointer + sizeof(uintptr_t) > payload_len) + return NULL; + + if (re.value == nullPtr) + continue; + + /* We don't know the size of the data that is being pointed and + * therefor cannot make any stricter assertions. For example, + * if we enforce: + * re.pointer + re.value + sizeof(uintptr_t) < payload_len + * then we cannot write values with bytesize < 8. + */ + if (re.pointer + re.value >= payload_len) + return NULL; } + + /* + * To guarantee correct alignment of structures within the payload, we + * allocate a new buffer that is aligned to the next power of two + * greater than either the size of the payload + 1 or the maximum + * alignment of the nested structures. We add one to the payload length + * and call kzalloc to ensure that the payload is padded by trailing + * zeros to prevent false-positives on non-null terminated strings. + */ + alloc_size = + MAX(roundup_pow_of_two(payload_len + 1), rt->max_alignment); + out = kzalloc(alloc_size, GFP_KERNEL); + if (!out) + return NULL; + memcpy(out, payload_start, payload_len); /* @@ -346,10 +392,6 @@ static void *kftf_parse_input(void *input, size_t input_size) for (i = 0; i < rt->num_entries; i++) { re = rt->entries[i]; ptr_location = (uintptr_t *)(out + re.pointer); - if ((void *)ptr_location + sizeof(uintptr_t) >= - input + input_size) - return NULL; - if (re.value == nullPtr) { *ptr_location = (uintptr_t)NULL; } else { From 51cadf213d11c0abf9cf2175570b90bc72cc5012 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 25 Jul 2025 08:35:58 +0000 Subject: [PATCH 27/32] kfuzztest: update naming from "kftf" to "kfuzztest" --- arch/x86/kernel/vmlinux.lds.S | 22 +- include/linux/{kftf.h => kfuzztest.h} | 190 ++++++++---------- lib/Kconfig.debug | 2 +- lib/Kconfig.kftf | 5 - lib/Kconfig.kfuzztest | 5 + lib/Makefile | 2 +- lib/kftf/Makefile | 4 - lib/kfuzztest/Makefile | 4 + .../kfuzztest_main.c} | 124 +++++------- .../kfuzztest_tests.h} | 14 +- 10 files changed, 172 insertions(+), 200 deletions(-) rename include/linux/{kftf.h => kfuzztest.h} (66%) delete mode 100644 lib/Kconfig.kftf create mode 100644 lib/Kconfig.kfuzztest delete mode 100644 lib/kftf/Makefile create mode 100644 lib/kfuzztest/Makefile rename lib/{kftf/kftf_main.c => kfuzztest/kfuzztest_main.c} (54%) rename lib/{kftf/kftf_tests.h => kfuzztest/kfuzztest_tests.h} (68%) diff --git a/arch/x86/kernel/vmlinux.lds.S b/arch/x86/kernel/vmlinux.lds.S index d7f403f6432ad0..fdcca6b4b2f44e 100644 --- a/arch/x86/kernel/vmlinux.lds.S +++ b/arch/x86/kernel/vmlinux.lds.S @@ -113,19 +113,19 @@ ASSERT(__relocate_kernel_end - __relocate_kernel_start <= KEXEC_CONTROL_CODE_MAX #define KEXEC_RELOCATE_KERNEL #endif -#define KFTF_TABLE \ +#define KFUZZTEST_TABLE \ . = ALIGN(PAGE_SIZE); \ - __kftf_test_case_start = .; \ - KEEP(*(.kftf_test)); \ - __kftf_test_case_end = .; \ + __kfuzztest_targets_start = .; \ + KEEP(*(.kfuzztest_target)); \ + __kfuzztest_targets_end = .; \ . = ALIGN(PAGE_SIZE); \ - __kftf_constraint_start = .; \ - KEEP(*(.kftf_constraint)); \ - __kftf_constraint_end = .; \ + __kfuzztest_constraints_start = .; \ + KEEP(*(.kfuzztest_constraint)); \ + __kfuzztest_constraints_end = .; \ . = ALIGN(PAGE_SIZE); \ - __kftf_annotation_start = .; \ - KEEP(*(.kftf_annotation)); \ - __kftf_annotation_end = .; \ + __kfuzztest_annotations_start = .; \ + KEEP(*(.kfuzztest_annotation)); \ + __kfuzztest_annotations_end = .; \ PHDRS { text PT_LOAD FLAGS(5); /* R_E */ @@ -214,7 +214,7 @@ SECTIONS CONSTRUCTORS KEXEC_RELOCATE_KERNEL - KFTF_TABLE + KFUZZTEST_TABLE /* rarely changed data like cpu maps */ READ_MOSTLY_DATA(INTERNODE_CACHE_BYTES) diff --git a/include/linux/kftf.h b/include/linux/kfuzztest.h similarity index 66% rename from include/linux/kftf.h rename to include/linux/kfuzztest.h index b0bbf2e7b1db9a..43e74308d5f5ac 100644 --- a/include/linux/kftf.h +++ b/include/linux/kfuzztest.h @@ -1,31 +1,16 @@ -#ifndef KFTF_H -#define KFTF_H +#ifndef KFUZZTEST_H +#define KFUZZTEST_H #include MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ethan Graham "); -MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFTF)"); +MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFuzzTest)"); /* forward decl */ -static void *kftf_parse_input(void *input, size_t input_size); +static void *kfuzztest_parse_input(void *input, size_t input_size); -/** - * struct kftf_test case defines a single fuzz test case. These should not - * be created manually. Instead the user should use the FUZZ_TEST macro defined - * below. - * @name: The name of the test case, generally the function being fuzzed. - * @arg_type_name: string representation of the type of argument being fuzzed, - * for example "struct func_arg_type" - * @write_input_cb: Callback invoked when a write's to the test case's debugfs - * input file. This is the entry point for fuzzing data. It is responsible - * for parsing any data written to the input file and invoking the fuzzing - * logic. It should return the number of bytes consumed from `buf` - * @read_metadata_cb: Callback invoked when a user reads from the test case's - * "metadata" debugfs file. It should simply return whatever is contained - * in the `arg_type_name` field. - */ -struct kftf_test_case { +struct kfuzztest_target { const char *name; const char *arg_type_name; ssize_t (*write_input_cb)(struct file *filp, const char __user *buf, @@ -57,14 +42,13 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, * example below. * * - * This macro generates all of the necessary boilerplate for a KFTF test case, - * which is placed in a dedicated ".kftf" section so that the dedicated KFTF - * module can discover all defined tests at runtime. + * This macro generates all of the necessary boilerplate for a KFuzzTest driver, + * which is placed in a dedicated ".kfuzztest_target" section. * * For each test, this macro generates * - A buffer to receive input through the debugfs entry * - A mutex to protect the input buffer - * - A `struct kftf_test_case` instance + * - A `struct kfuzztest_target` instance * * Example usagea: * @@ -93,13 +77,13 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, struct file *filp, char __user *buf, size_t len, loff_t *off); \ static void _fuzz_test_logic_##func(func_arg_type *arg); \ /* test case struct initialization */ \ - const struct kftf_test_case __fuzz_test__##func \ - __attribute__((__section__(".kftf_test"), __used__)) = { \ - .name = #func, \ - .arg_type_name = #func_arg_type, \ - .write_input_cb = _write_callback_##func, \ - .read_metadata_cb = _read_metadata_callback_##func \ - }; \ + const struct kfuzztest_target __fuzz_test__##func __attribute__(( \ + __section__(".kfuzztest_target"), __used__)) = { \ + .name = #func, \ + .arg_type_name = #func_arg_type, \ + .write_input_cb = _write_callback_##func, \ + .read_metadata_cb = _read_metadata_callback_##func \ + }; \ /* callback that simply returns the type name to the user */ \ static ssize_t _read_metadata_callback_##func( \ struct file *filp, char __user *buf, size_t len, loff_t *off) \ @@ -124,7 +108,7 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, kfree(buffer); \ return err; \ } \ - void *payload = kftf_parse_input(buffer, len); \ + void *payload = kfuzztest_parse_input(buffer, len); \ if (!payload) { \ kfree(buffer); \ return -1; \ @@ -144,13 +128,13 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, * Reports a bug with a predictable prefix so that it can be parsed by a * fuzzing driver. */ -#define KFTF_REPORT_BUG(msg, fmt) pr_warn("bug: " #msg, fmt) +#define KFUZZTEST_REPORT_BUG(msg, fmt) pr_warn("bug: " #msg, fmt) /** - * struct kftf_constraint_type defines a type of constraint. The fuzzing driver + * struct kfuzztest_constraint_type defines a type of constraint. The fuzzing driver * should be aware of these. */ -enum kftf_constraint_type : uint8_t { +enum kfuzztest_constraint_type : uint8_t { EXPECT_EQ = 0, EXPECT_NE, EXPECT_LE, @@ -159,8 +143,8 @@ enum kftf_constraint_type : uint8_t { }; /** - * ktft_constraint defines a domain constraint for a struct variable that is - * taken as input for a FUZZ_TEST + * kfuzztest_constraint defines a domain constraint for a struct variable that + * is taken as input for a FUZZ_TEST. * * @input_type: the name of the input (a struct name) * @field_name: the name of the field that this domain constraint applies to @@ -174,100 +158,102 @@ enum kftf_constraint_type : uint8_t { * we enforce 64 Byte alignment and statically assert that this struct has size * 64 Bytes. */ -struct kftf_constraint { +struct kfuzztest_constraint { const char *input_type; const char *field_name; uintptr_t value1; uintptr_t value2; - enum kftf_constraint_type type; + enum kfuzztest_constraint_type type; } __attribute__((aligned(64))); -static_assert(sizeof(struct kftf_constraint) == 64, - "struct kftf_constraint should have size 64"); +static_assert(sizeof(struct kfuzztest_constraint) == 64, + "struct kfuzztest_constraint should have size 64"); /** - * __KFTF_DEFINE_CONSTRAINT - defines a fuzz test constraint linked to a given + * __KFUZZTEST_DEFINE_CONSTRAINT - defines a fuzz test constraint linked to a given * argument type belonging to a fuzz test. See FUZZ_TEST above. * * @arg_type: the type of argument (a struct) without the leading "struct" in * its name, which will be prepended. * @field: the field on which the constraint is defined. - * @val: used for comparison constraints such as KFTF_EXPECT_NE + * @val: used for comparison constraints such as KFUZZTEST_EXPECT_NE * @tpe: the type of constaint that this defines * * This macro is intended for internal use. A user should opt for - * KFTF_EXPECT_* instead when defining fuzz test constraints. + * KFUZZTEST_EXPECT_* instead when defining fuzz test constraints. */ -#define __KFTF_DEFINE_CONSTRAINT(arg_type, field, val1, val2, tpe) \ - static struct kftf_constraint __constraint_##arg_type##_##field \ - __attribute__((__section__(".kftf_constraint"), __used__)) = { \ - .input_type = "struct " #arg_type, \ - .field_name = #field, \ - .value1 = (uintptr_t)val1, \ - .value2 = (uintptr_t)val2, \ - .type = tpe, \ +#define __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val1, val2, tpe) \ + static struct kfuzztest_constraint __constraint_##arg_type##_##field \ + __attribute__((__section__(".kfuzztest_constraint"), \ + __used__)) = { \ + .input_type = "struct " #arg_type, \ + .field_name = #field, \ + .value1 = (uintptr_t)val1, \ + .value2 = (uintptr_t)val2, \ + .type = tpe, \ }; -#define KFTF_EXPECT_EQ(arg_type, field, val) \ - if (arg->field != val) \ - return; \ - __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_EQ) - -#define KFTF_EXPECT_NE(arg_type, field, val) \ - if (arg->field == val) \ - return; \ - __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_NE) - -#define KFTF_EXPECT_LE(arg_type, field, val) \ - if (arg->field > val) \ - return; \ - __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_LE) - -#define KFTF_EXPECT_GT(arg_type, field, val) \ - if (arg->field <= val) \ - return; \ - __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_GT) - -#define KFTF_EXPECT_NOT_NULL(arg_type, field) \ - KFTF_EXPECT_NE(arg_type, field, 0x0) - -#define KFTF_EXPECT_IN_RANGE(arg_type, field, lower_bound, upper_bound) \ - if (arg->field < lower_bound || arg->field > upper_bound) \ - return; \ - __KFTF_DEFINE_CONSTRAINT(arg_type, field, lower_bound, upper_bound, \ - EXPECT_IN_RANGE) - -#define KFTF_EXPECT_LEN(expected_len, actual_len) \ - if ((expected_len) != (actual_len)) \ +#define KFUZZTEST_EXPECT_EQ(arg_type, field, val) \ + if (arg->field != val) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_EQ) + +#define KFUZZTEST_EXPECT_NE(arg_type, field, val) \ + if (arg->field == val) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_NE) + +#define KFUZZTEST_EXPECT_LE(arg_type, field, val) \ + if (arg->field > val) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_LE) + +#define KFUZZTEST_EXPECT_GT(arg_type, field, val) \ + if (arg->field <= val) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_GT) + +#define KFUZZTEST_EXPECT_NOT_NULL(arg_type, field) \ + KFUZZTEST_EXPECT_NE(arg_type, field, 0x0) + +#define KFUZZTEST_EXPECT_IN_RANGE(arg_type, field, lower_bound, upper_bound) \ + if (arg->field < lower_bound || arg->field > upper_bound) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, lower_bound, \ + upper_bound, EXPECT_IN_RANGE) + +#define KFUZZTEST_EXPECT_LEN(expected_len, actual_len) \ + if ((expected_len) != (actual_len)) \ return; -enum kftf_annotation_attribute : uint8_t { +enum kfuzztest_annotation_attribute : uint8_t { ATTRIBUTE_LEN = 0, ATTRIBUTE_STRING, ATTRIBUTE_ARRAY, }; -struct kftf_annotation { +struct kfuzztest_annotation { const char *input_type; const char *field_name; const char *linked_field_name; - enum kftf_annotation_attribute attrib; + enum kfuzztest_annotation_attribute attrib; } __attribute__((aligned(32))); -#define __KFTF_ANNOTATE(arg_type, field, linked_field, attribute) \ - static struct kftf_annotation __annotation_##arg_type##_##field \ - __attribute__((__section__(".kftf_annotation"), __used__)) = { \ - .input_type = "struct " #arg_type, \ - .field_name = #field, \ - .linked_field_name = #linked_field, \ - .attrib = attribute, \ +#define __KFUZZTEST_ANNOTATE(arg_type, field, linked_field, attribute) \ + static struct kfuzztest_annotation __annotation_##arg_type##_##field \ + __attribute__((__section__(".kfuzztest_annotation"), \ + __used__)) = { \ + .input_type = "struct " #arg_type, \ + .field_name = #field, \ + .linked_field_name = #linked_field, \ + .attrib = attribute, \ }; /** * Annotates arg_type.field as a string */ -#define KFTF_ANNOTATE_STRING(arg_type, field) \ - __KFTF_ANNOTATE(arg_type, field, , ATTRIBUTE_STRING) +#define KFUZZTEST_ANNOTATE_STRING(arg_type, field) \ + __KFUZZTEST_ANNOTATE(arg_type, field, , ATTRIBUTE_STRING) /** * Annotates arg_type.field as an arrray. For example, assume that @@ -275,14 +261,14 @@ struct kftf_annotation { * a pointer to a single value or an array. Using this annotation removes that * ambiguity. */ -#define KFTF_ANNOTEATE_ARRAY(arg_type, field) \ - __KFTF_ANNOTATE(arg_type, field, , ATTRIBUTE_ARRAY) +#define KFUZZTEST_ANNOTEATE_ARRAY(arg_type, field) \ + __KFUZZTEST_ANNOTATE(arg_type, field, , ATTRIBUTE_ARRAY) /** * Annotates arg_type.field as the length of arg_type.linked_field */ -#define KFTF_ANNOTATE_LEN(arg_type, field, linked_field) \ - __KFTF_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) +#define KFUZZTEST_ANNOTATE_LEN(arg_type, field, linked_field) \ + __KFUZZTEST_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) struct reloc_entry { uintptr_t pointer; /* offset from the beginning of the payload */ @@ -312,8 +298,8 @@ static const uintptr_t nullPtr = (uintptr_t)-1; /* XXX: wasn't building before without attribute unused, but it is used in * several locations - weird... */ -__attribute__((unused)) static void *kftf_parse_input(void *input, - size_t input_size) +__attribute__((unused)) static void *kfuzztest_parse_input(void *input, + size_t input_size) { size_t i; void *payload_start, *out; @@ -402,4 +388,4 @@ __attribute__((unused)) static void *kftf_parse_input(void *input, return out; } -#endif /* KFTF_H */ +#endif /* KFUZZTEST_H */ diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 42cd345f93ad81..5a33c34b107469 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1040,7 +1040,7 @@ config MEM_ALLOC_PROFILING_DEBUG source "lib/Kconfig.kasan" source "lib/Kconfig.kfence" source "lib/Kconfig.kmsan" -source "lib/Kconfig.kftf" +source "lib/Kconfig.kfuzztest" endmenu # "Memory Debugging" diff --git a/lib/Kconfig.kftf b/lib/Kconfig.kftf deleted file mode 100644 index 6a7d35a25b5f50..00000000000000 --- a/lib/Kconfig.kftf +++ /dev/null @@ -1,5 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only - -config KFTF - tristate "Enable Kernel Fuzz Testing Framework (KFTF)" - depends on DEBUG_FS diff --git a/lib/Kconfig.kfuzztest b/lib/Kconfig.kfuzztest new file mode 100644 index 00000000000000..0fb000fe305c6a --- /dev/null +++ b/lib/Kconfig.kfuzztest @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config KFUZZTEST + bool "Enable Kernel Fuzz Testing Framework (KFuzzTest)" + depends on DEBUG_FS diff --git a/lib/Makefile b/lib/Makefile index 33e75b3682018e..511c44ef4b19e9 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -354,7 +354,7 @@ obj-$(CONFIG_GENERIC_LIB_CMPDI2) += cmpdi2.o obj-$(CONFIG_GENERIC_LIB_UCMPDI2) += ucmpdi2.o obj-$(CONFIG_OBJAGG) += objagg.o -obj-$(CONFIG_KFTF) += kftf/ +obj-$(CONFIG_KFUZZTEST) += kfuzztest/ # pldmfw library obj-$(CONFIG_PLDMFW) += pldmfw/ diff --git a/lib/kftf/Makefile b/lib/kftf/Makefile deleted file mode 100644 index 6e1df924ce37ae..00000000000000 --- a/lib/kftf/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only - -obj-$(CONFIG_KFTF) += kftf.o -kftf-objs := kftf_main.o diff --git a/lib/kfuzztest/Makefile b/lib/kfuzztest/Makefile new file mode 100644 index 00000000000000..ddaab873324c2a --- /dev/null +++ b/lib/kfuzztest/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_KFUZZTEST) += kfuzztest.o +kfuzztest-objs := kfuzztest_main.o diff --git a/lib/kftf/kftf_main.c b/lib/kfuzztest/kfuzztest_main.c similarity index 54% rename from lib/kftf/kftf_main.c rename to lib/kfuzztest/kfuzztest_main.c index 133ee92b33ab31..cc38250e4f11a4 100644 --- a/lib/kftf/kftf_main.c +++ b/lib/kfuzztest/kfuzztest_main.c @@ -4,62 +4,60 @@ * * This module is responsible for discovering and initializing all fuzz test * cases defined using the FUZZ_TEST() macro. It creates a debugfs interface -* under /sys/kernel/debug/kftf/ for userspace to interact with each test. +* under /sys/kernel/debug/kfuzztest/ for userspace to interact with each test. */ #include #include -#include +#include #include -#include "kftf_tests.h" +#include "kfuzztest_tests.h" -extern const struct kftf_test_case __kftf_test_case_start[]; -extern const struct kftf_test_case __kftf_test_case_end[]; -extern const struct kftf_constraint __kftf_constraint_start[]; -extern const struct kftf_constraint __kftf_constraint_end[]; +extern const struct kfuzztest_target __kfuzztest_targets_start[]; +extern const struct kfuzztest_target __kfuzztest_targets_end[]; /** - * struct kftf_dentry - A container for a debugfs dentry and its fops. + * struct kfuzztest_dentry - A container for a debugfs dentry and its fops. * @dentry: Pointer to the created debugfs dentry. * @fops: The file_operations struct associated with this dentry. * * This simplifies state management by keeping a file's dentry and its * operations bundled together. */ -struct kftf_dentry { +struct kfuzztest_dentry { struct dentry *dentry; struct file_operations fops; }; /** - * struct kftf_debugfs_state - Per-test-case debugfs state. + * struct kfuzztest_debugfs_state - Per-test-case debugfs state. * @test_dir: The top-level debugfs directory for a single test case, e.g., - * /sys/kernel/debug/kftf//. + * /sys/kernel/debug/kfuzztest//. * @input_dentry: The state for the "input" file, which is write-only. * @metadata_dentry: The state for the "metadata" file, which is read-only. * * Wraps all debugfs components created for a single test case. */ -struct kftf_debugfs_state { +struct kfuzztest_debugfs_state { struct dentry *test_dir; - struct kftf_dentry input_dentry; - struct kftf_dentry metadata_dentry; + struct kfuzztest_dentry input_dentry; + struct kfuzztest_dentry metadata_dentry; }; /** - * struct kftf_simple_fuzzer_state - Global state for the KFTF module. - * @kftf_dir: The root debugfs directory, /sys/kernel/debug/kftf/. + * struct kfuzztest_simple_fuzzer_state - Global state for the KFTF module. + * @kfuzztest_dir: The root debugfs directory, /sys/kernel/debug/kfuzztest/. * @debugfs_state: A statically sized array holding the state for each * registered test case. */ -struct kftf_simple_fuzzer_state { +struct kfuzztest_simple_fuzzer_state { struct file_operations fops; - struct dentry *kftf_dir; - struct kftf_debugfs_state *debugfs_state; + struct dentry *kfuzztest_dir; + struct kfuzztest_debugfs_state *debugfs_state; }; /* Global static variable to hold all state for the module. */ -static struct kftf_simple_fuzzer_state st; +static struct kfuzztest_simple_fuzzer_state st; /* * Default file permissions for the debugfs entries. @@ -68,18 +66,18 @@ static struct kftf_simple_fuzzer_state st; * * XXX: should formally define what the permissions should be on these files */ -const umode_t kftf_flags_w = 0222; -const umode_t kftf_flags_r = 0444; +const umode_t kfuzztest_flags_w = 0222; +const umode_t kfuzztest_flags_r = 0444; /** - * kftf_init - Initializes the debug filesystem for KFTF. + * kfuzztest_init - Initializes the debug filesystem for KFTF. * * This function is the entry point for the KFTF module, populating the debugfs * that is used for IO interaction between the individual fuzzing drivers and * a userspace fuzzing tool like syzkaller. * - * Each registered test in the ".kftf" section gets its own subdirectory - * under "/sys/kernel/debug/kftf/" with two files: + * Each registered test in the ".kfuzztest" section gets its own subdirectory + * under "/sys/kernel/debug/kfuzztest/" with two files: * - input: write-only file to send input to the fuzz driver * - metadata: used to read the type name that the fuzz driver expects * @@ -88,39 +86,39 @@ const umode_t kftf_flags_r = 0444; * -EINVAL if the number of tests exceeds KFTF_MAX_TEST_CASES * -ENODEV or other error codes if debugfs creation fails. */ -static int __init kftf_init(void) +static int __init kfuzztest_init(void) { - const struct kftf_test_case *test; - const struct kftf_constraint *constraint; + const struct kfuzztest_target *targ; int ret = 0; int i = 0; size_t num_test_cases; - num_test_cases = __kftf_test_case_end - __kftf_test_case_start; + num_test_cases = __kfuzztest_targets_end - __kfuzztest_targets_start; - st.debugfs_state = kmalloc( - num_test_cases * sizeof(struct kftf_debugfs_state), GFP_KERNEL); + st.debugfs_state = + kmalloc(num_test_cases * sizeof(struct kfuzztest_debugfs_state), + GFP_KERNEL); if (!st.debugfs_state) return -ENOMEM; - /* create the main "kftf" directory in `/sys/kernel/debug` */ - st.kftf_dir = debugfs_create_dir("kftf", NULL); - if (!st.kftf_dir) { - pr_warn("kftf: could not create debugfs"); + /* create the main "kfuzztest" directory in `/sys/kernel/debug` */ + st.kfuzztest_dir = debugfs_create_dir("kfuzztest", NULL); + if (!st.kfuzztest_dir) { + pr_warn("KFuzzTest: could not create debugfs"); return -ENODEV; } - if (IS_ERR(st.kftf_dir)) { - st.kftf_dir = NULL; - return PTR_ERR(st.kftf_dir); + if (IS_ERR(st.kfuzztest_dir)) { + st.kfuzztest_dir = NULL; + return PTR_ERR(st.kfuzztest_dir); } /* iterate over all discovered test cases and set up debugfs entries */ - for (test = __kftf_test_case_start; test < __kftf_test_case_end; - test++, i++) { + for (targ = __kfuzztest_targets_start; targ < __kfuzztest_targets_end; + targ++, i++) { /* create a directory for the discovered test case */ st.debugfs_state[i].test_dir = - debugfs_create_dir(test->name, st.kftf_dir); + debugfs_create_dir(targ->name, st.kfuzztest_dir); if (!st.debugfs_state[i].test_dir) { ret = -ENOMEM; @@ -134,11 +132,12 @@ static int __init kftf_init(void) st.debugfs_state[i].input_dentry.fops = (struct file_operations){ .owner = THIS_MODULE, - .write = test->write_input_cb, + .write = targ->write_input_cb, }; st.debugfs_state[i].input_dentry.dentry = debugfs_create_file( - "input", kftf_flags_w, st.debugfs_state[i].test_dir, - NULL, &st.debugfs_state[i].input_dentry.fops); + "input", kfuzztest_flags_w, + st.debugfs_state[i].test_dir, NULL, + &st.debugfs_state[i].input_dentry.fops); if (!st.debugfs_state[i].input_dentry.dentry) { ret = -ENOMEM; goto cleanup_failure; @@ -150,13 +149,13 @@ static int __init kftf_init(void) st.debugfs_state[i].metadata_dentry.fops = (struct file_operations){ .owner = THIS_MODULE, - .read = test->read_metadata_cb, + .read = targ->read_metadata_cb, }; /* create "metadata" file for fuzz test */ st.debugfs_state[i].metadata_dentry.dentry = debugfs_create_file( - "metadata", kftf_flags_r, + "metadata", kfuzztest_flags_r, st.debugfs_state[i].test_dir, NULL, &st.debugfs_state[i].metadata_dentry.fops); if (!st.debugfs_state[i].metadata_dentry.dentry) { @@ -168,41 +167,28 @@ static int __init kftf_init(void) goto cleanup_failure; } - pr_info("kftf: registered %s\n", test->name); - } - - // TODO: make debugfs entries for these constraints - size_t num_constraints = 0; - for (constraint = __kftf_constraint_start; - constraint < __kftf_constraint_end; constraint++) { - pr_info("kftf: addr = 0x%lX\n", (size_t)constraint); - pr_info("input type: %s\n", constraint->input_type); - pr_info("field name: %s\n", constraint->field_name); - pr_info("value1: %lx\n", constraint->value1); - pr_info("value2: %lx\n", constraint->value2); - pr_info("type: %d\n", constraint->type); - num_constraints++; + pr_info("KFuzzTest: registered %s\n", targ->name); } return 0; cleanup_failure: - debugfs_remove_recursive(st.kftf_dir); + debugfs_remove_recursive(st.kfuzztest_dir); return ret; } /** - * kftf_exit - Cleans up the module. + * kfuzztest_exit - Cleans up the module. */ -static void __exit kftf_exit(void) +static void __exit kfuzztest_exit(void) { - pr_info("kftf: shutting down\n"); - if (!st.kftf_dir) + pr_info("KFuzzTest: exiting\n"); + if (!st.kfuzztest_dir) return; - debugfs_remove_recursive(st.kftf_dir); - st.kftf_dir = NULL; + debugfs_remove_recursive(st.kfuzztest_dir); + st.kfuzztest_dir = NULL; } -module_init(kftf_init); -module_exit(kftf_exit); +module_init(kfuzztest_init); +module_exit(kfuzztest_exit); diff --git a/lib/kftf/kftf_tests.h b/lib/kfuzztest/kfuzztest_tests.h similarity index 68% rename from lib/kftf/kftf_tests.h rename to lib/kfuzztest/kfuzztest_tests.h index 3c2bed01093846..f7f53312ddf358 100644 --- a/lib/kftf/kftf_tests.h +++ b/lib/kfuzztest/kfuzztest_tests.h @@ -1,7 +1,7 @@ -#ifndef KFTF_TESTS_H -#define KFTF_TESTS_H +#ifndef KFUZZTEST_TESTS_H +#define KFUZZTEST_TESTS_H -#include +#include #include struct kftf_simple_arg { @@ -30,10 +30,10 @@ static void kftf_fuzzable(char first, char second, char third) FUZZ_TEST(kftf_fuzzable, struct kftf_simple_arg) { - KFTF_EXPECT_NOT_NULL(kftf_simple_arg, first); - KFTF_EXPECT_IN_RANGE(kftf_simple_arg, second, 'a', 'z'); - KFTF_EXPECT_IN_RANGE(kftf_simple_arg, third, 'a', 'z'); + KFUZZTEST_EXPECT_NOT_NULL(kftf_simple_arg, first); + KFUZZTEST_EXPECT_IN_RANGE(kftf_simple_arg, second, 'a', 'z'); + KFUZZTEST_EXPECT_IN_RANGE(kftf_simple_arg, third, 'a', 'z'); kftf_fuzzable(arg->first, arg->second, arg->third); } -#endif /* KFTF_TESTS_H */ +#endif /* KFUZZTEST_TESTS_H */ From e32895373877884f0d4b42985b24245a7f911604 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 25 Jul 2025 09:43:33 +0000 Subject: [PATCH 28/32] kfuzztest: cleanup code and update documentation --- include/linux/kfuzztest.h | 145 ++++++++++++++++----------------- lib/kfuzztest/kfuzztest_main.c | 84 ++++++------------- 2 files changed, 98 insertions(+), 131 deletions(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index 43e74308d5f5ac..afa9ef91ab9642 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -7,7 +7,6 @@ MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ethan Graham "); MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFuzzTest)"); -/* forward decl */ static void *kfuzztest_parse_input(void *input, size_t input_size); struct kfuzztest_target { @@ -15,11 +14,8 @@ struct kfuzztest_target { const char *arg_type_name; ssize_t (*write_input_cb)(struct file *filp, const char __user *buf, size_t len, loff_t *off); - ssize_t (*read_metadata_cb)(struct file *, char __user *, size_t, - loff_t *); }; -// XXX: why can't we use without the attribute unused anymore?? __attribute__((unused)) static int write_input_cb_common(struct file *filp, const char __user *buf, size_t len, loff_t *off, void *arg, size_t arg_size) @@ -34,71 +30,63 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, } /** - * FUZZ_TEST - defines a fuzz test case for a function. - * @func: the function to be fuzzed. This is used to name the test case and - * create associated debufs entries. - * @func_arg_type: the input type of func. If func takes multiple arguments, - * then one should wrap that inside of a multi-fielded struct. See usage - * example below. + * FUZZ_TEST - defines a KFuzzTest target. * + * @test_name: Name of the fuzz target, which is used to create the associated + * debufs entries. + * @func_arg_type: the input type of fuzz target. This should always be a + * struct type even when fuzzing with a single input parameter in order + * to take advantage of the domain constraint and annotation systems. See + * usage example below. * - * This macro generates all of the necessary boilerplate for a KFuzzTest driver, - * which is placed in a dedicated ".kfuzztest_target" section. + * + * This macro generates all of the necessary boilerplate for a KFuzzTest + * driver, which is placed in a dedicated ".kfuzztest_target" that is used by + * the KFuzzTest module and can be read by a fuzzing engine. * * For each test, this macro generates * - A buffer to receive input through the debugfs entry * - A mutex to protect the input buffer * - A `struct kfuzztest_target` instance * - * Example usagea: + * Example usage: * - * Assume some function `func(T1 param1, ... TN paramN)` - * // Define input type of the target function + * // Assume that we are fuzzing some function func(T1 param1, ... TN paramN). + * // Define input type of the fuzz target. This should be always be a struct. * struct func_arg_type { * T1 arg1; * ... * TN argn; * }; * - * // Define the test case - * FUZZ_TEST(func, struct func_arg_type) + * // Define the test case. + * FUZZ_TEST(test_func, struct func_arg_type) * { - * // arg is provided by the macro, and is of type `struct func_arg_type` + * int ret; + * // arg is provided by the macro, and is of type struct func_arg_type. * ret = func(arg.arg1, ..., arg.argn); - * validate(ret); + * // Validate the return value if testing for correctness. + * if (ret != expected_value) { + * KFUZZTEST_REPORT_BUG("Unexpected return value"); + * } * } */ -#define FUZZ_TEST(func, func_arg_type) \ - /* forward decls */ \ +#define FUZZ_TEST(test_name, func_arg_type) \ static ssize_t _write_callback_##func(struct file *filp, \ const char __user *buf, \ size_t len, loff_t *off); \ - static ssize_t _read_metadata_callback_##func( \ - struct file *filp, char __user *buf, size_t len, loff_t *off); \ static void _fuzz_test_logic_##func(func_arg_type *arg); \ - /* test case struct initialization */ \ const struct kfuzztest_target __fuzz_test__##func __attribute__(( \ __section__(".kfuzztest_target"), __used__)) = { \ - .name = #func, \ + .name = #test_name, \ .arg_type_name = #func_arg_type, \ .write_input_cb = _write_callback_##func, \ - .read_metadata_cb = _read_metadata_callback_##func \ }; \ - /* callback that simply returns the type name to the user */ \ - static ssize_t _read_metadata_callback_##func( \ - struct file *filp, char __user *buf, size_t len, loff_t *off) \ - { \ - const char *message = __fuzz_test__##func.arg_type_name; \ - int message_len = strlen(message); \ - return simple_read_from_buffer(buf, len, off, message, \ - message_len); \ - } \ - /* user-defined write callback */ \ + /* Invoked when data is written into the target's input file. */ \ static ssize_t _write_callback_##func(struct file *filp, \ const char __user *buf, \ size_t len, loff_t *off) \ { \ - pr_info("[ENTER] %s\n", __FUNCTION__); \ int err; \ void *buffer = kmalloc(len, GFP_KERNEL); \ if (!buffer || IS_ERR(buffer)) \ @@ -114,9 +102,7 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, return -1; \ } \ func_arg_type *arg = payload; \ - /* call the user's logic on the provided arg. */ \ - /* NOTE: define some success/failure return types? */ \ - pr_info("invoking fuzz logic for %s\n", #func); \ + /* Call the user's logic on the provided written input. */ \ _fuzz_test_logic_##func(arg); \ kfree(buffer); \ kfree(payload); \ @@ -130,10 +116,6 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, */ #define KFUZZTEST_REPORT_BUG(msg, fmt) pr_warn("bug: " #msg, fmt) -/** - * struct kfuzztest_constraint_type defines a type of constraint. The fuzzing driver - * should be aware of these. - */ enum kfuzztest_constraint_type : uint8_t { EXPECT_EQ = 0, EXPECT_NE, @@ -143,8 +125,14 @@ enum kfuzztest_constraint_type : uint8_t { }; /** - * kfuzztest_constraint defines a domain constraint for a struct variable that - * is taken as input for a FUZZ_TEST. + * Domain constraints are used to restrict the values that the fuzz driver + * accepts, enforcing early exit when not satisfied. Domain constraints are + * encoded in vmlinux under the `__kfuzztest_constraint` section. A good + * fuzzing engine should be aware of these domain constraints during input + * generation and mutation. + * + * struct kfuzztest_constraint defines a domain constraint for a structure + * field. * * @input_type: the name of the input (a struct name) * @field_name: the name of the field that this domain constraint applies to @@ -153,10 +141,21 @@ enum kfuzztest_constraint_type : uint8_t { * constraints * @type: the type of the constraint, enumerated above * - * Note: if this struct is not a multiple of 64 bytes, everything breaks and - * we get corrupted data and occasional kernel panics. To avoid this happening, - * we enforce 64 Byte alignment and statically assert that this struct has size - * 64 Bytes. + * Example usage: + * + * struct foo { + * struct bar *a; + * int b + * }; + * + * FUZZ_TEST(test_name, struct foo) + * { + * // Early exit if foo.a == NULL. + * KFUZZTEST_EXPECT_NOT_NULL(foo, a); + * // Early exit if foo < 23 || foo > 42 + * KFUZZTEST_EXPECT_IN_RANGE(foo, b, 23, 42); + * // User-defined fuzz logic. + * } */ struct kfuzztest_constraint { const char *input_type; @@ -169,19 +168,6 @@ struct kfuzztest_constraint { static_assert(sizeof(struct kfuzztest_constraint) == 64, "struct kfuzztest_constraint should have size 64"); -/** - * __KFUZZTEST_DEFINE_CONSTRAINT - defines a fuzz test constraint linked to a given - * argument type belonging to a fuzz test. See FUZZ_TEST above. - * - * @arg_type: the type of argument (a struct) without the leading "struct" in - * its name, which will be prepended. - * @field: the field on which the constraint is defined. - * @val: used for comparison constraints such as KFUZZTEST_EXPECT_NE - * @tpe: the type of constaint that this defines - * - * This macro is intended for internal use. A user should opt for - * KFUZZTEST_EXPECT_* instead when defining fuzz test constraints. - */ #define __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val1, val2, tpe) \ static struct kfuzztest_constraint __constraint_##arg_type##_##field \ __attribute__((__section__(".kfuzztest_constraint"), \ @@ -226,6 +212,16 @@ static_assert(sizeof(struct kfuzztest_constraint) == 64, if ((expected_len) != (actual_len)) \ return; +/** + * Annotations express attributes about structure fields that can't be easily + * verified at runtime, and are intended as a hint to the fuzzing engine. + * + * For example, a char* could either be a raw byte buffer or a string, where + * the latter is null terminated. If a function accepts a null-terminated + * string without a length and is passed an arbitrary byte buffer, we + * may get false positive KASAN reports, for example. However, verifying that + * the char buffer is null-termined could itself trigger a memory overflow. + */ enum kfuzztest_annotation_attribute : uint8_t { ATTRIBUTE_LEN = 0, ATTRIBUTE_STRING, @@ -250,18 +246,17 @@ struct kfuzztest_annotation { }; /** - * Annotates arg_type.field as a string + * Annotates a char* field as a string, which is the subset of char arrays that + * are null-terminated. */ #define KFUZZTEST_ANNOTATE_STRING(arg_type, field) \ __KFUZZTEST_ANNOTATE(arg_type, field, , ATTRIBUTE_STRING) /** - * Annotates arg_type.field as an arrray. For example, assume that - * arg_type.field is of type `unsigned long *`, which could either represent - * a pointer to a single value or an array. Using this annotation removes that - * ambiguity. + * Annotates a pointer field as an array, which is a contiguous memory region + * containing zero or more elements of the same type. */ -#define KFUZZTEST_ANNOTEATE_ARRAY(arg_type, field) \ +#define KFUZZTEST_ANNOTATE_ARRAY(arg_type, field) \ __KFUZZTEST_ANNOTATE(arg_type, field, , ATTRIBUTE_ARRAY) /** @@ -271,8 +266,8 @@ struct kfuzztest_annotation { __KFUZZTEST_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) struct reloc_entry { - uintptr_t pointer; /* offset from the beginning of the payload */ - uintptr_t value; /* difference between the pointed to address and the address itself */ + uintptr_t pointer; /* Offset from the beginning of the payload. */ + uintptr_t value; /* Offset between the pointer and the pointed-to address */ }; /* @@ -292,12 +287,14 @@ static_assert(offsetof(struct reloc_table, entries) % 0); /** - * This value should be known the fuzz engine. + * The relocation table format encodes pointer values as a relative offset from + * the location of the pointer. A relative offset of zero could indicate that + * the pointer points to its own address, which is valid. We encode a null + * pointer as 0xFF...FF as adding this value to any address would result in an + * overflow anyways, and is therefore invalid in any other circumstance. */ static const uintptr_t nullPtr = (uintptr_t)-1; -/* XXX: wasn't building before without attribute unused, but it is used in - * several locations - weird... */ __attribute__((unused)) static void *kfuzztest_parse_input(void *input, size_t input_size) { diff --git a/lib/kfuzztest/kfuzztest_main.c b/lib/kfuzztest/kfuzztest_main.c index cc38250e4f11a4..c038892c826fb4 100644 --- a/lib/kfuzztest/kfuzztest_main.c +++ b/lib/kfuzztest/kfuzztest_main.c @@ -4,15 +4,13 @@ * * This module is responsible for discovering and initializing all fuzz test * cases defined using the FUZZ_TEST() macro. It creates a debugfs interface -* under /sys/kernel/debug/kfuzztest/ for userspace to interact with each test. -*/ + * under /sys/kernel/debug/kfuzztest/ for userspace to interact with each test. + */ #include #include #include #include -#include "kfuzztest_tests.h" - extern const struct kfuzztest_target __kfuzztest_targets_start[]; extern const struct kfuzztest_target __kfuzztest_targets_end[]; @@ -34,14 +32,12 @@ struct kfuzztest_dentry { * @test_dir: The top-level debugfs directory for a single test case, e.g., * /sys/kernel/debug/kfuzztest//. * @input_dentry: The state for the "input" file, which is write-only. - * @metadata_dentry: The state for the "metadata" file, which is read-only. * * Wraps all debugfs components created for a single test case. */ struct kfuzztest_debugfs_state { - struct dentry *test_dir; + struct dentry *target_dir; struct kfuzztest_dentry input_dentry; - struct kfuzztest_dentry metadata_dentry; }; /** @@ -50,41 +46,33 @@ struct kfuzztest_debugfs_state { * @debugfs_state: A statically sized array holding the state for each * registered test case. */ -struct kfuzztest_simple_fuzzer_state { +struct kfuzztest_state { struct file_operations fops; struct dentry *kfuzztest_dir; struct kfuzztest_debugfs_state *debugfs_state; }; /* Global static variable to hold all state for the module. */ -static struct kfuzztest_simple_fuzzer_state st; +static struct kfuzztest_state st; -/* +/** * Default file permissions for the debugfs entries. * 0222: World-writable for the 'input' file. - * 0444: World-readable for the 'metadata' file. * - * XXX: should formally define what the permissions should be on these files + * XXX: should formally define what the permissions should be on these files. */ const umode_t kfuzztest_flags_w = 0222; -const umode_t kfuzztest_flags_r = 0444; /** - * kfuzztest_init - Initializes the debug filesystem for KFTF. - * - * This function is the entry point for the KFTF module, populating the debugfs - * that is used for IO interaction between the individual fuzzing drivers and - * a userspace fuzzing tool like syzkaller. + * kfuzztest_init - Initializes the debug filesystem for KFuzzTest. * * Each registered test in the ".kfuzztest" section gets its own subdirectory - * under "/sys/kernel/debug/kfuzztest/" with two files: + * under "/sys/kernel/debug/kfuzztest/" with one files: * - input: write-only file to send input to the fuzz driver - * - metadata: used to read the type name that the fuzz driver expects * * Returns: - * 0 on success. - * -EINVAL if the number of tests exceeds KFTF_MAX_TEST_CASES - * -ENODEV or other error codes if debugfs creation fails. + * 0 on success. + * -ENODEV or other error codes if debugfs creation fails. */ static int __init kfuzztest_init(void) { @@ -100,8 +88,10 @@ static int __init kfuzztest_init(void) GFP_KERNEL); if (!st.debugfs_state) return -ENOMEM; + else if (IS_ERR(st.debugfs_state)) + return PTR_ERR(st.debugfs_state); - /* create the main "kfuzztest" directory in `/sys/kernel/debug` */ + /* Create the main "kfuzztest" directory in /sys/kernel/debug. */ st.kfuzztest_dir = debugfs_create_dir("kfuzztest", NULL); if (!st.kfuzztest_dir) { pr_warn("KFuzzTest: could not create debugfs"); @@ -113,22 +103,21 @@ static int __init kfuzztest_init(void) return PTR_ERR(st.kfuzztest_dir); } - /* iterate over all discovered test cases and set up debugfs entries */ for (targ = __kfuzztest_targets_start; targ < __kfuzztest_targets_end; targ++, i++) { - /* create a directory for the discovered test case */ - st.debugfs_state[i].test_dir = + /* Create debugfs directory for the target. */ + st.debugfs_state[i].target_dir = debugfs_create_dir(targ->name, st.kfuzztest_dir); - if (!st.debugfs_state[i].test_dir) { + if (!st.debugfs_state[i].target_dir) { ret = -ENOMEM; goto cleanup_failure; - } else if (IS_ERR(st.debugfs_state[i].test_dir)) { - ret = PTR_ERR(st.debugfs_state[i].test_dir); + } else if (IS_ERR(st.debugfs_state[i].target_dir)) { + ret = PTR_ERR(st.debugfs_state[i].target_dir); goto cleanup_failure; } - /* create "input" file for fuzz test */ + /* Create an input file under the target's directory. */ st.debugfs_state[i].input_dentry.fops = (struct file_operations){ .owner = THIS_MODULE, @@ -136,7 +125,7 @@ static int __init kfuzztest_init(void) }; st.debugfs_state[i].input_dentry.dentry = debugfs_create_file( "input", kfuzztest_flags_w, - st.debugfs_state[i].test_dir, NULL, + st.debugfs_state[i].target_dir, NULL, &st.debugfs_state[i].input_dentry.fops); if (!st.debugfs_state[i].input_dentry.dentry) { ret = -ENOMEM; @@ -146,28 +135,7 @@ static int __init kfuzztest_init(void) goto cleanup_failure; } - st.debugfs_state[i].metadata_dentry.fops = - (struct file_operations){ - .owner = THIS_MODULE, - .read = targ->read_metadata_cb, - }; - - /* create "metadata" file for fuzz test */ - st.debugfs_state[i].metadata_dentry.dentry = - debugfs_create_file( - "metadata", kfuzztest_flags_r, - st.debugfs_state[i].test_dir, NULL, - &st.debugfs_state[i].metadata_dentry.fops); - if (!st.debugfs_state[i].metadata_dentry.dentry) { - ret = -ENOMEM; - goto cleanup_failure; - } else if (IS_ERR(st.debugfs_state[i].metadata_dentry.dentry)) { - ret = PTR_ERR( - st.debugfs_state[i].metadata_dentry.dentry); - goto cleanup_failure; - } - - pr_info("KFuzzTest: registered %s\n", targ->name); + pr_info("KFuzzTest: registered target %s\n", targ->name); } return 0; @@ -177,9 +145,6 @@ static int __init kfuzztest_init(void) return ret; } -/** - * kfuzztest_exit - Cleans up the module. - */ static void __exit kfuzztest_exit(void) { pr_info("KFuzzTest: exiting\n"); @@ -188,6 +153,11 @@ static void __exit kfuzztest_exit(void) debugfs_remove_recursive(st.kfuzztest_dir); st.kfuzztest_dir = NULL; + + if (st.debugfs_state) { + kfree(st.debugfs_state); + st.debugfs_state = NULL; + } } module_init(kfuzztest_init); From 8cb9faa5d0c615bf247812b584a73e56f22d2e18 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 25 Jul 2025 09:45:54 +0000 Subject: [PATCH 29/32] kfuzztest: remove dummy test file --- lib/kfuzztest/kfuzztest_tests.h | 39 --------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 lib/kfuzztest/kfuzztest_tests.h diff --git a/lib/kfuzztest/kfuzztest_tests.h b/lib/kfuzztest/kfuzztest_tests.h deleted file mode 100644 index f7f53312ddf358..00000000000000 --- a/lib/kfuzztest/kfuzztest_tests.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef KFUZZTEST_TESTS_H -#define KFUZZTEST_TESTS_H - -#include -#include - -struct kftf_simple_arg { - char first; - char second; - char third; -}; - -// contains a bug! -static void kftf_fuzzable(char first, char second, char third) -{ - // can buffer overflow or underflow, or cause a null pointer dereference - // crashing the kernel - if (first == 'a') { - pr_info("first was a"); - if (second == 'b') { - pr_info("second was b"); - if (third == 'c') { - pr_info("third was c"); - volatile char *ptr = (void *)0xBEEF; - pr_info("reading %p: 0x%x", ptr, *(uint *)ptr); - } - } - } -} - -FUZZ_TEST(kftf_fuzzable, struct kftf_simple_arg) -{ - KFUZZTEST_EXPECT_NOT_NULL(kftf_simple_arg, first); - KFUZZTEST_EXPECT_IN_RANGE(kftf_simple_arg, second, 'a', 'z'); - KFUZZTEST_EXPECT_IN_RANGE(kftf_simple_arg, third, 'a', 'z'); - kftf_fuzzable(arg->first, arg->second, arg->third); -} - -#endif /* KFUZZTEST_TESTS_H */ From aeeb29fc73c204b808d3b59a42714dcf024e03fa Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 25 Jul 2025 09:49:46 +0000 Subject: [PATCH 30/32] kfuzztest: update header comment in kfuzztest_main.c --- lib/kfuzztest/kfuzztest_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/kfuzztest/kfuzztest_main.c b/lib/kfuzztest/kfuzztest_main.c index c038892c826fb4..f0adc62c9882ad 100644 --- a/lib/kfuzztest/kfuzztest_main.c +++ b/lib/kfuzztest/kfuzztest_main.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Kernel Fuzz Testing Framework (KFTF) - Core Module + * Kernel Fuzz Testing Framework (KFuzzTest) - Core Module * * This module is responsible for discovering and initializing all fuzz test * cases defined using the FUZZ_TEST() macro. It creates a debugfs interface From 57fddceb119321a5a08b9243f6a9cd6d17306341 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 25 Jul 2025 10:03:23 +0000 Subject: [PATCH 31/32] kfuzztest: update macros to fix naming --- include/linux/kfuzztest.h | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index afa9ef91ab9642..d3de9fa3d93238 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -34,7 +34,7 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, * * @test_name: Name of the fuzz target, which is used to create the associated * debufs entries. - * @func_arg_type: the input type of fuzz target. This should always be a + * @test_arg_type: the input type of fuzz target. This should always be a * struct type even when fuzzing with a single input parameter in order * to take advantage of the domain constraint and annotation systems. See * usage example below. @@ -53,17 +53,17 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, * * // Assume that we are fuzzing some function func(T1 param1, ... TN paramN). * // Define input type of the fuzz target. This should be always be a struct. - * struct func_arg_type { + * struct test_arg_type { * T1 arg1; * ... * TN argn; * }; * * // Define the test case. - * FUZZ_TEST(test_func, struct func_arg_type) + * FUZZ_TEST(test_func, struct test_arg_type) * { * int ret; - * // arg is provided by the macro, and is of type struct func_arg_type. + * // arg is provided by the macro, and is of type struct test_arg_type. * ret = func(arg.arg1, ..., arg.argn); * // Validate the return value if testing for correctness. * if (ret != expected_value) { @@ -71,21 +71,21 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, * } * } */ -#define FUZZ_TEST(test_name, func_arg_type) \ - static ssize_t _write_callback_##func(struct file *filp, \ - const char __user *buf, \ - size_t len, loff_t *off); \ - static void _fuzz_test_logic_##func(func_arg_type *arg); \ - const struct kfuzztest_target __fuzz_test__##func __attribute__(( \ +#define FUZZ_TEST(test_name, test_arg_type) \ + static ssize_t _write_callback_##test_name(struct file *filp, \ + const char __user *buf, \ + size_t len, loff_t *off); \ + static void _fuzz_test_logic_##test_name(test_arg_type *arg); \ + const struct kfuzztest_target __fuzz_test__##test_name __attribute__(( \ __section__(".kfuzztest_target"), __used__)) = { \ .name = #test_name, \ - .arg_type_name = #func_arg_type, \ - .write_input_cb = _write_callback_##func, \ + .arg_type_name = #test_arg_type, \ + .write_input_cb = _write_callback_##test_name, \ }; \ /* Invoked when data is written into the target's input file. */ \ - static ssize_t _write_callback_##func(struct file *filp, \ - const char __user *buf, \ - size_t len, loff_t *off) \ + static ssize_t _write_callback_##test_name(struct file *filp, \ + const char __user *buf, \ + size_t len, loff_t *off) \ { \ int err; \ void *buffer = kmalloc(len, GFP_KERNEL); \ @@ -101,14 +101,14 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, kfree(buffer); \ return -1; \ } \ - func_arg_type *arg = payload; \ + test_arg_type *arg = payload; \ /* Call the user's logic on the provided written input. */ \ - _fuzz_test_logic_##func(arg); \ + _fuzz_test_logic_##test_name(arg); \ kfree(buffer); \ kfree(payload); \ return len; \ } \ - static void _fuzz_test_logic_##func(func_arg_type *arg) + static void _fuzz_test_logic_##test_name(test_arg_type *arg) /** * Reports a bug with a predictable prefix so that it can be parsed by a From 4ebed5f60d971772062a2f5ebdc8bb98df2ec694 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 25 Jul 2025 10:53:15 +0000 Subject: [PATCH 32/32] kfuzztest: align targets to 32-byte boundary Iterating through a section where structures aren't aligned to a power-of-2 boundary leads to incorrect memory accesses. --- include/linux/kfuzztest.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index d3de9fa3d93238..cae69cd9eb9c5c 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -14,7 +14,9 @@ struct kfuzztest_target { const char *arg_type_name; ssize_t (*write_input_cb)(struct file *filp, const char __user *buf, size_t len, loff_t *off); -}; +} __attribute__((aligned(32))); +static_assert(sizeof(struct kfuzztest_target) == 32, + "struct kfuzztest_target should have size 32"); __attribute__((unused)) static int write_input_cb_common(struct file *filp, const char __user *buf, size_t len,