-
Notifications
You must be signed in to change notification settings - Fork 0
KFuzzTest: macros for defining fuzz tests #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
1e0587e
43dc893
42d6acc
4a35a64
e8992cb
d7adb90
ca5ba48
96ae8d5
ece3042
07bfafa
0c5cd7e
a4bb002
0bddc3f
5971467
d799f5c
aae72ff
72e3dec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| #ifndef KFTF_H | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In addition to the include guard, every kernel file should contain an SPDX license header ( Check include/linux/kfence.h for an example (drop |
||
| #define KFTF_H | ||
|
|
||
| #include <linux/module.h> | ||
|
|
||
| MODULE_LICENSE("GPL"); | ||
| MODULE_AUTHOR("Ethan Graham <ethangraham@google.com>"); | ||
| 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. | ||
| * | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: extra newline. |
||
| * | ||
| * 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: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: 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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: single-line comments should end with a period. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By the way, maybe change them to C-style? I don't insist though, as they are inside a comment anyway. |
||
| * 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); | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODOI think it would be great to have some kind of
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It could look something like #define KFTF_BUG_ON(condition) if (condition) pr_warn("some output to indicate failure")Or perhaps There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, some sort of an assertion would be good to have. |
||
| * } | ||
| */ | ||
| #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 */ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest moving it one line above, so that the source directive are sorted. |
||
|
|
||
| endmenu # "Memory Debugging" | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| # SPDX-License-Identifier: GPL-2.0-only | ||
|
|
||
| config KFTF | ||
| tristate "Enable Kernel Fuzz Testing Framework (KFTF)" | ||
| depends on DEBUG_FS |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| # SPDX-License-Identifier: GPL-2.0-only | ||
|
|
||
| obj-$(CONFIG_KFTF) += kftf.o | ||
| kftf-objs := kftf_main.o |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 <linux/debugfs.h> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file also needs an SPDX header |
||
| #include <linux/fs.h> | ||
| #include <linux/kftf.h> | ||
| #include <linux/printk.h> | ||
|
|
||
| extern const struct kftf_test_case __kftf_start[]; | ||
| extern const struct kftf_test_case __kftf_end[]; | ||
|
Comment on lines
+14
to
+15
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NOTE: these aren't being linked against properly if we build with That being said, not sure how to link against the fuzz targets if the module is built separately from the kernel. |
||
|
|
||
| /** | ||
| * 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/<test-name>/. | ||
| * @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/<test-name>" 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); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's more or less up to you to pick a name for this subsystem, and it's not too late to change it if you prefer something different from "KFTF" :)