Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions arch/x86/kernel/vmlinux.lds.S
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,13 @@ ASSERT(__relocate_kernel_end - __relocate_kernel_start <= KEXEC_CONTROL_CODE_MAX
#else
#define KEXEC_RELOCATE_KERNEL
#endif

#define KFTF_TABLE \

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" :)

. = 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_ */
Expand Down Expand Up @@ -199,6 +206,8 @@ SECTIONS
CONSTRUCTORS
KEXEC_RELOCATE_KERNEL

KFTF_TABLE

/* rarely changed data like cpu maps */
READ_MOSTLY_DATA(INTERNODE_CACHE_BYTES)

Expand Down
128 changes: 128 additions & 0 deletions include/linux/kftf.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#ifndef KFTF_H

Choose a reason for hiding this comment

The 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 (/* SPDX-License-Identifier: GPL-2.0 */) and a short comment explaining what it is doing.

Check include/linux/kfence.h for an example (drop (C) per go/copyright).

#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.
*

Choose a reason for hiding this comment

The 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:

Choose a reason for hiding this comment

The 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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: single-line comments should end with a period.

Choose a reason for hiding this comment

The 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);
Copy link
Owner Author

@ethangraham2001 ethangraham2001 Jun 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO

I think it would be great to have some kind of KFTF_BUG_ON(condition) or similar so that a bug is reported in a consistent way in the case that a fuzz tests fails.

Copy link
Owner Author

@ethangraham2001 ethangraham2001 Jun 25, 2025

Choose a reason for hiding this comment

The 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 ASSERT_TRUE(condition) as is the case in libFuzzer

Choose a reason for hiding this comment

The 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 */
1 change: 1 addition & 0 deletions lib/Kconfig.debug
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Choose a reason for hiding this comment

The 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"

Expand Down
5 changes: 5 additions & 0 deletions lib/Kconfig.kftf
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
2 changes: 2 additions & 0 deletions lib/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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/

Expand Down
4 changes: 4 additions & 0 deletions lib/kftf/Makefile
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
189 changes: 189 additions & 0 deletions lib/kftf/kftf_main.c
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>

Choose a reason for hiding this comment

The 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
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE: these aren't being linked against properly if we build with CONFIG_KFTF=M (I.e. as a module). It seems a little too restrictive to enforce that the fuzz test targets always be exposed as a built-in.

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);
Loading