From 1e0587e1d36211d7ea3ba8bb9eb1305bc74bd013 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Wed, 18 Jun 2025 10:02:55 +0000 Subject: [PATCH 01/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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 4a7f93e9a618e9b6e3531d9b8174695eba0fa0c9 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Tue, 8 Jul 2025 18:28:40 +0000 Subject: [PATCH 22/26] kftf: add test case example using annotations --- lib/kftf/kftf_tests.h | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/lib/kftf/kftf_tests.h b/lib/kftf/kftf_tests.h index 2f59bfbd71b556..b5c35e3261e598 100644 --- a/lib/kftf/kftf_tests.h +++ b/lib/kftf/kftf_tests.h @@ -36,4 +36,47 @@ FUZZ_TEST(kftf_fuzzable, struct kftf_simple_arg) kftf_fuzzable(arg.first, arg.second, arg.third); } +struct my_fun_func_arg { + const char *string; + char *buffer; + size_t buffer_size; +}; + +static void my_fun_func(const char *string, char *buffer, size_t buffer_size) +{ + size_t i; + /* string should be NULL terminated! */ + pr_info("this is my string: %s", string); + + for (i = 0; i < buffer_size; i++) { + buffer[i]++; + pr_info("buffer[%zu] = %c\n", i, buffer[i]); + } +} + +FUZZ_TEST(my_memncpy, struct my_fun_func_arg) +{ + const char *kernel_string; + char *kernel_buffer; + + KFTF_ANNOTATE_STRING(my_fun_func_arg, string); + KFTF_ANNOTATE_LEN(my_fun_func_arg, buffer_size, buffer); + KFTF_EXPECT_NOT_NULL(my_fun_func_arg, string); + KFTF_EXPECT_NOT_NULL(my_fun_func_arg, buffer); + + kernel_string = strndup_user(arg.string, PAGE_SIZE); + if (!kernel_string || IS_ERR(kernel_string)) + return; + + kernel_buffer = memdup_user(arg.buffer, arg.buffer_size); + if (!kernel_buffer || IS_ERR(kernel_buffer)) { + kfree(kernel_string); + return; + } + + my_fun_func(kernel_string, kernel_buffer, arg.buffer_size); + kfree(kernel_string); + kfree(kernel_buffer); +} + #endif /* KFTF_TESTS_H */ From ce561e57e165dfdbe48ebaecb7debbe0344802ea Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Wed, 9 Jul 2025 14:56:57 +0000 Subject: [PATCH 23/26] 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 | 8 ++--- lib/math/int_sqrt.c | 18 +--------- 3 files changed, 78 insertions(+), 31 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 5a7cc1a9c1ae93..f0e36c5ed2b60d 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) @@ -256,4 +272,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 b5c35e3261e598..5257783c936622 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); } struct my_fun_func_arg { @@ -64,17 +64,17 @@ FUZZ_TEST(my_memncpy, struct my_fun_func_arg) KFTF_EXPECT_NOT_NULL(my_fun_func_arg, string); KFTF_EXPECT_NOT_NULL(my_fun_func_arg, buffer); - kernel_string = strndup_user(arg.string, PAGE_SIZE); + kernel_string = strndup_user(arg->string, PAGE_SIZE); if (!kernel_string || IS_ERR(kernel_string)) return; - kernel_buffer = memdup_user(arg.buffer, arg.buffer_size); + kernel_buffer = memdup_user(arg->buffer, arg->buffer_size); if (!kernel_buffer || IS_ERR(kernel_buffer)) { kfree(kernel_string); return; } - my_fun_func(kernel_string, kernel_buffer, arg.buffer_size); + my_fun_func(kernel_string, kernel_buffer, arg->buffer_size); kfree(kernel_string); kfree(kernel_buffer); } 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 006c3572d2a825d4af20b73fd072b5bd51d2fef2 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 11 Jul 2025 14:00:03 +0000 Subject: [PATCH 24/26] kftf: remove some prints from write_input_callback, update test case to remove copying from userspace --- include/linux/kftf.h | 30 ++++++++++++++++-------------- lib/kftf/kftf_tests.h | 25 +++++++------------------ 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index f0e36c5ed2b60d..e36ef95cb8fd65 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; \ } \ @@ -273,49 +270,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; } diff --git a/lib/kftf/kftf_tests.h b/lib/kftf/kftf_tests.h index 5257783c936622..46b23d15230d52 100644 --- a/lib/kftf/kftf_tests.h +++ b/lib/kftf/kftf_tests.h @@ -42,41 +42,30 @@ struct my_fun_func_arg { size_t buffer_size; }; +volatile char __w; static void my_fun_func(const char *string, char *buffer, size_t buffer_size) { size_t i; /* string should be NULL terminated! */ - pr_info("this is my string: %s", string); + pr_info("string length = %zu", + strlen(string) + 1 /* null terminated str */); + pr_info("buffer_size = %zu\n", buffer_size); for (i = 0; i < buffer_size; i++) { buffer[i]++; - pr_info("buffer[%zu] = %c\n", i, buffer[i]); + __w = buffer[i]; // avoid inlining } } FUZZ_TEST(my_memncpy, struct my_fun_func_arg) { - const char *kernel_string; - char *kernel_buffer; + pr_info("[ENTER] %s\n", __FUNCTION__); KFTF_ANNOTATE_STRING(my_fun_func_arg, string); KFTF_ANNOTATE_LEN(my_fun_func_arg, buffer_size, buffer); KFTF_EXPECT_NOT_NULL(my_fun_func_arg, string); KFTF_EXPECT_NOT_NULL(my_fun_func_arg, buffer); - - kernel_string = strndup_user(arg->string, PAGE_SIZE); - if (!kernel_string || IS_ERR(kernel_string)) - return; - - kernel_buffer = memdup_user(arg->buffer, arg->buffer_size); - if (!kernel_buffer || IS_ERR(kernel_buffer)) { - kfree(kernel_string); - return; - } - - my_fun_func(kernel_string, kernel_buffer, arg->buffer_size); - kfree(kernel_string); - kfree(kernel_buffer); + my_fun_func(arg->string, arg->buffer, arg->buffer_size); } #endif /* KFTF_TESTS_H */ From 8f89c46392ee4e08c584f09f6bf2f4068d23ce27 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Mon, 14 Jul 2025 09:45:27 +0000 Subject: [PATCH 25/26] 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 e36ef95cb8fd65..744db30c457457 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) @@ -274,9 +275,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) % @@ -288,8 +296,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); @@ -297,16 +307,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; @@ -318,7 +347,7 @@ static void *kftf_parse_input(void *input, size_t input_size) } } - return payload_start; + return out; } #endif /* KFTF_H */ From 8d95315254894646e61db1a3ff84b1472d06f5d5 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Tue, 15 Jul 2025 17:48:48 +0000 Subject: [PATCH 26/26] kftf: harden binary input parsing --- include/linux/kftf.h | 77 ++++++++++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 744db30c457457..20c5bc2782423f 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, @@ -291,17 +295,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__); @@ -309,24 +320,54 @@ 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; + + if (re.pointer + re.value >= payload_len || + re.pointer + re.value + sizeof(uintptr_t) > 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); /* @@ -336,10 +377,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 {