From 4a7f93e9a618e9b6e3531d9b8174695eba0fa0c9 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Tue, 8 Jul 2025 18:28:40 +0000 Subject: [PATCH 1/5] kftf: add test case example using annotations --- lib/kftf/kftf_tests.h | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/lib/kftf/kftf_tests.h b/lib/kftf/kftf_tests.h index 2f59bfbd71b556..b5c35e3261e598 100644 --- a/lib/kftf/kftf_tests.h +++ b/lib/kftf/kftf_tests.h @@ -36,4 +36,47 @@ FUZZ_TEST(kftf_fuzzable, struct kftf_simple_arg) kftf_fuzzable(arg.first, arg.second, arg.third); } +struct my_fun_func_arg { + const char *string; + char *buffer; + size_t buffer_size; +}; + +static void my_fun_func(const char *string, char *buffer, size_t buffer_size) +{ + size_t i; + /* string should be NULL terminated! */ + pr_info("this is my string: %s", string); + + for (i = 0; i < buffer_size; i++) { + buffer[i]++; + pr_info("buffer[%zu] = %c\n", i, buffer[i]); + } +} + +FUZZ_TEST(my_memncpy, struct my_fun_func_arg) +{ + const char *kernel_string; + char *kernel_buffer; + + KFTF_ANNOTATE_STRING(my_fun_func_arg, string); + KFTF_ANNOTATE_LEN(my_fun_func_arg, buffer_size, buffer); + KFTF_EXPECT_NOT_NULL(my_fun_func_arg, string); + KFTF_EXPECT_NOT_NULL(my_fun_func_arg, buffer); + + kernel_string = strndup_user(arg.string, PAGE_SIZE); + if (!kernel_string || IS_ERR(kernel_string)) + return; + + kernel_buffer = memdup_user(arg.buffer, arg.buffer_size); + if (!kernel_buffer || IS_ERR(kernel_buffer)) { + kfree(kernel_string); + return; + } + + my_fun_func(kernel_string, kernel_buffer, arg.buffer_size); + kfree(kernel_string); + kfree(kernel_buffer); +} + #endif /* KFTF_TESTS_H */ From ce561e57e165dfdbe48ebaecb7debbe0344802ea Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Wed, 9 Jul 2025 14:56:57 +0000 Subject: [PATCH 2/5] kftf: remove int_sqrt test which is no longer really needed, added relocation parsing code. --- include/linux/kftf.h | 83 +++++++++++++++++++++++++++++++++++++------ lib/kftf/kftf_tests.h | 8 ++--- lib/math/int_sqrt.c | 18 +--------- 3 files changed, 78 insertions(+), 31 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 5a7cc1a9c1ae93..f0e36c5ed2b60d 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -7,6 +7,9 @@ MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ethan Graham "); MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFTF)"); +/* forward decl */ +static void *kftf_parse_input(void *input, size_t input_size); + /** * struct kftf_test case defines a single fuzz test case. These should not * be created manually. Instead the user should use the FUZZ_TEST macro defined @@ -88,7 +91,7 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, size_t len, loff_t *off); \ static ssize_t _read_metadata_callback_##func( \ struct file *filp, char __user *buf, size_t len, loff_t *off); \ - static void _fuzz_test_logic_##func(func_arg_type arg); \ + static void _fuzz_test_logic_##func(func_arg_type *arg); \ /* test case struct initialization */ \ const struct kftf_test_case __fuzz_test__##func \ __attribute__((__section__(".kftf_test"), __used__)) = { \ @@ -111,20 +114,33 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, const char __user *buf, \ size_t len, loff_t *off) \ { \ + pr_info("[ENTER] %s\n", __FUNCTION__); \ int err; \ - func_arg_type arg; \ - err = write_input_cb_common(filp, buf, len, off, &arg, \ - sizeof(arg)); \ + void *buffer = kmalloc(len, GFP_KERNEL); \ + if (!buffer || IS_ERR(buffer)) \ + return PTR_ERR(buffer); \ + err = write_input_cb_common(filp, buf, len, off, buffer, \ + sizeof(buffer)); \ if (err != 0) { \ + pr_info("%s: failed to read data, len = %zu\n", \ + __FUNCTION__, len); \ + kfree(buffer); \ return err; \ } \ + void *payload = kftf_parse_input(buffer, len); \ + if (!payload) { \ + kfree(buffer); \ + return -1; \ + } \ + func_arg_type *arg = payload; \ /* call the user's logic on the provided arg. */ \ /* NOTE: define some success/failure return types? */ \ pr_info("invoking fuzz logic for %s\n", #func); \ _fuzz_test_logic_##func(arg); \ + kfree(buffer); \ return len; \ } \ - static void _fuzz_test_logic_##func(func_arg_type arg) + static void _fuzz_test_logic_##func(func_arg_type *arg) /** * Reports a bug with a predictable prefix so that it can be parsed by a @@ -195,22 +211,22 @@ static_assert(sizeof(struct kftf_constraint) == 64, }; #define KFTF_EXPECT_EQ(arg_type, field, val) \ - if (arg.field != val) \ + if (arg->field != val) \ return; \ __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_EQ) #define KFTF_EXPECT_NE(arg_type, field, val) \ - if (arg.field == val) \ + if (arg->field == val) \ return; \ __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_NE) #define KFTF_EXPECT_LE(arg_type, field, val) \ - if (arg.field > val) \ + if (arg->field > val) \ return; \ __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_LE) #define KFTF_EXPECT_GT(arg_type, field, val) \ - if (arg.field <= val) \ + if (arg->field <= val) \ return; \ __KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_GT) @@ -218,7 +234,7 @@ static_assert(sizeof(struct kftf_constraint) == 64, KFTF_EXPECT_NE(arg_type, field, 0x0) #define KFTF_EXPECT_IN_RANGE(arg_type, field, lower_bound, upper_bound) \ - if (arg.field < lower_bound || arg.field > upper_bound) \ + if (arg->field < lower_bound || arg->field > upper_bound) \ return; \ __KFTF_DEFINE_CONSTRAINT(arg_type, field, lower_bound, upper_bound, \ EXPECT_IN_RANGE) @@ -256,4 +272,51 @@ struct kftf_annotation { #define KFTF_ANNOTATE_LEN(arg_type, field, linked_field) \ __KFTF_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) +struct reloc_entry { + off_t pointer; + off_t value; +}; + +struct reloc_table { + int num_entries; + int padding[3]; // why? + struct reloc_entry entries[]; +}; +static_assert(offsetof(struct reloc_table, entries) % + sizeof(struct reloc_entry) == + 0); + +static void *kftf_parse_input(void *input, size_t input_size) +{ + pr_info("[ENTER]%s\n", __FUNCTION__); + size_t i; + void *payload_start; + uintptr_t *ptr_location; + struct reloc_entry re; + + if (input_size < sizeof(struct reloc_table)) { + pr_warn("got misformed input in %s\n", __FUNCTION__); + } + struct reloc_table *rt = input; + pr_info("%s: num_entries = %d\n", __FUNCTION__, rt->num_entries); + + payload_start = input + offsetof(struct reloc_table, entries) + + rt->num_entries * sizeof(struct reloc_entry); + + if (payload_start >= input + input_size) + return NULL; + + for (i = 0; i < rt->num_entries; i++) { + re = rt->entries[i]; + ptr_location = (uintptr_t *)(payload_start + re.pointer); + if ((void *)ptr_location >= input + input_size) + return NULL; + + *ptr_location = (uintptr_t)ptr_location + re.value; + } + + pr_info("%s: parsed %d entries\n", __FUNCTION__, rt->num_entries); + return payload_start; +} + #endif /* KFTF_H */ diff --git a/lib/kftf/kftf_tests.h b/lib/kftf/kftf_tests.h index b5c35e3261e598..5257783c936622 100644 --- a/lib/kftf/kftf_tests.h +++ b/lib/kftf/kftf_tests.h @@ -33,7 +33,7 @@ FUZZ_TEST(kftf_fuzzable, struct kftf_simple_arg) KFTF_EXPECT_NOT_NULL(kftf_simple_arg, first); KFTF_EXPECT_IN_RANGE(kftf_simple_arg, second, 'a', 'z'); KFTF_EXPECT_IN_RANGE(kftf_simple_arg, third, 'a', 'z'); - kftf_fuzzable(arg.first, arg.second, arg.third); + kftf_fuzzable(arg->first, arg->second, arg->third); } struct my_fun_func_arg { @@ -64,17 +64,17 @@ FUZZ_TEST(my_memncpy, struct my_fun_func_arg) KFTF_EXPECT_NOT_NULL(my_fun_func_arg, string); KFTF_EXPECT_NOT_NULL(my_fun_func_arg, buffer); - kernel_string = strndup_user(arg.string, PAGE_SIZE); + kernel_string = strndup_user(arg->string, PAGE_SIZE); if (!kernel_string || IS_ERR(kernel_string)) return; - kernel_buffer = memdup_user(arg.buffer, arg.buffer_size); + kernel_buffer = memdup_user(arg->buffer, arg->buffer_size); if (!kernel_buffer || IS_ERR(kernel_buffer)) { kfree(kernel_string); return; } - my_fun_func(kernel_string, kernel_buffer, arg.buffer_size); + my_fun_func(kernel_string, kernel_buffer, arg->buffer_size); kfree(kernel_string); kfree(kernel_buffer); } diff --git a/lib/math/int_sqrt.c b/lib/math/int_sqrt.c index a52b16f8045393..a8170bb9142f39 100644 --- a/lib/math/int_sqrt.c +++ b/lib/math/int_sqrt.c @@ -10,17 +10,6 @@ #include #include #include -#include - -struct int_sqrt_arg { - unsigned long x; -}; - -FUZZ_TEST(int_sqrt, struct int_sqrt_arg) -{ - unsigned long res = int_sqrt(arg.x); - pr_info("fuzz_arg.x = %lu, res = %lu", arg.x, res); -} /** * int_sqrt - computes the integer square root @@ -32,11 +21,6 @@ unsigned long int_sqrt(unsigned long x) { unsigned long b, m, y = 0; - // deliberate bug - if (x == 0) { - char c = *(char *)x; - pr_info("should crash here! %c\n", c); - } if (x <= 1) return x; @@ -67,7 +51,7 @@ u32 int_sqrt64(u64 x) u64 b, m, y = 0; if (x <= ULONG_MAX) - return int_sqrt((unsigned long)x); + return int_sqrt((unsigned long) x); m = 1ULL << ((fls64(x) - 1) & ~1ULL); while (m != 0) { From 006c3572d2a825d4af20b73fd072b5bd51d2fef2 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Fri, 11 Jul 2025 14:00:03 +0000 Subject: [PATCH 3/5] kftf: remove some prints from write_input_callback, update test case to remove copying from userspace --- include/linux/kftf.h | 30 ++++++++++++++++-------------- lib/kftf/kftf_tests.h | 25 +++++++------------------ 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index f0e36c5ed2b60d..e36ef95cb8fd65 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -119,11 +119,8 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, void *buffer = kmalloc(len, GFP_KERNEL); \ if (!buffer || IS_ERR(buffer)) \ return PTR_ERR(buffer); \ - err = write_input_cb_common(filp, buf, len, off, buffer, \ - sizeof(buffer)); \ + err = write_input_cb_common(filp, buf, len, off, buffer, len); \ if (err != 0) { \ - pr_info("%s: failed to read data, len = %zu\n", \ - __FUNCTION__, len); \ kfree(buffer); \ return err; \ } \ @@ -273,49 +270,54 @@ struct kftf_annotation { __KFTF_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) struct reloc_entry { - off_t pointer; - off_t value; + uintptr_t pointer; /* offset from the beginning of the payload */ + uintptr_t value; /* difference between the pointed to address and the address itself */ }; struct reloc_table { int num_entries; - int padding[3]; // why? + int padding[3]; struct reloc_entry entries[]; }; static_assert(offsetof(struct reloc_table, entries) % sizeof(struct reloc_entry) == 0); +static const uintptr_t nullPtr = (uintptr_t)-1; + static void *kftf_parse_input(void *input, size_t input_size) { - pr_info("[ENTER]%s\n", __FUNCTION__); size_t i; void *payload_start; uintptr_t *ptr_location; struct reloc_entry re; + pr_info("%s: input_size = %zu\n", __FUNCTION__, input_size); if (input_size < sizeof(struct reloc_table)) { pr_warn("got misformed input in %s\n", __FUNCTION__); + return NULL; } struct reloc_table *rt = input; - pr_info("%s: num_entries = %d\n", __FUNCTION__, rt->num_entries); - payload_start = input + offsetof(struct reloc_table, entries) + + payload_start = (char *)input + offsetof(struct reloc_table, entries) + rt->num_entries * sizeof(struct reloc_entry); - if (payload_start >= input + input_size) return NULL; for (i = 0; i < rt->num_entries; i++) { re = rt->entries[i]; ptr_location = (uintptr_t *)(payload_start + re.pointer); - if ((void *)ptr_location >= input + input_size) + if ((void *)ptr_location + sizeof(uintptr_t) >= + input + input_size) return NULL; - *ptr_location = (uintptr_t)ptr_location + re.value; + if (re.value == nullPtr) { + *ptr_location = (uintptr_t)NULL; + } else { + *ptr_location = (uintptr_t)ptr_location + re.value; + } } - pr_info("%s: parsed %d entries\n", __FUNCTION__, rt->num_entries); return payload_start; } diff --git a/lib/kftf/kftf_tests.h b/lib/kftf/kftf_tests.h index 5257783c936622..46b23d15230d52 100644 --- a/lib/kftf/kftf_tests.h +++ b/lib/kftf/kftf_tests.h @@ -42,41 +42,30 @@ struct my_fun_func_arg { size_t buffer_size; }; +volatile char __w; static void my_fun_func(const char *string, char *buffer, size_t buffer_size) { size_t i; /* string should be NULL terminated! */ - pr_info("this is my string: %s", string); + pr_info("string length = %zu", + strlen(string) + 1 /* null terminated str */); + pr_info("buffer_size = %zu\n", buffer_size); for (i = 0; i < buffer_size; i++) { buffer[i]++; - pr_info("buffer[%zu] = %c\n", i, buffer[i]); + __w = buffer[i]; // avoid inlining } } FUZZ_TEST(my_memncpy, struct my_fun_func_arg) { - const char *kernel_string; - char *kernel_buffer; + pr_info("[ENTER] %s\n", __FUNCTION__); KFTF_ANNOTATE_STRING(my_fun_func_arg, string); KFTF_ANNOTATE_LEN(my_fun_func_arg, buffer_size, buffer); KFTF_EXPECT_NOT_NULL(my_fun_func_arg, string); KFTF_EXPECT_NOT_NULL(my_fun_func_arg, buffer); - - kernel_string = strndup_user(arg->string, PAGE_SIZE); - if (!kernel_string || IS_ERR(kernel_string)) - return; - - kernel_buffer = memdup_user(arg->buffer, arg->buffer_size); - if (!kernel_buffer || IS_ERR(kernel_buffer)) { - kfree(kernel_string); - return; - } - - my_fun_func(kernel_string, kernel_buffer, arg->buffer_size); - kfree(kernel_string); - kfree(kernel_buffer); + my_fun_func(arg->string, arg->buffer, arg->buffer_size); } #endif /* KFTF_TESTS_H */ From 8f89c46392ee4e08c584f09f6bf2f4068d23ce27 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Mon, 14 Jul 2025 09:45:27 +0000 Subject: [PATCH 4/5] kftf: introduce new buffer for payload that provides alignment guarantees for encoded structures. --- include/linux/kftf.h | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index e36ef95cb8fd65..744db30c457457 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -135,6 +135,7 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len, pr_info("invoking fuzz logic for %s\n", #func); \ _fuzz_test_logic_##func(arg); \ kfree(buffer); \ + kfree(payload); \ return len; \ } \ static void _fuzz_test_logic_##func(func_arg_type *arg) @@ -274,9 +275,16 @@ struct reloc_entry { uintptr_t value; /* difference between the pointed to address and the address itself */ }; +/* + * How many integers of padding in the relocation table between the header + * information and the relocation entries + */ +#define RELOC_TABLE_PADDING 2 + struct reloc_table { int num_entries; - int padding[3]; + uint32_t max_alignment; + int padding[RELOC_TABLE_PADDING]; struct reloc_entry entries[]; }; static_assert(offsetof(struct reloc_table, entries) % @@ -288,8 +296,10 @@ static const uintptr_t nullPtr = (uintptr_t)-1; static void *kftf_parse_input(void *input, size_t input_size) { size_t i; - void *payload_start; + void *payload_start, *out; uintptr_t *ptr_location; + size_t payload_len, alloc_size; + struct reloc_table *rt; struct reloc_entry re; pr_info("%s: input_size = %zu\n", __FUNCTION__, input_size); @@ -297,16 +307,35 @@ static void *kftf_parse_input(void *input, size_t input_size) pr_warn("got misformed input in %s\n", __FUNCTION__); return NULL; } - struct reloc_table *rt = input; + rt = input; payload_start = (char *)input + offsetof(struct reloc_table, entries) + rt->num_entries * sizeof(struct reloc_entry); if (payload_start >= input + input_size) return NULL; + /* + * To guarantee correct alignment of structures within the payload, we + * allocate a new property that is aligned to the next power of two + * greater than either the size of the payload or the maximum alignment + * of the nested structures. + */ + payload_len = input_size - (payload_start - input); + alloc_size = MAX(roundup_pow_of_two(payload_len), + roundup_pow_of_two(rt->max_alignment)); + out = kmalloc(alloc_size, GFP_KERNEL); + if (!out) { + return NULL; + } + memcpy(out, payload_start, payload_len); + + /* + * Iterate through entries in the relocation table and patch the + * pointers. + */ for (i = 0; i < rt->num_entries; i++) { re = rt->entries[i]; - ptr_location = (uintptr_t *)(payload_start + re.pointer); + ptr_location = (uintptr_t *)(out + re.pointer); if ((void *)ptr_location + sizeof(uintptr_t) >= input + input_size) return NULL; @@ -318,7 +347,7 @@ static void *kftf_parse_input(void *input, size_t input_size) } } - return payload_start; + return out; } #endif /* KFTF_H */ From 8d95315254894646e61db1a3ff84b1472d06f5d5 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Tue, 15 Jul 2025 17:48:48 +0000 Subject: [PATCH 5/5] kftf: harden binary input parsing --- include/linux/kftf.h | 77 ++++++++++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/include/linux/kftf.h b/include/linux/kftf.h index 744db30c457457..20c5bc2782423f 100644 --- a/include/linux/kftf.h +++ b/include/linux/kftf.h @@ -237,6 +237,10 @@ static_assert(sizeof(struct kftf_constraint) == 64, __KFTF_DEFINE_CONSTRAINT(arg_type, field, lower_bound, upper_bound, \ EXPECT_IN_RANGE) +#define KFTF_EXPECT_LEN(expected_len, actual_len) \ + if ((expected_len) != (actual_len)) \ + return; + enum kftf_annotation_attribute : uint8_t { ATTRIBUTE_LEN = 0, ATTRIBUTE_STRING, @@ -291,17 +295,24 @@ static_assert(offsetof(struct reloc_table, entries) % sizeof(struct reloc_entry) == 0); +/** + * This value should be known the fuzz engine. + */ static const uintptr_t nullPtr = (uintptr_t)-1; -static void *kftf_parse_input(void *input, size_t input_size) +/* XXX: wasn't building before without attribute unused, but it is used in + * several locations - weird... */ +__attribute__((unused)) static void *kftf_parse_input(void *input, + size_t input_size) { size_t i; void *payload_start, *out; uintptr_t *ptr_location; - size_t payload_len, alloc_size; + size_t payload_len, alloc_size, entries_size, header_size; struct reloc_table *rt; struct reloc_entry re; - pr_info("%s: input_size = %zu\n", __FUNCTION__, input_size); + if (input_size > KMALLOC_MAX_SIZE) + return NULL; if (input_size < sizeof(struct reloc_table)) { pr_warn("got misformed input in %s\n", __FUNCTION__); @@ -309,24 +320,54 @@ static void *kftf_parse_input(void *input, size_t input_size) } rt = input; - payload_start = (char *)input + offsetof(struct reloc_table, entries) + - rt->num_entries * sizeof(struct reloc_entry); + if (check_mul_overflow(rt->num_entries, sizeof(struct reloc_entry), + &entries_size)) + return NULL; + header_size = offsetof(struct reloc_table, entries) + entries_size; + if (header_size > input_size) + return NULL; + + payload_start = (char *)input + header_size; if (payload_start >= input + input_size) return NULL; - /* - * To guarantee correct alignment of structures within the payload, we - * allocate a new property that is aligned to the next power of two - * greater than either the size of the payload or the maximum alignment - * of the nested structures. - */ - payload_len = input_size - (payload_start - input); - alloc_size = MAX(roundup_pow_of_two(payload_len), - roundup_pow_of_two(rt->max_alignment)); - out = kmalloc(alloc_size, GFP_KERNEL); - if (!out) { + if (!is_power_of_2(rt->max_alignment) || rt->max_alignment > PAGE_SIZE) return NULL; + + payload_len = input_size - (payload_start - input); + + /* + * Check input for out-of-bounds pointers before before allocating + * aligned output buffer. + */ + for (i = 0; i < rt->num_entries; i++) { + re = rt->entries[i]; + if (re.pointer > payload_len || + re.pointer + sizeof(uintptr_t) > payload_len) + return NULL; + + if (re.value == nullPtr) + continue; + + if (re.pointer + re.value >= payload_len || + re.pointer + re.value + sizeof(uintptr_t) > payload_len) + return NULL; } + + /* + * To guarantee correct alignment of structures within the payload, we + * allocate a new buffer that is aligned to the next power of two + * greater than either the size of the payload + 1 or the maximum + * alignment of the nested structures. We add one to the payload length + * and call kzalloc to ensure that the payload is padded by trailing + * zeros to prevent false-positives on non-null terminated strings. + */ + alloc_size = + MAX(roundup_pow_of_two(payload_len + 1), rt->max_alignment); + out = kzalloc(alloc_size, GFP_KERNEL); + if (!out) + return NULL; + memcpy(out, payload_start, payload_len); /* @@ -336,10 +377,6 @@ static void *kftf_parse_input(void *input, size_t input_size) for (i = 0; i < rt->num_entries; i++) { re = rt->entries[i]; ptr_location = (uintptr_t *)(out + re.pointer); - if ((void *)ptr_location + sizeof(uintptr_t) >= - input + input_size) - return NULL; - if (re.value == nullPtr) { *ptr_location = (uintptr_t)NULL; } else {