From 6673d6fd0d655ce221c8bf6db36a82cce9a03d44 Mon Sep 17 00:00:00 2001 From: Ethan Graham Date: Thu, 3 Jul 2025 19:32:19 +0000 Subject: [PATCH 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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 */