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 new file mode 100644 index 00000000000000..ecfb3cb1c3695b --- /dev/null +++ b/include/linux/kftf.h @@ -0,0 +1,128 @@ +#ifndef KFTF_H +#define KFTF_H + +#include + +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; + 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 *); +}; + +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 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. + * + * 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); + * } + */ +#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) \ + { \ + int err; \ + func_arg_type arg; \ + err = write_input_cb_common(filp, buf, len, off, &arg, \ + sizeof(arg)); \ + if (err != 0) { \ + return err; \ + } \ + /* 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); \ + return len; \ + } \ + static void _fuzz_test_logic_##func(func_arg_type arg) + +#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..6a7d35a25b5f50 --- /dev/null +++ b/lib/Kconfig.kftf @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config KFTF + tristate "Enable Kernel Fuzz Testing Framework (KFTF)" + depends on DEBUG_FS 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..b151164d327802 --- /dev/null +++ b/lib/kftf/kftf_main.c @@ -0,0 +1,189 @@ +/* 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 + +extern const struct kftf_test_case __kftf_start[]; +extern const struct kftf_test_case __kftf_end[]; + +/** + * 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; + struct file_operations fops; +}; + +/** + * 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; + struct kftf_dentry input_dentry; + 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; + struct kftf_debugfs_state *debugfs_state; +}; + +/* Global static variable to hold all state for the module. */ +static struct kftf_simple_fuzzer_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 + */ +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; + int ret = 0; + int i = 0; + size_t num_test_cases; + + num_test_cases = __kftf_end - __kftf_start; + + 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); + 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); + } + + /* 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); + + 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; + } + + /* 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); + 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; + } + + st.debugfs_state[i].metadata_dentry.fops = + (struct file_operations){ + .owner = THIS_MODULE, + .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, + 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("kftf: registered %s\n", test->name); + } + + return 0; + +cleanup_failure: + debugfs_remove_recursive(st.kftf_dir); + 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; + + debugfs_remove_recursive(st.kftf_dir); + st.kftf_dir = NULL; +} + +module_init(kftf_init); +module_exit(kftf_exit); diff --git a/lib/kftf/kftf_tests.h b/lib/kftf/kftf_tests.h new file mode 100644 index 00000000000000..7cc0a4676d0ec8 --- /dev/null +++ b/lib/kftf/kftf_tests.h @@ -0,0 +1,36 @@ +#ifndef KFTF_TESTS_H +#define KFTF_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) +{ + 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 a8170bb9142f39..a52b16f8045393 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); +} /** * int_sqrt - computes the integer square root @@ -21,6 +32,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 +67,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) {