From 1e0587e1d36211d7ea3ba8bb9eb1305bc74bd013 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Wed, 18 Jun 2025 10:02:55 +0000 Subject: [PATCH 01/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] 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/73] kftf: add annotations macros to express field types (for example strings) and a field being the length of another. --- arch/x86/kernel/vmlinux.lds.S | 8 ++++++-- include/linux/kftf.h | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/arch/x86/kernel/vmlinux.lds.S b/arch/x86/kernel/vmlinux.lds.S index 523132f08557cb..d7f403f6432ad0 100644 --- a/arch/x86/kernel/vmlinux.lds.S +++ b/arch/x86/kernel/vmlinux.lds.S @@ -114,14 +114,18 @@ ASSERT(__relocate_kernel_end - __relocate_kernel_start <= KEXEC_CONTROL_CODE_MAX #endif #define KFTF_TABLE \ - . = ALIGN(0X100); \ + . = ALIGN(PAGE_SIZE); \ __kftf_test_case_start = .; \ KEEP(*(.kftf_test)); \ __kftf_test_case_end = .; \ - . = ALIGN(0x100); \ + . = ALIGN(PAGE_SIZE); \ __kftf_constraint_start = .; \ KEEP(*(.kftf_constraint)); \ __kftf_constraint_end = .; \ + . = ALIGN(PAGE_SIZE); \ + __kftf_annotation_start = .; \ + KEEP(*(.kftf_annotation)); \ + __kftf_annotation_end = .; \ PHDRS { text PT_LOAD FLAGS(5); /* R_E */ diff --git a/include/linux/kftf.h b/include/linux/kftf.h index e96e1f9d834e55..5a7cc1a9c1ae93 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -223,4 +223,37 @@ static_assert(sizeof(struct kftf_constraint) == 64, __KFTF_DEFINE_CONSTRAINT(arg_type, field, lower_bound, upper_bound, \ EXPECT_IN_RANGE) +enum kftf_annotation_attribute : uint8_t { + ATTRIBUTE_LEN = 0, + ATTRIBUTE_STRING, +}; + +struct kftf_annotation { + const char *input_type; + const char *field_name; + const char *linked_field_name; + enum kftf_annotation_attribute attrib; +} __attribute__((aligned(32))); + +#define __KFTF_ANNOTATE(arg_type, field, linked_field, attribute) \ + static struct kftf_annotation __annotation_##arg_type##_##field \ + __attribute__((__section__(".kftf_annotation"), __used__)) = { \ + .input_type = "struct " #arg_type, \ + .field_name = #field, \ + .linked_field_name = #linked_field, \ + .attrib = attribute, \ + }; + +/** + * Annotates arg_type.field as a string + */ +#define KFTF_ANNOTATE_STRING(arg_type, field) \ + __KFTF_ANNOTATE(arg_type, field, , ATTRIBUTE_STRING) + +/** + * Annotates arg_type.field as the length of arg_type.linked_field + */ +#define KFTF_ANNOTATE_LEN(arg_type, field, linked_field) \ + __KFTF_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) + #endif /* KFTF_H */ From 8e1f787df5a8484e28e56598057464091752ef78 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 18 Jul 2025 10:40:41 +0000 Subject: [PATCH 22/73] kfuzztest: add array annotation There was not any mechanism in place in the KFuzzTest for distinguishing between value pointers and array pointers in fuzz test inputs. This information is difficult to parse without additional semantic information on the context. We introduce a new KFTF_ANNOTATE_ARRAY macro that annotates a field in a KFuzzTest input struct as an array, removing this ambiguity. --- include/linux/kftf.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 5a7cc1a9c1ae93..865526109bf5fc 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -226,6 +226,7 @@ static_assert(sizeof(struct kftf_constraint) == 64, enum kftf_annotation_attribute : uint8_t { ATTRIBUTE_LEN = 0, ATTRIBUTE_STRING, + ATTRIBUTE_ARRAY, }; struct kftf_annotation { @@ -250,6 +251,15 @@ struct kftf_annotation { #define KFTF_ANNOTATE_STRING(arg_type, field) \ __KFTF_ANNOTATE(arg_type, field, , ATTRIBUTE_STRING) +/** + * Annotates arg_type.field as an arrray. For example, assume that + * arg_type.field is of type `unsigned long *`, which could either represent + * a pointer to a single value or an array. Using this annotation removes that + * ambiguity. + */ +#define KFTF_ANNOTEATE_ARRAY(arg_type, field) \ + __KFTF_ANNOTATE(arg_type, field, , ATTRIBUTE_ARRAY) + /** * Annotates arg_type.field as the length of arg_type.linked_field */ From c74d33aa8f444187eef26e789ca73ebc9df97d63 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Wed, 9 Jul 2025 14:56:57 +0000 Subject: [PATCH 23/73] kftf: remove int_sqrt test which is no longer really needed, added relocation parsing code. --- include/linux/kftf.h | 83 +++++++++++++++++++++++++++++++++++++------ lib/kftf/kftf_tests.h | 2 +- lib/math/int_sqrt.c | 18 +--------- 3 files changed, 75 insertions(+), 28 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 865526109bf5fc..1679e6475f072e 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -7,6 +7,9 @@ MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ethan Graham "); MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFTF)"); +/* forward decl */ +static void *kftf_parse_input(void *input, size_t input_size); + /** * struct kftf_test case defines a single fuzz test case. These should not * be created manually. Instead the user should use the FUZZ_TEST macro defined @@ -88,7 +91,7 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, size_t len, loff_t *off); \ static ssize_t _read_metadata_callback_##func( \ struct file *filp, char __user *buf, size_t len, loff_t *off); \ - static void _fuzz_test_logic_##func(func_arg_type arg); \ + static void _fuzz_test_logic_##func(func_arg_type *arg); \ /* test case struct initialization */ \ const struct kftf_test_case __fuzz_test__##func \ __attribute__((__section__(".kftf_test"), __used__)) = { \ @@ -111,20 +114,33 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, const char __user *buf, \ size_t len, loff_t *off) \ { \ + pr_info("[ENTER] %s\n", __FUNCTION__); \ int err; \ - func_arg_type arg; \ - err = write_input_cb_common(filp, buf, len, off, &arg, \ - sizeof(arg)); \ + void *buffer = kmalloc(len, GFP_KERNEL); \ + if (!buffer || IS_ERR(buffer)) \ + return PTR_ERR(buffer); \ + err = write_input_cb_common(filp, buf, len, off, buffer, \ + sizeof(buffer)); \ if (err != 0) { \ + pr_info("%s: failed to read data, len = %zu\n", \ + __FUNCTION__, len); \ + kfree(buffer); \ return err; \ } \ + void *payload = kftf_parse_input(buffer, len); \ + if (!payload) { \ + kfree(buffer); \ + return -1; \ + } \ + func_arg_type *arg = payload; \ /* call the user's logic on the provided arg. */ \ /* NOTE: define some success/failure return types? */ \ pr_info("invoking fuzz logic for %s\n", #func); \ _fuzz_test_logic_##func(arg); \ + kfree(buffer); \ return len; \ } \ - static void _fuzz_test_logic_##func(func_arg_type arg) + static void _fuzz_test_logic_##func(func_arg_type *arg) /** * Reports a bug with a predictable prefix so that it can be parsed by a @@ -195,22 +211,22 @@ static_assert(sizeof(struct kftf_constraint) == 64, }; #define KFTF_EXPECT_EQ(arg_type, field, val) \ - if (arg.field != val) \ + if (arg->field != val) \ return; \ __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_EQ) #define KFTF_EXPECT_NE(arg_type, field, val) \ - if (arg.field == val) \ + if (arg->field == val) \ return; \ __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_NE) #define KFTF_EXPECT_LE(arg_type, field, val) \ - if (arg.field > val) \ + if (arg->field > val) \ return; \ __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_LE) #define KFTF_EXPECT_GT(arg_type, field, val) \ - if (arg.field <= val) \ + if (arg->field <= val) \ return; \ __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_GT) @@ -218,7 +234,7 @@ static_assert(sizeof(struct kftf_constraint) == 64, KFTF_EXPECT_NE(arg_type, field, 0x0) #define KFTF_EXPECT_IN_RANGE(arg_type, field, lower_bound, upper_bound) \ - if (arg.field < lower_bound || arg.field > upper_bound) \ + if (arg->field < lower_bound || arg->field > upper_bound) \ return; \ __KFTF_DEFINE_CONSTRAINT(arg_type, field, lower_bound, upper_bound, \ EXPECT_IN_RANGE) @@ -266,4 +282,51 @@ struct kftf_annotation { #define KFTF_ANNOTATE_LEN(arg_type, field, linked_field) \ __KFTF_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) +struct reloc_entry { + off_t pointer; + off_t value; +}; + +struct reloc_table { + int num_entries; + int padding[3]; // why? + struct reloc_entry entries[]; +}; +static_assert(offsetof(struct reloc_table, entries) % + sizeof(struct reloc_entry) == + 0); + +static void *kftf_parse_input(void *input, size_t input_size) +{ + pr_info("[ENTER]%s\n", __FUNCTION__); + size_t i; + void *payload_start; + uintptr_t *ptr_location; + struct reloc_entry re; + + if (input_size < sizeof(struct reloc_table)) { + pr_warn("got misformed input in %s\n", __FUNCTION__); + } + struct reloc_table *rt = input; + pr_info("%s: num_entries = %d\n", __FUNCTION__, rt->num_entries); + + payload_start = input + offsetof(struct reloc_table, entries) + + rt->num_entries * sizeof(struct reloc_entry); + + if (payload_start >= input + input_size) + return NULL; + + for (i = 0; i < rt->num_entries; i++) { + re = rt->entries[i]; + ptr_location = (uintptr_t *)(payload_start + re.pointer); + if ((void *)ptr_location >= input + input_size) + return NULL; + + *ptr_location = (uintptr_t)ptr_location + re.value; + } + + pr_info("%s: parsed %d entries\n", __FUNCTION__, rt->num_entries); + return payload_start; +} + #endif /* KFTF_H */ diff --git a/lib/kftf/kftf_tests.h b/lib/kftf/kftf_tests.h index 2f59bfbd71b556..3c2bed01093846 100644 --- a/lib/kftf/kftf_tests.h +++ b/lib/kftf/kftf_tests.h @@ -33,7 +33,7 @@ FUZZ_TEST(kftf_fuzzable, struct kftf_simple_arg) KFTF_EXPECT_NOT_NULL(kftf_simple_arg, first); KFTF_EXPECT_IN_RANGE(kftf_simple_arg, second, 'a', 'z'); KFTF_EXPECT_IN_RANGE(kftf_simple_arg, third, 'a', 'z'); - kftf_fuzzable(arg.first, arg.second, arg.third); + kftf_fuzzable(arg->first, arg->second, arg->third); } #endif /* KFTF_TESTS_H */ diff --git a/lib/math/int_sqrt.c b/lib/math/int_sqrt.c index a52b16f8045393..a8170bb9142f39 100644 --- a/lib/math/int_sqrt.c +++ b/lib/math/int_sqrt.c @@ -10,17 +10,6 @@ #include #include #include -#include - -struct int_sqrt_arg { - unsigned long x; -}; - -FUZZ_TEST(int_sqrt, struct int_sqrt_arg) -{ - unsigned long res = int_sqrt(arg.x); - pr_info("fuzz_arg.x = %lu, res = %lu", arg.x, res); -} /** * int_sqrt - computes the integer square root @@ -32,11 +21,6 @@ unsigned long int_sqrt(unsigned long x) { unsigned long b, m, y = 0; - // deliberate bug - if (x == 0) { - char c = *(char *)x; - pr_info("should crash here! %c\n", c); - } if (x <= 1) return x; @@ -67,7 +51,7 @@ u32 int_sqrt64(u64 x) u64 b, m, y = 0; if (x <= ULONG_MAX) - return int_sqrt((unsigned long)x); + return int_sqrt((unsigned long) x); m = 1ULL << ((fls64(x) - 1) & ~1ULL); while (m != 0) { From 6b03486d28877adf8ec084ef6eb75798d1630c7c Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 11 Jul 2025 14:00:03 +0000 Subject: [PATCH 24/73] kftf: remove some prints from write_input_callback, update test case to remove copying from userspace --- include/linux/kftf.h | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 1679e6475f072e..807fcf9221958b 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -119,11 +119,8 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, void *buffer = kmalloc(len, GFP_KERNEL); \ if (!buffer || IS_ERR(buffer)) \ return PTR_ERR(buffer); \ - err = write_input_cb_common(filp, buf, len, off, buffer, \ - sizeof(buffer)); \ + err = write_input_cb_common(filp, buf, len, off, buffer, len); \ if (err != 0) { \ - pr_info("%s: failed to read data, len = %zu\n", \ - __FUNCTION__, len); \ kfree(buffer); \ return err; \ } \ @@ -283,49 +280,54 @@ struct kftf_annotation { __KFTF_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) struct reloc_entry { - off_t pointer; - off_t value; + uintptr_t pointer; /* offset from the beginning of the payload */ + uintptr_t value; /* difference between the pointed to address and the address itself */ }; struct reloc_table { int num_entries; - int padding[3]; // why? + int padding[3]; struct reloc_entry entries[]; }; static_assert(offsetof(struct reloc_table, entries) % sizeof(struct reloc_entry) == 0); +static const uintptr_t nullPtr = (uintptr_t)-1; + static void *kftf_parse_input(void *input, size_t input_size) { - pr_info("[ENTER]%s\n", __FUNCTION__); size_t i; void *payload_start; uintptr_t *ptr_location; struct reloc_entry re; + pr_info("%s: input_size = %zu\n", __FUNCTION__, input_size); if (input_size < sizeof(struct reloc_table)) { pr_warn("got misformed input in %s\n", __FUNCTION__); + return NULL; } struct reloc_table *rt = input; - pr_info("%s: num_entries = %d\n", __FUNCTION__, rt->num_entries); - payload_start = input + offsetof(struct reloc_table, entries) + + payload_start = (char *)input + offsetof(struct reloc_table, entries) + rt->num_entries * sizeof(struct reloc_entry); - if (payload_start >= input + input_size) return NULL; for (i = 0; i < rt->num_entries; i++) { re = rt->entries[i]; ptr_location = (uintptr_t *)(payload_start + re.pointer); - if ((void *)ptr_location >= input + input_size) + if ((void *)ptr_location + sizeof(uintptr_t) >= + input + input_size) return NULL; - *ptr_location = (uintptr_t)ptr_location + re.value; + if (re.value == nullPtr) { + *ptr_location = (uintptr_t)NULL; + } else { + *ptr_location = (uintptr_t)ptr_location + re.value; + } } - pr_info("%s: parsed %d entries\n", __FUNCTION__, rt->num_entries); return payload_start; } From ba24eb3f5cc6522d8d43ffdc51851fe670bf56ee Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Mon, 14 Jul 2025 09:45:27 +0000 Subject: [PATCH 25/73] kftf: introduce new buffer for payload that provides alignment guarantees for encoded structures. --- include/linux/kftf.h | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 807fcf9221958b..3bf79bbc319f3d 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -135,6 +135,7 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, pr_info("invoking fuzz logic for %s\n", #func); \ _fuzz_test_logic_##func(arg); \ kfree(buffer); \ + kfree(payload); \ return len; \ } \ static void _fuzz_test_logic_##func(func_arg_type *arg) @@ -284,9 +285,16 @@ struct reloc_entry { uintptr_t value; /* difference between the pointed to address and the address itself */ }; +/* + * How many integers of padding in the relocation table between the header + * information and the relocation entries + */ +#define RELOC_TABLE_PADDING 2 + struct reloc_table { int num_entries; - int padding[3]; + uint32_t max_alignment; + int padding[RELOC_TABLE_PADDING]; struct reloc_entry entries[]; }; static_assert(offsetof(struct reloc_table, entries) % @@ -298,8 +306,10 @@ static const uintptr_t nullPtr = (uintptr_t)-1; static void *kftf_parse_input(void *input, size_t input_size) { size_t i; - void *payload_start; + void *payload_start, *out; uintptr_t *ptr_location; + size_t payload_len, alloc_size; + struct reloc_table *rt; struct reloc_entry re; pr_info("%s: input_size = %zu\n", __FUNCTION__, input_size); @@ -307,16 +317,35 @@ static void *kftf_parse_input(void *input, size_t input_size) pr_warn("got misformed input in %s\n", __FUNCTION__); return NULL; } - struct reloc_table *rt = input; + rt = input; payload_start = (char *)input + offsetof(struct reloc_table, entries) + rt->num_entries * sizeof(struct reloc_entry); if (payload_start >= input + input_size) return NULL; + /* + * To guarantee correct alignment of structures within the payload, we + * allocate a new property that is aligned to the next power of two + * greater than either the size of the payload or the maximum alignment + * of the nested structures. + */ + payload_len = input_size - (payload_start - input); + alloc_size = MAX(roundup_pow_of_two(payload_len), + roundup_pow_of_two(rt->max_alignment)); + out = kmalloc(alloc_size, GFP_KERNEL); + if (!out) { + return NULL; + } + memcpy(out, payload_start, payload_len); + + /* + * Iterate through entries in the relocation table and patch the + * pointers. + */ for (i = 0; i < rt->num_entries; i++) { re = rt->entries[i]; - ptr_location = (uintptr_t *)(payload_start + re.pointer); + ptr_location = (uintptr_t *)(out + re.pointer); if ((void *)ptr_location + sizeof(uintptr_t) >= input + input_size) return NULL; @@ -328,7 +357,7 @@ static void *kftf_parse_input(void *input, size_t input_size) } } - return payload_start; + return out; } #endif /* KFTF_H */ From 799cdcdd85baf8a9407669d23691bc73a0d2af4a Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Tue, 15 Jul 2025 17:48:48 +0000 Subject: [PATCH 26/73] kftf: harden binary input parsing --- include/linux/kftf.h | 82 +++++++++++++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 3bf79bbc319f3d..b0bbf2e7b1db9a 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -237,6 +237,10 @@ static_assert(sizeof(struct kftf_constraint) == 64, __KFTF_DEFINE_CONSTRAINT(arg_type, field, lower_bound, upper_bound, \ EXPECT_IN_RANGE) +#define KFTF_EXPECT_LEN(expected_len, actual_len) \ + if ((expected_len) != (actual_len)) \ + return; + enum kftf_annotation_attribute : uint8_t { ATTRIBUTE_LEN = 0, ATTRIBUTE_STRING, @@ -301,17 +305,24 @@ static_assert(offsetof(struct reloc_table, entries) % sizeof(struct reloc_entry) == 0); +/** + * This value should be known the fuzz engine. + */ static const uintptr_t nullPtr = (uintptr_t)-1; -static void *kftf_parse_input(void *input, size_t input_size) +/* XXX: wasn't building before without attribute unused, but it is used in + * several locations - weird... */ +__attribute__((unused)) static void *kftf_parse_input(void *input, + size_t input_size) { size_t i; void *payload_start, *out; uintptr_t *ptr_location; - size_t payload_len, alloc_size; + size_t payload_len, alloc_size, entries_size, header_size; struct reloc_table *rt; struct reloc_entry re; - pr_info("%s: input_size = %zu\n", __FUNCTION__, input_size); + if (input_size > KMALLOC_MAX_SIZE) + return NULL; if (input_size < sizeof(struct reloc_table)) { pr_warn("got misformed input in %s\n", __FUNCTION__); @@ -319,24 +330,59 @@ static void *kftf_parse_input(void *input, size_t input_size) } rt = input; - payload_start = (char *)input + offsetof(struct reloc_table, entries) + - rt->num_entries * sizeof(struct reloc_entry); + if (check_mul_overflow(rt->num_entries, sizeof(struct reloc_entry), + &entries_size)) + return NULL; + header_size = offsetof(struct reloc_table, entries) + entries_size; + if (header_size > input_size) + return NULL; + + payload_start = (char *)input + header_size; if (payload_start >= input + input_size) return NULL; - /* - * To guarantee correct alignment of structures within the payload, we - * allocate a new property that is aligned to the next power of two - * greater than either the size of the payload or the maximum alignment - * of the nested structures. - */ - payload_len = input_size - (payload_start - input); - alloc_size = MAX(roundup_pow_of_two(payload_len), - roundup_pow_of_two(rt->max_alignment)); - out = kmalloc(alloc_size, GFP_KERNEL); - if (!out) { + if (!is_power_of_2(rt->max_alignment) || rt->max_alignment > PAGE_SIZE) return NULL; + + payload_len = input_size - (payload_start - input); + + /* + * Check input for out-of-bounds pointers before before allocating + * aligned output buffer. + */ + for (i = 0; i < rt->num_entries; i++) { + re = rt->entries[i]; + if (re.pointer > payload_len || + re.pointer + sizeof(uintptr_t) > payload_len) + return NULL; + + if (re.value == nullPtr) + continue; + + /* We don't know the size of the data that is being pointed and + * therefor cannot make any stricter assertions. For example, + * if we enforce: + * re.pointer + re.value + sizeof(uintptr_t) < payload_len + * then we cannot write values with bytesize < 8. + */ + if (re.pointer + re.value >= payload_len) + return NULL; } + + /* + * To guarantee correct alignment of structures within the payload, we + * allocate a new buffer that is aligned to the next power of two + * greater than either the size of the payload + 1 or the maximum + * alignment of the nested structures. We add one to the payload length + * and call kzalloc to ensure that the payload is padded by trailing + * zeros to prevent false-positives on non-null terminated strings. + */ + alloc_size = + MAX(roundup_pow_of_two(payload_len + 1), rt->max_alignment); + out = kzalloc(alloc_size, GFP_KERNEL); + if (!out) + return NULL; + memcpy(out, payload_start, payload_len); /* @@ -346,10 +392,6 @@ static void *kftf_parse_input(void *input, size_t input_size) for (i = 0; i < rt->num_entries; i++) { re = rt->entries[i]; ptr_location = (uintptr_t *)(out + re.pointer); - if ((void *)ptr_location + sizeof(uintptr_t) >= - input + input_size) - return NULL; - if (re.value == nullPtr) { *ptr_location = (uintptr_t)NULL; } else { From 51cadf213d11c0abf9cf2175570b90bc72cc5012 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 25 Jul 2025 08:35:58 +0000 Subject: [PATCH 27/73] kfuzztest: update naming from "kftf" to "kfuzztest" --- arch/x86/kernel/vmlinux.lds.S | 22 +- include/linux/{kftf.h => kfuzztest.h} | 190 ++++++++---------- lib/Kconfig.debug | 2 +- lib/Kconfig.kftf | 5 - lib/Kconfig.kfuzztest | 5 + lib/Makefile | 2 +- lib/kftf/Makefile | 4 - lib/kfuzztest/Makefile | 4 + .../kfuzztest_main.c} | 124 +++++------- .../kfuzztest_tests.h} | 14 +- 10 files changed, 172 insertions(+), 200 deletions(-) rename include/linux/{kftf.h => kfuzztest.h} (66%) delete mode 100644 lib/Kconfig.kftf create mode 100644 lib/Kconfig.kfuzztest delete mode 100644 lib/kftf/Makefile create mode 100644 lib/kfuzztest/Makefile rename lib/{kftf/kftf_main.c => kfuzztest/kfuzztest_main.c} (54%) rename lib/{kftf/kftf_tests.h => kfuzztest/kfuzztest_tests.h} (68%) diff --git a/arch/x86/kernel/vmlinux.lds.S b/arch/x86/kernel/vmlinux.lds.S index d7f403f6432ad0..fdcca6b4b2f44e 100644 --- a/arch/x86/kernel/vmlinux.lds.S +++ b/arch/x86/kernel/vmlinux.lds.S @@ -113,19 +113,19 @@ ASSERT(__relocate_kernel_end - __relocate_kernel_start <= KEXEC_CONTROL_CODE_MAX #define KEXEC_RELOCATE_KERNEL #endif -#define KFTF_TABLE \ +#define KFUZZTEST_TABLE \ . = ALIGN(PAGE_SIZE); \ - __kftf_test_case_start = .; \ - KEEP(*(.kftf_test)); \ - __kftf_test_case_end = .; \ + __kfuzztest_targets_start = .; \ + KEEP(*(.kfuzztest_target)); \ + __kfuzztest_targets_end = .; \ . = ALIGN(PAGE_SIZE); \ - __kftf_constraint_start = .; \ - KEEP(*(.kftf_constraint)); \ - __kftf_constraint_end = .; \ + __kfuzztest_constraints_start = .; \ + KEEP(*(.kfuzztest_constraint)); \ + __kfuzztest_constraints_end = .; \ . = ALIGN(PAGE_SIZE); \ - __kftf_annotation_start = .; \ - KEEP(*(.kftf_annotation)); \ - __kftf_annotation_end = .; \ + __kfuzztest_annotations_start = .; \ + KEEP(*(.kfuzztest_annotation)); \ + __kfuzztest_annotations_end = .; \ PHDRS { text PT_LOAD FLAGS(5); /* R_E */ @@ -214,7 +214,7 @@ SECTIONS CONSTRUCTORS KEXEC_RELOCATE_KERNEL - KFTF_TABLE + KFUZZTEST_TABLE /* rarely changed data like cpu maps */ READ_MOSTLY_DATA(INTERNODE_CACHE_BYTES) diff --git a/include/linux/kftf.h b/include/linux/kfuzztest.h similarity index 66% rename from include/linux/kftf.h rename to include/linux/kfuzztest.h index b0bbf2e7b1db9a..43e74308d5f5ac 100644 --- a/include/linux/kftf.h +++ b/include/linux/kfuzztest.h @@ -1,31 +1,16 @@ -#ifndef KFTF_H -#define KFTF_H +#ifndef KFUZZTEST_H +#define KFUZZTEST_H #include MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ethan Graham "); -MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFTF)"); +MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFuzzTest)"); /* forward decl */ -static void *kftf_parse_input(void *input, size_t input_size); +static void *kfuzztest_parse_input(void *input, size_t input_size); -/** - * struct kftf_test case defines a single fuzz test case. These should not - * be created manually. Instead the user should use the FUZZ_TEST macro defined - * below. - * @name: The name of the test case, generally the function being fuzzed. - * @arg_type_name: string representation of the type of argument being fuzzed, - * for example "struct func_arg_type" - * @write_input_cb: Callback invoked when a write's to the test case's debugfs - * input file. This is the entry point for fuzzing data. It is responsible - * for parsing any data written to the input file and invoking the fuzzing - * logic. It should return the number of bytes consumed from `buf` - * @read_metadata_cb: Callback invoked when a user reads from the test case's - * "metadata" debugfs file. It should simply return whatever is contained - * in the `arg_type_name` field. - */ -struct kftf_test_case { +struct kfuzztest_target { const char *name; const char *arg_type_name; ssize_t (*write_input_cb)(struct file *filp, const char __user *buf, @@ -57,14 +42,13 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, * example below. * * - * This macro generates all of the necessary boilerplate for a KFTF test case, - * which is placed in a dedicated ".kftf" section so that the dedicated KFTF - * module can discover all defined tests at runtime. + * This macro generates all of the necessary boilerplate for a KFuzzTest driver, + * which is placed in a dedicated ".kfuzztest_target" section. * * For each test, this macro generates * - A buffer to receive input through the debugfs entry * - A mutex to protect the input buffer - * - A `struct kftf_test_case` instance + * - A `struct kfuzztest_target` instance * * Example usagea: * @@ -93,13 +77,13 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, struct file *filp, char __user *buf, size_t len, loff_t *off); \ static void _fuzz_test_logic_##func(func_arg_type *arg); \ /* test case struct initialization */ \ - const struct kftf_test_case __fuzz_test__##func \ - __attribute__((__section__(".kftf_test"), __used__)) = { \ - .name = #func, \ - .arg_type_name = #func_arg_type, \ - .write_input_cb = _write_callback_##func, \ - .read_metadata_cb = _read_metadata_callback_##func \ - }; \ + const struct kfuzztest_target __fuzz_test__##func __attribute__(( \ + __section__(".kfuzztest_target"), __used__)) = { \ + .name = #func, \ + .arg_type_name = #func_arg_type, \ + .write_input_cb = _write_callback_##func, \ + .read_metadata_cb = _read_metadata_callback_##func \ + }; \ /* callback that simply returns the type name to the user */ \ static ssize_t _read_metadata_callback_##func( \ struct file *filp, char __user *buf, size_t len, loff_t *off) \ @@ -124,7 +108,7 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, kfree(buffer); \ return err; \ } \ - void *payload = kftf_parse_input(buffer, len); \ + void *payload = kfuzztest_parse_input(buffer, len); \ if (!payload) { \ kfree(buffer); \ return -1; \ @@ -144,13 +128,13 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, * Reports a bug with a predictable prefix so that it can be parsed by a * fuzzing driver. */ -#define KFTF_REPORT_BUG(msg, fmt) pr_warn("bug: " #msg, fmt) +#define KFUZZTEST_REPORT_BUG(msg, fmt) pr_warn("bug: " #msg, fmt) /** - * struct kftf_constraint_type defines a type of constraint. The fuzzing driver + * struct kfuzztest_constraint_type defines a type of constraint. The fuzzing driver * should be aware of these. */ -enum kftf_constraint_type : uint8_t { +enum kfuzztest_constraint_type : uint8_t { EXPECT_EQ = 0, EXPECT_NE, EXPECT_LE, @@ -159,8 +143,8 @@ enum kftf_constraint_type : uint8_t { }; /** - * ktft_constraint defines a domain constraint for a struct variable that is - * taken as input for a FUZZ_TEST + * kfuzztest_constraint defines a domain constraint for a struct variable that + * is taken as input for a FUZZ_TEST. * * @input_type: the name of the input (a struct name) * @field_name: the name of the field that this domain constraint applies to @@ -174,100 +158,102 @@ enum kftf_constraint_type : uint8_t { * we enforce 64 Byte alignment and statically assert that this struct has size * 64 Bytes. */ -struct kftf_constraint { +struct kfuzztest_constraint { const char *input_type; const char *field_name; uintptr_t value1; uintptr_t value2; - enum kftf_constraint_type type; + enum kfuzztest_constraint_type type; } __attribute__((aligned(64))); -static_assert(sizeof(struct kftf_constraint) == 64, - "struct kftf_constraint should have size 64"); +static_assert(sizeof(struct kfuzztest_constraint) == 64, + "struct kfuzztest_constraint should have size 64"); /** - * __KFTF_DEFINE_CONSTRAINT - defines a fuzz test constraint linked to a given + * __KFUZZTEST_DEFINE_CONSTRAINT - defines a fuzz test constraint linked to a given * argument type belonging to a fuzz test. See FUZZ_TEST above. * * @arg_type: the type of argument (a struct) without the leading "struct" in * its name, which will be prepended. * @field: the field on which the constraint is defined. - * @val: used for comparison constraints such as KFTF_EXPECT_NE + * @val: used for comparison constraints such as KFUZZTEST_EXPECT_NE * @tpe: the type of constaint that this defines * * This macro is intended for internal use. A user should opt for - * KFTF_EXPECT_* instead when defining fuzz test constraints. + * KFUZZTEST_EXPECT_* instead when defining fuzz test constraints. */ -#define __KFTF_DEFINE_CONSTRAINT(arg_type, field, val1, val2, tpe) \ - static struct kftf_constraint __constraint_##arg_type##_##field \ - __attribute__((__section__(".kftf_constraint"), __used__)) = { \ - .input_type = "struct " #arg_type, \ - .field_name = #field, \ - .value1 = (uintptr_t)val1, \ - .value2 = (uintptr_t)val2, \ - .type = tpe, \ +#define __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val1, val2, tpe) \ + static struct kfuzztest_constraint __constraint_##arg_type##_##field \ + __attribute__((__section__(".kfuzztest_constraint"), \ + __used__)) = { \ + .input_type = "struct " #arg_type, \ + .field_name = #field, \ + .value1 = (uintptr_t)val1, \ + .value2 = (uintptr_t)val2, \ + .type = tpe, \ }; -#define KFTF_EXPECT_EQ(arg_type, field, val) \ - if (arg->field != val) \ - return; \ - __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_EQ) - -#define KFTF_EXPECT_NE(arg_type, field, val) \ - if (arg->field == val) \ - return; \ - __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_NE) - -#define KFTF_EXPECT_LE(arg_type, field, val) \ - if (arg->field > val) \ - return; \ - __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_LE) - -#define KFTF_EXPECT_GT(arg_type, field, val) \ - if (arg->field <= val) \ - return; \ - __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_GT) - -#define KFTF_EXPECT_NOT_NULL(arg_type, field) \ - KFTF_EXPECT_NE(arg_type, field, 0x0) - -#define KFTF_EXPECT_IN_RANGE(arg_type, field, lower_bound, upper_bound) \ - if (arg->field < lower_bound || arg->field > upper_bound) \ - return; \ - __KFTF_DEFINE_CONSTRAINT(arg_type, field, lower_bound, upper_bound, \ - EXPECT_IN_RANGE) - -#define KFTF_EXPECT_LEN(expected_len, actual_len) \ - if ((expected_len) != (actual_len)) \ +#define KFUZZTEST_EXPECT_EQ(arg_type, field, val) \ + if (arg->field != val) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_EQ) + +#define KFUZZTEST_EXPECT_NE(arg_type, field, val) \ + if (arg->field == val) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_NE) + +#define KFUZZTEST_EXPECT_LE(arg_type, field, val) \ + if (arg->field > val) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_LE) + +#define KFUZZTEST_EXPECT_GT(arg_type, field, val) \ + if (arg->field <= val) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_GT) + +#define KFUZZTEST_EXPECT_NOT_NULL(arg_type, field) \ + KFUZZTEST_EXPECT_NE(arg_type, field, 0x0) + +#define KFUZZTEST_EXPECT_IN_RANGE(arg_type, field, lower_bound, upper_bound) \ + if (arg->field < lower_bound || arg->field > upper_bound) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, lower_bound, \ + upper_bound, EXPECT_IN_RANGE) + +#define KFUZZTEST_EXPECT_LEN(expected_len, actual_len) \ + if ((expected_len) != (actual_len)) \ return; -enum kftf_annotation_attribute : uint8_t { +enum kfuzztest_annotation_attribute : uint8_t { ATTRIBUTE_LEN = 0, ATTRIBUTE_STRING, ATTRIBUTE_ARRAY, }; -struct kftf_annotation { +struct kfuzztest_annotation { const char *input_type; const char *field_name; const char *linked_field_name; - enum kftf_annotation_attribute attrib; + enum kfuzztest_annotation_attribute attrib; } __attribute__((aligned(32))); -#define __KFTF_ANNOTATE(arg_type, field, linked_field, attribute) \ - static struct kftf_annotation __annotation_##arg_type##_##field \ - __attribute__((__section__(".kftf_annotation"), __used__)) = { \ - .input_type = "struct " #arg_type, \ - .field_name = #field, \ - .linked_field_name = #linked_field, \ - .attrib = attribute, \ +#define __KFUZZTEST_ANNOTATE(arg_type, field, linked_field, attribute) \ + static struct kfuzztest_annotation __annotation_##arg_type##_##field \ + __attribute__((__section__(".kfuzztest_annotation"), \ + __used__)) = { \ + .input_type = "struct " #arg_type, \ + .field_name = #field, \ + .linked_field_name = #linked_field, \ + .attrib = attribute, \ }; /** * Annotates arg_type.field as a string */ -#define KFTF_ANNOTATE_STRING(arg_type, field) \ - __KFTF_ANNOTATE(arg_type, field, , ATTRIBUTE_STRING) +#define KFUZZTEST_ANNOTATE_STRING(arg_type, field) \ + __KFUZZTEST_ANNOTATE(arg_type, field, , ATTRIBUTE_STRING) /** * Annotates arg_type.field as an arrray. For example, assume that @@ -275,14 +261,14 @@ struct kftf_annotation { * a pointer to a single value or an array. Using this annotation removes that * ambiguity. */ -#define KFTF_ANNOTEATE_ARRAY(arg_type, field) \ - __KFTF_ANNOTATE(arg_type, field, , ATTRIBUTE_ARRAY) +#define KFUZZTEST_ANNOTEATE_ARRAY(arg_type, field) \ + __KFUZZTEST_ANNOTATE(arg_type, field, , ATTRIBUTE_ARRAY) /** * Annotates arg_type.field as the length of arg_type.linked_field */ -#define KFTF_ANNOTATE_LEN(arg_type, field, linked_field) \ - __KFTF_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) +#define KFUZZTEST_ANNOTATE_LEN(arg_type, field, linked_field) \ + __KFUZZTEST_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) struct reloc_entry { uintptr_t pointer; /* offset from the beginning of the payload */ @@ -312,8 +298,8 @@ static const uintptr_t nullPtr = (uintptr_t)-1; /* XXX: wasn't building before without attribute unused, but it is used in * several locations - weird... */ -__attribute__((unused)) static void *kftf_parse_input(void *input, - size_t input_size) +__attribute__((unused)) static void *kfuzztest_parse_input(void *input, + size_t input_size) { size_t i; void *payload_start, *out; @@ -402,4 +388,4 @@ __attribute__((unused)) static void *kftf_parse_input(void *input, return out; } -#endif /* KFTF_H */ +#endif /* KFUZZTEST_H */ diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 42cd345f93ad81..5a33c34b107469 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1040,7 +1040,7 @@ config MEM_ALLOC_PROFILING_DEBUG source "lib/Kconfig.kasan" source "lib/Kconfig.kfence" source "lib/Kconfig.kmsan" -source "lib/Kconfig.kftf" +source "lib/Kconfig.kfuzztest" endmenu # "Memory Debugging" diff --git a/lib/Kconfig.kftf b/lib/Kconfig.kftf deleted file mode 100644 index 6a7d35a25b5f50..00000000000000 --- a/lib/Kconfig.kftf +++ /dev/null @@ -1,5 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only - -config KFTF - tristate "Enable Kernel Fuzz Testing Framework (KFTF)" - depends on DEBUG_FS diff --git a/lib/Kconfig.kfuzztest b/lib/Kconfig.kfuzztest new file mode 100644 index 00000000000000..0fb000fe305c6a --- /dev/null +++ b/lib/Kconfig.kfuzztest @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config KFUZZTEST + bool "Enable Kernel Fuzz Testing Framework (KFuzzTest)" + depends on DEBUG_FS diff --git a/lib/Makefile b/lib/Makefile index 33e75b3682018e..511c44ef4b19e9 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -354,7 +354,7 @@ obj-$(CONFIG_GENERIC_LIB_CMPDI2) += cmpdi2.o obj-$(CONFIG_GENERIC_LIB_UCMPDI2) += ucmpdi2.o obj-$(CONFIG_OBJAGG) += objagg.o -obj-$(CONFIG_KFTF) += kftf/ +obj-$(CONFIG_KFUZZTEST) += kfuzztest/ # pldmfw library obj-$(CONFIG_PLDMFW) += pldmfw/ diff --git a/lib/kftf/Makefile b/lib/kftf/Makefile deleted file mode 100644 index 6e1df924ce37ae..00000000000000 --- a/lib/kftf/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only - -obj-$(CONFIG_KFTF) += kftf.o -kftf-objs := kftf_main.o diff --git a/lib/kfuzztest/Makefile b/lib/kfuzztest/Makefile new file mode 100644 index 00000000000000..ddaab873324c2a --- /dev/null +++ b/lib/kfuzztest/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_KFUZZTEST) += kfuzztest.o +kfuzztest-objs := kfuzztest_main.o diff --git a/lib/kftf/kftf_main.c b/lib/kfuzztest/kfuzztest_main.c similarity index 54% rename from lib/kftf/kftf_main.c rename to lib/kfuzztest/kfuzztest_main.c index 133ee92b33ab31..cc38250e4f11a4 100644 --- a/lib/kftf/kftf_main.c +++ b/lib/kfuzztest/kfuzztest_main.c @@ -4,62 +4,60 @@ * * This module is responsible for discovering and initializing all fuzz test * cases defined using the FUZZ_TEST() macro. It creates a debugfs interface -* under /sys/kernel/debug/kftf/ for userspace to interact with each test. +* under /sys/kernel/debug/kfuzztest/ for userspace to interact with each test. */ #include #include -#include +#include #include -#include "kftf_tests.h" +#include "kfuzztest_tests.h" -extern const struct kftf_test_case __kftf_test_case_start[]; -extern const struct kftf_test_case __kftf_test_case_end[]; -extern const struct kftf_constraint __kftf_constraint_start[]; -extern const struct kftf_constraint __kftf_constraint_end[]; +extern const struct kfuzztest_target __kfuzztest_targets_start[]; +extern const struct kfuzztest_target __kfuzztest_targets_end[]; /** - * struct kftf_dentry - A container for a debugfs dentry and its fops. + * struct kfuzztest_dentry - A container for a debugfs dentry and its fops. * @dentry: Pointer to the created debugfs dentry. * @fops: The file_operations struct associated with this dentry. * * This simplifies state management by keeping a file's dentry and its * operations bundled together. */ -struct kftf_dentry { +struct kfuzztest_dentry { struct dentry *dentry; struct file_operations fops; }; /** - * struct kftf_debugfs_state - Per-test-case debugfs state. + * struct kfuzztest_debugfs_state - Per-test-case debugfs state. * @test_dir: The top-level debugfs directory for a single test case, e.g., - * /sys/kernel/debug/kftf//. + * /sys/kernel/debug/kfuzztest//. * @input_dentry: The state for the "input" file, which is write-only. * @metadata_dentry: The state for the "metadata" file, which is read-only. * * Wraps all debugfs components created for a single test case. */ -struct kftf_debugfs_state { +struct kfuzztest_debugfs_state { struct dentry *test_dir; - struct kftf_dentry input_dentry; - struct kftf_dentry metadata_dentry; + struct kfuzztest_dentry input_dentry; + struct kfuzztest_dentry metadata_dentry; }; /** - * struct kftf_simple_fuzzer_state - Global state for the KFTF module. - * @kftf_dir: The root debugfs directory, /sys/kernel/debug/kftf/. + * struct kfuzztest_simple_fuzzer_state - Global state for the KFTF module. + * @kfuzztest_dir: The root debugfs directory, /sys/kernel/debug/kfuzztest/. * @debugfs_state: A statically sized array holding the state for each * registered test case. */ -struct kftf_simple_fuzzer_state { +struct kfuzztest_simple_fuzzer_state { struct file_operations fops; - struct dentry *kftf_dir; - struct kftf_debugfs_state *debugfs_state; + struct dentry *kfuzztest_dir; + struct kfuzztest_debugfs_state *debugfs_state; }; /* Global static variable to hold all state for the module. */ -static struct kftf_simple_fuzzer_state st; +static struct kfuzztest_simple_fuzzer_state st; /* * Default file permissions for the debugfs entries. @@ -68,18 +66,18 @@ static struct kftf_simple_fuzzer_state st; * * XXX: should formally define what the permissions should be on these files */ -const umode_t kftf_flags_w = 0222; -const umode_t kftf_flags_r = 0444; +const umode_t kfuzztest_flags_w = 0222; +const umode_t kfuzztest_flags_r = 0444; /** - * kftf_init - Initializes the debug filesystem for KFTF. + * kfuzztest_init - Initializes the debug filesystem for KFTF. * * This function is the entry point for the KFTF module, populating the debugfs * that is used for IO interaction between the individual fuzzing drivers and * a userspace fuzzing tool like syzkaller. * - * Each registered test in the ".kftf" section gets its own subdirectory - * under "/sys/kernel/debug/kftf/" with two files: + * Each registered test in the ".kfuzztest" section gets its own subdirectory + * under "/sys/kernel/debug/kfuzztest/" with two files: * - input: write-only file to send input to the fuzz driver * - metadata: used to read the type name that the fuzz driver expects * @@ -88,39 +86,39 @@ const umode_t kftf_flags_r = 0444; * -EINVAL if the number of tests exceeds KFTF_MAX_TEST_CASES * -ENODEV or other error codes if debugfs creation fails. */ -static int __init kftf_init(void) +static int __init kfuzztest_init(void) { - const struct kftf_test_case *test; - const struct kftf_constraint *constraint; + const struct kfuzztest_target *targ; int ret = 0; int i = 0; size_t num_test_cases; - num_test_cases = __kftf_test_case_end - __kftf_test_case_start; + num_test_cases = __kfuzztest_targets_end - __kfuzztest_targets_start; - st.debugfs_state = kmalloc( - num_test_cases * sizeof(struct kftf_debugfs_state), GFP_KERNEL); + st.debugfs_state = + kmalloc(num_test_cases * sizeof(struct kfuzztest_debugfs_state), + GFP_KERNEL); if (!st.debugfs_state) return -ENOMEM; - /* create the main "kftf" directory in `/sys/kernel/debug` */ - st.kftf_dir = debugfs_create_dir("kftf", NULL); - if (!st.kftf_dir) { - pr_warn("kftf: could not create debugfs"); + /* create the main "kfuzztest" directory in `/sys/kernel/debug` */ + st.kfuzztest_dir = debugfs_create_dir("kfuzztest", NULL); + if (!st.kfuzztest_dir) { + pr_warn("KFuzzTest: could not create debugfs"); return -ENODEV; } - if (IS_ERR(st.kftf_dir)) { - st.kftf_dir = NULL; - return PTR_ERR(st.kftf_dir); + if (IS_ERR(st.kfuzztest_dir)) { + st.kfuzztest_dir = NULL; + return PTR_ERR(st.kfuzztest_dir); } /* iterate over all discovered test cases and set up debugfs entries */ - for (test = __kftf_test_case_start; test < __kftf_test_case_end; - test++, i++) { + for (targ = __kfuzztest_targets_start; targ < __kfuzztest_targets_end; + targ++, i++) { /* create a directory for the discovered test case */ st.debugfs_state[i].test_dir = - debugfs_create_dir(test->name, st.kftf_dir); + debugfs_create_dir(targ->name, st.kfuzztest_dir); if (!st.debugfs_state[i].test_dir) { ret = -ENOMEM; @@ -134,11 +132,12 @@ static int __init kftf_init(void) st.debugfs_state[i].input_dentry.fops = (struct file_operations){ .owner = THIS_MODULE, - .write = test->write_input_cb, + .write = targ->write_input_cb, }; st.debugfs_state[i].input_dentry.dentry = debugfs_create_file( - "input", kftf_flags_w, st.debugfs_state[i].test_dir, - NULL, &st.debugfs_state[i].input_dentry.fops); + "input", kfuzztest_flags_w, + st.debugfs_state[i].test_dir, NULL, + &st.debugfs_state[i].input_dentry.fops); if (!st.debugfs_state[i].input_dentry.dentry) { ret = -ENOMEM; goto cleanup_failure; @@ -150,13 +149,13 @@ static int __init kftf_init(void) st.debugfs_state[i].metadata_dentry.fops = (struct file_operations){ .owner = THIS_MODULE, - .read = test->read_metadata_cb, + .read = targ->read_metadata_cb, }; /* create "metadata" file for fuzz test */ st.debugfs_state[i].metadata_dentry.dentry = debugfs_create_file( - "metadata", kftf_flags_r, + "metadata", kfuzztest_flags_r, st.debugfs_state[i].test_dir, NULL, &st.debugfs_state[i].metadata_dentry.fops); if (!st.debugfs_state[i].metadata_dentry.dentry) { @@ -168,41 +167,28 @@ static int __init kftf_init(void) goto cleanup_failure; } - pr_info("kftf: registered %s\n", test->name); - } - - // TODO: make debugfs entries for these constraints - size_t num_constraints = 0; - for (constraint = __kftf_constraint_start; - constraint < __kftf_constraint_end; constraint++) { - pr_info("kftf: addr = 0x%lX\n", (size_t)constraint); - pr_info("input type: %s\n", constraint->input_type); - pr_info("field name: %s\n", constraint->field_name); - pr_info("value1: %lx\n", constraint->value1); - pr_info("value2: %lx\n", constraint->value2); - pr_info("type: %d\n", constraint->type); - num_constraints++; + pr_info("KFuzzTest: registered %s\n", targ->name); } return 0; cleanup_failure: - debugfs_remove_recursive(st.kftf_dir); + debugfs_remove_recursive(st.kfuzztest_dir); return ret; } /** - * kftf_exit - Cleans up the module. + * kfuzztest_exit - Cleans up the module. */ -static void __exit kftf_exit(void) +static void __exit kfuzztest_exit(void) { - pr_info("kftf: shutting down\n"); - if (!st.kftf_dir) + pr_info("KFuzzTest: exiting\n"); + if (!st.kfuzztest_dir) return; - debugfs_remove_recursive(st.kftf_dir); - st.kftf_dir = NULL; + debugfs_remove_recursive(st.kfuzztest_dir); + st.kfuzztest_dir = NULL; } -module_init(kftf_init); -module_exit(kftf_exit); +module_init(kfuzztest_init); +module_exit(kfuzztest_exit); diff --git a/lib/kftf/kftf_tests.h b/lib/kfuzztest/kfuzztest_tests.h similarity index 68% rename from lib/kftf/kftf_tests.h rename to lib/kfuzztest/kfuzztest_tests.h index 3c2bed01093846..f7f53312ddf358 100644 --- a/lib/kftf/kftf_tests.h +++ b/lib/kfuzztest/kfuzztest_tests.h @@ -1,7 +1,7 @@ -#ifndef KFTF_TESTS_H -#define KFTF_TESTS_H +#ifndef KFUZZTEST_TESTS_H +#define KFUZZTEST_TESTS_H -#include +#include #include struct kftf_simple_arg { @@ -30,10 +30,10 @@ static void kftf_fuzzable(char first, char second, char third) FUZZ_TEST(kftf_fuzzable, struct kftf_simple_arg) { - KFTF_EXPECT_NOT_NULL(kftf_simple_arg, first); - KFTF_EXPECT_IN_RANGE(kftf_simple_arg, second, 'a', 'z'); - KFTF_EXPECT_IN_RANGE(kftf_simple_arg, third, 'a', 'z'); + KFUZZTEST_EXPECT_NOT_NULL(kftf_simple_arg, first); + KFUZZTEST_EXPECT_IN_RANGE(kftf_simple_arg, second, 'a', 'z'); + KFUZZTEST_EXPECT_IN_RANGE(kftf_simple_arg, third, 'a', 'z'); kftf_fuzzable(arg->first, arg->second, arg->third); } -#endif /* KFTF_TESTS_H */ +#endif /* KFUZZTEST_TESTS_H */ From e32895373877884f0d4b42985b24245a7f911604 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 25 Jul 2025 09:43:33 +0000 Subject: [PATCH 28/73] kfuzztest: cleanup code and update documentation --- include/linux/kfuzztest.h | 145 ++++++++++++++++----------------- lib/kfuzztest/kfuzztest_main.c | 84 ++++++------------- 2 files changed, 98 insertions(+), 131 deletions(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index 43e74308d5f5ac..afa9ef91ab9642 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -7,7 +7,6 @@ MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ethan Graham "); MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFuzzTest)"); -/* forward decl */ static void *kfuzztest_parse_input(void *input, size_t input_size); struct kfuzztest_target { @@ -15,11 +14,8 @@ struct kfuzztest_target { const char *arg_type_name; ssize_t (*write_input_cb)(struct file *filp, const char __user *buf, size_t len, loff_t *off); - ssize_t (*read_metadata_cb)(struct file *, char __user *, size_t, - loff_t *); }; -// XXX: why can't we use without the attribute unused anymore?? __attribute__((unused)) static int write_input_cb_common(struct file *filp, const char __user *buf, size_t len, loff_t *off, void *arg, size_t arg_size) @@ -34,71 +30,63 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, } /** - * FUZZ_TEST - defines a fuzz test case for a function. - * @func: the function to be fuzzed. This is used to name the test case and - * create associated debufs entries. - * @func_arg_type: the input type of func. If func takes multiple arguments, - * then one should wrap that inside of a multi-fielded struct. See usage - * example below. + * FUZZ_TEST - defines a KFuzzTest target. * + * @test_name: Name of the fuzz target, which is used to create the associated + * debufs entries. + * @func_arg_type: the input type of fuzz target. This should always be a + * struct type even when fuzzing with a single input parameter in order + * to take advantage of the domain constraint and annotation systems. See + * usage example below. * - * This macro generates all of the necessary boilerplate for a KFuzzTest driver, - * which is placed in a dedicated ".kfuzztest_target" section. + * + * This macro generates all of the necessary boilerplate for a KFuzzTest + * driver, which is placed in a dedicated ".kfuzztest_target" that is used by + * the KFuzzTest module and can be read by a fuzzing engine. * * For each test, this macro generates * - A buffer to receive input through the debugfs entry * - A mutex to protect the input buffer * - A `struct kfuzztest_target` instance * - * Example usagea: + * Example usage: * - * Assume some function `func(T1 param1, ... TN paramN)` - * // Define input type of the target function + * // Assume that we are fuzzing some function func(T1 param1, ... TN paramN). + * // Define input type of the fuzz target. This should be always be a struct. * struct func_arg_type { * T1 arg1; * ... * TN argn; * }; * - * // Define the test case - * FUZZ_TEST(func, struct func_arg_type) + * // Define the test case. + * FUZZ_TEST(test_func, struct func_arg_type) * { - * // arg is provided by the macro, and is of type `struct func_arg_type` + * int ret; + * // arg is provided by the macro, and is of type struct func_arg_type. * ret = func(arg.arg1, ..., arg.argn); - * validate(ret); + * // Validate the return value if testing for correctness. + * if (ret != expected_value) { + * KFUZZTEST_REPORT_BUG("Unexpected return value"); + * } * } */ -#define FUZZ_TEST(func, func_arg_type) \ - /* forward decls */ \ +#define FUZZ_TEST(test_name, func_arg_type) \ static ssize_t _write_callback_##func(struct file *filp, \ const char __user *buf, \ size_t len, loff_t *off); \ - static ssize_t _read_metadata_callback_##func( \ - struct file *filp, char __user *buf, size_t len, loff_t *off); \ static void _fuzz_test_logic_##func(func_arg_type *arg); \ - /* test case struct initialization */ \ const struct kfuzztest_target __fuzz_test__##func __attribute__(( \ __section__(".kfuzztest_target"), __used__)) = { \ - .name = #func, \ + .name = #test_name, \ .arg_type_name = #func_arg_type, \ .write_input_cb = _write_callback_##func, \ - .read_metadata_cb = _read_metadata_callback_##func \ }; \ - /* callback that simply returns the type name to the user */ \ - static ssize_t _read_metadata_callback_##func( \ - struct file *filp, char __user *buf, size_t len, loff_t *off) \ - { \ - const char *message = __fuzz_test__##func.arg_type_name; \ - int message_len = strlen(message); \ - return simple_read_from_buffer(buf, len, off, message, \ - message_len); \ - } \ - /* user-defined write callback */ \ + /* Invoked when data is written into the target's input file. */ \ static ssize_t _write_callback_##func(struct file *filp, \ const char __user *buf, \ size_t len, loff_t *off) \ { \ - pr_info("[ENTER] %s\n", __FUNCTION__); \ int err; \ void *buffer = kmalloc(len, GFP_KERNEL); \ if (!buffer || IS_ERR(buffer)) \ @@ -114,9 +102,7 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, return -1; \ } \ func_arg_type *arg = payload; \ - /* call the user's logic on the provided arg. */ \ - /* NOTE: define some success/failure return types? */ \ - pr_info("invoking fuzz logic for %s\n", #func); \ + /* Call the user's logic on the provided written input. */ \ _fuzz_test_logic_##func(arg); \ kfree(buffer); \ kfree(payload); \ @@ -130,10 +116,6 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, */ #define KFUZZTEST_REPORT_BUG(msg, fmt) pr_warn("bug: " #msg, fmt) -/** - * struct kfuzztest_constraint_type defines a type of constraint. The fuzzing driver - * should be aware of these. - */ enum kfuzztest_constraint_type : uint8_t { EXPECT_EQ = 0, EXPECT_NE, @@ -143,8 +125,14 @@ enum kfuzztest_constraint_type : uint8_t { }; /** - * kfuzztest_constraint defines a domain constraint for a struct variable that - * is taken as input for a FUZZ_TEST. + * Domain constraints are used to restrict the values that the fuzz driver + * accepts, enforcing early exit when not satisfied. Domain constraints are + * encoded in vmlinux under the `__kfuzztest_constraint` section. A good + * fuzzing engine should be aware of these domain constraints during input + * generation and mutation. + * + * struct kfuzztest_constraint defines a domain constraint for a structure + * field. * * @input_type: the name of the input (a struct name) * @field_name: the name of the field that this domain constraint applies to @@ -153,10 +141,21 @@ enum kfuzztest_constraint_type : uint8_t { * constraints * @type: the type of the constraint, enumerated above * - * Note: if this struct is not a multiple of 64 bytes, everything breaks and - * we get corrupted data and occasional kernel panics. To avoid this happening, - * we enforce 64 Byte alignment and statically assert that this struct has size - * 64 Bytes. + * Example usage: + * + * struct foo { + * struct bar *a; + * int b + * }; + * + * FUZZ_TEST(test_name, struct foo) + * { + * // Early exit if foo.a == NULL. + * KFUZZTEST_EXPECT_NOT_NULL(foo, a); + * // Early exit if foo < 23 || foo > 42 + * KFUZZTEST_EXPECT_IN_RANGE(foo, b, 23, 42); + * // User-defined fuzz logic. + * } */ struct kfuzztest_constraint { const char *input_type; @@ -169,19 +168,6 @@ struct kfuzztest_constraint { static_assert(sizeof(struct kfuzztest_constraint) == 64, "struct kfuzztest_constraint should have size 64"); -/** - * __KFUZZTEST_DEFINE_CONSTRAINT - defines a fuzz test constraint linked to a given - * argument type belonging to a fuzz test. See FUZZ_TEST above. - * - * @arg_type: the type of argument (a struct) without the leading "struct" in - * its name, which will be prepended. - * @field: the field on which the constraint is defined. - * @val: used for comparison constraints such as KFUZZTEST_EXPECT_NE - * @tpe: the type of constaint that this defines - * - * This macro is intended for internal use. A user should opt for - * KFUZZTEST_EXPECT_* instead when defining fuzz test constraints. - */ #define __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val1, val2, tpe) \ static struct kfuzztest_constraint __constraint_##arg_type##_##field \ __attribute__((__section__(".kfuzztest_constraint"), \ @@ -226,6 +212,16 @@ static_assert(sizeof(struct kfuzztest_constraint) == 64, if ((expected_len) != (actual_len)) \ return; +/** + * Annotations express attributes about structure fields that can't be easily + * verified at runtime, and are intended as a hint to the fuzzing engine. + * + * For example, a char* could either be a raw byte buffer or a string, where + * the latter is null terminated. If a function accepts a null-terminated + * string without a length and is passed an arbitrary byte buffer, we + * may get false positive KASAN reports, for example. However, verifying that + * the char buffer is null-termined could itself trigger a memory overflow. + */ enum kfuzztest_annotation_attribute : uint8_t { ATTRIBUTE_LEN = 0, ATTRIBUTE_STRING, @@ -250,18 +246,17 @@ struct kfuzztest_annotation { }; /** - * Annotates arg_type.field as a string + * Annotates a char* field as a string, which is the subset of char arrays that + * are null-terminated. */ #define KFUZZTEST_ANNOTATE_STRING(arg_type, field) \ __KFUZZTEST_ANNOTATE(arg_type, field, , ATTRIBUTE_STRING) /** - * Annotates arg_type.field as an arrray. For example, assume that - * arg_type.field is of type `unsigned long *`, which could either represent - * a pointer to a single value or an array. Using this annotation removes that - * ambiguity. + * Annotates a pointer field as an array, which is a contiguous memory region + * containing zero or more elements of the same type. */ -#define KFUZZTEST_ANNOTEATE_ARRAY(arg_type, field) \ +#define KFUZZTEST_ANNOTATE_ARRAY(arg_type, field) \ __KFUZZTEST_ANNOTATE(arg_type, field, , ATTRIBUTE_ARRAY) /** @@ -271,8 +266,8 @@ struct kfuzztest_annotation { __KFUZZTEST_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) struct reloc_entry { - uintptr_t pointer; /* offset from the beginning of the payload */ - uintptr_t value; /* difference between the pointed to address and the address itself */ + uintptr_t pointer; /* Offset from the beginning of the payload. */ + uintptr_t value; /* Offset between the pointer and the pointed-to address */ }; /* @@ -292,12 +287,14 @@ static_assert(offsetof(struct reloc_table, entries) % 0); /** - * This value should be known the fuzz engine. + * The relocation table format encodes pointer values as a relative offset from + * the location of the pointer. A relative offset of zero could indicate that + * the pointer points to its own address, which is valid. We encode a null + * pointer as 0xFF...FF as adding this value to any address would result in an + * overflow anyways, and is therefore invalid in any other circumstance. */ static const uintptr_t nullPtr = (uintptr_t)-1; -/* XXX: wasn't building before without attribute unused, but it is used in - * several locations - weird... */ __attribute__((unused)) static void *kfuzztest_parse_input(void *input, size_t input_size) { diff --git a/lib/kfuzztest/kfuzztest_main.c b/lib/kfuzztest/kfuzztest_main.c index cc38250e4f11a4..c038892c826fb4 100644 --- a/lib/kfuzztest/kfuzztest_main.c +++ b/lib/kfuzztest/kfuzztest_main.c @@ -4,15 +4,13 @@ * * This module is responsible for discovering and initializing all fuzz test * cases defined using the FUZZ_TEST() macro. It creates a debugfs interface -* under /sys/kernel/debug/kfuzztest/ for userspace to interact with each test. -*/ + * under /sys/kernel/debug/kfuzztest/ for userspace to interact with each test. + */ #include #include #include #include -#include "kfuzztest_tests.h" - extern const struct kfuzztest_target __kfuzztest_targets_start[]; extern const struct kfuzztest_target __kfuzztest_targets_end[]; @@ -34,14 +32,12 @@ struct kfuzztest_dentry { * @test_dir: The top-level debugfs directory for a single test case, e.g., * /sys/kernel/debug/kfuzztest//. * @input_dentry: The state for the "input" file, which is write-only. - * @metadata_dentry: The state for the "metadata" file, which is read-only. * * Wraps all debugfs components created for a single test case. */ struct kfuzztest_debugfs_state { - struct dentry *test_dir; + struct dentry *target_dir; struct kfuzztest_dentry input_dentry; - struct kfuzztest_dentry metadata_dentry; }; /** @@ -50,41 +46,33 @@ struct kfuzztest_debugfs_state { * @debugfs_state: A statically sized array holding the state for each * registered test case. */ -struct kfuzztest_simple_fuzzer_state { +struct kfuzztest_state { struct file_operations fops; struct dentry *kfuzztest_dir; struct kfuzztest_debugfs_state *debugfs_state; }; /* Global static variable to hold all state for the module. */ -static struct kfuzztest_simple_fuzzer_state st; +static struct kfuzztest_state st; -/* +/** * Default file permissions for the debugfs entries. * 0222: World-writable for the 'input' file. - * 0444: World-readable for the 'metadata' file. * - * XXX: should formally define what the permissions should be on these files + * XXX: should formally define what the permissions should be on these files. */ const umode_t kfuzztest_flags_w = 0222; -const umode_t kfuzztest_flags_r = 0444; /** - * kfuzztest_init - Initializes the debug filesystem for KFTF. - * - * This function is the entry point for the KFTF module, populating the debugfs - * that is used for IO interaction between the individual fuzzing drivers and - * a userspace fuzzing tool like syzkaller. + * kfuzztest_init - Initializes the debug filesystem for KFuzzTest. * * Each registered test in the ".kfuzztest" section gets its own subdirectory - * under "/sys/kernel/debug/kfuzztest/" with two files: + * under "/sys/kernel/debug/kfuzztest/" with one files: * - input: write-only file to send input to the fuzz driver - * - metadata: used to read the type name that the fuzz driver expects * * Returns: - * 0 on success. - * -EINVAL if the number of tests exceeds KFTF_MAX_TEST_CASES - * -ENODEV or other error codes if debugfs creation fails. + * 0 on success. + * -ENODEV or other error codes if debugfs creation fails. */ static int __init kfuzztest_init(void) { @@ -100,8 +88,10 @@ static int __init kfuzztest_init(void) GFP_KERNEL); if (!st.debugfs_state) return -ENOMEM; + else if (IS_ERR(st.debugfs_state)) + return PTR_ERR(st.debugfs_state); - /* create the main "kfuzztest" directory in `/sys/kernel/debug` */ + /* Create the main "kfuzztest" directory in /sys/kernel/debug. */ st.kfuzztest_dir = debugfs_create_dir("kfuzztest", NULL); if (!st.kfuzztest_dir) { pr_warn("KFuzzTest: could not create debugfs"); @@ -113,22 +103,21 @@ static int __init kfuzztest_init(void) return PTR_ERR(st.kfuzztest_dir); } - /* iterate over all discovered test cases and set up debugfs entries */ for (targ = __kfuzztest_targets_start; targ < __kfuzztest_targets_end; targ++, i++) { - /* create a directory for the discovered test case */ - st.debugfs_state[i].test_dir = + /* Create debugfs directory for the target. */ + st.debugfs_state[i].target_dir = debugfs_create_dir(targ->name, st.kfuzztest_dir); - if (!st.debugfs_state[i].test_dir) { + if (!st.debugfs_state[i].target_dir) { ret = -ENOMEM; goto cleanup_failure; - } else if (IS_ERR(st.debugfs_state[i].test_dir)) { - ret = PTR_ERR(st.debugfs_state[i].test_dir); + } else if (IS_ERR(st.debugfs_state[i].target_dir)) { + ret = PTR_ERR(st.debugfs_state[i].target_dir); goto cleanup_failure; } - /* create "input" file for fuzz test */ + /* Create an input file under the target's directory. */ st.debugfs_state[i].input_dentry.fops = (struct file_operations){ .owner = THIS_MODULE, @@ -136,7 +125,7 @@ static int __init kfuzztest_init(void) }; st.debugfs_state[i].input_dentry.dentry = debugfs_create_file( "input", kfuzztest_flags_w, - st.debugfs_state[i].test_dir, NULL, + st.debugfs_state[i].target_dir, NULL, &st.debugfs_state[i].input_dentry.fops); if (!st.debugfs_state[i].input_dentry.dentry) { ret = -ENOMEM; @@ -146,28 +135,7 @@ static int __init kfuzztest_init(void) goto cleanup_failure; } - st.debugfs_state[i].metadata_dentry.fops = - (struct file_operations){ - .owner = THIS_MODULE, - .read = targ->read_metadata_cb, - }; - - /* create "metadata" file for fuzz test */ - st.debugfs_state[i].metadata_dentry.dentry = - debugfs_create_file( - "metadata", kfuzztest_flags_r, - st.debugfs_state[i].test_dir, NULL, - &st.debugfs_state[i].metadata_dentry.fops); - if (!st.debugfs_state[i].metadata_dentry.dentry) { - ret = -ENOMEM; - goto cleanup_failure; - } else if (IS_ERR(st.debugfs_state[i].metadata_dentry.dentry)) { - ret = PTR_ERR( - st.debugfs_state[i].metadata_dentry.dentry); - goto cleanup_failure; - } - - pr_info("KFuzzTest: registered %s\n", targ->name); + pr_info("KFuzzTest: registered target %s\n", targ->name); } return 0; @@ -177,9 +145,6 @@ static int __init kfuzztest_init(void) return ret; } -/** - * kfuzztest_exit - Cleans up the module. - */ static void __exit kfuzztest_exit(void) { pr_info("KFuzzTest: exiting\n"); @@ -188,6 +153,11 @@ static void __exit kfuzztest_exit(void) debugfs_remove_recursive(st.kfuzztest_dir); st.kfuzztest_dir = NULL; + + if (st.debugfs_state) { + kfree(st.debugfs_state); + st.debugfs_state = NULL; + } } module_init(kfuzztest_init); From 8cb9faa5d0c615bf247812b584a73e56f22d2e18 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 25 Jul 2025 09:45:54 +0000 Subject: [PATCH 29/73] kfuzztest: remove dummy test file --- lib/kfuzztest/kfuzztest_tests.h | 39 --------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 lib/kfuzztest/kfuzztest_tests.h diff --git a/lib/kfuzztest/kfuzztest_tests.h b/lib/kfuzztest/kfuzztest_tests.h deleted file mode 100644 index f7f53312ddf358..00000000000000 --- a/lib/kfuzztest/kfuzztest_tests.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef KFUZZTEST_TESTS_H -#define KFUZZTEST_TESTS_H - -#include -#include - -struct kftf_simple_arg { - char first; - char second; - char third; -}; - -// contains a bug! -static void kftf_fuzzable(char first, char second, char third) -{ - // can buffer overflow or underflow, or cause a null pointer dereference - // crashing the kernel - if (first == 'a') { - pr_info("first was a"); - if (second == 'b') { - pr_info("second was b"); - if (third == 'c') { - pr_info("third was c"); - volatile char *ptr = (void *)0xBEEF; - pr_info("reading %p: 0x%x", ptr, *(uint *)ptr); - } - } - } -} - -FUZZ_TEST(kftf_fuzzable, struct kftf_simple_arg) -{ - KFUZZTEST_EXPECT_NOT_NULL(kftf_simple_arg, first); - KFUZZTEST_EXPECT_IN_RANGE(kftf_simple_arg, second, 'a', 'z'); - KFUZZTEST_EXPECT_IN_RANGE(kftf_simple_arg, third, 'a', 'z'); - kftf_fuzzable(arg->first, arg->second, arg->third); -} - -#endif /* KFUZZTEST_TESTS_H */ From aeeb29fc73c204b808d3b59a42714dcf024e03fa Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 25 Jul 2025 09:49:46 +0000 Subject: [PATCH 30/73] kfuzztest: update header comment in kfuzztest_main.c --- lib/kfuzztest/kfuzztest_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/kfuzztest/kfuzztest_main.c b/lib/kfuzztest/kfuzztest_main.c index c038892c826fb4..f0adc62c9882ad 100644 --- a/lib/kfuzztest/kfuzztest_main.c +++ b/lib/kfuzztest/kfuzztest_main.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Kernel Fuzz Testing Framework (KFTF) - Core Module + * Kernel Fuzz Testing Framework (KFuzzTest) - Core Module * * This module is responsible for discovering and initializing all fuzz test * cases defined using the FUZZ_TEST() macro. It creates a debugfs interface From 57fddceb119321a5a08b9243f6a9cd6d17306341 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 25 Jul 2025 10:03:23 +0000 Subject: [PATCH 31/73] kfuzztest: update macros to fix naming --- include/linux/kfuzztest.h | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index afa9ef91ab9642..d3de9fa3d93238 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -34,7 +34,7 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, * * @test_name: Name of the fuzz target, which is used to create the associated * debufs entries. - * @func_arg_type: the input type of fuzz target. This should always be a + * @test_arg_type: the input type of fuzz target. This should always be a * struct type even when fuzzing with a single input parameter in order * to take advantage of the domain constraint and annotation systems. See * usage example below. @@ -53,17 +53,17 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, * * // Assume that we are fuzzing some function func(T1 param1, ... TN paramN). * // Define input type of the fuzz target. This should be always be a struct. - * struct func_arg_type { + * struct test_arg_type { * T1 arg1; * ... * TN argn; * }; * * // Define the test case. - * FUZZ_TEST(test_func, struct func_arg_type) + * FUZZ_TEST(test_func, struct test_arg_type) * { * int ret; - * // arg is provided by the macro, and is of type struct func_arg_type. + * // arg is provided by the macro, and is of type struct test_arg_type. * ret = func(arg.arg1, ..., arg.argn); * // Validate the return value if testing for correctness. * if (ret != expected_value) { @@ -71,21 +71,21 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, * } * } */ -#define FUZZ_TEST(test_name, func_arg_type) \ - static ssize_t _write_callback_##func(struct file *filp, \ - const char __user *buf, \ - size_t len, loff_t *off); \ - static void _fuzz_test_logic_##func(func_arg_type *arg); \ - const struct kfuzztest_target __fuzz_test__##func __attribute__(( \ +#define FUZZ_TEST(test_name, test_arg_type) \ + static ssize_t _write_callback_##test_name(struct file *filp, \ + const char __user *buf, \ + size_t len, loff_t *off); \ + static void _fuzz_test_logic_##test_name(test_arg_type *arg); \ + const struct kfuzztest_target __fuzz_test__##test_name __attribute__(( \ __section__(".kfuzztest_target"), __used__)) = { \ .name = #test_name, \ - .arg_type_name = #func_arg_type, \ - .write_input_cb = _write_callback_##func, \ + .arg_type_name = #test_arg_type, \ + .write_input_cb = _write_callback_##test_name, \ }; \ /* Invoked when data is written into the target's input file. */ \ - static ssize_t _write_callback_##func(struct file *filp, \ - const char __user *buf, \ - size_t len, loff_t *off) \ + static ssize_t _write_callback_##test_name(struct file *filp, \ + const char __user *buf, \ + size_t len, loff_t *off) \ { \ int err; \ void *buffer = kmalloc(len, GFP_KERNEL); \ @@ -101,14 +101,14 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, kfree(buffer); \ return -1; \ } \ - func_arg_type *arg = payload; \ + test_arg_type *arg = payload; \ /* Call the user's logic on the provided written input. */ \ - _fuzz_test_logic_##func(arg); \ + _fuzz_test_logic_##test_name(arg); \ kfree(buffer); \ kfree(payload); \ return len; \ } \ - static void _fuzz_test_logic_##func(func_arg_type *arg) + static void _fuzz_test_logic_##test_name(test_arg_type *arg) /** * Reports a bug with a predictable prefix so that it can be parsed by a From 4ebed5f60d971772062a2f5ebdc8bb98df2ec694 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 25 Jul 2025 10:53:15 +0000 Subject: [PATCH 32/73] kfuzztest: align targets to 32-byte boundary Iterating through a section where structures aren't aligned to a power-of-2 boundary leads to incorrect memory accesses. --- include/linux/kfuzztest.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index d3de9fa3d93238..cae69cd9eb9c5c 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -14,7 +14,9 @@ struct kfuzztest_target { const char *arg_type_name; ssize_t (*write_input_cb)(struct file *filp, const char __user *buf, size_t len, loff_t *off); -}; +} __attribute__((aligned(32))); +static_assert(sizeof(struct kfuzztest_target) == 32, + "struct kfuzztest_target should have size 32"); __attribute__((unused)) static int write_input_cb_common(struct file *filp, const char __user *buf, size_t len, From fd61eeb89e6ec176a1ea8271e9288b56339b4b84 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 1 Aug 2025 13:46:37 +0000 Subject: [PATCH 33/73] linux/kfuzztest: update serialization format for KFuzzTest inputs --- include/linux/kfuzztest.h | 190 ++++++++++++++++++++++++-------------- 1 file changed, 123 insertions(+), 67 deletions(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index cae69cd9eb9c5c..d7214280fa89f5 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -7,7 +7,8 @@ MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ethan Graham "); MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFuzzTest)"); -static void *kfuzztest_parse_input(void *input, size_t input_size); +static void *kfuzztest_parse_input(void *input, size_t input_size, + size_t *num_regions); struct kfuzztest_target { const char *name; @@ -90,6 +91,8 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, size_t len, loff_t *off) \ { \ int err; \ + size_t i; \ + void *region, *regions; \ void *buffer = kmalloc(len, GFP_KERNEL); \ if (!buffer || IS_ERR(buffer)) \ return PTR_ERR(buffer); \ @@ -98,16 +101,27 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, kfree(buffer); \ return err; \ } \ - void *payload = kfuzztest_parse_input(buffer, len); \ - if (!payload) { \ + size_t num_regions; \ + regions = kfuzztest_parse_input(buffer, len, &num_regions); \ + if (!regions) { \ kfree(buffer); \ return -1; \ + } else if (IS_ERR(regions)) { \ + kfree(buffer); \ + return PTR_ERR(regions); \ } \ - test_arg_type *arg = payload; \ + pr_info("KFuzzTest: regions = 0x%px\n", regions); \ + /* The input argument is the first region. */ \ + test_arg_type *arg = ((void **)regions)[0]; \ /* Call the user's logic on the provided written input. */ \ _fuzz_test_logic_##test_name(arg); \ kfree(buffer); \ - kfree(payload); \ + for (i = 0; i < num_regions; i++) { \ + region = ((void **)regions)[i]; \ + if (region) \ + kfree(region); \ + } \ + kfree(regions); \ return len; \ } \ static void _fuzz_test_logic_##test_name(test_arg_type *arg) @@ -268,19 +282,20 @@ struct kfuzztest_annotation { __KFUZZTEST_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) struct reloc_entry { - uintptr_t pointer; /* Offset from the beginning of the payload. */ - uintptr_t value; /* Offset between the pointer and the pointed-to address */ + uint64_t region_id; /* Region that pointer belongs to. */ + uint64_t region_offset; /* Offset from the beginning of the region. */ + uint64_t value; /* Pointee tegion identifier, or (void*)-1 if NULL */ + uint64_t padding; }; /* * How many integers of padding in the relocation table between the header * information and the relocation entries */ -#define RELOC_TABLE_PADDING 2 +#define RELOC_TABLE_PADDING 7 struct reloc_table { int num_entries; - uint32_t max_alignment; int padding[RELOC_TABLE_PADDING]; struct reloc_entry entries[]; }; @@ -288,6 +303,28 @@ static_assert(offsetof(struct reloc_table, entries) % sizeof(struct reloc_entry) == 0); +struct reloc_region { + uint64_t id; + uint64_t start; /* Offset from the start of the payload */ + uint64_t size; + uint64_t alignment; +}; + +/** + * How many `uint64_t`s of padding are required. + */ +#define RELOC_REGION_PADDING 3 + +struct reloc_region_array { + uint64_t num_regions; + uint64_t padding[RELOC_REGION_PADDING]; + struct reloc_region regions[]; +}; + +static_assert(offsetof(struct reloc_region_array, regions) % + sizeof(struct reloc_region) == + 0); + /** * The relocation table format encodes pointer values as a relative offset from * the location of the pointer. A relative offset of zero could indicate that @@ -297,94 +334,113 @@ static_assert(offsetof(struct reloc_table, entries) % */ static const uintptr_t nullPtr = (uintptr_t)-1; -__attribute__((unused)) static void *kfuzztest_parse_input(void *input, - size_t input_size) +__attribute__((unused)) static void * +kfuzztest_parse_input(void *input, size_t input_size, size_t *num_regions) { size_t i; - void *payload_start, *out; + void *payload_start; uintptr_t *ptr_location; - size_t payload_len, alloc_size, entries_size, header_size; + size_t payload_len, reloc_entries_size, reloc_table_size, + reloc_regions_size, region_array_size; struct reloc_table *rt; struct reloc_entry re; - if (input_size > KMALLOC_MAX_SIZE) - return NULL; - - if (input_size < sizeof(struct reloc_table)) { - pr_warn("got misformed input in %s\n", __FUNCTION__); + struct reloc_region_array *region_array; + struct reloc_region reg; + void **allocated_regions = NULL; + + pr_info("KFuzzTest: input [ 0x%px, 0x%px ]", input, + (char *)input + input_size); + if (input_size < + sizeof(struct reloc_table) + sizeof(struct reloc_region_array)) { + pr_warn("KFuzzTest: input was not well-formed"); return NULL; } rt = input; if (check_mul_overflow(rt->num_entries, sizeof(struct reloc_entry), - &entries_size)) + &reloc_entries_size)) return NULL; - header_size = offsetof(struct reloc_table, entries) + entries_size; - if (header_size > input_size) + reloc_table_size = + offsetof(struct reloc_table, entries) + reloc_entries_size; + pr_info("reloc_table_size: 0x%lx\n", reloc_table_size); + if (reloc_table_size > input_size) return NULL; + pr_info("num reloc entries: %d\n", rt->num_entries); - payload_start = (char *)input + header_size; - if (payload_start >= input + input_size) + region_array = + (struct reloc_region_array *)((char *)input + reloc_table_size); + if (check_mul_overflow(region_array->num_regions, + sizeof(struct reloc_region), + &reloc_regions_size)) return NULL; - if (!is_power_of_2(rt->max_alignment) || rt->max_alignment > PAGE_SIZE) + region_array_size = offsetof(struct reloc_region_array, regions) + + reloc_regions_size; + pr_info("region_array_size: 0x%lx", region_array_size); + pr_info("num regions = %llu", region_array->num_regions); + if (reloc_table_size + region_array_size > input_size) + return NULL; + + allocated_regions = + kzalloc(region_array->num_regions * sizeof(void *), GFP_KERNEL); + if (!allocated_regions) + return NULL; + + payload_start = (char *)region_array + region_array_size; + if (payload_start >= input + input_size) return NULL; payload_len = input_size - (payload_start - input); - /* - * Check input for out-of-bounds pointers before before allocating - * aligned output buffer. - */ - for (i = 0; i < rt->num_entries; i++) { - re = rt->entries[i]; - if (re.pointer > payload_len || - re.pointer + sizeof(uintptr_t) > payload_len) - return NULL; - - if (re.value == nullPtr) - continue; - - /* We don't know the size of the data that is being pointed and - * therefor cannot make any stricter assertions. For example, - * if we enforce: - * re.pointer + re.value + sizeof(uintptr_t) < payload_len - * then we cannot write values with bytesize < 8. - */ - if (re.pointer + re.value >= payload_len) - return NULL; - } + pr_info("KFuzzTest: allocating regions"); + /* Allocate regions, and copy data in. */ + for (i = 0; i < region_array->num_regions; i++) { + reg = region_array->regions[i]; - /* - * 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; + /* kzalloc guarantees 8-byte alignment, which is enough. */ + allocated_regions[i] = kzalloc(reg.size, GFP_KERNEL); + if (!allocated_regions[i]) + goto fail; + + pr_info("copying from %px to %px with size 0x%llx", + (char *)payload_start + reg.start, allocated_regions[i], + reg.size); - memcpy(out, payload_start, payload_len); + memcpy(allocated_regions[i], (char *)payload_start + reg.start, + reg.size); + + pr_info("KFuzzTest: allocated region_%llu of size %llu\n", + reg.id, reg.size); + } - /* - * Iterate through entries in the relocation table and patch the - * pointers. - */ + /* Patch pointers. */ for (i = 0; i < rt->num_entries; i++) { re = rt->entries[i]; - ptr_location = (uintptr_t *)(out + re.pointer); + ptr_location = + (uintptr_t *)((char *)allocated_regions[re.region_id] + + re.region_offset); if (re.value == nullPtr) { *ptr_location = (uintptr_t)NULL; } else { - *ptr_location = (uintptr_t)ptr_location + re.value; + *ptr_location = (uintptr_t)allocated_regions[re.value]; + pr_info("KFuzzTest: pointer at offset %llu in region %llu pointer to region %llu (0x%px)", + re.region_offset, re.region_id, re.value, + (void *)*ptr_location); } } - return out; + if (num_regions) + *num_regions = region_array->num_regions; + return allocated_regions; + +fail: + if (!allocated_regions) + return NULL; + for (i = 0; i < region_array->num_regions; i++) { + if (allocated_regions[i]) + kfree(allocated_regions[i]); + } + return NULL; } #endif /* KFUZZTEST_H */ From c14dbe8c4e3e69cf362c9f924866f973d4f4b4da Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Mon, 4 Aug 2025 15:36:49 +0000 Subject: [PATCH 34/73] kfuzztest: reduce sizes of fields to 32-bit to reduce wastage --- include/linux/kfuzztest.h | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index d7214280fa89f5..df53ec61924fb5 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -282,21 +282,21 @@ struct kfuzztest_annotation { __KFUZZTEST_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) struct reloc_entry { - uint64_t region_id; /* Region that pointer belongs to. */ - uint64_t region_offset; /* Offset from the beginning of the region. */ - uint64_t value; /* Pointee tegion identifier, or (void*)-1 if NULL */ - uint64_t padding; + uint32_t region_id; /* Region that pointer belongs to. */ + uint32_t region_offset; /* Offset from the beginning of the region. */ + uint32_t value; /* Pointee tegion identifier, or (void*)-1 if NULL */ + uint32_t padding; }; /* * How many integers of padding in the relocation table between the header * information and the relocation entries */ -#define RELOC_TABLE_PADDING 7 +#define RELOC_TABLE_PADDING 3 struct reloc_table { - int num_entries; - int padding[RELOC_TABLE_PADDING]; + uint32_t num_entries; + uint32_t padding[RELOC_TABLE_PADDING]; struct reloc_entry entries[]; }; static_assert(offsetof(struct reloc_table, entries) % @@ -304,20 +304,23 @@ static_assert(offsetof(struct reloc_table, entries) % 0); struct reloc_region { - uint64_t id; - uint64_t start; /* Offset from the start of the payload */ - uint64_t size; - uint64_t alignment; + uint32_t id; + uint32_t start; /* Offset from the start of the payload */ + uint32_t size; + uint32_t alignment; }; +enum reloc_mode : uint32_t { DISTINCT = 0, POISONED }; + /** * How many `uint64_t`s of padding are required. */ -#define RELOC_REGION_PADDING 3 +#define RELOC_REGION_PADDING 2 struct reloc_region_array { - uint64_t num_regions; - uint64_t padding[RELOC_REGION_PADDING]; + uint32_t num_regions; + enum reloc_mode mode; + uint32_t padding[RELOC_REGION_PADDING]; struct reloc_region regions[]; }; From e763e701b17a81af44cbf3b3c1c427b17cfe0e87 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Mon, 4 Aug 2025 18:01:57 +0000 Subject: [PATCH 35/73] kfuzztest: refactor code, introduce distinct and poisoned modes. WIP. Lots of validation to introduce, and more refactoring so that data doesn't leak. --- include/linux/kfuzztest.h | 358 +++++++++++++++++++++++++------------- 1 file changed, 234 insertions(+), 124 deletions(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index df53ec61924fb5..030b24835733e0 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -2,13 +2,77 @@ #define KFUZZTEST_H #include +#include MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ethan Graham "); MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFuzzTest)"); -static void *kfuzztest_parse_input(void *input, size_t input_size, - size_t *num_regions); +struct reloc_region { + uint32_t start; /* Offset from the start of the payload */ + uint32_t size; + uint32_t alignment; + uint32_t padding; +}; + +enum reloc_mode : uint32_t { DISTINCT = 0, POISONED }; + +struct reloc_region_array { + uint32_t num_regions; + enum reloc_mode mode; + uint32_t padding[2]; + struct reloc_region regions[]; +}; + +struct reloc_entry { + uint32_t region_id; /* Region that pointer belongs to. */ + uint32_t region_offset; /* Offset from the beginning of the region. */ + uint32_t value; /* Pointee tegion identifier, or (void*)-1 if NULL */ + uint32_t padding; +}; + +struct reloc_table { + uint32_t num_entries; + uint32_t padding[3]; + struct reloc_entry entries[]; +}; +static_assert(offsetof(struct reloc_table, entries) % + sizeof(struct reloc_entry) == + 0); + +static_assert(offsetof(struct reloc_region_array, regions) % + sizeof(struct reloc_region) == + 0); + +typedef void *reloc_handle_t; + +__attribute__((unused)) static int __kfuzztest_parse_input( + void *input, size_t input_size, struct reloc_region_array **ret_regions, + struct reloc_table **ret_reloc_table, void **ret_payload); + +__attribute__((unused)) static reloc_handle_t +__kfuzztest_relocate(struct reloc_region_array *regions, struct reloc_table *rt, + void *payload, void **data_ret); + +static reloc_handle_t +__kfuzztest_relocate_poisoned(struct reloc_region_array *regions, + struct reloc_table *rt, void *payload, + void **data_ret); + +static reloc_handle_t +__kfuzztest_relocate_distinct(struct reloc_region_array *regions, + struct reloc_table *rt, void *payload, + void **data_ret); + +__attribute__((unused)) static void +__kfuzztest_release_relocated(struct reloc_region_array *regions, + reloc_handle_t handle); + +static void __kfuzztest_release_relocated_poisoned(reloc_handle_t handle); + +static void +__kfuzztest_release_relocated_distinct(struct reloc_region_array *regions, + reloc_handle_t handle); struct kfuzztest_target { const char *name; @@ -91,37 +155,37 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, size_t len, loff_t *off) \ { \ int err; \ - size_t i; \ - void *region, *regions; \ + struct reloc_region_array *regions; \ + struct reloc_table *rt; \ + void *payload, *relocated; \ + test_arg_type *arg; \ + \ void *buffer = kmalloc(len, GFP_KERNEL); \ - if (!buffer || IS_ERR(buffer)) \ + if (!buffer) \ + return -ENOMEM; \ + else if (IS_ERR(buffer)) \ return PTR_ERR(buffer); \ err = write_input_cb_common(filp, buf, len, off, buffer, len); \ if (err != 0) { \ kfree(buffer); \ return err; \ } \ - size_t num_regions; \ - regions = kfuzztest_parse_input(buffer, len, &num_regions); \ - if (!regions) { \ + err = __kfuzztest_parse_input(buffer, len, ®ions, &rt, \ + &payload); \ + if (err) { \ kfree(buffer); \ - return -1; \ - } else if (IS_ERR(regions)) { \ + return err; \ + } \ + relocated = __kfuzztest_relocate(regions, rt, payload, \ + (void *)&arg); \ + if (!relocated) { \ kfree(buffer); \ - return PTR_ERR(regions); \ + return -EINVAL; \ } \ - pr_info("KFuzzTest: regions = 0x%px\n", regions); \ - /* The input argument is the first region. */ \ - test_arg_type *arg = ((void **)regions)[0]; \ /* Call the user's logic on the provided written input. */ \ _fuzz_test_logic_##test_name(arg); \ - kfree(buffer); \ - for (i = 0; i < num_regions; i++) { \ - region = ((void **)regions)[i]; \ - if (region) \ - kfree(region); \ - } \ - kfree(regions); \ + /* XXX: buffer leaks in distinct mode. */ \ + __kfuzztest_release_relocated(regions, relocated); \ return len; \ } \ static void _fuzz_test_logic_##test_name(test_arg_type *arg) @@ -281,142 +345,155 @@ struct kfuzztest_annotation { #define KFUZZTEST_ANNOTATE_LEN(arg_type, field, linked_field) \ __KFUZZTEST_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) -struct reloc_entry { - uint32_t region_id; /* Region that pointer belongs to. */ - uint32_t region_offset; /* Offset from the beginning of the region. */ - uint32_t value; /* Pointee tegion identifier, or (void*)-1 if NULL */ - uint32_t padding; -}; - -/* - * How many integers of padding in the relocation table between the header - * information and the relocation entries +/** + * The relocation table format encodes pointer values as a relative offset from + * the location of the pointer. A relative offset of zero could indicate that + * the pointer points to its own address, which is valid. We encode a null + * pointer as 0xFF...FF as adding this value to any address would result in an + * overflow anyways, and is therefore invalid in any other circumstance. */ -#define RELOC_TABLE_PADDING 3 +static const uintptr_t nullPtr = (uint32_t)-1; -struct reloc_table { - uint32_t num_entries; - uint32_t padding[RELOC_TABLE_PADDING]; - struct reloc_entry entries[]; -}; -static_assert(offsetof(struct reloc_table, entries) % - sizeof(struct reloc_entry) == - 0); +/* Performs some input validation before relocating data. */ +__attribute__((unused)) static int __kfuzztest_parse_input( + void *input, size_t input_size, struct reloc_region_array **ret_regions, + struct reloc_table **ret_reloc_table, void **ret_payload) +{ + void *input_end, *payload_start; + size_t reloc_entries_size, regions_size; + struct reloc_table *rt; + struct reloc_region_array *regions; -struct reloc_region { - uint32_t id; - uint32_t start; /* Offset from the start of the payload */ - uint32_t size; - uint32_t alignment; -}; + if (input_size < + sizeof(struct reloc_region_array) + sizeof(struct reloc_table)) + return -EINVAL; -enum reloc_mode : uint32_t { DISTINCT = 0, POISONED }; + input_end = (char *)input + input_size; -/** - * How many `uint64_t`s of padding are required. - */ -#define RELOC_REGION_PADDING 2 + regions = input; + regions_size = sizeof(struct reloc_region_array) + + regions->num_regions * sizeof(struct reloc_region); -struct reloc_region_array { - uint32_t num_regions; - enum reloc_mode mode; - uint32_t padding[RELOC_REGION_PADDING]; - struct reloc_region regions[]; -}; + rt = (struct reloc_table *)((char *)regions + regions_size); + if ((char *)rt > (char *)input_end) + return -EINVAL; -static_assert(offsetof(struct reloc_region_array, regions) % - sizeof(struct reloc_region) == - 0); + reloc_entries_size = sizeof(struct reloc_table) + + rt->num_entries * sizeof(struct reloc_entry); + if ((char *)rt + reloc_entries_size > (char *)input_end) + return -EINVAL; + + payload_start = (char *)(rt->entries + rt->num_entries); + if ((char *)payload_start > (char *)input_end) + return -EINVAL; + + if (ret_regions) + *ret_regions = regions; + if (ret_reloc_table) + *ret_reloc_table = rt; + if (ret_payload) + *ret_payload = payload_start; + return 0; +} /** - * The relocation table format encodes pointer values as a relative offset from - * the location of the pointer. A relative offset of zero could indicate that - * the pointer points to its own address, which is valid. We encode a null - * pointer as 0xFF...FF as adding this value to any address would result in an - * overflow anyways, and is therefore invalid in any other circumstance. + * Relocates payload as per `regions` and `rt`. Assumes that the input is + * already sanitized. */ -static const uintptr_t nullPtr = (uintptr_t)-1; +__attribute__((unused)) static reloc_handle_t +__kfuzztest_relocate(struct reloc_region_array *regions, struct reloc_table *rt, + void *payload, void **data_ret) +{ + switch (regions->mode) { + case DISTINCT: + return __kfuzztest_relocate_distinct(regions, rt, payload, + data_ret); + case POISONED: + return __kfuzztest_relocate_poisoned(regions, rt, payload, + data_ret); + default: + return ERR_PTR(-EINVAL); + } +} -__attribute__((unused)) static void * -kfuzztest_parse_input(void *input, size_t input_size, size_t *num_regions) +static reloc_handle_t +__kfuzztest_relocate_poisoned(struct reloc_region_array *regions, + struct reloc_table *rt, void *payload, + void **data_ret) { size_t i; - void *payload_start; + struct reloc_region reg, src, dst; uintptr_t *ptr_location; - size_t payload_len, reloc_entries_size, reloc_table_size, - reloc_regions_size, region_array_size; - struct reloc_table *rt; struct reloc_entry re; - struct reloc_region_array *region_array; - struct reloc_region reg; - void **allocated_regions = NULL; + void *ptr; - pr_info("KFuzzTest: input [ 0x%px, 0x%px ]", input, - (char *)input + input_size); - if (input_size < - sizeof(struct reloc_table) + sizeof(struct reloc_region_array)) { - pr_warn("KFuzzTest: input was not well-formed"); - return NULL; + /* Poison the padding between regions */ + for (i = 0; i < regions->num_regions; i++) { + reg = regions->regions[i]; + ptr = payload + reg.start + reg.size; + // TODO: poison this range. Figure out import for kasan.h. } - rt = input; - if (check_mul_overflow(rt->num_entries, sizeof(struct reloc_entry), - &reloc_entries_size)) - return NULL; - reloc_table_size = - offsetof(struct reloc_table, entries) + reloc_entries_size; - pr_info("reloc_table_size: 0x%lx\n", reloc_table_size); - if (reloc_table_size > input_size) - return NULL; - pr_info("num reloc entries: %d\n", rt->num_entries); + // TODO: failure when the region points out of bounds. - region_array = - (struct reloc_region_array *)((char *)input + reloc_table_size); - if (check_mul_overflow(region_array->num_regions, - sizeof(struct reloc_region), - &reloc_regions_size)) - return NULL; + /* Patch pointers */ + for (i = 0; i < rt->num_entries; i++) { + re = rt->entries[i]; + src = regions->regions[re.region_id]; + ptr_location = (uintptr_t *)(char *)payload + src.start + + re.region_offset; - region_array_size = offsetof(struct reloc_region_array, regions) + - reloc_regions_size; - pr_info("region_array_size: 0x%lx", region_array_size); - pr_info("num regions = %llu", region_array->num_regions); - if (reloc_table_size + region_array_size > input_size) - return NULL; + if (re.value == nullPtr) { + *ptr_location = (uintptr_t)NULL; + } else { + if (re.value >= regions->num_regions) + goto fail; + dst = regions->regions[re.value]; + *ptr_location = (uintptr_t)(char *)payload + dst.start; + } + } + + return regions; +fail: + __kfuzztest_release_relocated_poisoned(regions); + return NULL; +} + +static reloc_handle_t +__kfuzztest_relocate_distinct(struct reloc_region_array *regions, + struct reloc_table *rt, void *payload, + void **data_ret) +{ + void **allocated_regions; + size_t i; + struct reloc_region reg; + struct reloc_entry re; + uintptr_t *ptr_location; allocated_regions = - kzalloc(region_array->num_regions * sizeof(void *), GFP_KERNEL); + kzalloc(regions->num_regions * sizeof(void *), GFP_KERNEL); if (!allocated_regions) return NULL; - payload_start = (char *)region_array + region_array_size; - if (payload_start >= input + input_size) - return NULL; - - payload_len = input_size - (payload_start - input); - - pr_info("KFuzzTest: allocating regions"); - /* Allocate regions, and copy data in. */ - for (i = 0; i < region_array->num_regions; i++) { - reg = region_array->regions[i]; + for (i = 0; i < regions->num_regions; i++) { + reg = regions->regions[i]; /* kzalloc guarantees 8-byte alignment, which is enough. */ allocated_regions[i] = kzalloc(reg.size, GFP_KERNEL); if (!allocated_regions[i]) goto fail; - pr_info("copying from %px to %px with size 0x%llx", - (char *)payload_start + reg.start, allocated_regions[i], + pr_info("copying from %px to %px with size 0x%x", + (char *)payload + reg.start, allocated_regions[i], reg.size); - memcpy(allocated_regions[i], (char *)payload_start + reg.start, + memcpy(allocated_regions[i], (char *)payload + reg.start, reg.size); - pr_info("KFuzzTest: allocated region_%llu of size %llu\n", - reg.id, reg.size); + pr_info("KFuzzTest: allocated region_%zu of size %u\n", i, + reg.size); } - /* Patch pointers. */ for (i = 0; i < rt->num_entries; i++) { re = rt->entries[i]; ptr_location = @@ -426,24 +503,57 @@ kfuzztest_parse_input(void *input, size_t input_size, size_t *num_regions) *ptr_location = (uintptr_t)NULL; } else { *ptr_location = (uintptr_t)allocated_regions[re.value]; - pr_info("KFuzzTest: pointer at offset %llu in region %llu pointer to region %llu (0x%px)", + pr_info("KFuzzTest: pointer at offset %u in region %u pointer to region %u (0x%px)", re.region_offset, re.region_id, re.value, (void *)*ptr_location); } } - if (num_regions) - *num_regions = region_array->num_regions; + if (data_ret) + *data_ret = allocated_regions[0]; return allocated_regions; fail: if (!allocated_regions) return NULL; - for (i = 0; i < region_array->num_regions; i++) { + for (i = 0; i < regions->num_regions; i++) { if (allocated_regions[i]) kfree(allocated_regions[i]); } return NULL; } +__attribute__((unused)) static void +__kfuzztest_release_relocated(struct reloc_region_array *regions, + reloc_handle_t handle) +{ + switch (regions->mode) { + case DISTINCT: + __kfuzztest_release_relocated_distinct(regions, handle); + break; + case POISONED: + __kfuzztest_release_relocated_poisoned(handle); + break; + default: + pr_warn("KFuzzTest: invalid release operation"); + } +} + +static void __kfuzztest_release_relocated_poisoned(reloc_handle_t handle) +{ + kfree(handle); +} + +static void +__kfuzztest_release_relocated_distinct(struct reloc_region_array *regions, + reloc_handle_t handle) +{ + size_t i; + void **allocations = (void **)handle; + for (i = 0; i < regions->num_regions; i++) + if (allocations[i]) + kfree(allocations[i]); + kfree((void *)handle); +} + #endif /* KFUZZTEST_H */ From 76c3b85f08a3572604a0d65a0ec1e77c3faa9e5e Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Tue, 5 Aug 2025 06:41:19 +0000 Subject: [PATCH 36/73] kfuzztest: extract relocations logic into separate .c file. --- include/linux/kfuzztest.h | 187 +----------------------------------- lib/kfuzztest/Makefile | 2 +- lib/kfuzztest/relocations.c | 157 ++++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+), 183 deletions(-) create mode 100644 lib/kfuzztest/relocations.c diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index 030b24835733e0..97ca5471d57ee1 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -50,29 +50,12 @@ __attribute__((unused)) static int __kfuzztest_parse_input( void *input, size_t input_size, struct reloc_region_array **ret_regions, struct reloc_table **ret_reloc_table, void **ret_payload); -__attribute__((unused)) static reloc_handle_t -__kfuzztest_relocate(struct reloc_region_array *regions, struct reloc_table *rt, - void *payload, void **data_ret); +reloc_handle_t __kfuzztest_relocate(struct reloc_region_array *regions, + struct reloc_table *rt, void *payload, + void **data_ret); -static reloc_handle_t -__kfuzztest_relocate_poisoned(struct reloc_region_array *regions, - struct reloc_table *rt, void *payload, - void **data_ret); - -static reloc_handle_t -__kfuzztest_relocate_distinct(struct reloc_region_array *regions, - struct reloc_table *rt, void *payload, - void **data_ret); - -__attribute__((unused)) static void -__kfuzztest_release_relocated(struct reloc_region_array *regions, - reloc_handle_t handle); - -static void __kfuzztest_release_relocated_poisoned(reloc_handle_t handle); - -static void -__kfuzztest_release_relocated_distinct(struct reloc_region_array *regions, - reloc_handle_t handle); +void __kfuzztest_release_relocated(struct reloc_region_array *regions, + reloc_handle_t handle); struct kfuzztest_target { const char *name; @@ -396,164 +379,4 @@ __attribute__((unused)) static int __kfuzztest_parse_input( return 0; } -/** - * Relocates payload as per `regions` and `rt`. Assumes that the input is - * already sanitized. - */ -__attribute__((unused)) static reloc_handle_t -__kfuzztest_relocate(struct reloc_region_array *regions, struct reloc_table *rt, - void *payload, void **data_ret) -{ - switch (regions->mode) { - case DISTINCT: - return __kfuzztest_relocate_distinct(regions, rt, payload, - data_ret); - case POISONED: - return __kfuzztest_relocate_poisoned(regions, rt, payload, - data_ret); - default: - return ERR_PTR(-EINVAL); - } -} - -static reloc_handle_t -__kfuzztest_relocate_poisoned(struct reloc_region_array *regions, - struct reloc_table *rt, void *payload, - void **data_ret) -{ - size_t i; - struct reloc_region reg, src, dst; - uintptr_t *ptr_location; - struct reloc_entry re; - void *ptr; - - /* Poison the padding between regions */ - for (i = 0; i < regions->num_regions; i++) { - reg = regions->regions[i]; - ptr = payload + reg.start + reg.size; - // TODO: poison this range. Figure out import for kasan.h. - } - - // TODO: failure when the region points out of bounds. - - /* Patch pointers */ - for (i = 0; i < rt->num_entries; i++) { - re = rt->entries[i]; - src = regions->regions[re.region_id]; - ptr_location = (uintptr_t *)(char *)payload + src.start + - re.region_offset; - - if (re.value == nullPtr) { - *ptr_location = (uintptr_t)NULL; - } else { - if (re.value >= regions->num_regions) - goto fail; - dst = regions->regions[re.value]; - *ptr_location = (uintptr_t)(char *)payload + dst.start; - } - } - - return regions; -fail: - __kfuzztest_release_relocated_poisoned(regions); - return NULL; -} - -static reloc_handle_t -__kfuzztest_relocate_distinct(struct reloc_region_array *regions, - struct reloc_table *rt, void *payload, - void **data_ret) -{ - void **allocated_regions; - size_t i; - struct reloc_region reg; - struct reloc_entry re; - uintptr_t *ptr_location; - - allocated_regions = - kzalloc(regions->num_regions * sizeof(void *), GFP_KERNEL); - if (!allocated_regions) - return NULL; - - for (i = 0; i < regions->num_regions; i++) { - reg = regions->regions[i]; - - /* kzalloc guarantees 8-byte alignment, which is enough. */ - allocated_regions[i] = kzalloc(reg.size, GFP_KERNEL); - if (!allocated_regions[i]) - goto fail; - - pr_info("copying from %px to %px with size 0x%x", - (char *)payload + reg.start, allocated_regions[i], - reg.size); - - memcpy(allocated_regions[i], (char *)payload + reg.start, - reg.size); - - pr_info("KFuzzTest: allocated region_%zu of size %u\n", i, - reg.size); - } - - for (i = 0; i < rt->num_entries; i++) { - re = rt->entries[i]; - ptr_location = - (uintptr_t *)((char *)allocated_regions[re.region_id] + - re.region_offset); - if (re.value == nullPtr) { - *ptr_location = (uintptr_t)NULL; - } else { - *ptr_location = (uintptr_t)allocated_regions[re.value]; - pr_info("KFuzzTest: pointer at offset %u in region %u pointer to region %u (0x%px)", - re.region_offset, re.region_id, re.value, - (void *)*ptr_location); - } - } - - if (data_ret) - *data_ret = allocated_regions[0]; - return allocated_regions; - -fail: - if (!allocated_regions) - return NULL; - for (i = 0; i < regions->num_regions; i++) { - if (allocated_regions[i]) - kfree(allocated_regions[i]); - } - return NULL; -} - -__attribute__((unused)) static void -__kfuzztest_release_relocated(struct reloc_region_array *regions, - reloc_handle_t handle) -{ - switch (regions->mode) { - case DISTINCT: - __kfuzztest_release_relocated_distinct(regions, handle); - break; - case POISONED: - __kfuzztest_release_relocated_poisoned(handle); - break; - default: - pr_warn("KFuzzTest: invalid release operation"); - } -} - -static void __kfuzztest_release_relocated_poisoned(reloc_handle_t handle) -{ - kfree(handle); -} - -static void -__kfuzztest_release_relocated_distinct(struct reloc_region_array *regions, - reloc_handle_t handle) -{ - size_t i; - void **allocations = (void **)handle; - for (i = 0; i < regions->num_regions; i++) - if (allocations[i]) - kfree(allocations[i]); - kfree((void *)handle); -} - #endif /* KFUZZTEST_H */ diff --git a/lib/kfuzztest/Makefile b/lib/kfuzztest/Makefile index ddaab873324c2a..462f229fdabdbd 100644 --- a/lib/kfuzztest/Makefile +++ b/lib/kfuzztest/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_KFUZZTEST) += kfuzztest.o -kfuzztest-objs := kfuzztest_main.o +kfuzztest-objs := kfuzztest_main.o relocations.o diff --git a/lib/kfuzztest/relocations.c b/lib/kfuzztest/relocations.c new file mode 100644 index 00000000000000..c2a2de1254b5e1 --- /dev/null +++ b/lib/kfuzztest/relocations.c @@ -0,0 +1,157 @@ +#include +#include + +static void __kfuzztest_release_relocated_poisoned(reloc_handle_t handle) +{ + kfree(handle); +} + +static void +__kfuzztest_release_relocated_distinct(struct reloc_region_array *regions, + reloc_handle_t handle) +{ + size_t i; + void **allocations = (void **)handle; + for (i = 0; i < regions->num_regions; i++) + if (allocations[i]) + kfree(allocations[i]); + kfree((void *)handle); +} + +void __kfuzztest_release_relocated(struct reloc_region_array *regions, + reloc_handle_t handle) +{ + switch (regions->mode) { + case DISTINCT: + __kfuzztest_release_relocated_distinct(regions, handle); + break; + case POISONED: + __kfuzztest_release_relocated_poisoned(handle); + break; + default: + pr_warn("KFuzzTest: invalid release operation"); + } +} + +static reloc_handle_t +__kfuzztest_relocate_poisoned(struct reloc_region_array *regions, + struct reloc_table *rt, void *payload, + void **data_ret) +{ + size_t i; + struct reloc_region reg, src, dst; + uintptr_t *ptr_location; + struct reloc_entry re; + void *ptr; + + /* Poison the padding between regions */ + for (i = 0; i < regions->num_regions; i++) { + reg = regions->regions[i]; + ptr = payload + reg.start + reg.size; + // TODO: poison this range. Figure out import for kasan.h. + } + + // TODO: failure when the region points out of bounds. + + /* Patch pointers */ + for (i = 0; i < rt->num_entries; i++) { + re = rt->entries[i]; + src = regions->regions[re.region_id]; + ptr_location = (uintptr_t *)(char *)payload + src.start + + re.region_offset; + + if (re.value == nullPtr) { + *ptr_location = (uintptr_t)NULL; + } else { + if (re.value >= regions->num_regions) + goto fail; + dst = regions->regions[re.value]; + *ptr_location = (uintptr_t)(char *)payload + dst.start; + } + } + + return regions; +fail: + __kfuzztest_release_relocated_poisoned(regions); + return NULL; +} + +static reloc_handle_t +__kfuzztest_relocate_distinct(struct reloc_region_array *regions, + struct reloc_table *rt, void *payload, + void **data_ret) +{ + void **allocated_regions; + size_t i; + struct reloc_region reg; + struct reloc_entry re; + uintptr_t *ptr_location; + + allocated_regions = + kzalloc(regions->num_regions * sizeof(void *), GFP_KERNEL); + if (!allocated_regions) + return NULL; + + for (i = 0; i < regions->num_regions; i++) { + reg = regions->regions[i]; + + /* kzalloc guarantees 8-byte alignment, which is enough. */ + allocated_regions[i] = kzalloc(reg.size, GFP_KERNEL); + if (!allocated_regions[i]) + goto fail; + + pr_info("copying from %px to %px with size 0x%x", + (char *)payload + reg.start, allocated_regions[i], + reg.size); + + memcpy(allocated_regions[i], (char *)payload + reg.start, + reg.size); + + pr_info("KFuzzTest: allocated region_%zu of size %u\n", i, + reg.size); + } + + for (i = 0; i < rt->num_entries; i++) { + re = rt->entries[i]; + ptr_location = + (uintptr_t *)((char *)allocated_regions[re.region_id] + + re.region_offset); + if (re.value == nullPtr) { + *ptr_location = (uintptr_t)NULL; + } else { + *ptr_location = (uintptr_t)allocated_regions[re.value]; + pr_info("KFuzzTest: pointer at offset %u in region %u pointer to region %u (0x%px)", + re.region_offset, re.region_id, re.value, + (void *)*ptr_location); + } + } + + if (data_ret) + *data_ret = allocated_regions[0]; + return allocated_regions; + +fail: + if (!allocated_regions) + return NULL; + for (i = 0; i < regions->num_regions; i++) { + if (allocated_regions[i]) + kfree(allocated_regions[i]); + } + return NULL; +} + +reloc_handle_t __kfuzztest_relocate(struct reloc_region_array *regions, + struct reloc_table *rt, void *payload, + void **data_ret) +{ + switch (regions->mode) { + case DISTINCT: + return __kfuzztest_relocate_distinct(regions, rt, payload, + data_ret); + case POISONED: + return __kfuzztest_relocate_poisoned(regions, rt, payload, + data_ret); + default: + return ERR_PTR(-EINVAL); + } +} From f0bd54aded2e85c66279b73bc929c62f1e62db00 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Tue, 5 Aug 2025 08:52:01 +0000 Subject: [PATCH 37/73] kfuzztest: refactoring functions into separate .c files, modify kasan Export kasan_poison by moving it to include/linux. Move lots of things around into separate .c files to keep kfuzztest.h barebones. --- include/linux/kasan.h | 2 + include/linux/kfuzztest.h | 116 ++++++++++++++++-------------------- lib/kfuzztest/Makefile | 2 +- lib/kfuzztest/parse.c | 51 ++++++++++++++++ lib/kfuzztest/relocations.c | 91 +++++++++++++++------------- mm/kasan/kasan.h | 9 --- 6 files changed, 152 insertions(+), 119 deletions(-) create mode 100644 lib/kfuzztest/parse.c diff --git a/include/linux/kasan.h b/include/linux/kasan.h index 890011071f2b14..d6dba15bc57555 100644 --- a/include/linux/kasan.h +++ b/include/linux/kasan.h @@ -126,6 +126,8 @@ static __always_inline bool kasan_unpoison_pages(struct page *page, return false; } +void kasan_poison(const void *addr, size_t size, u8 value, bool init); + void __kasan_poison_slab(struct slab *slab); static __always_inline void kasan_poison_slab(struct slab *slab) { diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index 97ca5471d57ee1..dbe60f9533a5d4 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -2,20 +2,19 @@ #define KFUZZTEST_H #include -#include MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ethan Graham "); MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFuzzTest)"); struct reloc_region { - uint32_t start; /* Offset from the start of the payload */ + uint32_t start; /* Offset from the start of the payload. */ uint32_t size; uint32_t alignment; uint32_t padding; }; -enum reloc_mode : uint32_t { DISTINCT = 0, POISONED }; +enum reloc_mode { DISTINCT = 0, POISONED }; struct reloc_region_array { uint32_t num_regions; @@ -24,6 +23,10 @@ struct reloc_region_array { struct reloc_region regions[]; }; +static_assert(offsetof(struct reloc_region_array, regions) % + sizeof(struct reloc_region) == + 0); + struct reloc_entry { uint32_t region_id; /* Region that pointer belongs to. */ uint32_t region_offset; /* Offset from the beginning of the region. */ @@ -40,20 +43,42 @@ static_assert(offsetof(struct reloc_table, entries) % sizeof(struct reloc_entry) == 0); -static_assert(offsetof(struct reloc_region_array, regions) % - sizeof(struct reloc_region) == - 0); - +/** + * Internal handle used for releasing resources, returned to the caller of + * __kfuzztest_relocate. + */ typedef void *reloc_handle_t; -__attribute__((unused)) static int __kfuzztest_parse_input( - void *input, size_t input_size, struct reloc_region_array **ret_regions, - struct reloc_table **ret_reloc_table, void **ret_payload); +/** + * Parses a binary input of size input_size. Input should be a pointer to a + * heap-allocated buffer, and it's ownership is transferred to this function + * on call. + * @input: a heap-allocated buffer (ownership transferred). + * @input_size: the byte-length of input + * @ret_regions: return pointer to the relocation region array + * @ret_reloc_table: return pointer to the relocation table + * @ret_payload_start: return pointer to the start of payload the data + * @ret_payload_end: return pointer to the end of the payload data, i.e., the + * first address that is out of the bounds of the payload. + * + * @return 0 on success, or an error code. + */ +int __kfuzztest_parse_input(void *input, size_t input_size, + struct reloc_region_array **ret_regions, + struct reloc_table **ret_reloc_table, + void **ret_payload_start, void **ret_payload_end); +/** + * Relocates a parsed input into kernel memory. + */ reloc_handle_t __kfuzztest_relocate(struct reloc_region_array *regions, - struct reloc_table *rt, void *payload, - void **data_ret); + struct reloc_table *rt, void *payload_start, + void *payload_end, void **data_ret); +/** + * Release the relocated data, freeing the initial buffer that was copied from + * user space. + */ void __kfuzztest_release_relocated(struct reloc_region_array *regions, reloc_handle_t handle); @@ -140,7 +165,7 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, int err; \ struct reloc_region_array *regions; \ struct reloc_table *rt; \ - void *payload, *relocated; \ + void *payload_start, *payload_end, *relocated; \ test_arg_type *arg; \ \ void *buffer = kmalloc(len, GFP_KERNEL); \ @@ -154,20 +179,17 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, return err; \ } \ err = __kfuzztest_parse_input(buffer, len, ®ions, &rt, \ - &payload); \ - if (err) { \ - kfree(buffer); \ + &payload_start, &payload_end); \ + if (err) \ return err; \ - } \ - relocated = __kfuzztest_relocate(regions, rt, payload, \ - (void *)&arg); \ - if (!relocated) { \ - kfree(buffer); \ + /* Frees `buffer` on failure. */ \ + relocated = __kfuzztest_relocate(regions, rt, payload_start, \ + payload_end, (void *)&arg); \ + if (!relocated) \ return -EINVAL; \ - } \ /* Call the user's logic on the provided written input. */ \ _fuzz_test_logic_##test_name(arg); \ - /* XXX: buffer leaks in distinct mode. */ \ + /* Frees `buffer`. */ \ __kfuzztest_release_relocated(regions, relocated); \ return len; \ } \ @@ -332,51 +354,13 @@ struct kfuzztest_annotation { * The relocation table format encodes pointer values as a relative offset from * the location of the pointer. A relative offset of zero could indicate that * the pointer points to its own address, which is valid. We encode a null - * pointer as 0xFF...FF as adding this value to any address would result in an + * pointer as 0xFFFFFFFF as adding this value to any address would result in an * overflow anyways, and is therefore invalid in any other circumstance. */ -static const uintptr_t nullPtr = (uint32_t)-1; - -/* Performs some input validation before relocating data. */ -__attribute__((unused)) static int __kfuzztest_parse_input( - void *input, size_t input_size, struct reloc_region_array **ret_regions, - struct reloc_table **ret_reloc_table, void **ret_payload) -{ - void *input_end, *payload_start; - size_t reloc_entries_size, regions_size; - struct reloc_table *rt; - struct reloc_region_array *regions; - - if (input_size < - sizeof(struct reloc_region_array) + sizeof(struct reloc_table)) - return -EINVAL; - - input_end = (char *)input + input_size; - - regions = input; - regions_size = sizeof(struct reloc_region_array) + - regions->num_regions * sizeof(struct reloc_region); +#define KFUZZTEST_REGIONID_NULL U32_MAX - rt = (struct reloc_table *)((char *)regions + regions_size); - if ((char *)rt > (char *)input_end) - return -EINVAL; - - reloc_entries_size = sizeof(struct reloc_table) + - rt->num_entries * sizeof(struct reloc_entry); - if ((char *)rt + reloc_entries_size > (char *)input_end) - return -EINVAL; - - payload_start = (char *)(rt->entries + rt->num_entries); - if ((char *)payload_start > (char *)input_end) - return -EINVAL; - - if (ret_regions) - *ret_regions = regions; - if (ret_reloc_table) - *ret_reloc_table = rt; - if (ret_payload) - *ret_payload = payload_start; - return 0; -} +/* Performs some input validation, and returns the rerloc region array, and + * reloc table. + */ #endif /* KFUZZTEST_H */ diff --git a/lib/kfuzztest/Makefile b/lib/kfuzztest/Makefile index 462f229fdabdbd..60414722c0c073 100644 --- a/lib/kfuzztest/Makefile +++ b/lib/kfuzztest/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_KFUZZTEST) += kfuzztest.o -kfuzztest-objs := kfuzztest_main.o relocations.o +kfuzztest-objs := kfuzztest_main.o relocations.o parse.o diff --git a/lib/kfuzztest/parse.c b/lib/kfuzztest/parse.c new file mode 100644 index 00000000000000..3378113a0a370b --- /dev/null +++ b/lib/kfuzztest/parse.c @@ -0,0 +1,51 @@ +#include + +int __kfuzztest_parse_input(void *input, size_t input_size, + struct reloc_region_array **ret_regions, + struct reloc_table **ret_reloc_table, + void **ret_payload_start, void **ret_payload_end) +{ + int err; + void *payload_end, *payload_start; + size_t reloc_entries_size, regions_size; + struct reloc_table *rt; + struct reloc_region_array *regions; + + if (input_size < + sizeof(struct reloc_region_array) + sizeof(struct reloc_table)) + return -EINVAL; + + payload_end = (char *)input + input_size; + + regions = input; + regions_size = sizeof(struct reloc_region_array) + + regions->num_regions * sizeof(struct reloc_region); + + rt = (struct reloc_table *)((char *)regions + regions_size); + if ((char *)rt > (char *)payload_end) { + err = -EINVAL; + goto fail; + } + + reloc_entries_size = sizeof(struct reloc_table) + + rt->num_entries * sizeof(struct reloc_entry); + if ((char *)rt + reloc_entries_size > (char *)payload_end) { + err = -EINVAL; + goto fail; + } + + payload_start = (char *)(rt->entries + rt->num_entries); + if ((char *)payload_start > (char *)payload_end) { + err = -EINVAL; + goto fail; + } + + *ret_regions = regions; + *ret_reloc_table = rt; + *ret_payload_start = payload_start; + *ret_payload_end = payload_end; + return 0; +fail: + kfree(input); + return err; +} diff --git a/lib/kfuzztest/relocations.c b/lib/kfuzztest/relocations.c index c2a2de1254b5e1..f373e75324582e 100644 --- a/lib/kfuzztest/relocations.c +++ b/lib/kfuzztest/relocations.c @@ -16,6 +16,8 @@ __kfuzztest_release_relocated_distinct(struct reloc_region_array *regions, if (allocations[i]) kfree(allocations[i]); kfree((void *)handle); + /* Regions points to the beginning of the user buffer. */ + kfree((void *)regions); } void __kfuzztest_release_relocated(struct reloc_region_array *regions, @@ -33,10 +35,12 @@ void __kfuzztest_release_relocated(struct reloc_region_array *regions, } } +#define POISON_REGION_END 0xFC + static reloc_handle_t __kfuzztest_relocate_poisoned(struct reloc_region_array *regions, - struct reloc_table *rt, void *payload, - void **data_ret) + struct reloc_table *rt, void *payload_start, + void *payload_end, void **data_ret) { size_t i; struct reloc_region reg, src, dst; @@ -44,32 +48,40 @@ __kfuzztest_relocate_poisoned(struct reloc_region_array *regions, struct reloc_entry re; void *ptr; - /* Poison the padding between regions */ - for (i = 0; i < regions->num_regions; i++) { - reg = regions->regions[i]; - ptr = payload + reg.start + reg.size; - // TODO: poison this range. Figure out import for kasan.h. - } - - // TODO: failure when the region points out of bounds. - - /* Patch pointers */ + /* Patch pointers. */ for (i = 0; i < rt->num_entries; i++) { re = rt->entries[i]; + if (re.region_id > regions->num_regions) + goto fail; + src = regions->regions[re.region_id]; - ptr_location = (uintptr_t *)(char *)payload + src.start + + ptr_location = (uintptr_t *)(char *)payload_start + src.start + re.region_offset; + if ((char *)ptr_location >= (char *)payload_end) + goto fail; - if (re.value == nullPtr) { + if (re.value == KFUZZTEST_REGIONID_NULL) { *ptr_location = (uintptr_t)NULL; } else { if (re.value >= regions->num_regions) goto fail; dst = regions->regions[re.value]; - *ptr_location = (uintptr_t)(char *)payload + dst.start; + *ptr_location = + (uintptr_t)(char *)payload_start + dst.start; } } + /* Poison the padding between regions. */ + for (i = 0; i < regions->num_regions; i++) { + reg = regions->regions[i]; + /* Points to the 8 bytes of padding following every region. */ + ptr = payload_start + reg.start + reg.size; + if ((char *)ptr + 8 >= (char *)payload_end) + goto fail; + kasan_poison(ptr, 8, POISON_REGION_END, false); + } + + /* Returned as `reloc_handle_t`. */ return regions; fail: __kfuzztest_release_relocated_poisoned(regions); @@ -78,8 +90,8 @@ __kfuzztest_relocate_poisoned(struct reloc_region_array *regions, static reloc_handle_t __kfuzztest_relocate_distinct(struct reloc_region_array *regions, - struct reloc_table *rt, void *payload, - void **data_ret) + struct reloc_table *rt, void *payload_start, + void *payload_end, void **data_ret) { void **allocated_regions; size_t i; @@ -94,36 +106,34 @@ __kfuzztest_relocate_distinct(struct reloc_region_array *regions, for (i = 0; i < regions->num_regions; i++) { reg = regions->regions[i]; + if (reg.size > KMALLOC_MAX_SIZE) + goto fail; /* kzalloc guarantees 8-byte alignment, which is enough. */ allocated_regions[i] = kzalloc(reg.size, GFP_KERNEL); if (!allocated_regions[i]) goto fail; - pr_info("copying from %px to %px with size 0x%x", - (char *)payload + reg.start, allocated_regions[i], - reg.size); - - memcpy(allocated_regions[i], (char *)payload + reg.start, + memcpy(allocated_regions[i], (char *)payload_start + reg.start, reg.size); - - pr_info("KFuzzTest: allocated region_%zu of size %u\n", i, - reg.size); } for (i = 0; i < rt->num_entries; i++) { re = rt->entries[i]; + if (re.value >= regions->num_regions) + goto fail; + ptr_location = (uintptr_t *)((char *)allocated_regions[re.region_id] + re.region_offset); - if (re.value == nullPtr) { + + if ((char *)ptr_location >= (char *)payload_end) + goto fail; + + if (re.value == KFUZZTEST_REGIONID_NULL) *ptr_location = (uintptr_t)NULL; - } else { + else *ptr_location = (uintptr_t)allocated_regions[re.value]; - pr_info("KFuzzTest: pointer at offset %u in region %u pointer to region %u (0x%px)", - re.region_offset, re.region_id, re.value, - (void *)*ptr_location); - } } if (data_ret) @@ -131,26 +141,21 @@ __kfuzztest_relocate_distinct(struct reloc_region_array *regions, return allocated_regions; fail: - if (!allocated_regions) - return NULL; - for (i = 0; i < regions->num_regions; i++) { - if (allocated_regions[i]) - kfree(allocated_regions[i]); - } + __kfuzztest_release_relocated_distinct(regions, allocated_regions); return NULL; } reloc_handle_t __kfuzztest_relocate(struct reloc_region_array *regions, - struct reloc_table *rt, void *payload, - void **data_ret) + struct reloc_table *rt, void *payload_start, + void *payload_end, void **data_ret) { switch (regions->mode) { case DISTINCT: - return __kfuzztest_relocate_distinct(regions, rt, payload, - data_ret); + return __kfuzztest_relocate_distinct(regions, rt, payload_start, + payload_end, data_ret); case POISONED: - return __kfuzztest_relocate_poisoned(regions, rt, payload, - data_ret); + return __kfuzztest_relocate_poisoned(regions, rt, payload_start, + payload_end, data_ret); default: return ERR_PTR(-EINVAL); } diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h index 129178be5e6492..45f67188621cc4 100644 --- a/mm/kasan/kasan.h +++ b/mm/kasan/kasan.h @@ -499,15 +499,6 @@ static inline bool kasan_byte_accessible(const void *addr) #else /* CONFIG_KASAN_HW_TAGS */ -/** - * kasan_poison - mark the memory range as inaccessible - * @addr: range start address, must be aligned to KASAN_GRANULE_SIZE - * @size: range size, must be aligned to KASAN_GRANULE_SIZE - * @value: value that's written to metadata for the range - * @init: whether to initialize the memory range (only for hardware tag-based) - */ -void kasan_poison(const void *addr, size_t size, u8 value, bool init); - /** * kasan_unpoison - mark the memory range as accessible * @addr: range start address, must be aligned to KASAN_GRANULE_SIZE From 624a56639f2ceef79b341ecfe967c774034e57c1 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Tue, 5 Aug 2025 09:01:40 +0000 Subject: [PATCH 38/73] kfuzztest: add toy example --- lib/kfuzztest/kfuzztest_main.c | 2 ++ lib/kfuzztest/kfuzzztest_examples.h | 39 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 lib/kfuzztest/kfuzzztest_examples.h diff --git a/lib/kfuzztest/kfuzztest_main.c b/lib/kfuzztest/kfuzztest_main.c index f0adc62c9882ad..7d9ef5c1ce30f2 100644 --- a/lib/kfuzztest/kfuzztest_main.c +++ b/lib/kfuzztest/kfuzztest_main.c @@ -11,6 +11,8 @@ #include #include +#include "kfuzzztest_examples.h" + extern const struct kfuzztest_target __kfuzztest_targets_start[]; extern const struct kfuzztest_target __kfuzztest_targets_end[]; diff --git a/lib/kfuzztest/kfuzzztest_examples.h b/lib/kfuzztest/kfuzzztest_examples.h new file mode 100644 index 00000000000000..34fde1377eb2ef --- /dev/null +++ b/lib/kfuzztest/kfuzzztest_examples.h @@ -0,0 +1,39 @@ +#ifndef KFUZZTEST_EXAMPLES_H +#define KFUZZTEST_EXAMPLES_H + +#include + +struct nested_buffers { + const char *a; + size_t a_len; + const char *b; + size_t b_len; +}; + +/** + * The KFuzzTest input format specifies that struct nested buffers should + * be expanded as: + * + * | a | b | pad[8] | *a | pad[8] | *b | + * + * In DISTINCT mode, this will result in 3 distinct kmalloc'd regions, and + * in POISONED mode, the buffer is untouched but the padding will be poisoned. + * + * In this test case, we look to see that a KASAN warning is triggered in both + * cases when overflowing on *a by one. + */ +FUZZ_TEST(test_overflow_on_nested_buffer, struct nested_buffers) +{ + KFUZZTEST_EXPECT_NOT_NULL(some_input, a); + KFUZZTEST_EXPECT_NOT_NULL(some_input, b); + KFUZZTEST_ANNOTATE_LEN(some_input, a_len, a); + KFUZZTEST_ANNOTATE_LEN(some_input, b_len, b); + + volatile char c; + pr_info("a = [%px, %px)", arg->a, arg->a + arg->a_len); + /* Buffer overflow out of a bounds. This should be caught by KASAN. */ + for (size_t i = 0; i <= arg->a_len; i++) + c = arg->a[i]; +} + +#endif /* KFUZZTEST_EXAMPLES_H */ From 0ccd3a390e8afe95c1563d45fe435e0e1e811049 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Tue, 5 Aug 2025 13:55:41 +0000 Subject: [PATCH 39/73] kfuzztest: backup before only supporting poisoned mode We are going to remove distinct mode in favor of only poisoning the padding areas between payload regions. --- include/linux/kfuzztest.h | 8 +++----- lib/kfuzztest/parse.c | 14 +++++++++++--- lib/kfuzztest/relocations.c | 34 +++++++++++++++++++++++++--------- 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index dbe60f9533a5d4..060952fa42d056 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -14,12 +14,9 @@ struct reloc_region { uint32_t padding; }; -enum reloc_mode { DISTINCT = 0, POISONED }; - struct reloc_region_array { uint32_t num_regions; - enum reloc_mode mode; - uint32_t padding[2]; + uint32_t padding[3]; struct reloc_region regions[]; }; @@ -36,7 +33,8 @@ struct reloc_entry { struct reloc_table { uint32_t num_entries; - uint32_t padding[3]; + uint32_t payloadOffset; /* Offset from start of relocation table */ + uint32_t padding[2]; struct reloc_entry entries[]; }; static_assert(offsetof(struct reloc_table, entries) % diff --git a/lib/kfuzztest/parse.c b/lib/kfuzztest/parse.c index 3378113a0a370b..720c9a970435e7 100644 --- a/lib/kfuzztest/parse.c +++ b/lib/kfuzztest/parse.c @@ -5,6 +5,7 @@ int __kfuzztest_parse_input(void *input, size_t input_size, struct reloc_table **ret_reloc_table, void **ret_payload_start, void **ret_payload_end) { + pr_info("[ENTER] %s", __FUNCTION__); int err; void *payload_end, *payload_start; size_t reloc_entries_size, regions_size; @@ -18,28 +19,35 @@ int __kfuzztest_parse_input(void *input, size_t input_size, payload_end = (char *)input + input_size; regions = input; - regions_size = sizeof(struct reloc_region_array) + + regions_size = sizeof(*regions) + regions->num_regions * sizeof(struct reloc_region); + pr_info("kfuzztest: num regions = %u", regions->num_regions); + rt = (struct reloc_table *)((char *)regions + regions_size); if ((char *)rt > (char *)payload_end) { err = -EINVAL; goto fail; } - reloc_entries_size = sizeof(struct reloc_table) + - rt->num_entries * sizeof(struct reloc_entry); + reloc_entries_size = + sizeof(*rt) + rt->num_entries * sizeof(struct reloc_entry); if ((char *)rt + reloc_entries_size > (char *)payload_end) { err = -EINVAL; goto fail; } + pr_info("kfuzztest: num relocations = %u, size = %zu", rt->num_entries, + reloc_entries_size); + payload_start = (char *)(rt->entries + rt->num_entries); if ((char *)payload_start > (char *)payload_end) { err = -EINVAL; goto fail; } + pr_info("kfuzztest: payload: [ %px, %px )", payload_start, payload_end); + *ret_regions = regions; *ret_reloc_table = rt; *ret_payload_start = payload_start; diff --git a/lib/kfuzztest/relocations.c b/lib/kfuzztest/relocations.c index f373e75324582e..a1171e61da059b 100644 --- a/lib/kfuzztest/relocations.c +++ b/lib/kfuzztest/relocations.c @@ -42,6 +42,7 @@ __kfuzztest_relocate_poisoned(struct reloc_region_array *regions, struct reloc_table *rt, void *payload_start, void *payload_end, void **data_ret) { + pr_info("[ENTER] %s", __FUNCTION__); size_t i; struct reloc_region reg, src, dst; uintptr_t *ptr_location; @@ -51,14 +52,17 @@ __kfuzztest_relocate_poisoned(struct reloc_region_array *regions, /* Patch pointers. */ for (i = 0; i < rt->num_entries; i++) { re = rt->entries[i]; - if (re.region_id > regions->num_regions) - goto fail; + if (re.region_id >= regions->num_regions) + goto fail; src = regions->regions[re.region_id]; - ptr_location = (uintptr_t *)(char *)payload_start + src.start + - re.region_offset; + + ptr_location = (uintptr_t *)((char *)payload_start + src.start + + re.region_offset); if ((char *)ptr_location >= (char *)payload_end) goto fail; + if (src.start >= src.size) + goto fail; if (re.value == KFUZZTEST_REGIONID_NULL) { *ptr_location = (uintptr_t)NULL; @@ -67,20 +71,32 @@ __kfuzztest_relocate_poisoned(struct reloc_region_array *regions, goto fail; dst = regions->regions[re.value]; *ptr_location = - (uintptr_t)(char *)payload_start + dst.start; + (uintptr_t)((char *)payload_start + dst.start); } } /* Poison the padding between regions. */ for (i = 0; i < regions->num_regions; i++) { reg = regions->regions[i]; + + pr_info("kfuzztest: region starts @ %px, with size %x", + payload_start + reg.start, reg.size); /* Points to the 8 bytes of padding following every region. */ ptr = payload_start + reg.start + reg.size; if ((char *)ptr + 8 >= (char *)payload_end) goto fail; - kasan_poison(ptr, 8, POISON_REGION_END, false); + + kasan_poison((char *)payload_start + reg.start, 8, + POISON_REGION_END, false); + + /* Currently only works if the length of the data is a multiple + * of 8. */ + void *next_boundary = (void *)round_up((uintptr_t)ptr, 8); + kasan_poison(next_boundary, 8, POISON_REGION_END, false); + pr_info("kfuzztest: poisoned %px", next_boundary); } + *data_ret = payload_start; /* Returned as `reloc_handle_t`. */ return regions; fail: @@ -93,6 +109,7 @@ __kfuzztest_relocate_distinct(struct reloc_region_array *regions, struct reloc_table *rt, void *payload_start, void *payload_end, void **data_ret) { + pr_info("[ENTER] %s", __FUNCTION__); void **allocated_regions; size_t i; struct reloc_region reg; @@ -124,7 +141,7 @@ __kfuzztest_relocate_distinct(struct reloc_region_array *regions, goto fail; ptr_location = - (uintptr_t *)((char *)allocated_regions[re.region_id] + + (uintptr_t *)((char *)(allocated_regions[re.region_id]) + re.region_offset); if ((char *)ptr_location >= (char *)payload_end) @@ -136,8 +153,7 @@ __kfuzztest_relocate_distinct(struct reloc_region_array *regions, *ptr_location = (uintptr_t)allocated_regions[re.value]; } - if (data_ret) - *data_ret = allocated_regions[0]; + *data_ret = allocated_regions[0]; return allocated_regions; fail: From 614ce927f09ccc74f882fcdebc7de1af8736df68 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Tue, 5 Aug 2025 18:08:58 +0000 Subject: [PATCH 40/73] kfuzztest: relocate in poison mode - the new only mode --- include/linux/kfuzztest.h | 31 +++-- lib/kfuzztest/kfuzzztest_examples.h | 3 + lib/kfuzztest/relocations.c | 179 +++++++++------------------- 3 files changed, 72 insertions(+), 141 deletions(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index 060952fa42d056..5816ee03f2c518 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -73,13 +73,6 @@ reloc_handle_t __kfuzztest_relocate(struct reloc_region_array *regions, struct reloc_table *rt, void *payload_start, void *payload_end, void **data_ret); -/** - * Release the relocated data, freeing the initial buffer that was copied from - * user space. - */ -void __kfuzztest_release_relocated(struct reloc_region_array *regions, - reloc_handle_t handle); - struct kfuzztest_target { const char *name; const char *arg_type_name; @@ -172,24 +165,28 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, else if (IS_ERR(buffer)) \ return PTR_ERR(buffer); \ err = write_input_cb_common(filp, buf, len, off, buffer, len); \ - if (err != 0) { \ - kfree(buffer); \ - return err; \ - } \ + if (err != 0) \ + goto fail; \ err = __kfuzztest_parse_input(buffer, len, ®ions, &rt, \ &payload_start, &payload_end); \ if (err) \ - return err; \ + goto fail; \ /* Frees `buffer` on failure. */ \ relocated = __kfuzztest_relocate(regions, rt, payload_start, \ payload_end, (void *)&arg); \ - if (!relocated) \ - return -EINVAL; \ - /* Call the user's logic on the provided written input. */ \ + if (!relocated) { \ + err = -EINVAL; \ + goto fail; \ + } \ + pr_info("kfuzztest: success, invoking fuzz logic\n"); \ + /* Call the fuzz logic on the provided written input. */ \ _fuzz_test_logic_##test_name(arg); \ - /* Frees `buffer`. */ \ - __kfuzztest_release_relocated(regions, relocated); \ + kfree(buffer); \ return len; \ +fail: \ + pr_info("kfuzztest: a failure occured"); \ + kfree(buffer); \ + return err; \ } \ static void _fuzz_test_logic_##test_name(test_arg_type *arg) diff --git a/lib/kfuzztest/kfuzzztest_examples.h b/lib/kfuzztest/kfuzzztest_examples.h index 34fde1377eb2ef..0209c779e9958a 100644 --- a/lib/kfuzztest/kfuzzztest_examples.h +++ b/lib/kfuzztest/kfuzzztest_examples.h @@ -31,6 +31,9 @@ FUZZ_TEST(test_overflow_on_nested_buffer, struct nested_buffers) volatile char c; pr_info("a = [%px, %px)", arg->a, arg->a + arg->a_len); + pr_info("b = [%px, %px)", arg->b, arg->b + arg->b_len); + pr_info("a_len = %zu", arg->a_len); + pr_info("b_len = %zu", arg->b_len); /* Buffer overflow out of a bounds. This should be caught by KASAN. */ for (size_t i = 0; i <= arg->a_len; i++) c = arg->a[i]; diff --git a/lib/kfuzztest/relocations.c b/lib/kfuzztest/relocations.c index a1171e61da059b..707d87d66616fa 100644 --- a/lib/kfuzztest/relocations.c +++ b/lib/kfuzztest/relocations.c @@ -1,53 +1,52 @@ #include #include -static void __kfuzztest_release_relocated_poisoned(reloc_handle_t handle) -{ - kfree(handle); -} - -static void -__kfuzztest_release_relocated_distinct(struct reloc_region_array *regions, - reloc_handle_t handle) -{ - size_t i; - void **allocations = (void **)handle; - for (i = 0; i < regions->num_regions; i++) - if (allocations[i]) - kfree(allocations[i]); - kfree((void *)handle); - /* Regions points to the beginning of the user buffer. */ - kfree((void *)regions); -} +#define POISON_REGION_END 0xFC -void __kfuzztest_release_relocated(struct reloc_region_array *regions, - reloc_handle_t handle) +static void __kfuzztest_poison_range(void *start, void *end) { - switch (regions->mode) { - case DISTINCT: - __kfuzztest_release_relocated_distinct(regions, handle); - break; - case POISONED: - __kfuzztest_release_relocated_poisoned(handle); - break; - default: - pr_warn("KFuzzTest: invalid release operation"); - } + uintptr_t start_addr = (uintptr_t)start; + uintptr_t end_addr = (uintptr_t)end; + uintptr_t poison_start; + uintptr_t poison_end; + + /* + * Calculate the largest region within [start, end) that is aligned + * to KASAN_GRANULE_SIZE. This is the only part we can safely poison. + */ + poison_start = ALIGN(start_addr, 0x8); + poison_end = ALIGN_DOWN(end_addr, 0x8); + + /* If there's no fully-aligned granule in the range, we can't do anything. */ + if (poison_start >= poison_end) + return; + + /* + * Poison the aligned region. KASAN_SLAB_REDZONE is a suitable + * poison value for padding that should never be accessed. + */ + kasan_poison((void *)poison_start, poison_end - poison_start, + POISON_REGION_END, false); + pr_info("kfuzztest: poisoned [%px, %px)", (void *)poison_start, + (void *)poison_end); } -#define POISON_REGION_END 0xFC - -static reloc_handle_t -__kfuzztest_relocate_poisoned(struct reloc_region_array *regions, - struct reloc_table *rt, void *payload_start, - void *payload_end, void **data_ret) +reloc_handle_t __kfuzztest_relocate(struct reloc_region_array *regions, + struct reloc_table *rt, void *payload_start, + void *payload_end, void **data_ret) { pr_info("[ENTER] %s", __FUNCTION__); size_t i; struct reloc_region reg, src, dst; uintptr_t *ptr_location; struct reloc_entry re; - void *ptr; + void *poison_start, *poison_end; + + pr_info("kfuzztest: %d regions, %d relocations", regions->num_regions, + rt->num_entries); + + pr_info("kfuzztest: regions = %px, rt = %px, payload_start = %px, payload_end = %px", + regions, rt, payload_start, payload_end); /* Patch pointers. */ for (i = 0; i < rt->num_entries; i++) { @@ -65,6 +64,7 @@ __kfuzztest_relocate_poisoned(struct reloc_region_array *regions, goto fail; if (re.value == KFUZZTEST_REGIONID_NULL) { + pr_info("%px = NULL", ptr_location); *ptr_location = (uintptr_t)NULL; } else { if (re.value >= regions->num_regions) @@ -72,6 +72,8 @@ __kfuzztest_relocate_poisoned(struct reloc_region_array *regions, dst = regions->regions[re.value]; *ptr_location = (uintptr_t)((char *)payload_start + dst.start); + pr_info("%px -> %px", ptr_location, + (void *)*ptr_location); } } @@ -79,100 +81,29 @@ __kfuzztest_relocate_poisoned(struct reloc_region_array *regions, for (i = 0; i < regions->num_regions; i++) { reg = regions->regions[i]; - pr_info("kfuzztest: region starts @ %px, with size %x", - payload_start + reg.start, reg.size); - /* Points to the 8 bytes of padding following every region. */ - ptr = payload_start + reg.start + reg.size; - if ((char *)ptr + 8 >= (char *)payload_end) - goto fail; + pr_info("kfuzztest: region %zu [%px, %px) (size = %u)", i, + payload_start + reg.start, + payload_start + reg.start + reg.size, reg.size); + /* Points to the beginning of the inter-region padding */ + poison_start = payload_start + reg.start + reg.size; + if (i < regions->num_regions - 1) { + poison_end = + payload_start + regions->regions[i + 1].start; + } else { + poison_end = poison_start + 8; + } - kasan_poison((char *)payload_start + reg.start, 8, - POISON_REGION_END, false); + if ((char *)poison_end > (char *)payload_end) { + pr_info("kfuzztest: poison region out of bounds"); + goto fail; + } - /* Currently only works if the length of the data is a multiple - * of 8. */ - void *next_boundary = (void *)round_up((uintptr_t)ptr, 8); - kasan_poison(next_boundary, 8, POISON_REGION_END, false); - pr_info("kfuzztest: poisoned %px", next_boundary); + __kfuzztest_poison_range(poison_start, poison_end); } *data_ret = payload_start; /* Returned as `reloc_handle_t`. */ return regions; fail: - __kfuzztest_release_relocated_poisoned(regions); - return NULL; -} - -static reloc_handle_t -__kfuzztest_relocate_distinct(struct reloc_region_array *regions, - struct reloc_table *rt, void *payload_start, - void *payload_end, void **data_ret) -{ - pr_info("[ENTER] %s", __FUNCTION__); - void **allocated_regions; - size_t i; - struct reloc_region reg; - struct reloc_entry re; - uintptr_t *ptr_location; - - allocated_regions = - kzalloc(regions->num_regions * sizeof(void *), GFP_KERNEL); - if (!allocated_regions) - return NULL; - - for (i = 0; i < regions->num_regions; i++) { - reg = regions->regions[i]; - if (reg.size > KMALLOC_MAX_SIZE) - goto fail; - - /* kzalloc guarantees 8-byte alignment, which is enough. */ - allocated_regions[i] = kzalloc(reg.size, GFP_KERNEL); - if (!allocated_regions[i]) - goto fail; - - memcpy(allocated_regions[i], (char *)payload_start + reg.start, - reg.size); - } - - for (i = 0; i < rt->num_entries; i++) { - re = rt->entries[i]; - if (re.value >= regions->num_regions) - goto fail; - - ptr_location = - (uintptr_t *)((char *)(allocated_regions[re.region_id]) + - re.region_offset); - - if ((char *)ptr_location >= (char *)payload_end) - goto fail; - - if (re.value == KFUZZTEST_REGIONID_NULL) - *ptr_location = (uintptr_t)NULL; - else - *ptr_location = (uintptr_t)allocated_regions[re.value]; - } - - *data_ret = allocated_regions[0]; - return allocated_regions; - -fail: - __kfuzztest_release_relocated_distinct(regions, allocated_regions); return NULL; } - -reloc_handle_t __kfuzztest_relocate(struct reloc_region_array *regions, - struct reloc_table *rt, void *payload_start, - void *payload_end, void **data_ret) -{ - switch (regions->mode) { - case DISTINCT: - return __kfuzztest_relocate_distinct(regions, rt, payload_start, - payload_end, data_ret); - case POISONED: - return __kfuzztest_relocate_poisoned(regions, rt, payload_start, - payload_end, data_ret); - default: - return ERR_PTR(-EINVAL); - } -} From 62053d4f306a419678aa82115195de071ec22682 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Tue, 5 Aug 2025 19:08:28 +0000 Subject: [PATCH 41/73] kfuzztest: update poisoning to use kasan_poison_last_granule Also move functions out of mm/kasan/kasan.h into include/linux/kasan.h --- include/linux/kasan.h | 21 ++++++++++ include/linux/kfuzztest.h | 14 ++++--- lib/kfuzztest/kfuzzztest_examples.h | 10 ++++- lib/kfuzztest/relocations.c | 61 +++++++++++++++++++---------- mm/kasan/kasan.h | 19 --------- 5 files changed, 78 insertions(+), 47 deletions(-) diff --git a/include/linux/kasan.h b/include/linux/kasan.h index d6dba15bc57555..345ab363c7ba3e 100644 --- a/include/linux/kasan.h +++ b/include/linux/kasan.h @@ -126,6 +126,27 @@ static __always_inline bool kasan_unpoison_pages(struct page *page, return false; } +#ifdef CONFIG_KASAN_GENERIC + +/** + * kasan_poison_last_granule - mark the last granule of the memory range as + * inaccessible + * @address: range start address, must be aligned to KASAN_GRANULE_SIZE + * @size: range size + * + * This function is only available for the generic mode, as it's the only mode + * that has partially poisoned memory granules. + */ +void kasan_poison_last_granule(const void *address, size_t size); + +#else /* CONFIG_KASAN_GENERIC */ + +static inline void kasan_poison_last_granule(const void *address, size_t size) +{ +} + +#endif /* CONFIG_KASAN_GENERIC */ + void kasan_poison(const void *addr, size_t size, u8 value, bool init); void __kasan_poison_slab(struct slab *slab); diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index 5816ee03f2c518..ecfa43bf19bcd3 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -141,7 +141,8 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, static ssize_t _write_callback_##test_name(struct file *filp, \ const char __user *buf, \ size_t len, loff_t *off); \ - static void _fuzz_test_logic_##test_name(test_arg_type *arg); \ + static void _fuzz_test_logic_##test_name( \ + test_arg_type *arg, struct reloc_region_array *regions); \ const struct kfuzztest_target __fuzz_test__##test_name __attribute__(( \ __section__(".kfuzztest_target"), __used__)) = { \ .name = #test_name, \ @@ -180,7 +181,7 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, } \ pr_info("kfuzztest: success, invoking fuzz logic\n"); \ /* Call the fuzz logic on the provided written input. */ \ - _fuzz_test_logic_##test_name(arg); \ + _fuzz_test_logic_##test_name(arg, regions); \ kfree(buffer); \ return len; \ fail: \ @@ -188,7 +189,8 @@ fail: \ kfree(buffer); \ return err; \ } \ - static void _fuzz_test_logic_##test_name(test_arg_type *arg) + static void _fuzz_test_logic_##test_name( \ + test_arg_type *arg, struct reloc_region_array *regions) /** * Reports a bug with a predictable prefix so that it can be parsed by a @@ -354,8 +356,8 @@ struct kfuzztest_annotation { */ #define KFUZZTEST_REGIONID_NULL U32_MAX -/* Performs some input validation, and returns the rerloc region array, and - * reloc table. - */ +/* The size of a region if it exists, or 0 if it does not. */ +#define KFUZZTEST_REGION_SIZE(n) \ + ((n) < (regions->num_regions) ? (regions->regions[n].size) : 0) #endif /* KFUZZTEST_H */ diff --git a/lib/kfuzztest/kfuzzztest_examples.h b/lib/kfuzztest/kfuzzztest_examples.h index 0209c779e9958a..65c6deda8e1915 100644 --- a/lib/kfuzztest/kfuzzztest_examples.h +++ b/lib/kfuzztest/kfuzzztest_examples.h @@ -24,6 +24,9 @@ struct nested_buffers { */ FUZZ_TEST(test_overflow_on_nested_buffer, struct nested_buffers) { + u32 a_size; + u32 b_size; + KFUZZTEST_EXPECT_NOT_NULL(some_input, a); KFUZZTEST_EXPECT_NOT_NULL(some_input, b); KFUZZTEST_ANNOTATE_LEN(some_input, a_len, a); @@ -34,8 +37,13 @@ FUZZ_TEST(test_overflow_on_nested_buffer, struct nested_buffers) pr_info("b = [%px, %px)", arg->b, arg->b + arg->b_len); pr_info("a_len = %zu", arg->a_len); pr_info("b_len = %zu", arg->b_len); + + a_size = KFUZZTEST_REGION_SIZE(1); + b_size = KFUZZTEST_REGION_SIZE(2); + pr_info("actual sizes = %u, %u", a_size, b_size); + /* Buffer overflow out of a bounds. This should be caught by KASAN. */ - for (size_t i = 0; i <= arg->a_len; i++) + for (size_t i = 0; i <= a_size; i++) c = arg->a[i]; } diff --git a/lib/kfuzztest/relocations.c b/lib/kfuzztest/relocations.c index 707d87d66616fa..c76d47584d2297 100644 --- a/lib/kfuzztest/relocations.c +++ b/lib/kfuzztest/relocations.c @@ -3,32 +3,51 @@ #define POISON_REGION_END 0xFC +/** + * Poison the half open interval [start, end], where end should be 8-byte + * aligned if it is not, then we cannot guarantee that the whole range will + * be poisoned. + * + * If start is not 8-byte-aligned, the remaining bytes in its 8-byte granule + * can only be poisoned if CONFIG_KASAN_GENERIC is enabled. + */ static void __kfuzztest_poison_range(void *start, void *end) { uintptr_t start_addr = (uintptr_t)start; - uintptr_t end_addr = (uintptr_t)end; - uintptr_t poison_start; - uintptr_t poison_end; - - /* - * Calculate the largest region within [start, end) that is aligned - * to KASAN_GRANULE_SIZE. This is the only part we can safely poison. - */ - poison_start = ALIGN(start_addr, 0x8); - poison_end = ALIGN_DOWN(end_addr, 0x8); - - /* If there's no fully-aligned granule in the range, we can't do anything. */ - if (poison_start >= poison_end) + uintptr_t end_addr = ALIGN_DOWN((uintptr_t)end, 0x8); + + uintptr_t poison_body_start; + uintptr_t poison_body_end; + uintptr_t head_granule_start; + size_t head_prefix_size; + + if (start_addr >= end_addr) return; - /* - * Poison the aligned region. KASAN_SLAB_REDZONE is a suitable - * poison value for padding that should never be accessed. - */ - kasan_poison((void *)poison_start, poison_end - poison_start, - POISON_REGION_END, false); - pr_info("kfuzztest: poisoned [%px, %px)", (void *)poison_start, - (void *)poison_end); + head_granule_start = ALIGN_DOWN(start_addr, 0x8); + head_prefix_size = start_addr - head_granule_start; + + if (IS_ENABLED(CONFIG_KASAN_GENERIC) && head_prefix_size > 0) { + kasan_poison_last_granule((void *)head_granule_start, + head_prefix_size); + pr_info("kfuzztest: poisoned [%px, %px)", + (void *)head_granule_start + head_prefix_size, + (void *)head_granule_start + 8); + } + + poison_body_start = ALIGN(start_addr, 0x8); + poison_body_end = ALIGN_DOWN(end_addr, 0x8); + + pr_info("kfuzztest: want to additionally poison [%px, %px)", + (void *)poison_body_start, (void *)poison_body_end); + if (poison_body_start < poison_body_end) { + kasan_poison((void *)poison_body_start, + poison_body_end - poison_body_start, + POISON_REGION_END, false); + + pr_info("kfuzztest: poisoned [%px, %px)", + (void *)poison_body_start, (void *)poison_body_end); + } } reloc_handle_t __kfuzztest_relocate(struct reloc_region_array *regions, diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h index 45f67188621cc4..c4a478f2d22e42 100644 --- a/mm/kasan/kasan.h +++ b/mm/kasan/kasan.h @@ -516,25 +516,6 @@ bool kasan_byte_accessible(const void *addr); #endif /* CONFIG_KASAN_HW_TAGS */ -#ifdef CONFIG_KASAN_GENERIC - -/** - * kasan_poison_last_granule - mark the last granule of the memory range as - * inaccessible - * @address: range start address, must be aligned to KASAN_GRANULE_SIZE - * @size: range size - * - * This function is only available for the generic mode, as it's the only mode - * that has partially poisoned memory granules. - */ -void kasan_poison_last_granule(const void *address, size_t size); - -#else /* CONFIG_KASAN_GENERIC */ - -static inline void kasan_poison_last_granule(const void *address, size_t size) { } - -#endif /* CONFIG_KASAN_GENERIC */ - #ifndef kasan_arch_is_ready static inline bool kasan_arch_is_ready(void) { return true; } #elif !defined(CONFIG_KASAN_GENERIC) || !defined(CONFIG_KASAN_OUTLINE) From 5116dccf541652e54dc7537c5b4dc3ae8ab3879a Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Wed, 6 Aug 2025 08:35:47 +0000 Subject: [PATCH 42/73] kfuzztest: cleanups WIP --- include/linux/kfuzztest.h | 102 ++++++++++------------------ lib/kfuzztest/kfuzztest_main.c | 14 ++-- lib/kfuzztest/kfuzzztest_examples.h | 8 +-- lib/kfuzztest/parse.c | 13 ++++ lib/kfuzztest/relocations.c | 47 ++++--------- 5 files changed, 71 insertions(+), 113 deletions(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index ecfa43bf19bcd3..120c8519971c9a 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -41,11 +41,9 @@ static_assert(offsetof(struct reloc_table, entries) % sizeof(struct reloc_entry) == 0); -/** - * Internal handle used for releasing resources, returned to the caller of - * __kfuzztest_relocate. - */ -typedef void *reloc_handle_t; +int __kfuzztest_write_cb_common(struct file *filp, const char __user *buf, + size_t len, loff_t *off, void *arg, + size_t arg_size); /** * Parses a binary input of size input_size. Input should be a pointer to a @@ -69,9 +67,9 @@ int __kfuzztest_parse_input(void *input, size_t input_size, /** * Relocates a parsed input into kernel memory. */ -reloc_handle_t __kfuzztest_relocate(struct reloc_region_array *regions, - struct reloc_table *rt, void *payload_start, - void *payload_end, void **data_ret); +int __kfuzztest_relocate(struct reloc_region_array *regions, + struct reloc_table *rt, void *payload_start, + void *payload_end); struct kfuzztest_target { const char *name; @@ -82,19 +80,6 @@ struct kfuzztest_target { static_assert(sizeof(struct kfuzztest_target) == 32, "struct kfuzztest_target should have size 32"); -__attribute__((unused)) static int -write_input_cb_common(struct file *filp, const char __user *buf, size_t len, - loff_t *off, void *arg, size_t arg_size) -{ - if (len != arg_size) { - return -EINVAL; - } - if (simple_write_to_buffer((void *)arg, arg_size, off, buf, len) < 0) { - return -EFAULT; - } - return 0; -} - /** * FUZZ_TEST - defines a KFuzzTest target. * @@ -138,66 +123,56 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, * } */ #define FUZZ_TEST(test_name, test_arg_type) \ - static ssize_t _write_callback_##test_name(struct file *filp, \ - const char __user *buf, \ - size_t len, loff_t *off); \ - static void _fuzz_test_logic_##test_name( \ + static ssize_t kfuzztest_write_cb_##test_name(struct file *filp, \ + const char __user *buf, \ + size_t len, \ + loff_t *off); \ + static void kfuzztest_logic_##test_name( \ test_arg_type *arg, struct reloc_region_array *regions); \ const struct kfuzztest_target __fuzz_test__##test_name __attribute__(( \ __section__(".kfuzztest_target"), __used__)) = { \ .name = #test_name, \ .arg_type_name = #test_arg_type, \ - .write_input_cb = _write_callback_##test_name, \ + .write_input_cb = kfuzztest_write_cb_##test_name, \ }; \ - /* Invoked when data is written into the target's input file. */ \ - static ssize_t _write_callback_##test_name(struct file *filp, \ - const char __user *buf, \ - size_t len, loff_t *off) \ + static ssize_t kfuzztest_write_cb_##test_name(struct file *filp, \ + const char __user *buf, \ + size_t len, loff_t *off) \ { \ - int err; \ + int ret; \ struct reloc_region_array *regions; \ struct reloc_table *rt; \ - void *payload_start, *payload_end, *relocated; \ + void *payload_start, *payload_end, *buffer; \ test_arg_type *arg; \ \ - void *buffer = kmalloc(len, GFP_KERNEL); \ + buffer = kmalloc(len, GFP_KERNEL); \ if (!buffer) \ return -ENOMEM; \ else if (IS_ERR(buffer)) \ return PTR_ERR(buffer); \ - err = write_input_cb_common(filp, buf, len, off, buffer, len); \ - if (err != 0) \ - goto fail; \ - err = __kfuzztest_parse_input(buffer, len, ®ions, &rt, \ + ret = __kfuzztest_write_cb_common(filp, buf, len, off, buffer, \ + len); \ + if (ret) \ + goto out; \ + ret = __kfuzztest_parse_input(buffer, len, ®ions, &rt, \ &payload_start, &payload_end); \ - if (err) \ - goto fail; \ - /* Frees `buffer` on failure. */ \ - relocated = __kfuzztest_relocate(regions, rt, payload_start, \ - payload_end, (void *)&arg); \ - if (!relocated) { \ - err = -EINVAL; \ - goto fail; \ - } \ - pr_info("kfuzztest: success, invoking fuzz logic\n"); \ + if (ret) \ + goto out; \ + ret = __kfuzztest_relocate(regions, rt, payload_start, \ + payload_end); \ + if (ret) \ + goto out; \ /* Call the fuzz logic on the provided written input. */ \ - _fuzz_test_logic_##test_name(arg, regions); \ - kfree(buffer); \ - return len; \ -fail: \ - pr_info("kfuzztest: a failure occured"); \ + arg = (test_arg_type *)payload_start; \ + kfuzztest_logic_##test_name(arg, regions); \ + ret = len; \ +out: \ kfree(buffer); \ - return err; \ + return ret; \ } \ - static void _fuzz_test_logic_##test_name( \ + static void kfuzztest_logic_##test_name( \ test_arg_type *arg, struct reloc_region_array *regions) -/** - * Reports a bug with a predictable prefix so that it can be parsed by a - * fuzzing driver. - */ -#define KFUZZTEST_REPORT_BUG(msg, fmt) pr_warn("bug: " #msg, fmt) - enum kfuzztest_constraint_type : uint8_t { EXPECT_EQ = 0, EXPECT_NE, @@ -348,11 +323,8 @@ struct kfuzztest_annotation { __KFUZZTEST_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) /** - * The relocation table format encodes pointer values as a relative offset from - * the location of the pointer. A relative offset of zero could indicate that - * the pointer points to its own address, which is valid. We encode a null - * pointer as 0xFFFFFFFF as adding this value to any address would result in an - * overflow anyways, and is therefore invalid in any other circumstance. + * The input format is such that the value field is a region index. We reserve + * this value to encode a NULL pointer in the input. */ #define KFUZZTEST_REGIONID_NULL U32_MAX diff --git a/lib/kfuzztest/kfuzztest_main.c b/lib/kfuzztest/kfuzztest_main.c index 7d9ef5c1ce30f2..4d26d3df46b10b 100644 --- a/lib/kfuzztest/kfuzztest_main.c +++ b/lib/kfuzztest/kfuzztest_main.c @@ -57,13 +57,7 @@ struct kfuzztest_state { /* Global static variable to hold all state for the module. */ static struct kfuzztest_state st; -/** - * Default file permissions for the debugfs entries. - * 0222: World-writable for the 'input' file. - * - * XXX: should formally define what the permissions should be on these files. - */ -const umode_t kfuzztest_flags_w = 0222; +const umode_t KFUZZTEST_INPUT_PERMS = 0222; /** * kfuzztest_init - Initializes the debug filesystem for KFuzzTest. @@ -126,7 +120,7 @@ static int __init kfuzztest_init(void) .write = targ->write_input_cb, }; st.debugfs_state[i].input_dentry.dentry = debugfs_create_file( - "input", kfuzztest_flags_w, + "input", KFUZZTEST_INPUT_PERMS, st.debugfs_state[i].target_dir, NULL, &st.debugfs_state[i].input_dentry.fops); if (!st.debugfs_state[i].input_dentry.dentry) { @@ -137,7 +131,7 @@ static int __init kfuzztest_init(void) goto cleanup_failure; } - pr_info("KFuzzTest: registered target %s\n", targ->name); + pr_info("KFuzzTest: registered target %s", targ->name); } return 0; @@ -149,7 +143,7 @@ static int __init kfuzztest_init(void) static void __exit kfuzztest_exit(void) { - pr_info("KFuzzTest: exiting\n"); + pr_info("KFuzzTest: exiting"); if (!st.kfuzztest_dir) return; diff --git a/lib/kfuzztest/kfuzzztest_examples.h b/lib/kfuzztest/kfuzzztest_examples.h index 65c6deda8e1915..ed6c1005947184 100644 --- a/lib/kfuzztest/kfuzzztest_examples.h +++ b/lib/kfuzztest/kfuzzztest_examples.h @@ -27,10 +27,10 @@ FUZZ_TEST(test_overflow_on_nested_buffer, struct nested_buffers) u32 a_size; u32 b_size; - KFUZZTEST_EXPECT_NOT_NULL(some_input, a); - KFUZZTEST_EXPECT_NOT_NULL(some_input, b); - KFUZZTEST_ANNOTATE_LEN(some_input, a_len, a); - KFUZZTEST_ANNOTATE_LEN(some_input, b_len, b); + KFUZZTEST_EXPECT_NOT_NULL(nested_buffers, a); + KFUZZTEST_EXPECT_NOT_NULL(nested_buffers, b); + KFUZZTEST_ANNOTATE_LEN(nested_buffers, a_len, a); + KFUZZTEST_ANNOTATE_LEN(nested_buffers, b_len, b); volatile char c; pr_info("a = [%px, %px)", arg->a, arg->a + arg->a_len); diff --git a/lib/kfuzztest/parse.c b/lib/kfuzztest/parse.c index 720c9a970435e7..31b74d8135a0d0 100644 --- a/lib/kfuzztest/parse.c +++ b/lib/kfuzztest/parse.c @@ -1,5 +1,18 @@ #include +int __kfuzztest_write_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((void *)arg, arg_size, off, buf, len) < 0) { + return -EFAULT; + } + return 0; +} + int __kfuzztest_parse_input(void *input, size_t input_size, struct reloc_region_array **ret_regions, struct reloc_table **ret_reloc_table, diff --git a/lib/kfuzztest/relocations.c b/lib/kfuzztest/relocations.c index c76d47584d2297..64669ed4ac17dd 100644 --- a/lib/kfuzztest/relocations.c +++ b/lib/kfuzztest/relocations.c @@ -11,11 +11,10 @@ * If start is not 8-byte-aligned, the remaining bytes in its 8-byte granule * can only be poisoned if CONFIG_KASAN_GENERIC is enabled. */ -static void __kfuzztest_poison_range(void *start, void *end) +static void kfuzztest_poison_range(void *start, void *end) { - uintptr_t start_addr = (uintptr_t)start; uintptr_t end_addr = ALIGN_DOWN((uintptr_t)end, 0x8); - + uintptr_t start_addr = (uintptr_t)start; uintptr_t poison_body_start; uintptr_t poison_body_end; uintptr_t head_granule_start; @@ -50,49 +49,38 @@ static void __kfuzztest_poison_range(void *start, void *end) } } -reloc_handle_t __kfuzztest_relocate(struct reloc_region_array *regions, - struct reloc_table *rt, void *payload_start, - void *payload_end, void **data_ret) +int __kfuzztest_relocate(struct reloc_region_array *regions, + struct reloc_table *rt, void *payload_start, + void *payload_end) { - pr_info("[ENTER] %s", __FUNCTION__); size_t i; struct reloc_region reg, src, dst; uintptr_t *ptr_location; struct reloc_entry re; void *poison_start, *poison_end; - pr_info("kfuzztest: %d regions, %d relocations", regions->num_regions, - rt->num_entries); - - pr_info("kfuzztest: regions = %px, rt = %px, payload_start = %px, payload_end = %px", - regions, rt, payload_start, payload_end); - /* Patch pointers. */ for (i = 0; i < rt->num_entries; i++) { re = rt->entries[i]; if (re.region_id >= regions->num_regions) - goto fail; + return -EINVAL; src = regions->regions[re.region_id]; ptr_location = (uintptr_t *)((char *)payload_start + src.start + re.region_offset); if ((char *)ptr_location >= (char *)payload_end) - goto fail; + return -EINVAL; if (src.start >= src.size) - goto fail; - + return -EINVAL; if (re.value == KFUZZTEST_REGIONID_NULL) { - pr_info("%px = NULL", ptr_location); *ptr_location = (uintptr_t)NULL; } else { if (re.value >= regions->num_regions) - goto fail; + return -EINVAL; dst = regions->regions[re.value]; *ptr_location = (uintptr_t)((char *)payload_start + dst.start); - pr_info("%px -> %px", ptr_location, - (void *)*ptr_location); } } @@ -100,9 +88,6 @@ reloc_handle_t __kfuzztest_relocate(struct reloc_region_array *regions, for (i = 0; i < regions->num_regions; i++) { reg = regions->regions[i]; - pr_info("kfuzztest: region %zu [%px, %px) (size = %u)", i, - payload_start + reg.start, - payload_start + reg.start + reg.size, reg.size); /* Points to the beginning of the inter-region padding */ poison_start = payload_start + reg.start + reg.size; if (i < regions->num_regions - 1) { @@ -112,17 +97,11 @@ reloc_handle_t __kfuzztest_relocate(struct reloc_region_array *regions, poison_end = poison_start + 8; } - if ((char *)poison_end > (char *)payload_end) { - pr_info("kfuzztest: poison region out of bounds"); - goto fail; - } + if ((char *)poison_end > (char *)payload_end) + return -EINVAL; - __kfuzztest_poison_range(poison_start, poison_end); + kfuzztest_poison_range(poison_start, poison_end); } - *data_ret = payload_start; - /* Returned as `reloc_handle_t`. */ - return regions; -fail: - return NULL; + return 0; } From 83dfd185fde7a438ada16158692635200fcf8923 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Wed, 6 Aug 2025 09:30:46 +0000 Subject: [PATCH 43/73] kfuzztest: remove unnecessary padding from kfuzztest.h structs --- include/linux/kfuzztest.h | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index 120c8519971c9a..e2657bc61d5e71 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -10,36 +10,24 @@ MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFuzzTest)"); struct reloc_region { uint32_t start; /* Offset from the start of the payload. */ uint32_t size; - uint32_t alignment; - uint32_t padding; }; struct reloc_region_array { uint32_t num_regions; - uint32_t padding[3]; struct reloc_region regions[]; }; -static_assert(offsetof(struct reloc_region_array, regions) % - sizeof(struct reloc_region) == - 0); - struct reloc_entry { uint32_t region_id; /* Region that pointer belongs to. */ uint32_t region_offset; /* Offset from the beginning of the region. */ uint32_t value; /* Pointee tegion identifier, or (void*)-1 if NULL */ - uint32_t padding; }; struct reloc_table { uint32_t num_entries; uint32_t payloadOffset; /* Offset from start of relocation table */ - uint32_t padding[2]; struct reloc_entry entries[]; }; -static_assert(offsetof(struct reloc_table, entries) % - sizeof(struct reloc_entry) == - 0); int __kfuzztest_write_cb_common(struct file *filp, const char __user *buf, size_t len, loff_t *off, void *arg, From 60f56f6318d0fa29cd374f150de8350d27b3733c Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Wed, 6 Aug 2025 09:42:26 +0000 Subject: [PATCH 44/73] kfuzztest: add SPDX headers to all new files --- include/linux/kfuzztest.h | 6 +++--- lib/kfuzztest/Makefile | 2 +- lib/kfuzztest/kfuzzztest_examples.h | 1 + lib/kfuzztest/parse.c | 1 + lib/kfuzztest/relocations.c | 1 + 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index e2657bc61d5e71..4bd6905dfda865 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef KFUZZTEST_H #define KFUZZTEST_H @@ -34,9 +35,8 @@ int __kfuzztest_write_cb_common(struct file *filp, const char __user *buf, size_t arg_size); /** - * Parses a binary input of size input_size. Input should be a pointer to a - * heap-allocated buffer, and it's ownership is transferred to this function - * on call. + * Parses a binary input of size input_size. + * * @input: a heap-allocated buffer (ownership transferred). * @input_size: the byte-length of input * @ret_regions: return pointer to the relocation region array diff --git a/lib/kfuzztest/Makefile b/lib/kfuzztest/Makefile index 60414722c0c073..a16caee9f66e9c 100644 --- a/lib/kfuzztest/Makefile +++ b/lib/kfuzztest/Makefile @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: GPL-2.0-only +# SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_KFUZZTEST) += kfuzztest.o kfuzztest-objs := kfuzztest_main.o relocations.o parse.o diff --git a/lib/kfuzztest/kfuzzztest_examples.h b/lib/kfuzztest/kfuzzztest_examples.h index ed6c1005947184..f895e76b481af7 100644 --- a/lib/kfuzztest/kfuzzztest_examples.h +++ b/lib/kfuzztest/kfuzzztest_examples.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef KFUZZTEST_EXAMPLES_H #define KFUZZTEST_EXAMPLES_H diff --git a/lib/kfuzztest/parse.c b/lib/kfuzztest/parse.c index 31b74d8135a0d0..effd475b744bc5 100644 --- a/lib/kfuzztest/parse.c +++ b/lib/kfuzztest/parse.c @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #include int __kfuzztest_write_cb_common(struct file *filp, const char __user *buf, diff --git a/lib/kfuzztest/relocations.c b/lib/kfuzztest/relocations.c index 64669ed4ac17dd..50b957be2e278a 100644 --- a/lib/kfuzztest/relocations.c +++ b/lib/kfuzztest/relocations.c @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #include #include From 3e9815ebb1776aee45057e2a21ed1aa2e099b0b6 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Wed, 6 Aug 2025 09:53:33 +0000 Subject: [PATCH 45/73] kfuzztest: add help section to KConfig.kfuzztest --- lib/Kconfig.kfuzztest | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/Kconfig.kfuzztest b/lib/Kconfig.kfuzztest index 0fb000fe305c6a..dd9047459e051c 100644 --- a/lib/Kconfig.kfuzztest +++ b/lib/Kconfig.kfuzztest @@ -1,5 +1,12 @@ # SPDX-License-Identifier: GPL-2.0-only config KFUZZTEST - bool "Enable Kernel Fuzz Testing Framework (KFuzzTest)" - depends on DEBUG_FS + bool "KFuzzTest - enable support for internal fuzz targets" + depends on DEBUG_FS && KASAN + help + Enables support for the kernel fuzz testing framework (KFuzzTest), an + interface for exposing internal kernel functions to a userspace fuzzing + engine. KFuzzTest targets are exposed via a debugfs interface that + accepts serialized userspace inputs, and is designed to make it easier + to fuzz deeply nested kernel code that is hard to reach from the system + call boundary. From cc985fe86a458a2638a4fb79fad0645b24e94576 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Wed, 6 Aug 2025 11:02:12 +0000 Subject: [PATCH 46/73] kfuzztest: update comments and one code block refactored --- include/linux/kfuzztest.h | 292 ++++++++++++++++++++++++++---------- lib/kfuzztest/parse.c | 11 +- lib/kfuzztest/relocations.c | 8 +- 3 files changed, 219 insertions(+), 92 deletions(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index 4bd6905dfda865..8de95a24f19187 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -8,25 +8,89 @@ MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ethan Graham "); MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFuzzTest)"); +/** + * @brief The KFuzzTest Input Serialization Format + * + * KFuzzTest receives its input from userspace as a single binary blob. This + * format allows for the serialization of complex, pointer-rich C structures + * into a flat buffer that can be safely passed into the kernel. This format + * requires only a single copy from userspace into a kenrel buffer, and no + * further kernel allocations. Pointers are patched internally using a "region" + * system where each region corresponds to some pointed-to data. + * + * Regions should be padded to respect alignment constraints of their + * underlying types. These padded regions are poisoned by KFuzzTest to ensure + * that KASAN catches OOB accesses. + * + * The format consists of three main components: + * 1. A reloc_region_array: Defines the memory layout of the target structure + * by partitioning the payload into logical regions. Each logical region + * should contain the byte representation of the type that it represents, + * including any necessary padding. + * 2. A reloc_table: Provides "linking" instructions that tell the kernel how + * to patch pointer fields to point to the correct regions. By design, + * the first region (index 0) is passed as input into a FUZZ_TEST. + * 3. A Payload: The raw binary data for the structure and its associated + * buffers. This should be aligned to the maximum alignment of all + * regions to satisfy alignment requirements of the input types, but this + * isn't checked by the parser. The payload should have at least 8 bytes + * of trailing padding. + * + * For a detailed specification of the binary layout see the full documentation + * at: Documentation/dev-tools/kfuzztest.rst + */ + +/** + * struct reloc_region - Describes a single contiguous memory region in the + * payload. + * + * @start: The byte offset of this region from the start of the payload, which + * should be aligned to the alignment requirements of the region's + * underlying type. + * @size: The size of this region in bytes. + */ struct reloc_region { - uint32_t start; /* Offset from the start of the payload. */ + uint32_t start; uint32_t size; }; +/** + * struct reloc_region_array - An array of all memory regions. + * @num_regions: The total number of regions defined. + * @regions: A flexible array of `num_regions` region descriptors. + */ struct reloc_region_array { uint32_t num_regions; struct reloc_region regions[]; }; +/** + * struct reloc_entry describes a single pointer to be patched. + * + * @region_id: The index of the region in the `reloc_region_array` that + * contains the pointer. + * @region_offset: The start offset of the pointer inside of the region. + * @value: contains the index of the pointee region, or KFUZZTEST_REGIONID_NULL + * if the pointer is NULL. + */ struct reloc_entry { - uint32_t region_id; /* Region that pointer belongs to. */ - uint32_t region_offset; /* Offset from the beginning of the region. */ - uint32_t value; /* Pointee tegion identifier, or (void*)-1 if NULL */ + uint32_t region_id; + uint32_t region_offset; + uint32_t value; }; +/** + * struct reloc_entry is an array of all pointer relocations required by an + * input. + * + * @num_entries: the number of pointer relocations. + * @payload_offset: the number of padded bytes between the last relocation in + * entries, and the start of the payload data. + * @entries: array of relocations. + */ struct reloc_table { uint32_t num_entries; - uint32_t payloadOffset; /* Offset from start of relocation table */ + uint32_t payload_offset; struct reloc_entry entries[]; }; @@ -35,17 +99,26 @@ int __kfuzztest_write_cb_common(struct file *filp, const char __user *buf, size_t arg_size); /** - * Parses a binary input of size input_size. + * __kfuzztest_parse_input validates and parses the KFuzzTest input format. + * + * @input: A buffer containing the serialized test case. + * @input_size: The size in bytes of the @input buffer. + * @ret_regions: On success, updated to point to the relocation region array + * within the @input buffer. + * @ret_reloc_table: On success, updated to point to the relocation table + * within the @input buffer. + * @ret_payload_start: On success, updated to point to the start of the data + * payload within the @input buffer. + * @ret_payload_end: On success, updated to point to the first byte after the + * end of the data payload. * - * @input: a heap-allocated buffer (ownership transferred). - * @input_size: the byte-length of input - * @ret_regions: return pointer to the relocation region array - * @ret_reloc_table: return pointer to the relocation table - * @ret_payload_start: return pointer to the start of payload the data - * @ret_payload_end: return pointer to the end of the payload data, i.e., the - * first address that is out of the bounds of the payload. + * This function performs the initial validation of the binary input blob. It + * checks that the declared sizes of the region array and relocation table are + * within the bounds of the total input size and extracts pointers to the + * main components of the format. It does not perform a deep validation of + * the entries themselves, which is instead done by __kfuzztest_relocate. * - * @return 0 on success, or an error code. + * Returns: 0 on success, or a negative error code on failure. */ int __kfuzztest_parse_input(void *input, size_t input_size, struct reloc_region_array **ret_regions, @@ -53,7 +126,25 @@ int __kfuzztest_parse_input(void *input, size_t input_size, void **ret_payload_start, void **ret_payload_end); /** - * Relocates a parsed input into kernel memory. + * __kfuzztest_relocate hydrates the payload by patching pointers. + * + * @regions: The relocation region array parsed from the input. + * @rt: The relocation table parsed from the input. + * @payload_start: A pointer to the start of the data payload. + * @payload_end: A pointer to the first byte after the end of the payload. + * + * This function iterates through the region array and relocation table to + * patch the pointers inside of the payload, reconstructing pointer-pointee + * relationships between the logical regions of the fuzz driver input. For + * each entry in @rt, it calculates the address the address of a pointer field + * within the payload and sets it to the start address of its target region, or + * a NULL pointer if marked with KFUZZTEST_REGIONID_NULL. + * + * The padded areas between each region are poisoned with a KASAN slab redzone + * to enable the detection of byte-accurate OOB accesses in the fuzz logic. + * + * Returns: 0 on success, or a negative error code if the relocation data is + * found to be corrupt (e.g., invalid pointers). */ int __kfuzztest_relocate(struct reloc_region_array *regions, struct reloc_table *rt, void *payload_start, @@ -69,45 +160,64 @@ static_assert(sizeof(struct kfuzztest_target) == 32, "struct kfuzztest_target should have size 32"); /** - * FUZZ_TEST - defines a KFuzzTest target. + * FUZZ_TEST defines a KFuzzTest target. + * + * @test_name: The unique identifier for the fuzz test, which is used to name + * the debugfs entry, e.g., /sys/kernel/debug/kftf/@test_name. + * @test_arg_type: The struct type that defines the inputs for the test. This + * must be the full struct type (e.g., "struct my_inputs"), not a typedef. * - * @test_name: Name of the fuzz target, which is used to create the associated - * debufs entries. - * @test_arg_type: the input type of fuzz target. This should always be a - * struct type even when fuzzing with a single input parameter in order - * to take advantage of the domain constraint and annotation systems. See - * usage example below. + * Context: + * This macro is the primary entry point for the KFuzzTest framework. It + * generates all the necessary boilerplate for a fuzz test, including: + * - A static `struct kfuzztest_target` instance that is placed in a + * dedicated ELF section for discovery by userspace tools. + * - A `debugfs` write callback that handles receiving serialized data from + * a fuzzer, parsing it, and "hydrating" it into a valid C struct. + * - A function stub where the developer places the test logic. * + * User-Provided Logic: + * The developer must provide the body of the fuzz test logic within the curly + * braces following the macro invocation. Within this scope, the framework + * provides the following variables: * - * This macro generates all of the necessary boilerplate for a KFuzzTest - * driver, which is placed in a dedicated ".kfuzztest_target" that is used by - * the KFuzzTest module and can be read by a fuzzing engine. + * - `arg`: A pointer of type `@test_arg_type *` to the fully hydrated input + * structure. All pointer fields within this struct have been relocated + * and are valid kernel pointers. This is the primary variable to use + * for accessing fuzzing inputs. * - * For each test, this macro generates - * - A buffer to receive input through the debugfs entry - * - A mutex to protect the input buffer - * - A `struct kfuzztest_target` instance + * - `regions`: A pointer of type `struct reloc_region_array *`. This is an + * advanced feature that allows access to the raw region metadata, which + * can be useful for checking the actual allocated size of a buffer via + * `KFUZZTEST_REGION_SIZE(n)`. * - * Example usage: + * Example Usage: * - * // Assume that we are fuzzing some function func(T1 param1, ... TN paramN). - * // Define input type of the fuzz target. This should be always be a struct. - * struct test_arg_type { - * T1 arg1; - * ... - * TN argn; + * // 1. The kernel function we want to fuzz. + * int process_data(const char *data, size_t len); + * + * // 2. Define a struct to hold all inputs for the function. + * struct process_data_inputs { + * const char *data; + * size_t len; * }; * - * // Define the test case. - * FUZZ_TEST(test_func, struct test_arg_type) + * // 3. Define the fuzz test using the FUZZ_TEST macro. + * FUZZ_TEST(process_data_fuzzer, struct process_data_inputs) * { - * int ret; - * // arg is provided by the macro, and is of type struct test_arg_type. - * ret = func(arg.arg1, ..., arg.argn); - * // Validate the return value if testing for correctness. - * if (ret != expected_value) { - * KFUZZTEST_REPORT_BUG("Unexpected return value"); - * } + * int ret; + * // Use KFUZZTEST_EXPECT_* to enforce preconditions. + * // The test will exit early if data is NULL. + * KFUZZTEST_EXPECT_NOT_NULL(process_data_inputs, data); + * + * // Use KFUZZTEST_ANNOTATE_* to provide hints to the fuzzer. + * // This links the 'len' field to the 'data' buffer. + * KFUZZTEST_ANNOTATE_LEN(process_data_inputs, len, data); + * + * // Call the function under test using the 'arg' variable. OOB memory + * // accesses will be caught by KASAN, but the user can also choose to + * // validate the return value and log any failures. + * ret = process_data(arg->data, arg->len); * } */ #define FUZZ_TEST(test_name, test_arg_type) \ @@ -170,37 +280,31 @@ enum kfuzztest_constraint_type : uint8_t { }; /** - * Domain constraints are used to restrict the values that the fuzz driver - * accepts, enforcing early exit when not satisfied. Domain constraints are - * encoded in vmlinux under the `__kfuzztest_constraint` section. A good - * fuzzing engine should be aware of these domain constraints during input - * generation and mutation. + * struct kfuzztest_constraint defines a metadata record for a domain + * constraint. * - * struct kfuzztest_constraint defines a domain constraint for a structure - * field. + * Domain constraints are rules about the input data that must be satisfied for + * a fuzz test to proceed. While they are enforced in the kernel with a runtime + * check, they are primarily intended as a discoverable contract for userspace + * fuzzers. * - * @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 + * Instances of this struct are generated by the KFUZZTEST_EXPECT_* macros + * and placed into the read-only ".kfuzztest_constraint" ELF section of the + * vmlinux binary. A fuzzer can parse this section to learn about the + * constraints and generate valid inputs more intelligently. * - * Example usage: + * For an example of how these constraints are used within a fuzz test, see the + * documentation for the FUZZ_TEST() macro. * - * struct foo { - * struct bar *a; - * int b - * }; - * - * FUZZ_TEST(test_name, struct foo) - * { - * // Early exit if foo.a == NULL. - * KFUZZTEST_EXPECT_NOT_NULL(foo, a); - * // Early exit if foo < 23 || foo > 42 - * KFUZZTEST_EXPECT_IN_RANGE(foo, b, 23, 42); - * // User-defined fuzz logic. - * } + * @input_type: The name of the input struct type, without the leading + * "struct ". + * @field_name: The name of the field within the struct that this constraint + * applies to. + * @value1: The primary value used in the comparison (e.g., the upper + * bound for EXPECT_LE). + * @value2: The secondary value, used only for multi-value comparisons + * (e.g., the upper bound for EXPECT_IN_RANGE). + * @type: The type of the constraint. */ struct kfuzztest_constraint { const char *input_type; @@ -259,13 +363,15 @@ static_assert(sizeof(struct kfuzztest_constraint) == 64, /** * Annotations express attributes about structure fields that can't be easily - * verified at runtime, and are intended as a hint to the fuzzing engine. + * or safely verified at runtime. They are intended as hints to the fuzzing + * engine to help it generate more semantically correct and effective inputs. + * Unlike constraints, annotations do not add any runtime checks and do not + * cause a test to exit early. * - * For example, a char* could either be a raw byte buffer or a string, where - * the latter is null terminated. If a function accepts a null-terminated - * string without a length and is passed an arbitrary byte buffer, we - * may get false positive KASAN reports, for example. However, verifying that - * the char buffer is null-termined could itself trigger a memory overflow. + * For example, a `char *` field could be a raw byte buffer or a C-style + * null-terminated string. A fuzzer that is aware of this distinction can avoid + * creating inputs that would cause trivial, uninteresting crashes from reading + * past the end of a non-null-terminated buffer. */ enum kfuzztest_annotation_attribute : uint8_t { ATTRIBUTE_LEN = 0, @@ -273,6 +379,30 @@ enum kfuzztest_annotation_attribute : uint8_t { ATTRIBUTE_ARRAY, }; +/** + * struct kfuzztest_annotation defines a metadata record for a fuzzer hint. + * + * This struct captures a single hint about a field in the input structure. + * Instances are generated by the KFUZZTEST_ANNOTATE_* macros and are placed + * into the read-only ".kfuzztest_annotation" ELF section of the vmlinux binary. + * + * A userspace fuzzer can parse this section to understand the semantic + * relationships between fields (e.g., which field is a length for which + * buffer) and the expected format of the data (e.g., a null-terminated + * string). This allows the fuzzer to be much more intelligent during input + * generation and mutation. + * + * For an example of how annotations are used within a fuzz test, see the + * documentation for the FUZZ_TEST() macro. + * + * @input_type: The name of the input struct type. + * @field_name: The name of the field being annotated (e.g., the data + * buffer field). + * @linked_field_name: For annotations that link two fields (like + * ATTRIBUTE_LEN), this is the name of the related field (e.g., the + * length field). For others, this may be unused. + * @attrib: The type of the annotation hint. + */ struct kfuzztest_annotation { const char *input_type; const char *field_name; @@ -310,13 +440,9 @@ struct kfuzztest_annotation { #define KFUZZTEST_ANNOTATE_LEN(arg_type, field, linked_field) \ __KFUZZTEST_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) -/** - * The input format is such that the value field is a region index. We reserve - * this value to encode a NULL pointer in the input. - */ #define KFUZZTEST_REGIONID_NULL U32_MAX -/* The size of a region if it exists, or 0 if it does not. */ +/** Get the size of a payload region from within a FUZZ_TEST body */ #define KFUZZTEST_REGION_SIZE(n) \ ((n) < (regions->num_regions) ? (regions->regions[n].size) : 0) diff --git a/lib/kfuzztest/parse.c b/lib/kfuzztest/parse.c index effd475b744bc5..82f87bd0fcec3c 100644 --- a/lib/kfuzztest/parse.c +++ b/lib/kfuzztest/parse.c @@ -22,7 +22,7 @@ int __kfuzztest_parse_input(void *input, size_t input_size, pr_info("[ENTER] %s", __FUNCTION__); int err; void *payload_end, *payload_start; - size_t reloc_entries_size, regions_size; + size_t reloc_table_size, regions_size; struct reloc_table *rt; struct reloc_region_array *regions; @@ -44,15 +44,16 @@ int __kfuzztest_parse_input(void *input, size_t input_size, goto fail; } - reloc_entries_size = - sizeof(*rt) + rt->num_entries * sizeof(struct reloc_entry); - if ((char *)rt + reloc_entries_size > (char *)payload_end) { + reloc_table_size = sizeof(*rt) + + rt->num_entries * sizeof(struct reloc_entry) + + rt->payload_offset; + if ((char *)rt + reloc_table_size > (char *)payload_end) { err = -EINVAL; goto fail; } pr_info("kfuzztest: num relocations = %u, size = %zu", rt->num_entries, - reloc_entries_size); + reloc_table_size); payload_start = (char *)(rt->entries + rt->num_entries); if ((char *)payload_start > (char *)payload_end) { diff --git a/lib/kfuzztest/relocations.c b/lib/kfuzztest/relocations.c index 50b957be2e278a..5636816f442b59 100644 --- a/lib/kfuzztest/relocations.c +++ b/lib/kfuzztest/relocations.c @@ -74,14 +74,15 @@ int __kfuzztest_relocate(struct reloc_region_array *regions, return -EINVAL; if (src.start >= src.size) return -EINVAL; + if (re.value == KFUZZTEST_REGIONID_NULL) { *ptr_location = (uintptr_t)NULL; - } else { - if (re.value >= regions->num_regions) - return -EINVAL; + } else if (re.value < regions->num_regions) { dst = regions->regions[re.value]; *ptr_location = (uintptr_t)((char *)payload_start + dst.start); + } else { + return -EINVAL; } } @@ -103,6 +104,5 @@ int __kfuzztest_relocate(struct reloc_region_array *regions, kfuzztest_poison_range(poison_start, poison_end); } - return 0; } From 4ad76d024c2d5324ad65b75179a26552a6e02299 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Wed, 6 Aug 2025 11:07:05 +0000 Subject: [PATCH 47/73] kfuzztest: move examples to .c file --- lib/kfuzztest/Makefile | 2 +- lib/kfuzztest/{kfuzzztest_examples.h => kfuzztest_examples.c} | 0 lib/kfuzztest/kfuzztest_main.c | 2 -- 3 files changed, 1 insertion(+), 3 deletions(-) rename lib/kfuzztest/{kfuzzztest_examples.h => kfuzztest_examples.c} (100%) diff --git a/lib/kfuzztest/Makefile b/lib/kfuzztest/Makefile index a16caee9f66e9c..af7fd847b89e30 100644 --- a/lib/kfuzztest/Makefile +++ b/lib/kfuzztest/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_KFUZZTEST) += kfuzztest.o -kfuzztest-objs := kfuzztest_main.o relocations.o parse.o +kfuzztest-objs := kfuzztest_main.o relocations.o parse.o kfuzztest_examples.o diff --git a/lib/kfuzztest/kfuzzztest_examples.h b/lib/kfuzztest/kfuzztest_examples.c similarity index 100% rename from lib/kfuzztest/kfuzzztest_examples.h rename to lib/kfuzztest/kfuzztest_examples.c diff --git a/lib/kfuzztest/kfuzztest_main.c b/lib/kfuzztest/kfuzztest_main.c index 4d26d3df46b10b..af16cae8acdd35 100644 --- a/lib/kfuzztest/kfuzztest_main.c +++ b/lib/kfuzztest/kfuzztest_main.c @@ -11,8 +11,6 @@ #include #include -#include "kfuzzztest_examples.h" - extern const struct kfuzztest_target __kfuzztest_targets_start[]; extern const struct kfuzztest_target __kfuzztest_targets_end[]; From 14f3851519726349e3f1e2422fe947a463f2d7f9 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Wed, 6 Aug 2025 12:00:16 +0000 Subject: [PATCH 48/73] kfuzztest: wrap KFUZZTEST_TABLE def in an ifdef block --- arch/x86/kernel/vmlinux.lds.S | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/arch/x86/kernel/vmlinux.lds.S b/arch/x86/kernel/vmlinux.lds.S index fdcca6b4b2f44e..484e3e1ffb9f9e 100644 --- a/arch/x86/kernel/vmlinux.lds.S +++ b/arch/x86/kernel/vmlinux.lds.S @@ -113,6 +113,7 @@ ASSERT(__relocate_kernel_end - __relocate_kernel_start <= KEXEC_CONTROL_CODE_MAX #define KEXEC_RELOCATE_KERNEL #endif +#ifdef CONFIG_KFUZZTEST #define KFUZZTEST_TABLE \ . = ALIGN(PAGE_SIZE); \ __kfuzztest_targets_start = .; \ @@ -125,7 +126,11 @@ ASSERT(__relocate_kernel_end - __relocate_kernel_start <= KEXEC_CONTROL_CODE_MAX . = ALIGN(PAGE_SIZE); \ __kfuzztest_annotations_start = .; \ KEEP(*(.kfuzztest_annotation)); \ - __kfuzztest_annotations_end = .; \ + __kfuzztest_annotations_end = .; + +#else /* CONFIG_KFUZZTEST */ +#define KFUZZTEST_TABLE +#endif /* CONFIG_KFUZZTEST */ PHDRS { text PT_LOAD FLAGS(5); /* R_E */ From bd5d83376476522e1860f35b73169e0c418d2729 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Wed, 6 Aug 2025 13:28:03 +0000 Subject: [PATCH 49/73] kfuzztest: start documentation kfuzztest: update documentation --- Documentation/dev-tools/kfuzztest.rst | 179 ++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 Documentation/dev-tools/kfuzztest.rst diff --git a/Documentation/dev-tools/kfuzztest.rst b/Documentation/dev-tools/kfuzztest.rst new file mode 100644 index 00000000000000..ad21a4fa5ad40b --- /dev/null +++ b/Documentation/dev-tools/kfuzztest.rst @@ -0,0 +1,179 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. Copyright (C) 2025, Google LLC. + +Kernel Fuzz Testing Framework (KFuzzTest) +========================================= + +Overview +-------- + +The Kernel Fuzz Testing Framework (KFuzzTest) is a framework designed to expose +internal kernel functions to a userspace fuzzing engine. + +It is intended for testing stateless or low-state functions that are difficult +to reach from the system call interface, such as routines involved in file +format parsing or complex data transformations. This provides a method for +in-situ fuzzing of kernel code without requiring that it be built as a separate +userspace library or that its dependencies be stubbed out. + +The framework consists of four main components: + +1. An API, based on the ``FUZZ_TEST`` macro, for defining test targets + directly in the kernel tree. +2. A binary serialization format for passing complex, pointer-rich data + structures from userspace to the kernel. +3. A ``debugfs`` interface through which a userspace fuzzer submits + serialized test inputs. +4. Metadata embedded in dedicated ELF sections of the ``vmlinux`` binary to + allow for the discovery of available fuzz targets by external tooling. + +Supported Architectures +----------------------- + +KFuzzTest is currently only supported for x86_64. + +Usage +----- + +To enable KFuzzTest, configure the kernel with:: + + CONFIG_KFUZZTEST=y + +which depends on ``CONFIG_DEBUGFS`` for receiving userspace inputs. + +Declaring a KFuzzTest target +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A fuzz target is defined directly in a .c file, typically alongside the function +being tested. This process involves three main parts: defining an input +structure, writing the test body using the ``FUZZ_TEST`` macro, and optionally +adding metadata for the fuzzer. + +The following example illustrates how to create a fuzz target for a function +``int process_data(const char *data, size_t len).`` + +.. code-block:: c + + // 1. Define a struct to model the inputs for the function under test. + // Each field corresponds to an argument needed by the function. + struct process_data_inputs { + const char *data; + size_t len; + }; + + // 2. Define the fuzz target using the FUZZ_TEST macro. + // The first parameter is a unique name for the target. + // The second parameter is the input struct defined above. + FUZZ_TEST(test_process_data, struct process_data_inputs) + { + // Within this body, the 'arg' variable is a pointer to a + // fully initialized 'struct process_data_inputs'. + + // 3. (Optional) Add constraints to define preconditions. + // This check ensures 'arg->data' is not NULL. If the condition + // is not met, the test exits early. This also creates metadata + // to inform the fuzzer + KFUZZTEST_EXPECT_NOT_NULL(process_data_inputs, data); + + // 4. (Optional) Add annotations to provide semantic hints. + // This annotation informs the fuzzer that the 'len' field + // is the length of the buffer pointed to by 'data'. + // Annotations do not add any runtime checks. + KFUZZTEST_ANNOTATE_LEN(process_data_inputs, len, data); + + // 5. Call the kernel function with the provided inputs. + // Memory errors like out-of-bounds accesses on 'arg->data' + // will be detected by KASAN. + process_data(arg->data, arg->len); + } + +KFuzzTest provides two families of macros to improve the quality of fuzzing: + +- ``KFUZZTEST_EXPECT_*``: These macros define constraints, which are + preconditions that must be true for the test to proceed. They are enforced + with a runtime check in the kernel. If a check fails, the current test run is + aborted. This metadata helps the userspace fuzzer avoid generating invalid + inputs. + +- ``KFUZZTEST_ANNOTATE_*``: These macros define annotations, which are purely + semantic hints for the fuzzer. They do not add any runtime checks and exist + only to help the fuzzer generate more intelligent and structurally correct + inputs. For example, KFUZZTEST_ANNOTATE_LEN links a size field to a pointer + field, which is a common pattern in C APIs. + +Input Format +------------ + +KFuzzTest targets receive their inputs from userspace via a write to a dedicated +debugfs ``/sys/kernel/debug/kfuzztest//input``. + +The data written to this file must be a single binary blob that follows a +specific serialization format. This format is designed to allow complex, +pointer-rich C structures to be represented in a flat buffer, requiring only a +single kernel allocation and copy from userspace. + +The format consists of three main parts laid out sequentially: a region array, +a relocation table, and the payload.:: + + +----------------+---------------------+-----------------+----------------+ + | region array | relocation table | padding | payload | + +----------------+---------------------+-----------------+----------------+ + +Region Array +~~~~~~~~~~~~ + +This component is a header that describes how the raw data in the Payload is +partitioned into logical memory regions. It consists of a count of regions +followed by an array of ``struct reloc_region``, where each entry defines a +single region with its size and offset from the start of the payload. + +.. code-block:: c + + struct reloc_region { + uint32_t offset; + uint32_t size; + }; + + struct reloc_region_array { + uint32_t num_regions; + struct reloc_region regions[]; + }; + +By convention, region 0 represents the top-level input struct that is passed +as the arg variable to the FUZZ_TEST body. Subsequent regions typically +represent data buffers pointed to by fields within that struct. Region array +entries must be ordered by offset ascending, and must not overlap with one +another. + +Relocation Table +~~~~~~~~~~~~~~~~ + +The relocation table provides the instructions for the kernel to "hydrate" the +payload by patching pointer fields. It contains an array of struct reloc_entry +items. Each entry acts as a linking instruction, specifying: + +- The location of a pointer that needs to be patched (identified by a region + ID and an offset within that region). + +- The target region that the pointer should point to (identified by the + target's region ID) or ``KFUZZTEST_REGIONID_NULL`` if the pointer is ``NULL``. + +This table also specifies the amount of padding between its end and the start +of the payload, which should be at least 8 bytes. + +Payload +~~~~~~~ + +The payload contains the raw binary data for all regions, concatenated together +according to their specified offsets. + +- Alignment: The start of the payload must be aligned to the most restrictive + alignment requirement of all its constituent regions. The framework ensures + that each region within the payload is then placed at an offset that respects + its own type's alignment. + +- Padding and Poisoning: The space between the end of one region's data and the + beginning of the next must be sufficient for padding. KFuzzTest poisons this + unused padding with KASAN, allowing for precise detection of out-of-bounds + memory accesses between adjacent buffers. This padding should be at least + ``KFUZZTEST_POISON_SIZE`` bytes as defined in `include/linux/kfuzztest.h``. From f39fd771266db8432e171a0d53bb780570478865 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Thu, 7 Aug 2025 09:14:01 +0000 Subject: [PATCH 50/73] kfuzztest: add Google copyright notice, remove ifdefs in .c file --- Documentation/dev-tools/kfuzztest.rst | 2 +- include/linux/kfuzztest.h | 3 ++- lib/kfuzztest/kfuzztest_examples.c | 8 ++------ lib/kfuzztest/kfuzztest_main.c | 4 +++- lib/kfuzztest/parse.c | 1 + lib/kfuzztest/relocations.c | 3 ++- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Documentation/dev-tools/kfuzztest.rst b/Documentation/dev-tools/kfuzztest.rst index ad21a4fa5ad40b..1c8723a5b9b980 100644 --- a/Documentation/dev-tools/kfuzztest.rst +++ b/Documentation/dev-tools/kfuzztest.rst @@ -1,5 +1,5 @@ .. SPDX-License-Identifier: GPL-2.0 -.. Copyright (C) 2025, Google LLC. +.. Copyright 2025 Google LLC Kernel Fuzz Testing Framework (KFuzzTest) ========================================= diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index 8de95a24f19187..4ec7813cfbea33 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -1,4 +1,5 @@ -/* SPDX-License-Identifier: GPL-2.0 */ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2025 Google LLC */ #ifndef KFUZZTEST_H #define KFUZZTEST_H diff --git a/lib/kfuzztest/kfuzztest_examples.c b/lib/kfuzztest/kfuzztest_examples.c index f895e76b481af7..58971edd4965c0 100644 --- a/lib/kfuzztest/kfuzztest_examples.c +++ b/lib/kfuzztest/kfuzztest_examples.c @@ -1,7 +1,5 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef KFUZZTEST_EXAMPLES_H -#define KFUZZTEST_EXAMPLES_H - +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2025 Google LLC */ #include struct nested_buffers { @@ -47,5 +45,3 @@ FUZZ_TEST(test_overflow_on_nested_buffer, struct nested_buffers) for (size_t i = 0; i <= a_size; i++) c = arg->a[i]; } - -#endif /* KFUZZTEST_EXAMPLES_H */ diff --git a/lib/kfuzztest/kfuzztest_main.c b/lib/kfuzztest/kfuzztest_main.c index af16cae8acdd35..766452b63ec449 100644 --- a/lib/kfuzztest/kfuzztest_main.c +++ b/lib/kfuzztest/kfuzztest_main.c @@ -1,10 +1,12 @@ -/* SPDX-License-Identifier: GPL-2.0 */ +// SPDX-License-Identifier: GPL-2.0 /* * Kernel Fuzz Testing Framework (KFuzzTest) - Core Module * * This module is responsible for discovering and initializing all fuzz test * cases defined using the FUZZ_TEST() macro. It creates a debugfs interface * under /sys/kernel/debug/kfuzztest/ for userspace to interact with each test. + * + * Copyright 2025 Google LLC */ #include #include diff --git a/lib/kfuzztest/parse.c b/lib/kfuzztest/parse.c index 82f87bd0fcec3c..0aa4150ba52dc5 100644 --- a/lib/kfuzztest/parse.c +++ b/lib/kfuzztest/parse.c @@ -1,4 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright 2025 Google LLC */ #include int __kfuzztest_write_cb_common(struct file *filp, const char __user *buf, diff --git a/lib/kfuzztest/relocations.c b/lib/kfuzztest/relocations.c index 5636816f442b59..a59b96733ea41f 100644 --- a/lib/kfuzztest/relocations.c +++ b/lib/kfuzztest/relocations.c @@ -1,4 +1,5 @@ -/* SPDX-License-Identifier: GPL-2.0 */ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2025 Google LLC */ #include #include From af9d76f5a9647328189740d3277926fe6e3cb987 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Thu, 7 Aug 2025 12:03:44 +0000 Subject: [PATCH 51/73] kfuzztest: fix parsing logic that didn't account for padding --- lib/kfuzztest/parse.c | 38 +++++++++----------------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/lib/kfuzztest/parse.c b/lib/kfuzztest/parse.c index 0aa4150ba52dc5..efa8da40b18887 100644 --- a/lib/kfuzztest/parse.c +++ b/lib/kfuzztest/parse.c @@ -6,12 +6,10 @@ int __kfuzztest_write_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) { + if (len != arg_size) return -EINVAL; - } - if (simple_write_to_buffer((void *)arg, arg_size, off, buf, len) < 0) { + if (simple_write_to_buffer((void *)arg, arg_size, off, buf, len) < 0) return -EFAULT; - } return 0; } @@ -20,8 +18,6 @@ int __kfuzztest_parse_input(void *input, size_t input_size, struct reloc_table **ret_reloc_table, void **ret_payload_start, void **ret_payload_end) { - pr_info("[ENTER] %s", __FUNCTION__); - int err; void *payload_end, *payload_start; size_t reloc_table_size, regions_size; struct reloc_table *rt; @@ -37,39 +33,23 @@ int __kfuzztest_parse_input(void *input, size_t input_size, regions_size = sizeof(*regions) + regions->num_regions * sizeof(struct reloc_region); - pr_info("kfuzztest: num regions = %u", regions->num_regions); - rt = (struct reloc_table *)((char *)regions + regions_size); - if ((char *)rt > (char *)payload_end) { - err = -EINVAL; - goto fail; - } + if ((char *)rt > (char *)payload_end) + return -EINVAL; reloc_table_size = sizeof(*rt) + rt->num_entries * sizeof(struct reloc_entry) + rt->payload_offset; - if ((char *)rt + reloc_table_size > (char *)payload_end) { - err = -EINVAL; - goto fail; - } - - pr_info("kfuzztest: num relocations = %u, size = %zu", rt->num_entries, - reloc_table_size); - - payload_start = (char *)(rt->entries + rt->num_entries); - if ((char *)payload_start > (char *)payload_end) { - err = -EINVAL; - goto fail; - } + if ((char *)rt + reloc_table_size > (char *)payload_end) + return -EINVAL; - pr_info("kfuzztest: payload: [ %px, %px )", payload_start, payload_end); + payload_start = (char *)(rt) + reloc_table_size; + if ((char *)payload_start > (char *)payload_end) + return -EINVAL; *ret_regions = regions; *ret_reloc_table = rt; *ret_payload_start = payload_start; *ret_payload_end = payload_end; return 0; -fail: - kfree(input); - return err; } From 1a286cb8f0186ff4d1facb523c29123cd07eb915 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Thu, 7 Aug 2025 12:04:17 +0000 Subject: [PATCH 52/73] kfuzztest: update examples file --- lib/kfuzztest/kfuzztest_examples.c | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/lib/kfuzztest/kfuzztest_examples.c b/lib/kfuzztest/kfuzztest_examples.c index 58971edd4965c0..d7d1fae6e54665 100644 --- a/lib/kfuzztest/kfuzztest_examples.c +++ b/lib/kfuzztest/kfuzztest_examples.c @@ -15,33 +15,17 @@ struct nested_buffers { * * | a | b | pad[8] | *a | pad[8] | *b | * - * In DISTINCT mode, this will result in 3 distinct kmalloc'd regions, and - * in POISONED mode, the buffer is untouched but the padding will be poisoned. + * We expect to see a KASAN warning by overflowing one byte into the A buffer. * - * In this test case, we look to see that a KASAN warning is triggered in both - * cases when overflowing on *a by one. */ FUZZ_TEST(test_overflow_on_nested_buffer, struct nested_buffers) { - u32 a_size; - u32 b_size; - KFUZZTEST_EXPECT_NOT_NULL(nested_buffers, a); KFUZZTEST_EXPECT_NOT_NULL(nested_buffers, b); KFUZZTEST_ANNOTATE_LEN(nested_buffers, a_len, a); KFUZZTEST_ANNOTATE_LEN(nested_buffers, b_len, b); - volatile char c; - pr_info("a = [%px, %px)", arg->a, arg->a + arg->a_len); - pr_info("b = [%px, %px)", arg->b, arg->b + arg->b_len); - pr_info("a_len = %zu", arg->a_len); - pr_info("b_len = %zu", arg->b_len); - - a_size = KFUZZTEST_REGION_SIZE(1); - b_size = KFUZZTEST_REGION_SIZE(2); - pr_info("actual sizes = %u, %u", a_size, b_size); - /* Buffer overflow out of a bounds. This should be caught by KASAN. */ - for (size_t i = 0; i <= a_size; i++) - c = arg->a[i]; + for (size_t i = 0; i <= arg->a_len; i++) + READ_ONCE(arg->a[i]); } From b25969f22ee711668639f502259b32c46fc0a458 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Thu, 7 Aug 2025 12:04:55 +0000 Subject: [PATCH 53/73] kfuzztest: update state name from st to state --- lib/kfuzztest/kfuzztest_main.c | 62 ++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/lib/kfuzztest/kfuzztest_main.c b/lib/kfuzztest/kfuzztest_main.c index 766452b63ec449..a0490b1088e69a 100644 --- a/lib/kfuzztest/kfuzztest_main.c +++ b/lib/kfuzztest/kfuzztest_main.c @@ -55,7 +55,7 @@ struct kfuzztest_state { }; /* Global static variable to hold all state for the module. */ -static struct kfuzztest_state st; +static struct kfuzztest_state state; const umode_t KFUZZTEST_INPUT_PERMS = 0222; @@ -79,55 +79,57 @@ static int __init kfuzztest_init(void) num_test_cases = __kfuzztest_targets_end - __kfuzztest_targets_start; - st.debugfs_state = + state.debugfs_state = kmalloc(num_test_cases * sizeof(struct kfuzztest_debugfs_state), GFP_KERNEL); - if (!st.debugfs_state) + if (!state.debugfs_state) return -ENOMEM; - else if (IS_ERR(st.debugfs_state)) - return PTR_ERR(st.debugfs_state); + else if (IS_ERR(state.debugfs_state)) + return PTR_ERR(state.debugfs_state); /* Create the main "kfuzztest" directory in /sys/kernel/debug. */ - st.kfuzztest_dir = debugfs_create_dir("kfuzztest", NULL); - if (!st.kfuzztest_dir) { + state.kfuzztest_dir = debugfs_create_dir("kfuzztest", NULL); + if (!state.kfuzztest_dir) { pr_warn("KFuzzTest: could not create debugfs"); return -ENODEV; } - if (IS_ERR(st.kfuzztest_dir)) { - st.kfuzztest_dir = NULL; - return PTR_ERR(st.kfuzztest_dir); + if (IS_ERR(state.kfuzztest_dir)) { + state.kfuzztest_dir = NULL; + return PTR_ERR(state.kfuzztest_dir); } for (targ = __kfuzztest_targets_start; targ < __kfuzztest_targets_end; targ++, i++) { /* Create debugfs directory for the target. */ - st.debugfs_state[i].target_dir = - debugfs_create_dir(targ->name, st.kfuzztest_dir); + state.debugfs_state[i].target_dir = + debugfs_create_dir(targ->name, state.kfuzztest_dir); - if (!st.debugfs_state[i].target_dir) { + if (!state.debugfs_state[i].target_dir) { ret = -ENOMEM; goto cleanup_failure; - } else if (IS_ERR(st.debugfs_state[i].target_dir)) { - ret = PTR_ERR(st.debugfs_state[i].target_dir); + } else if (IS_ERR(state.debugfs_state[i].target_dir)) { + ret = PTR_ERR(state.debugfs_state[i].target_dir); goto cleanup_failure; } /* Create an input file under the target's directory. */ - st.debugfs_state[i].input_dentry.fops = + state.debugfs_state[i].input_dentry.fops = (struct file_operations){ .owner = THIS_MODULE, .write = targ->write_input_cb, }; - st.debugfs_state[i].input_dentry.dentry = debugfs_create_file( - "input", KFUZZTEST_INPUT_PERMS, - st.debugfs_state[i].target_dir, NULL, - &st.debugfs_state[i].input_dentry.fops); - if (!st.debugfs_state[i].input_dentry.dentry) { + state.debugfs_state[i].input_dentry.dentry = + debugfs_create_file( + "input", KFUZZTEST_INPUT_PERMS, + state.debugfs_state[i].target_dir, NULL, + &state.debugfs_state[i].input_dentry.fops); + if (!state.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); + } else if (IS_ERR(state.debugfs_state[i].input_dentry.dentry)) { + ret = PTR_ERR( + state.debugfs_state[i].input_dentry.dentry); goto cleanup_failure; } @@ -137,22 +139,22 @@ static int __init kfuzztest_init(void) return 0; cleanup_failure: - debugfs_remove_recursive(st.kfuzztest_dir); + debugfs_remove_recursive(state.kfuzztest_dir); return ret; } static void __exit kfuzztest_exit(void) { pr_info("KFuzzTest: exiting"); - if (!st.kfuzztest_dir) + if (!state.kfuzztest_dir) return; - debugfs_remove_recursive(st.kfuzztest_dir); - st.kfuzztest_dir = NULL; + debugfs_remove_recursive(state.kfuzztest_dir); + state.kfuzztest_dir = NULL; - if (st.debugfs_state) { - kfree(st.debugfs_state); - st.debugfs_state = NULL; + if (state.debugfs_state) { + kfree(state.debugfs_state); + state.debugfs_state = NULL; } } From 3f96f02b00b7de0713c0921ed894f493964b5213 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Thu, 7 Aug 2025 12:05:17 +0000 Subject: [PATCH 54/73] kfuzztest: remove extraneous braces, prints, and add a check in poison Checks that KASAN is enabled before poisoning a region. --- lib/kfuzztest/relocations.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/lib/kfuzztest/relocations.c b/lib/kfuzztest/relocations.c index a59b96733ea41f..6021bc5be579a4 100644 --- a/lib/kfuzztest/relocations.c +++ b/lib/kfuzztest/relocations.c @@ -22,33 +22,26 @@ static void kfuzztest_poison_range(void *start, void *end) uintptr_t head_granule_start; size_t head_prefix_size; + if (!IS_ENABLED(CONFIG_KASAN)) + return; + if (start_addr >= end_addr) return; head_granule_start = ALIGN_DOWN(start_addr, 0x8); head_prefix_size = start_addr - head_granule_start; - if (IS_ENABLED(CONFIG_KASAN_GENERIC) && head_prefix_size > 0) { + if (IS_ENABLED(CONFIG_KASAN_GENERIC) && head_prefix_size > 0) kasan_poison_last_granule((void *)head_granule_start, head_prefix_size); - pr_info("kfuzztest: poisoned [%px, %px)", - (void *)head_granule_start + head_prefix_size, - (void *)head_granule_start + 8); - } poison_body_start = ALIGN(start_addr, 0x8); poison_body_end = ALIGN_DOWN(end_addr, 0x8); - pr_info("kfuzztest: want to additionally poison [%px, %px)", - (void *)poison_body_start, (void *)poison_body_end); - if (poison_body_start < poison_body_end) { + if (poison_body_start < poison_body_end) kasan_poison((void *)poison_body_start, poison_body_end - poison_body_start, POISON_REGION_END, false); - - pr_info("kfuzztest: poisoned [%px, %px)", - (void *)poison_body_start, (void *)poison_body_end); - } } int __kfuzztest_relocate(struct reloc_region_array *regions, From 1cba6b1e83c4957747b6fa9d0e3a2af90c206e07 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Thu, 7 Aug 2025 13:10:44 +0000 Subject: [PATCH 55/73] kfuzztest: add a debug helper function --- include/linux/kfuzztest.h | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index 4ec7813cfbea33..0e2f41abcfeea1 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -151,6 +151,37 @@ int __kfuzztest_relocate(struct reloc_region_array *regions, struct reloc_table *rt, void *payload_start, void *payload_end); +/* + * Dump some information on the parsed headers and payload. Can be useful for + * debugging inputs when writing an encoder for the KFuzzTest input format. + */ +__attribute__((unused)) static void +__kfuzztest_debug_header(struct reloc_region_array *regions, + struct reloc_table *rt, void *payload_start, + void *payload_end) +{ + uint32_t i; + pr_info("regions: { num_regions = %u } @ %px", regions->num_regions, + regions); + for (i = 0; i < regions->num_regions; i++) { + pr_info(" region_%u: { start: 0x%x, size: 0x%x }", i, + regions->regions[i].start, regions->regions[i].size); + } + + pr_info("reloc_table: { num_entries = %u, padding = %u } @ offset 0x%lx", + rt->num_entries, rt->payload_offset, + (char *)rt - (char *)regions); + for (i = 0; i < rt->num_entries; i++) { + pr_info(" reloc_%u: { src: %u, offset: 0x%x, dst: %u }", i, + rt->entries[i].region_id, rt->entries[i].region_offset, + rt->entries[i].value); + } + + pr_info("payload: [0x%lx, 0x%lx)", + (char *)payload_start - (char *)regions, + (char *)payload_end - (char *)regions); +} + struct kfuzztest_target { const char *name; const char *arg_type_name; From c85f16b02529ba72c67097dcddde9163bc6368ed Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Thu, 7 Aug 2025 13:12:10 +0000 Subject: [PATCH 56/73] kfuzztest: poison region preceding payload to catch underflows --- lib/kfuzztest/relocations.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/kfuzztest/relocations.c b/lib/kfuzztest/relocations.c index 6021bc5be579a4..6c515afd3692e9 100644 --- a/lib/kfuzztest/relocations.c +++ b/lib/kfuzztest/relocations.c @@ -98,5 +98,12 @@ int __kfuzztest_relocate(struct reloc_region_array *regions, kfuzztest_poison_range(poison_start, poison_end); } + + /* + * Poison the area preceding the payload. This corresponds to the end + * of the relocation table, which we can safely discard as it is no + * longer needed. + */ + kfuzztest_poison_range((char *)payload_start - 8, payload_start); return 0; } From 4820368849488faef4963e3c1f9f2edf33fa4796 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Thu, 7 Aug 2025 13:11:17 +0000 Subject: [PATCH 57/73] kfuzztest: update overflow example, add underflow example --- lib/kfuzztest/kfuzztest_examples.c | 40 ++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/lib/kfuzztest/kfuzztest_examples.c b/lib/kfuzztest/kfuzztest_examples.c index d7d1fae6e54665..4316f7a2a64939 100644 --- a/lib/kfuzztest/kfuzztest_examples.c +++ b/lib/kfuzztest/kfuzztest_examples.c @@ -20,12 +20,48 @@ struct nested_buffers { */ FUZZ_TEST(test_overflow_on_nested_buffer, struct nested_buffers) { + size_t i; + KFUZZTEST_EXPECT_NOT_NULL(nested_buffers, a); KFUZZTEST_EXPECT_NOT_NULL(nested_buffers, b); KFUZZTEST_ANNOTATE_LEN(nested_buffers, a_len, a); KFUZZTEST_ANNOTATE_LEN(nested_buffers, b_len, b); - /* Buffer overflow out of a bounds. This should be caught by KASAN. */ - for (size_t i = 0; i <= arg->a_len; i++) + pr_info("a = [%px, %px)", arg->a, arg->a + KFUZZTEST_REGION_SIZE(1)); + pr_info("b = [%px, %px)", arg->b, arg->b + KFUZZTEST_REGION_SIZE(2)); + + /* Ensure that all bytes in arg->b are accessible. */ + for (i = 0; i < arg->b_len; i++) + READ_ONCE(arg->b[i]); + /* + * Check that all bytes in arg->a are accessible, and provoke an OOB + * on the first byte to the right of the buffer which will trigger + * a KASAN report. + */ + for (i = 0; i < arg->a_len; i++) READ_ONCE(arg->a[i]); } + +struct some_buffer { + char *buf; + size_t buflen; +}; + +FUZZ_TEST(test_underflow_on_buffer, struct some_buffer) +{ + size_t i; + + KFUZZTEST_EXPECT_NOT_NULL(some_buffer, buf); + KFUZZTEST_ANNOTATE_LEN(some_buffer, buflen, buf); + + pr_info("buf = [%px, %px)", arg->buf, arg->buf + arg->buflen); + + /* First ensure that all bytes in arg->b are accessible. */ + for (i = 0; i < arg->buflen; i++) + READ_ONCE(arg->buf[i]); + /* + * Provoke a buffer overflow on the first byte preceding b, triggering + * a KASAN report. + */ + READ_ONCE(*((char *)arg->buf - 1)); +} From 366d8bb02b57e4bf050f36cd9c9a731a7b5f424e Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Thu, 7 Aug 2025 14:42:17 +0000 Subject: [PATCH 58/73] kfuzztest: define macros for granule size and slab redzone --- lib/kfuzztest/relocations.c | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/kfuzztest/relocations.c b/lib/kfuzztest/relocations.c index 6c515afd3692e9..94418d89231e8e 100644 --- a/lib/kfuzztest/relocations.c +++ b/lib/kfuzztest/relocations.c @@ -3,7 +3,13 @@ #include #include -#define POISON_REGION_END 0xFC +/* + * XXX: These are both defined in `mm/kasan/kasan.h`, but the build breaks if we + * define them in `include/linux/kasan.h` Since these values are unlikely to + * change, we redefine them here. + */ +#define __KASAN_SLAB_REDZONE 0xFC +#define __KASAN_GRANULE_SIZE 0x8 /** * Poison the half open interval [start, end], where end should be 8-byte @@ -15,7 +21,7 @@ */ static void kfuzztest_poison_range(void *start, void *end) { - uintptr_t end_addr = ALIGN_DOWN((uintptr_t)end, 0x8); + uintptr_t end_addr = ALIGN_DOWN((uintptr_t)end, __KASAN_GRANULE_SIZE); uintptr_t start_addr = (uintptr_t)start; uintptr_t poison_body_start; uintptr_t poison_body_end; @@ -28,20 +34,20 @@ static void kfuzztest_poison_range(void *start, void *end) if (start_addr >= end_addr) return; - head_granule_start = ALIGN_DOWN(start_addr, 0x8); + head_granule_start = ALIGN_DOWN(start_addr, __KASAN_GRANULE_SIZE); head_prefix_size = start_addr - head_granule_start; if (IS_ENABLED(CONFIG_KASAN_GENERIC) && head_prefix_size > 0) kasan_poison_last_granule((void *)head_granule_start, head_prefix_size); - poison_body_start = ALIGN(start_addr, 0x8); - poison_body_end = ALIGN_DOWN(end_addr, 0x8); + poison_body_start = ALIGN(start_addr, __KASAN_GRANULE_SIZE); + poison_body_end = ALIGN_DOWN(end_addr, __KASAN_GRANULE_SIZE); if (poison_body_start < poison_body_end) kasan_poison((void *)poison_body_start, poison_body_end - poison_body_start, - POISON_REGION_END, false); + __KASAN_SLAB_REDZONE, false); } int __kfuzztest_relocate(struct reloc_region_array *regions, @@ -69,15 +75,14 @@ int __kfuzztest_relocate(struct reloc_region_array *regions, if (src.start >= src.size) return -EINVAL; - if (re.value == KFUZZTEST_REGIONID_NULL) { + if (re.value == KFUZZTEST_REGIONID_NULL) *ptr_location = (uintptr_t)NULL; - } else if (re.value < regions->num_regions) { + else if (re.value < regions->num_regions) { dst = regions->regions[re.value]; *ptr_location = (uintptr_t)((char *)payload_start + dst.start); - } else { + } else return -EINVAL; - } } /* Poison the padding between regions. */ @@ -86,12 +91,12 @@ int __kfuzztest_relocate(struct reloc_region_array *regions, /* Points to the beginning of the inter-region padding */ poison_start = payload_start + reg.start + reg.size; - if (i < regions->num_regions - 1) { + if (i < regions->num_regions - 1) poison_end = payload_start + regions->regions[i + 1].start; - } else { - poison_end = poison_start + 8; - } + else + /* The last region is padded with 8 bytes. */ + poison_end = poison_start + 0x8; if ((char *)poison_end > (char *)payload_end) return -EINVAL; From 1f0ea222c4d255bf0f2b614ac7b9d0a3814d6d97 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Thu, 7 Aug 2025 14:57:34 +0000 Subject: [PATCH 59/73] kfuzztest: fix example so that it actually overflows --- lib/kfuzztest/kfuzztest_examples.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/kfuzztest/kfuzztest_examples.c b/lib/kfuzztest/kfuzztest_examples.c index 4316f7a2a64939..39dc5a6a0f64a7 100644 --- a/lib/kfuzztest/kfuzztest_examples.c +++ b/lib/kfuzztest/kfuzztest_examples.c @@ -38,7 +38,7 @@ FUZZ_TEST(test_overflow_on_nested_buffer, struct nested_buffers) * on the first byte to the right of the buffer which will trigger * a KASAN report. */ - for (i = 0; i < arg->a_len; i++) + for (i = 0; i <= arg->a_len; i++) READ_ONCE(arg->a[i]); } From 7b6ff7346de8acea66822efad68dc638e1219ca8 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Thu, 7 Aug 2025 17:16:45 +0000 Subject: [PATCH 60/73] kfuzztest: add stricter input parsing and validation, fix nits We now check for overflows a lot more carefully, and validate the input so that regions don't point out of bounds. Fix some nits etc... --- include/linux/kfuzztest.h | 54 +++++++++++++------- lib/kfuzztest/kfuzztest_main.c | 5 ++ lib/kfuzztest/parse.c | 92 ++++++++++++++++++++++++++++------ 3 files changed, 119 insertions(+), 32 deletions(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index 0e2f41abcfeea1..7642fc6956e65e 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -3,11 +3,9 @@ #ifndef KFUZZTEST_H #define KFUZZTEST_H -#include - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Ethan Graham "); -MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFuzzTest)"); +#include +#include +#include /** * @brief The KFuzzTest Input Serialization Format @@ -45,13 +43,13 @@ MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFuzzTest)"); * struct reloc_region - Describes a single contiguous memory region in the * payload. * - * @start: The byte offset of this region from the start of the payload, which + * @offset: The byte offset of this region from the start of the payload, which * should be aligned to the alignment requirements of the region's * underlying type. * @size: The size of this region in bytes. */ struct reloc_region { - uint32_t start; + uint32_t offset; uint32_t size; }; @@ -95,6 +93,7 @@ struct reloc_table { struct reloc_entry entries[]; }; +/* Helper for copying input data from userspace. */ int __kfuzztest_write_cb_common(struct file *filp, const char __user *buf, size_t len, loff_t *off, void *arg, size_t arg_size); @@ -155,7 +154,7 @@ int __kfuzztest_relocate(struct reloc_region_array *regions, * Dump some information on the parsed headers and payload. Can be useful for * debugging inputs when writing an encoder for the KFuzzTest input format. */ -__attribute__((unused)) static void +__attribute__((unused)) static inline void __kfuzztest_debug_header(struct reloc_region_array *regions, struct reloc_table *rt, void *payload_start, void *payload_end) @@ -165,7 +164,7 @@ __kfuzztest_debug_header(struct reloc_region_array *regions, regions); for (i = 0; i < regions->num_regions; i++) { pr_info(" region_%u: { start: 0x%x, size: 0x%x }", i, - regions->regions[i].start, regions->regions[i].size); + regions->regions[i].offset, regions->regions[i].size); } pr_info("reloc_table: { num_entries = %u, padding = %u } @ offset 0x%lx", @@ -188,6 +187,11 @@ struct kfuzztest_target { ssize_t (*write_input_cb)(struct file *filp, const char __user *buf, size_t len, loff_t *off); } __attribute__((aligned(32))); + +/* + * Enforce a fixed struct size to ensure a consistent stride when iterating + * over the array of these structs in the dedicated ELF section. + */ static_assert(sizeof(struct kfuzztest_target) == 32, "struct kfuzztest_target should have size 32"); @@ -269,17 +273,15 @@ static_assert(sizeof(struct kfuzztest_target) == 32, const char __user *buf, \ size_t len, loff_t *off) \ { \ - int ret; \ + void *payload_start, *payload_end, *buffer; \ struct reloc_region_array *regions; \ struct reloc_table *rt; \ - void *payload_start, *payload_end, *buffer; \ test_arg_type *arg; \ + int ret; \ \ buffer = kmalloc(len, GFP_KERNEL); \ if (!buffer) \ return -ENOMEM; \ - else if (IS_ERR(buffer)) \ - return PTR_ERR(buffer); \ ret = __kfuzztest_write_cb_common(filp, buf, len, off, buffer, \ len); \ if (ret) \ @@ -288,6 +290,8 @@ static_assert(sizeof(struct kfuzztest_target) == 32, &payload_start, &payload_end); \ if (ret) \ goto out; \ + __kfuzztest_debug_header(regions, rt, payload_start, \ + payload_end); \ ret = __kfuzztest_relocate(regions, rt, payload_start, \ payload_end); \ if (ret) \ @@ -303,7 +307,7 @@ out: \ static void kfuzztest_logic_##test_name( \ test_arg_type *arg, struct reloc_region_array *regions) -enum kfuzztest_constraint_type : uint8_t { +enum kfuzztest_constraint_type { EXPECT_EQ = 0, EXPECT_NE, EXPECT_LE, @@ -346,6 +350,10 @@ struct kfuzztest_constraint { enum kfuzztest_constraint_type type; } __attribute__((aligned(64))); +/* + * Enforce a fixed struct size to ensure a consistent stride when iterating + * over the array of these structs in the dedicated ELF section. + */ static_assert(sizeof(struct kfuzztest_constraint) == 64, "struct kfuzztest_constraint should have size 64"); @@ -389,10 +397,6 @@ static_assert(sizeof(struct kfuzztest_constraint) == 64, __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, lower_bound, \ upper_bound, EXPECT_IN_RANGE) -#define KFUZZTEST_EXPECT_LEN(expected_len, actual_len) \ - if ((expected_len) != (actual_len)) \ - return; - /** * Annotations express attributes about structure fields that can't be easily * or safely verified at runtime. They are intended as hints to the fuzzing @@ -442,6 +446,13 @@ struct kfuzztest_annotation { enum kfuzztest_annotation_attribute attrib; } __attribute__((aligned(32))); +/* + * Enforce a fixed struct size to ensure a consistent stride when iterating + * over the array of these structs in the dedicated ELF section. + */ +static_assert(sizeof(struct kfuzztest_annotation) == 32, + "struct kfuzztest_annotation should have size 32"); + #define __KFUZZTEST_ANNOTATE(arg_type, field, linked_field, attribute) \ static struct kfuzztest_annotation __annotation_##arg_type##_##field \ __attribute__((__section__(".kfuzztest_annotation"), \ @@ -478,4 +489,11 @@ struct kfuzztest_annotation { #define KFUZZTEST_REGION_SIZE(n) \ ((n) < (regions->num_regions) ? (regions->regions[n].size) : 0) +/** + * The end of the input should be padded by at least this number of bytes as + * it is poisoned to detect out of bounds accesses at the end of the last + * region. + */ +#define KFUZZTEST_TAIL_POISON_SIZE (0x8) + #endif /* KFUZZTEST_H */ diff --git a/lib/kfuzztest/kfuzztest_main.c b/lib/kfuzztest/kfuzztest_main.c index a0490b1088e69a..3cf4ef22dd577b 100644 --- a/lib/kfuzztest/kfuzztest_main.c +++ b/lib/kfuzztest/kfuzztest_main.c @@ -11,8 +11,13 @@ #include #include #include +#include #include +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ethan Graham "); +MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFuzzTest)"); + extern const struct kfuzztest_target __kfuzztest_targets_start[]; extern const struct kfuzztest_target __kfuzztest_targets_end[]; diff --git a/lib/kfuzztest/parse.c b/lib/kfuzztest/parse.c index efa8da40b18887..b5bbf5a40fd094 100644 --- a/lib/kfuzztest/parse.c +++ b/lib/kfuzztest/parse.c @@ -13,38 +13,102 @@ int __kfuzztest_write_cb_common(struct file *filp, const char __user *buf, return 0; } +static bool __kfuzztest_input_is_valid(struct reloc_region_array *regions, + struct reloc_table *rt, + void *payload_start, void *payload_end) +{ + size_t payload_size = (char *)payload_end - (char *)payload_start; + size_t usable_payload_size; + uint32_t region_end_offset; + struct reloc_entry reloc; + struct reloc_region reg; + uint32_t i; + + if ((char *)payload_start > (char *)payload_end) + return false; + if (payload_size < KFUZZTEST_TAIL_POISON_SIZE) + return false; + usable_payload_size = payload_size - KFUZZTEST_TAIL_POISON_SIZE; + + for (i = 0; i < regions->num_regions; i++) { + reg = regions->regions[i]; + if (check_add_overflow(reg.offset, reg.size, + ®ion_end_offset)) + return false; + if ((size_t)region_end_offset > usable_payload_size) + return false; + } + + for (i = 0; i < rt->num_entries; i++) { + reloc = rt->entries[i]; + if (reloc.region_id >= regions->num_regions) + return false; + if (reloc.value != KFUZZTEST_REGIONID_NULL && + reloc.value >= regions->num_regions) + return false; + + reg = regions->regions[reloc.region_id]; + if (reloc.region_offset % (sizeof(uintptr_t)) || + reloc.region_offset + sizeof(uintptr_t) > reg.size) + return false; + } + + return true; +} + int __kfuzztest_parse_input(void *input, size_t input_size, struct reloc_region_array **ret_regions, struct reloc_table **ret_reloc_table, void **ret_payload_start, void **ret_payload_end) { - void *payload_end, *payload_start; + size_t reloc_entries_size, reloc_regions_size; size_t reloc_table_size, regions_size; - struct reloc_table *rt; struct reloc_region_array *regions; + void *payload_end, *payload_start; + struct reloc_table *rt; + size_t curr_offset = 0; if (input_size < sizeof(struct reloc_region_array) + sizeof(struct reloc_table)) return -EINVAL; - payload_end = (char *)input + input_size; - regions = input; - regions_size = sizeof(*regions) + - regions->num_regions * sizeof(struct reloc_region); + if (check_mul_overflow(regions->num_regions, + sizeof(struct reloc_region), + &reloc_regions_size)) + return -EINVAL; + if (check_add_overflow(sizeof(*regions), reloc_regions_size, + ®ions_size)) + return -EINVAL; - rt = (struct reloc_table *)((char *)regions + regions_size); - if ((char *)rt > (char *)payload_end) + curr_offset = regions_size; + if (curr_offset > input_size) + return -EINVAL; + if (input_size - curr_offset < sizeof(struct reloc_table)) return -EINVAL; - reloc_table_size = sizeof(*rt) + - rt->num_entries * sizeof(struct reloc_entry) + - rt->payload_offset; - if ((char *)rt + reloc_table_size > (char *)payload_end) + rt = (struct reloc_table *)((char *)input + curr_offset); + + if (check_mul_overflow((size_t)rt->num_entries, + sizeof(struct reloc_entry), &reloc_entries_size)) + return -EINVAL; + if (check_add_overflow(sizeof(*rt), reloc_entries_size, + &reloc_table_size)) + return -EINVAL; + if (check_add_overflow(reloc_table_size, rt->payload_offset, + &reloc_table_size)) return -EINVAL; - payload_start = (char *)(rt) + reloc_table_size; - if ((char *)payload_start > (char *)payload_end) + if (check_add_overflow(curr_offset, reloc_table_size, &curr_offset)) + return -EINVAL; + if (curr_offset > input_size) + return -EINVAL; + + payload_start = (char *)input + curr_offset; + payload_end = (char *)input + input_size; + + if (!__kfuzztest_input_is_valid(regions, rt, payload_start, + payload_end)) return -EINVAL; *ret_regions = regions; From f1cd01f52e370be4052d4ba7bbd52ebb74164908 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Thu, 7 Aug 2025 17:51:11 +0000 Subject: [PATCH 61/73] kfuzztest: more validation. --- include/linux/kfuzztest.h | 25 ++++++++++++++++++------- lib/kfuzztest/parse.c | 19 ++++++++++++++++--- lib/kfuzztest/relocations.c | 34 ++++++++-------------------------- 3 files changed, 42 insertions(+), 36 deletions(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index 7642fc6956e65e..cd169dabe54913 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -17,23 +17,23 @@ * further kernel allocations. Pointers are patched internally using a "region" * system where each region corresponds to some pointed-to data. * - * Regions should be padded to respect alignment constraints of their - * underlying types. These padded regions are poisoned by KFuzzTest to ensure - * that KASAN catches OOB accesses. + * Regions should be padded to respect alignment constraints of their underlying + * types, and should be followed by at least 8 bytes of padding. These padded + * regions are poisoned by KFuzzTest to ensure that KASAN catches OOB accesses. * * The format consists of three main components: * 1. A reloc_region_array: Defines the memory layout of the target structure * by partitioning the payload into logical regions. Each logical region * should contain the byte representation of the type that it represents, - * including any necessary padding. + * including any necessary padding. The region descriptors should be + * ordered by offset ascending. * 2. A reloc_table: Provides "linking" instructions that tell the kernel how * to patch pointer fields to point to the correct regions. By design, * the first region (index 0) is passed as input into a FUZZ_TEST. * 3. A Payload: The raw binary data for the structure and its associated * buffers. This should be aligned to the maximum alignment of all * regions to satisfy alignment requirements of the input types, but this - * isn't checked by the parser. The payload should have at least 8 bytes - * of trailing padding. + * isn't checked by the parser. * * For a detailed specification of the binary layout see the full documentation * at: Documentation/dev-tools/kfuzztest.rst @@ -145,6 +145,9 @@ int __kfuzztest_parse_input(void *input, size_t input_size, * * Returns: 0 on success, or a negative error code if the relocation data is * found to be corrupt (e.g., invalid pointers). + * + * NOTE: this function only performs basic input validation. Full input + * validation is handled during parsing by __kfuzztest_parse_input. */ int __kfuzztest_relocate(struct reloc_region_array *regions, struct reloc_table *rt, void *payload_start, @@ -489,11 +492,19 @@ static_assert(sizeof(struct kfuzztest_annotation) == 32, #define KFUZZTEST_REGION_SIZE(n) \ ((n) < (regions->num_regions) ? (regions->regions[n].size) : 0) +/* + * FIXME: These are both defined in `mm/kasan/kasan.h`, but the build breaks + * if we define them in `include/linux/kasan.h` Since these values are unlikely + * to change, we redefine them here. + */ +#define __KASAN_SLAB_REDZONE 0xFC +#define __KASAN_GRANULE_SIZE 0x8 + /** * The end of the input should be padded by at least this number of bytes as * it is poisoned to detect out of bounds accesses at the end of the last * region. */ -#define KFUZZTEST_TAIL_POISON_SIZE (0x8) +#define KFUZZTEST_POISON_SIZE __KASAN_GRANULE_SIZE #endif /* KFUZZTEST_H */ diff --git a/lib/kfuzztest/parse.c b/lib/kfuzztest/parse.c index b5bbf5a40fd094..4f3df5b1319cd0 100644 --- a/lib/kfuzztest/parse.c +++ b/lib/kfuzztest/parse.c @@ -18,17 +18,17 @@ static bool __kfuzztest_input_is_valid(struct reloc_region_array *regions, void *payload_start, void *payload_end) { size_t payload_size = (char *)payload_end - (char *)payload_start; + struct reloc_region reg, next_reg; size_t usable_payload_size; uint32_t region_end_offset; struct reloc_entry reloc; - struct reloc_region reg; uint32_t i; if ((char *)payload_start > (char *)payload_end) return false; - if (payload_size < KFUZZTEST_TAIL_POISON_SIZE) + if (payload_size < KFUZZTEST_POISON_SIZE) return false; - usable_payload_size = payload_size - KFUZZTEST_TAIL_POISON_SIZE; + usable_payload_size = payload_size - KFUZZTEST_POISON_SIZE; for (i = 0; i < regions->num_regions; i++) { reg = regions->regions[i]; @@ -37,6 +37,19 @@ static bool __kfuzztest_input_is_valid(struct reloc_region_array *regions, return false; if ((size_t)region_end_offset > usable_payload_size) return false; + + if (i < regions->num_regions - 1) { + next_reg = regions->regions[i + 1]; + if (reg.offset > next_reg.offset) + return false; + /* + * Enforce the minimum poisonable gap between + * consecutive regions. + */ + if (reg.offset + reg.size + KFUZZTEST_POISON_SIZE > + next_reg.offset) + return false; + } } for (i = 0; i < rt->num_entries; i++) { diff --git a/lib/kfuzztest/relocations.c b/lib/kfuzztest/relocations.c index 94418d89231e8e..fa4ae848e1f9f6 100644 --- a/lib/kfuzztest/relocations.c +++ b/lib/kfuzztest/relocations.c @@ -3,14 +3,6 @@ #include #include -/* - * XXX: These are both defined in `mm/kasan/kasan.h`, but the build breaks if we - * define them in `include/linux/kasan.h` Since these values are unlikely to - * change, we redefine them here. - */ -#define __KASAN_SLAB_REDZONE 0xFC -#define __KASAN_GRANULE_SIZE 0x8 - /** * Poison the half open interval [start, end], where end should be 8-byte * aligned if it is not, then we cannot guarantee that the whole range will @@ -23,9 +15,9 @@ static void kfuzztest_poison_range(void *start, void *end) { uintptr_t end_addr = ALIGN_DOWN((uintptr_t)end, __KASAN_GRANULE_SIZE); uintptr_t start_addr = (uintptr_t)start; + uintptr_t head_granule_start; uintptr_t poison_body_start; uintptr_t poison_body_end; - uintptr_t head_granule_start; size_t head_prefix_size; if (!IS_ENABLED(CONFIG_KASAN)) @@ -54,33 +46,23 @@ int __kfuzztest_relocate(struct reloc_region_array *regions, struct reloc_table *rt, void *payload_start, void *payload_end) { - size_t i; struct reloc_region reg, src, dst; + void *poison_start, *poison_end; uintptr_t *ptr_location; struct reloc_entry re; - void *poison_start, *poison_end; + size_t i; /* Patch pointers. */ for (i = 0; i < rt->num_entries; i++) { re = rt->entries[i]; - - if (re.region_id >= regions->num_regions) - return -EINVAL; - src = regions->regions[re.region_id]; - - ptr_location = (uintptr_t *)((char *)payload_start + src.start + - re.region_offset); - if ((char *)ptr_location >= (char *)payload_end) - return -EINVAL; - if (src.start >= src.size) - return -EINVAL; - + ptr_location = (uintptr_t *)((char *)payload_start + + src.offset + re.region_offset); if (re.value == KFUZZTEST_REGIONID_NULL) *ptr_location = (uintptr_t)NULL; else if (re.value < regions->num_regions) { dst = regions->regions[re.value]; *ptr_location = - (uintptr_t)((char *)payload_start + dst.start); + (uintptr_t)((char *)payload_start + dst.offset); } else return -EINVAL; } @@ -90,10 +72,10 @@ int __kfuzztest_relocate(struct reloc_region_array *regions, reg = regions->regions[i]; /* Points to the beginning of the inter-region padding */ - poison_start = payload_start + reg.start + reg.size; + poison_start = payload_start + reg.offset + reg.size; if (i < regions->num_regions - 1) poison_end = - payload_start + regions->regions[i + 1].start; + payload_start + regions->regions[i + 1].offset; else /* The last region is padded with 8 bytes. */ poison_end = poison_start + 0x8; From be3ba1896028a54ec9a8c0b4f54f467f6ea9db56 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Thu, 7 Aug 2025 18:41:11 +0000 Subject: [PATCH 62/73] kfuzztest: assume at 8 bytes of padding before payload We now assume that there is at least 8 bytes of padding before the payload. This means that we never poison the relocation table, which isn't a bad thing in my opinion. --- include/linux/kfuzztest.h | 11 +++++------ lib/kfuzztest/parse.c | 8 +++++++- lib/kfuzztest/relocations.c | 10 ++++------ 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index cd169dabe54913..d52ff6ece02a53 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -83,13 +83,14 @@ struct reloc_entry { * input. * * @num_entries: the number of pointer relocations. - * @payload_offset: the number of padded bytes between the last relocation in - * entries, and the start of the payload data. + * @padding_size: the number of padded bytes between the last relocation in + * entries, and the start of the payload data. This should be at least + * 8 bytes, as it is used for poisoning. * @entries: array of relocations. */ struct reloc_table { uint32_t num_entries; - uint32_t payload_offset; + uint32_t padding_size; struct reloc_entry entries[]; }; @@ -171,7 +172,7 @@ __kfuzztest_debug_header(struct reloc_region_array *regions, } pr_info("reloc_table: { num_entries = %u, padding = %u } @ offset 0x%lx", - rt->num_entries, rt->payload_offset, + rt->num_entries, rt->padding_size, (char *)rt - (char *)regions); for (i = 0; i < rt->num_entries; i++) { pr_info(" reloc_%u: { src: %u, offset: 0x%x, dst: %u }", i, @@ -293,8 +294,6 @@ static_assert(sizeof(struct kfuzztest_target) == 32, &payload_start, &payload_end); \ if (ret) \ goto out; \ - __kfuzztest_debug_header(regions, rt, payload_start, \ - payload_end); \ ret = __kfuzztest_relocate(regions, rt, payload_start, \ payload_end); \ if (ret) \ diff --git a/lib/kfuzztest/parse.c b/lib/kfuzztest/parse.c index 4f3df5b1319cd0..e23d8ae88d151a 100644 --- a/lib/kfuzztest/parse.c +++ b/lib/kfuzztest/parse.c @@ -52,6 +52,12 @@ static bool __kfuzztest_input_is_valid(struct reloc_region_array *regions, } } + if (rt->padding_size < KFUZZTEST_POISON_SIZE) { + pr_info("validation failed because rt->padding_size = %u", + rt->padding_size); + return false; + } + for (i = 0; i < rt->num_entries; i++) { reloc = rt->entries[i]; if (reloc.region_id >= regions->num_regions) @@ -108,7 +114,7 @@ int __kfuzztest_parse_input(void *input, size_t input_size, if (check_add_overflow(sizeof(*rt), reloc_entries_size, &reloc_table_size)) return -EINVAL; - if (check_add_overflow(reloc_table_size, rt->payload_offset, + if (check_add_overflow(reloc_table_size, rt->padding_size, &reloc_table_size)) return -EINVAL; diff --git a/lib/kfuzztest/relocations.c b/lib/kfuzztest/relocations.c index fa4ae848e1f9f6..60434509788046 100644 --- a/lib/kfuzztest/relocations.c +++ b/lib/kfuzztest/relocations.c @@ -55,6 +55,7 @@ int __kfuzztest_relocate(struct reloc_region_array *regions, /* Patch pointers. */ for (i = 0; i < rt->num_entries; i++) { re = rt->entries[i]; + src = regions->regions[re.region_id]; ptr_location = (uintptr_t *)((char *)payload_start + src.offset + re.region_offset); if (re.value == KFUZZTEST_REGIONID_NULL) @@ -86,11 +87,8 @@ int __kfuzztest_relocate(struct reloc_region_array *regions, kfuzztest_poison_range(poison_start, poison_end); } - /* - * Poison the area preceding the payload. This corresponds to the end - * of the relocation table, which we can safely discard as it is no - * longer needed. - */ - kfuzztest_poison_range((char *)payload_start - 8, payload_start); + /* Poison the padded area preceding the payload. */ + kfuzztest_poison_range((char *)payload_start - rt->padding_size, + payload_start); return 0; } From eab275f3cc52adc2b69515e73c61788188ae6fb5 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Thu, 7 Aug 2025 18:46:11 +0000 Subject: [PATCH 63/73] kfuzztest: add GE and LT domain constraints for completeness --- include/linux/kfuzztest.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index d52ff6ece02a53..577ed315712d10 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -312,8 +312,10 @@ out: \ enum kfuzztest_constraint_type { EXPECT_EQ = 0, EXPECT_NE, + EXPECT_LT, EXPECT_LE, EXPECT_GT, + EXPECT_GE, EXPECT_IN_RANGE, }; @@ -380,6 +382,11 @@ static_assert(sizeof(struct kfuzztest_constraint) == 64, return; \ __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_NE) +#define KFUZZTEST_EXPECT_LT(arg_type, field, val) \ + if (arg->field >= val) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_LT) + #define KFUZZTEST_EXPECT_LE(arg_type, field, val) \ if (arg->field > val) \ return; \ @@ -390,6 +397,11 @@ static_assert(sizeof(struct kfuzztest_constraint) == 64, return; \ __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_GT) +#define KFUZZTEST_EXPECT_GE(arg_type, field, val) \ + if (arg->field < val) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_GE) + #define KFUZZTEST_EXPECT_NOT_NULL(arg_type, field) \ KFUZZTEST_EXPECT_NE(arg_type, field, 0x0) From c223a376a322d4da47405c9705b627cf43558ac7 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 8 Aug 2025 09:14:14 +0000 Subject: [PATCH 64/73] kfuzztest: remove __kfuzztest_write_cb_common as we no longer need it. --- include/linux/kfuzztest.h | 10 ++-------- lib/kfuzztest/parse.c | 11 ----------- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index 577ed315712d10..8cb796c31b4315 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -94,11 +94,6 @@ struct reloc_table { struct reloc_entry entries[]; }; -/* Helper for copying input data from userspace. */ -int __kfuzztest_write_cb_common(struct file *filp, const char __user *buf, - size_t len, loff_t *off, void *arg, - size_t arg_size); - /** * __kfuzztest_parse_input validates and parses the KFuzzTest input format. * @@ -286,9 +281,8 @@ static_assert(sizeof(struct kfuzztest_target) == 32, buffer = kmalloc(len, GFP_KERNEL); \ if (!buffer) \ return -ENOMEM; \ - ret = __kfuzztest_write_cb_common(filp, buf, len, off, buffer, \ - len); \ - if (ret) \ + ret = simple_write_to_buffer(buffer, len, off, buf, len); \ + if (ret < 0) \ goto out; \ ret = __kfuzztest_parse_input(buffer, len, ®ions, &rt, \ &payload_start, &payload_end); \ diff --git a/lib/kfuzztest/parse.c b/lib/kfuzztest/parse.c index e23d8ae88d151a..1e0967d7bac23f 100644 --- a/lib/kfuzztest/parse.c +++ b/lib/kfuzztest/parse.c @@ -2,17 +2,6 @@ /* Copyright 2025 Google LLC */ #include -int __kfuzztest_write_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((void *)arg, arg_size, off, buf, len) < 0) - return -EFAULT; - return 0; -} - static bool __kfuzztest_input_is_valid(struct reloc_region_array *regions, struct reloc_table *rt, void *payload_start, void *payload_end) From 55f9fe629fe3a63130e3c4db9fca765be45294bf Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 8 Aug 2025 09:20:40 +0000 Subject: [PATCH 65/73] kfuzztest: documentation updates --- Documentation/dev-tools/kfuzztest.rst | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Documentation/dev-tools/kfuzztest.rst b/Documentation/dev-tools/kfuzztest.rst index 1c8723a5b9b980..5a87f7af14c07e 100644 --- a/Documentation/dev-tools/kfuzztest.rst +++ b/Documentation/dev-tools/kfuzztest.rst @@ -72,7 +72,7 @@ The following example illustrates how to create a fuzz target for a function // 3. (Optional) Add constraints to define preconditions. // This check ensures 'arg->data' is not NULL. If the condition // is not met, the test exits early. This also creates metadata - // to inform the fuzzer + // to inform the fuzzer. KFUZZTEST_EXPECT_NOT_NULL(process_data_inputs, data); // 4. (Optional) Add annotations to provide semantic hints. @@ -82,8 +82,8 @@ The following example illustrates how to create a fuzz target for a function KFUZZTEST_ANNOTATE_LEN(process_data_inputs, len, data); // 5. Call the kernel function with the provided inputs. - // Memory errors like out-of-bounds accesses on 'arg->data' - // will be detected by KASAN. + // Memory errors like out-of-bounds accesses on 'arg->data' will + // be detected by KASAN or other memory error detection tools. process_data(arg->data, arg->len); } @@ -115,9 +115,9 @@ single kernel allocation and copy from userspace. The format consists of three main parts laid out sequentially: a region array, a relocation table, and the payload.:: - +----------------+---------------------+-----------------+----------------+ - | region array | relocation table | padding | payload | - +----------------+---------------------+-----------------+----------------+ + +----------------+---------------------+-----------+----------------+ + | region array | relocation table | padding | payload | + +----------------+---------------------+-----------+----------------+ Region Array ~~~~~~~~~~~~ @@ -149,8 +149,9 @@ Relocation Table ~~~~~~~~~~~~~~~~ The relocation table provides the instructions for the kernel to "hydrate" the -payload by patching pointer fields. It contains an array of struct reloc_entry -items. Each entry acts as a linking instruction, specifying: +payload by patching pointer fields. It contains an array of +``struct reloc_entry`` items. Each entry acts as a linking instruction, +specifying: - The location of a pointer that needs to be patched (identified by a region ID and an offset within that region). @@ -173,7 +174,8 @@ according to their specified offsets. its own type's alignment. - Padding and Poisoning: The space between the end of one region's data and the - beginning of the next must be sufficient for padding. KFuzzTest poisons this - unused padding with KASAN, allowing for precise detection of out-of-bounds - memory accesses between adjacent buffers. This padding should be at least - ``KFUZZTEST_POISON_SIZE`` bytes as defined in `include/linux/kfuzztest.h``. + beginning of the next must be sufficient for padding. In KASAN builds, + KFuzzTest poisons this unused padding, allowing for precise detection of + out-of-bounds memory accesses between adjacent buffers. This padding should + be at least ``KFUZZTEST_POISON_SIZE`` bytes as defined in + `include/linux/kfuzztest.h``. From 5ba009f39971e493a197f39c68f38e597e50ce8e Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 8 Aug 2025 09:30:36 +0000 Subject: [PATCH 66/73] kfuzztest: updates doc comments for consistency --- include/linux/kfuzztest.h | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index 8cb796c31b4315..4abee465e3b973 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -40,8 +40,7 @@ */ /** - * struct reloc_region - Describes a single contiguous memory region in the - * payload. + * struct reloc_region - single contiguous memory region in the payload * * @offset: The byte offset of this region from the start of the payload, which * should be aligned to the alignment requirements of the region's @@ -54,7 +53,7 @@ struct reloc_region { }; /** - * struct reloc_region_array - An array of all memory regions. + * struct reloc_region_array - array of regions in an input * @num_regions: The total number of regions defined. * @regions: A flexible array of `num_regions` region descriptors. */ @@ -64,7 +63,7 @@ struct reloc_region_array { }; /** - * struct reloc_entry describes a single pointer to be patched. + * struct reloc_entry - a single pointer to be patched in an input * * @region_id: The index of the region in the `reloc_region_array` that * contains the pointer. @@ -79,8 +78,7 @@ struct reloc_entry { }; /** - * struct reloc_entry is an array of all pointer relocations required by an - * input. + * struct reloc_entry - array of relocations required by an input * * @num_entries: the number of pointer relocations. * @padding_size: the number of padded bytes between the last relocation in @@ -95,7 +93,7 @@ struct reloc_table { }; /** - * __kfuzztest_parse_input validates and parses the KFuzzTest input format. + * __kfuzztest_parse_input - parse and validate a KFuzzTest input * * @input: A buffer containing the serialized test case. * @input_size: The size in bytes of the @input buffer. @@ -108,13 +106,7 @@ struct reloc_table { * @ret_payload_end: On success, updated to point to the first byte after the * end of the data payload. * - * This function performs the initial validation of the binary input blob. It - * checks that the declared sizes of the region array and relocation table are - * within the bounds of the total input size and extracts pointers to the - * main components of the format. It does not perform a deep validation of - * the entries themselves, which is instead done by __kfuzztest_relocate. - * - * Returns: 0 on success, or a negative error code on failure. + * Returns: 0 on success, or a negative error code if the input is corrupted. */ int __kfuzztest_parse_input(void *input, size_t input_size, struct reloc_region_array **ret_regions, @@ -122,7 +114,7 @@ int __kfuzztest_parse_input(void *input, size_t input_size, void **ret_payload_start, void **ret_payload_end); /** - * __kfuzztest_relocate hydrates the payload by patching pointers. + * __kfuzztest_relocate - resolve relocations in a serialized payload * * @regions: The relocation region array parsed from the input. * @rt: The relocation table parsed from the input. @@ -132,9 +124,9 @@ int __kfuzztest_parse_input(void *input, size_t input_size, * This function iterates through the region array and relocation table to * patch the pointers inside of the payload, reconstructing pointer-pointee * relationships between the logical regions of the fuzz driver input. For - * each entry in @rt, it calculates the address the address of a pointer field - * within the payload and sets it to the start address of its target region, or - * a NULL pointer if marked with KFUZZTEST_REGIONID_NULL. + * each entry in @rt, it calculates the address of a pointer field within the + * payload and sets it to the start address of its target region, or a NULL + * pointer if marked with KFUZZTEST_REGIONID_NULL. * * The padded areas between each region are poisoned with a KASAN slab redzone * to enable the detection of byte-accurate OOB accesses in the fuzz logic. @@ -195,7 +187,7 @@ static_assert(sizeof(struct kfuzztest_target) == 32, "struct kfuzztest_target should have size 32"); /** - * FUZZ_TEST defines a KFuzzTest target. + * FUZZ_TEST - defines a KFuzzTest target * * @test_name: The unique identifier for the fuzz test, which is used to name * the debugfs entry, e.g., /sys/kernel/debug/kftf/@test_name. @@ -314,8 +306,7 @@ enum kfuzztest_constraint_type { }; /** - * struct kfuzztest_constraint defines a metadata record for a domain - * constraint. + * struct kfuzztest_constraint - a metadata record for a domain constraint * * Domain constraints are rules about the input data that must be satisfied for * a fuzz test to proceed. While they are enforced in the kernel with a runtime @@ -424,7 +415,7 @@ enum kfuzztest_annotation_attribute : uint8_t { }; /** - * struct kfuzztest_annotation defines a metadata record for a fuzzer hint. + * struct kfuzztest_annotation - a metadata record for a fuzzer hint * * This struct captures a single hint about a field in the input structure. * Instances are generated by the KFUZZTEST_ANNOTATE_* macros and are placed From c22f0fc4fcf04a3a04eb5f1b0b5fc7021a4bbdef Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 8 Aug 2025 09:31:06 +0000 Subject: [PATCH 67/73] kfuzztest: update allocation / error handling for debufs_state --- lib/kfuzztest/kfuzztest_main.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/kfuzztest/kfuzztest_main.c b/lib/kfuzztest/kfuzztest_main.c index 3cf4ef22dd577b..4f90aeb776865a 100644 --- a/lib/kfuzztest/kfuzztest_main.c +++ b/lib/kfuzztest/kfuzztest_main.c @@ -85,12 +85,10 @@ static int __init kfuzztest_init(void) num_test_cases = __kfuzztest_targets_end - __kfuzztest_targets_start; state.debugfs_state = - kmalloc(num_test_cases * sizeof(struct kfuzztest_debugfs_state), + kzalloc(num_test_cases * sizeof(struct kfuzztest_debugfs_state), GFP_KERNEL); if (!state.debugfs_state) return -ENOMEM; - else if (IS_ERR(state.debugfs_state)) - return PTR_ERR(state.debugfs_state); /* Create the main "kfuzztest" directory in /sys/kernel/debug. */ state.kfuzztest_dir = debugfs_create_dir("kfuzztest", NULL); From 1eee647514cb206ff82c333f8eef5d68ab54cdcf Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 8 Aug 2025 09:50:07 +0000 Subject: [PATCH 68/73] kfuzztest: file header comments --- include/linux/kfuzztest.h | 9 ++++++++- lib/kfuzztest/kfuzztest_examples.c | 6 +++++- lib/kfuzztest/kfuzztest_main.c | 6 +----- lib/kfuzztest/parse.c | 6 +++++- lib/kfuzztest/relocations.c | 7 +++++-- 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h index 4abee465e3b973..3879acb18ccbb5 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -1,5 +1,12 @@ // SPDX-License-Identifier: GPL-2.0 -/* Copyright 2025 Google LLC */ +/* + * The Kernel Fuzz Testing Framework (KFuzzTest) API for defining fuzz targets + * for internal kernel functions. + * + * For more information please see Documentation/dev-tools/kfuzztest.rst. + * + * Copyright 2025 Google LLC + */ #ifndef KFUZZTEST_H #define KFUZZTEST_H diff --git a/lib/kfuzztest/kfuzztest_examples.c b/lib/kfuzztest/kfuzztest_examples.c index 39dc5a6a0f64a7..bdc182664116f4 100644 --- a/lib/kfuzztest/kfuzztest_examples.c +++ b/lib/kfuzztest/kfuzztest_examples.c @@ -1,5 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 -/* Copyright 2025 Google LLC */ +/* + * This file contains some KFuzzTest target examples. + * + * Copyright 2025 Google LLC + */ #include struct nested_buffers { diff --git a/lib/kfuzztest/kfuzztest_main.c b/lib/kfuzztest/kfuzztest_main.c index 4f90aeb776865a..fccda1319fb0d4 100644 --- a/lib/kfuzztest/kfuzztest_main.c +++ b/lib/kfuzztest/kfuzztest_main.c @@ -1,10 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Kernel Fuzz Testing Framework (KFuzzTest) - Core Module - * - * This module is responsible for discovering and initializing all fuzz test - * cases defined using the FUZZ_TEST() macro. It creates a debugfs interface - * under /sys/kernel/debug/kfuzztest/ for userspace to interact with each test. + * KFuzzTest core module initialization and debugfs interface. * * Copyright 2025 Google LLC */ diff --git a/lib/kfuzztest/parse.c b/lib/kfuzztest/parse.c index 1e0967d7bac23f..f19c8f1853e58a 100644 --- a/lib/kfuzztest/parse.c +++ b/lib/kfuzztest/parse.c @@ -1,5 +1,9 @@ /* SPDX-License-Identifier: GPL-2.0 */ -/* Copyright 2025 Google LLC */ +/* + * KFuzzTest input parsing and validation. + * + * Copyright 2025 Google LLC + */ #include static bool __kfuzztest_input_is_valid(struct reloc_region_array *regions, diff --git a/lib/kfuzztest/relocations.c b/lib/kfuzztest/relocations.c index 60434509788046..53751335393ff8 100644 --- a/lib/kfuzztest/relocations.c +++ b/lib/kfuzztest/relocations.c @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 -/* Copyright 2025 Google LLC */ -#include +/* + * KFuzzTest input relocation and memory poisoning. + * + * Copyright 2025 Google LLC + */ #include /** From fd8b6ec922749c24eb4da7846acd82e4fd73b5bb Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 8 Aug 2025 09:54:41 +0000 Subject: [PATCH 69/73] kfuzztest: update some kasan poison logic and comments Function now bails out when CONFIG_KASAN is not built in, which prevents checks. --- lib/kfuzztest/relocations.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/kfuzztest/relocations.c b/lib/kfuzztest/relocations.c index 53751335393ff8..8ff87dacb362cf 100644 --- a/lib/kfuzztest/relocations.c +++ b/lib/kfuzztest/relocations.c @@ -5,14 +5,19 @@ * Copyright 2025 Google LLC */ #include +#include + +#ifdef CONFIG_KASAN /** - * Poison the half open interval [start, end], where end should be 8-byte - * aligned if it is not, then we cannot guarantee that the whole range will - * be poisoned. + * kfuzztest_poison_range - poison the memory range [start, end) + * + * The exact behavior is subject to alignment with KASAN's 8-byte granule size: * - * If start is not 8-byte-aligned, the remaining bytes in its 8-byte granule - * can only be poisoned if CONFIG_KASAN_GENERIC is enabled. + * - If @start is unaligned, the initial partial granule at the beginning + * of the range is only poisoned if CONFIG_KASAN_GENERIC is enabled. + * - The poisoning of the range only extends up to the last full granule + * before @end. Any remaining bytes in a final partial granule are ignored. */ static void kfuzztest_poison_range(void *start, void *end) { @@ -45,6 +50,12 @@ static void kfuzztest_poison_range(void *start, void *end) __KASAN_SLAB_REDZONE, false); } +#else /* CONFIG_KASAN */ + +static inline void kfuzztest_poison_range(void *, void *) {} + +#endif /* CONFIG_KASAN */ + int __kfuzztest_relocate(struct reloc_region_array *regions, struct reloc_table *rt, void *payload_start, void *payload_end) @@ -81,8 +92,7 @@ int __kfuzztest_relocate(struct reloc_region_array *regions, poison_end = payload_start + regions->regions[i + 1].offset; else - /* The last region is padded with 8 bytes. */ - poison_end = poison_start + 0x8; + poison_end = payload_end; if ((char *)poison_end > (char *)payload_end) return -EINVAL; From f7ee465847914c1331eda050fcd17289269fc2d4 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 8 Aug 2025 10:08:13 +0000 Subject: [PATCH 70/73] kfuzztest: update test case comments --- lib/kfuzztest/kfuzztest_examples.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/kfuzztest/kfuzztest_examples.c b/lib/kfuzztest/kfuzztest_examples.c index bdc182664116f4..6c709d25818fda 100644 --- a/lib/kfuzztest/kfuzztest_examples.c +++ b/lib/kfuzztest/kfuzztest_examples.c @@ -19,8 +19,8 @@ struct nested_buffers { * * | a | b | pad[8] | *a | pad[8] | *b | * - * We expect to see a KASAN warning by overflowing one byte into the A buffer. - * + * where the padded regions are poisoned. We expect to trigger a KASAN report by + * overflowing one byte into the `a` buffer. */ FUZZ_TEST(test_overflow_on_nested_buffer, struct nested_buffers) { @@ -38,9 +38,9 @@ FUZZ_TEST(test_overflow_on_nested_buffer, struct nested_buffers) for (i = 0; i < arg->b_len; i++) READ_ONCE(arg->b[i]); /* - * Check that all bytes in arg->a are accessible, and provoke an OOB - * on the first byte to the right of the buffer which will trigger - * a KASAN report. + * Check that all bytes in arg->a are accessible, and provoke an OOB on + * the first byte to the right of the buffer which will trigger a KASAN + * report. */ for (i = 0; i <= arg->a_len; i++) READ_ONCE(arg->a[i]); @@ -51,6 +51,10 @@ struct some_buffer { size_t buflen; }; +/** + * Tests that the region between struct some_buffer and the expanded *buf field + * is correctly poisoned by accessing the first byte before *buf. + */ FUZZ_TEST(test_underflow_on_buffer, struct some_buffer) { size_t i; From 5411510703e6694f66307044192683f328dcd938 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 8 Aug 2025 10:19:54 +0000 Subject: [PATCH 71/73] kfuzztest: remove no longer needed CONFIG_KASAN check --- lib/kfuzztest/relocations.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/kfuzztest/relocations.c b/lib/kfuzztest/relocations.c index 8ff87dacb362cf..ed188a25076721 100644 --- a/lib/kfuzztest/relocations.c +++ b/lib/kfuzztest/relocations.c @@ -28,9 +28,6 @@ static void kfuzztest_poison_range(void *start, void *end) uintptr_t poison_body_end; size_t head_prefix_size; - if (!IS_ENABLED(CONFIG_KASAN)) - return; - if (start_addr >= end_addr) return; From 7ed28762f1641ea6f811512ed707f2a79b0cae64 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 8 Aug 2025 10:29:49 +0000 Subject: [PATCH 72/73] kfuzztest: update KConfig dependencies and help description --- lib/Kconfig.kfuzztest | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/Kconfig.kfuzztest b/lib/Kconfig.kfuzztest index dd9047459e051c..7a20f5325a3d44 100644 --- a/lib/Kconfig.kfuzztest +++ b/lib/Kconfig.kfuzztest @@ -2,11 +2,16 @@ config KFUZZTEST bool "KFuzzTest - enable support for internal fuzz targets" - depends on DEBUG_FS && KASAN - help - Enables support for the kernel fuzz testing framework (KFuzzTest), an - interface for exposing internal kernel functions to a userspace fuzzing - engine. KFuzzTest targets are exposed via a debugfs interface that - accepts serialized userspace inputs, and is designed to make it easier - to fuzz deeply nested kernel code that is hard to reach from the system - call boundary. + depends on DEBUG_FS + help + Enables support for the kernel fuzz testing framework (KFuzzTest), an + interface for exposing internal kernel functions to a userspace fuzzing + engine. KFuzzTest targets are exposed via a debugfs interface that + accepts serialized userspace inputs, and is designed to make it easier + to fuzz deeply nested kernel code that is hard to reach from the system + call boundary. Using a simple macro-based API, developers can add a new + fuzz target with minimal boilerplate code. + + It is strongly recommended to also enable CONFIG_KASAN for byte-accurate + out-of-bounds detection, as KFuzzTest was designed with this in mind. It + is also recommended to enable CONFIG_KCOV for coverage guided fuzzing. From 496ceefa5c78ea0fc2c28895c2d28804599f16f4 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 8 Aug 2025 10:32:41 +0000 Subject: [PATCH 73/73] kfuzztest: move period out of a quoted region --- Documentation/dev-tools/kfuzztest.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/dev-tools/kfuzztest.rst b/Documentation/dev-tools/kfuzztest.rst index 5a87f7af14c07e..5e96eddbfda63e 100644 --- a/Documentation/dev-tools/kfuzztest.rst +++ b/Documentation/dev-tools/kfuzztest.rst @@ -50,7 +50,7 @@ structure, writing the test body using the ``FUZZ_TEST`` macro, and optionally adding metadata for the fuzzer. The following example illustrates how to create a fuzz target for a function -``int process_data(const char *data, size_t len).`` +``int process_data(const char *data, size_t len)``. .. code-block:: c