diff --git a/arch/x86/kernel/vmlinux.lds.S b/arch/x86/kernel/vmlinux.lds.S index 4fa0be732af10f..d7f403f6432ad0 100644 --- a/arch/x86/kernel/vmlinux.lds.S +++ b/arch/x86/kernel/vmlinux.lds.S @@ -112,6 +112,21 @@ ASSERT(__relocate_kernel_end - __relocate_kernel_start <= KEXEC_CONTROL_CODE_MAX #else #define KEXEC_RELOCATE_KERNEL #endif + +#define KFTF_TABLE \ + . = ALIGN(PAGE_SIZE); \ + __kftf_test_case_start = .; \ + KEEP(*(.kftf_test)); \ + __kftf_test_case_end = .; \ + . = 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 */ data PT_LOAD FLAGS(6); /* RW_ */ @@ -199,6 +214,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..20c5bc2782423f --- /dev/null +++ b/include/linux/kftf.h @@ -0,0 +1,390 @@ +#ifndef KFTF_H +#define KFTF_H + +#include + +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 + * 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 *); +}; + +// 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; + } + 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_test"), __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) \ + { \ + pr_info("[ENTER] %s\n", __FUNCTION__); \ + int err; \ + 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, len); \ + if (err != 0) { \ + 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); \ + kfree(payload); \ + return 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_EQ = 0, + EXPECT_NE, + EXPECT_LE, + EXPECT_GT, + 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 + * 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, 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) \ + 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)) \ + return; + +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) + +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 */ +}; + +/* + * 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; + uint32_t max_alignment; + int padding[RELOC_TABLE_PADDING]; + struct reloc_entry entries[]; +}; +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; + +/* 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, entries_size, header_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__); + return NULL; + } + rt = input; + + 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; + + if (!is_power_of_2(rt->max_alignment) || rt->max_alignment > PAGE_SIZE) + return NULL; + + payload_len = input_size - (payload_start - input); + + /* + * Check input for out-of-bounds pointers before before allocating + * aligned output buffer. + */ + for (i = 0; i < rt->num_entries; i++) { + re = rt->entries[i]; + if (re.pointer > payload_len || + re.pointer + sizeof(uintptr_t) > payload_len) + return NULL; + + if (re.value == nullPtr) + continue; + + if (re.pointer + re.value >= payload_len || + re.pointer + re.value + sizeof(uintptr_t) > payload_len) + return NULL; + } + + /* + * To guarantee correct alignment of structures within the payload, we + * allocate a new buffer that is aligned to the next power of two + * greater than either the size of the payload + 1 or the maximum + * alignment of the nested structures. We add one to the payload length + * and call kzalloc to ensure that the payload is padded by trailing + * zeros to prevent false-positives on non-null terminated strings. + */ + alloc_size = + MAX(roundup_pow_of_two(payload_len + 1), rt->max_alignment); + out = kzalloc(alloc_size, GFP_KERNEL); + if (!out) + return NULL; + + memcpy(out, payload_start, payload_len); + + /* + * 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 *)(out + re.pointer); + if (re.value == nullPtr) { + *ptr_location = (uintptr_t)NULL; + } else { + *ptr_location = (uintptr_t)ptr_location + re.value; + } + } + + return out; +} + +#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..133ee92b33ab31 --- /dev/null +++ b/lib/kftf/kftf_main.c @@ -0,0 +1,208 @@ +/* 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_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. + * @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; + const struct kftf_constraint *constraint; + int ret = 0; + int i = 0; + size_t num_test_cases; + + 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); + 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_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); + + 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); + } + + // 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++; + } + + 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..46b23d15230d52 --- /dev/null +++ b/lib/kftf/kftf_tests.h @@ -0,0 +1,71 @@ +#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_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); +} + +struct my_fun_func_arg { + const char *string; + char *buffer; + size_t buffer_size; +}; + +volatile char __w; +static void my_fun_func(const char *string, char *buffer, size_t buffer_size) +{ + size_t i; + /* string should be NULL terminated! */ + pr_info("string length = %zu", + strlen(string) + 1 /* null terminated str */); + pr_info("buffer_size = %zu\n", buffer_size); + + for (i = 0; i < buffer_size; i++) { + buffer[i]++; + __w = buffer[i]; // avoid inlining + } +} + +FUZZ_TEST(my_memncpy, struct my_fun_func_arg) +{ + pr_info("[ENTER] %s\n", __FUNCTION__); + + KFTF_ANNOTATE_STRING(my_fun_func_arg, string); + KFTF_ANNOTATE_LEN(my_fun_func_arg, buffer_size, buffer); + KFTF_EXPECT_NOT_NULL(my_fun_func_arg, string); + KFTF_EXPECT_NOT_NULL(my_fun_func_arg, buffer); + my_fun_func(arg->string, arg->buffer, arg->buffer_size); +} + +#endif /* KFTF_TESTS_H */