linux-mm.kvack.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 00/10] KFuzzTest: a new kernel fuzzing framework
@ 2025-12-04 14:12 Ethan Graham
  2025-12-04 14:12 ` [PATCH 01/10] mm/kasan: implement kasan_poison_range Ethan Graham
                   ` (8 more replies)
  0 siblings, 9 replies; 21+ messages in thread
From: Ethan Graham @ 2025-12-04 14:12 UTC (permalink / raw)
  To: ethan.w.s.graham, glider
  Cc: andreyknvl, andy, andy.shevchenko, brauner, brendan.higgins,
	davem, davidgow, dhowells, dvyukov, elver, herbert, ignat, jack,
	jannh, johannes, kasan-dev, kees, kunit-dev, linux-crypto,
	linux-kernel, linux-mm, lukas, rmoar, shuah, sj, tarasmadan

This patch series introduces KFuzzTest, a lightweight framework for
creating in-kernel fuzz targets for internal kernel functions.

The primary motivation for KFuzzTest is to simplify the fuzzing of
low-level, relatively stateless functions (e.g., data parsers, format
converters) that are difficult to exercise effectively from the syscall
boundary. It is intended for in-situ fuzzing of kernel code without
requiring that it be built as a separate userspace library or that its
dependencies be stubbed out. Using a simple macro-based API, developers
can add a new fuzz target with minimal boilerplate code.

The core design consists of three main parts:
1. The `FUZZ_TEST(name, struct_type)` and `FUZZ_TEST_SIMPLE(name)`
   macros that allow developers to easily define a fuzz test.
2. A binary input format that allows a userspace fuzzer to serialize
   complex, pointer-rich C structures into a single buffer.
3. Metadata for test targets, constraints, and annotations, which is
   emitted into dedicated ELF sections to allow for discovery and
   inspection by userspace tools. These are found in
   ".kfuzztest_{targets, constraints, annotations}".

As of September 2025, syzkaller supports KFuzzTest targets out of the
box, and without requiring any hand-written descriptions - the fuzz
target and its constraints + annotations are the sole source of truth.

To validate the framework's end-to-end effectiveness, we performed an
experiment by manually introducing an off-by-one buffer over-read into
pkcs7_parse_message, like so:

- ret = asn1_ber_decoder(&pkcs7_decoder, ctx, data, datalen);
+ ret = asn1_ber_decoder(&pkcs7_decoder, ctx, data, datalen + 1);

A syzkaller instance fuzzing the new test_pkcs7_parse_message target
introduced in patch 7 successfully triggered the bug inside of
asn1_ber_decoder in under 30 seconds from a cold start. Similar
experiments on the other new fuzz targets (patches 8-9) also
successfully identified injected bugs, proving that KFuzzTest is
effective when paired with a coverage-guided fuzzing engine.


The patch series is structured as follows:
- Patch 1 adds and exposes kasan_poison_range for poisoning memory
  ranges with an unaligned start address and KASAN_GRANULE_SIZE aligned
  end address.
- Patch 2 introduces the core KFuzzTest API and data structures.
- Patch 3 introduces the FUZZ_TEST_SIMPLE API for blob-based fuzzing.
- Patch 4 adds the runtime implementation for the framework.
- Patch 5 adds a tool for sending structured inputs into a fuzz target.
- Patch 6 adds documentation.
- Patch 7 provides sample fuzz targets.
- Patch 8 defines fuzz targets for several functions in /crypto.
- Patch 9 defines a fuzz target for parse_xy in /drivers/auxdisplay.
- Patch 10 adds maintainer information for KFuzzTest.

Changes since PR v2:
- Introduce the FUZZ_TEST_SIMPLE macro (patch 3) for blob-based fuzzing,
  and update the module code (now patch 4) to initialize an input_simple
  debugfs file for such targets. While not explicitly requested by
  Johannes Berg, this was developed to address his concerns of the
  serialization format representing a hard barrier for entry.
- Update the crypto/ fuzz targets to use the FUZZ_TEST_SIMPLE macro.
- Per feedback from Kees Cook, the fuzz target for binfmt_load_script
  (previously patch 9/10) has been dropped as it is trivial to fuzz from
  userspace and therefore not a good example of KFuzzTest in action.
- Per feedback from Andrey Konovalov, introduce some WARN_ONs and remove
  redundant checks from kasan_poison_range.
- Per feedback from Andrey Konovalov, move kasan_poison_range's
  implementation into mm/kasan/common.c so that it is built with HW_TAGS
  mode enabled.
- Per feedback from Andy Shevchenko and Lukas Wunner, address the build
  system concerns.

Ethan Graham (10):
  mm/kasan: implement kasan_poison_range
  kfuzztest: add user-facing API and data structures
  kfuzztest: introduce the FUZZ_TEST_SIMPLE macro
  kfuzztest: implement core module and input processing
  tools: add kfuzztest-bridge utility
  kfuzztest: add ReST documentation
  kfuzztest: add KFuzzTest sample fuzz targets
  crypto: implement KFuzzTest targets for PKCS7 and RSA parsing
  drivers/auxdisplay: add a KFuzzTest for parse_xy()
  MAINTAINERS: add maintainer information for KFuzzTest

 Documentation/dev-tools/index.rst             |   1 +
 Documentation/dev-tools/kfuzztest.rst         | 491 +++++++++++++++
 MAINTAINERS                                   |   8 +
 crypto/asymmetric_keys/Makefile               |   2 +
 crypto/asymmetric_keys/tests/Makefile         |   4 +
 crypto/asymmetric_keys/tests/pkcs7_kfuzz.c    |  17 +
 .../asymmetric_keys/tests/rsa_helper_kfuzz.c  |  20 +
 drivers/auxdisplay/Makefile                   |   3 +
 drivers/auxdisplay/tests/charlcd_kfuzz.c      |  22 +
 include/asm-generic/vmlinux.lds.h             |  26 +-
 include/linux/kasan.h                         |  11 +
 include/linux/kfuzztest.h                     | 573 ++++++++++++++++++
 lib/Kconfig.debug                             |   1 +
 lib/Makefile                                  |   2 +
 lib/kfuzztest/Kconfig                         |  20 +
 lib/kfuzztest/Makefile                        |   4 +
 lib/kfuzztest/main.c                          | 278 +++++++++
 lib/kfuzztest/parse.c                         | 236 ++++++++
 mm/kasan/common.c                             |  37 ++
 samples/Kconfig                               |   7 +
 samples/Makefile                              |   1 +
 samples/kfuzztest/Makefile                    |   3 +
 samples/kfuzztest/overflow_on_nested_buffer.c |  71 +++
 samples/kfuzztest/underflow_on_buffer.c       |  51 ++
 tools/Makefile                                |  18 +-
 tools/testing/kfuzztest-bridge/.gitignore     |   2 +
 tools/testing/kfuzztest-bridge/Build          |   6 +
 tools/testing/kfuzztest-bridge/Makefile       |  49 ++
 tools/testing/kfuzztest-bridge/bridge.c       | 115 ++++
 tools/testing/kfuzztest-bridge/byte_buffer.c  |  85 +++
 tools/testing/kfuzztest-bridge/byte_buffer.h  |  31 +
 tools/testing/kfuzztest-bridge/encoder.c      | 390 ++++++++++++
 tools/testing/kfuzztest-bridge/encoder.h      |  16 +
 tools/testing/kfuzztest-bridge/input_lexer.c  | 256 ++++++++
 tools/testing/kfuzztest-bridge/input_lexer.h  |  58 ++
 tools/testing/kfuzztest-bridge/input_parser.c | 425 +++++++++++++
 tools/testing/kfuzztest-bridge/input_parser.h |  82 +++
 .../testing/kfuzztest-bridge/kfuzztest-bridge | Bin 0 -> 911160 bytes
 tools/testing/kfuzztest-bridge/rand_stream.c  |  77 +++
 tools/testing/kfuzztest-bridge/rand_stream.h  |  57 ++
 40 files changed, 3552 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/dev-tools/kfuzztest.rst
 create mode 100644 crypto/asymmetric_keys/tests/Makefile
 create mode 100644 crypto/asymmetric_keys/tests/pkcs7_kfuzz.c
 create mode 100644 crypto/asymmetric_keys/tests/rsa_helper_kfuzz.c
 create mode 100644 drivers/auxdisplay/tests/charlcd_kfuzz.c
 create mode 100644 include/linux/kfuzztest.h
 create mode 100644 lib/kfuzztest/Kconfig
 create mode 100644 lib/kfuzztest/Makefile
 create mode 100644 lib/kfuzztest/main.c
 create mode 100644 lib/kfuzztest/parse.c
 create mode 100644 samples/kfuzztest/Makefile
 create mode 100644 samples/kfuzztest/overflow_on_nested_buffer.c
 create mode 100644 samples/kfuzztest/underflow_on_buffer.c
 create mode 100644 tools/testing/kfuzztest-bridge/.gitignore
 create mode 100644 tools/testing/kfuzztest-bridge/Build
 create mode 100644 tools/testing/kfuzztest-bridge/Makefile
 create mode 100644 tools/testing/kfuzztest-bridge/bridge.c
 create mode 100644 tools/testing/kfuzztest-bridge/byte_buffer.c
 create mode 100644 tools/testing/kfuzztest-bridge/byte_buffer.h
 create mode 100644 tools/testing/kfuzztest-bridge/encoder.c
 create mode 100644 tools/testing/kfuzztest-bridge/encoder.h
 create mode 100644 tools/testing/kfuzztest-bridge/input_lexer.c
 create mode 100644 tools/testing/kfuzztest-bridge/input_lexer.h
 create mode 100644 tools/testing/kfuzztest-bridge/input_parser.c
 create mode 100644 tools/testing/kfuzztest-bridge/input_parser.h
 create mode 100755 tools/testing/kfuzztest-bridge/kfuzztest-bridge
 create mode 100644 tools/testing/kfuzztest-bridge/rand_stream.c
 create mode 100644 tools/testing/kfuzztest-bridge/rand_stream.h

-- 
2.51.0



^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 01/10] mm/kasan: implement kasan_poison_range
  2025-12-04 14:12 [PATCH v3 00/10] KFuzzTest: a new kernel fuzzing framework Ethan Graham
@ 2025-12-04 14:12 ` Ethan Graham
  2025-12-04 15:17   ` Andrey Konovalov
  2025-12-04 14:12 ` [PATCH 02/10] kfuzztest: add user-facing API and data structures Ethan Graham
                   ` (7 subsequent siblings)
  8 siblings, 1 reply; 21+ messages in thread
From: Ethan Graham @ 2025-12-04 14:12 UTC (permalink / raw)
  To: ethan.w.s.graham, glider
  Cc: andreyknvl, andy, andy.shevchenko, brauner, brendan.higgins,
	davem, davidgow, dhowells, dvyukov, elver, herbert, ignat, jack,
	jannh, johannes, kasan-dev, kees, kunit-dev, linux-crypto,
	linux-kernel, linux-mm, lukas, rmoar, shuah, sj, tarasmadan,
	Ethan Graham

From: Ethan Graham <ethangraham@google.com>

Introduce a new helper function, kasan_poison_range(), to encapsulate
the logic for poisoning an arbitrary memory range of a given size, and
expose it publically in <include/linux/kasan.h>.

This is a preparatory change for the upcoming KFuzzTest patches, which
requires the ability to poison the inter-region padding in its input
buffers.

No functional change to any other subsystem is intended by this commit.

Signed-off-by: Ethan Graham <ethangraham@google.com>
Signed-off-by: Ethan Graham <ethan.w.s.graham@gmail.com>
Reviewed-by: Alexander Potapenko <glider@google.com>

---
PR v3:
- Move kasan_poison_range into mm/kasan/common.c so that it is built
  with HW_TAGS mode enabled.
- Add a runtime check for kasan_enabled() in kasan_poison_range.
- Add two WARN_ON()s in kasan_poison_range when the input is invalid.
PR v1:
- Enforce KASAN_GRANULE_SIZE alignment for the end of the range in
  kasan_poison_range(), and return -EINVAL when this isn't respected.
---
---
 include/linux/kasan.h | 11 +++++++++++
 mm/kasan/common.c     | 37 +++++++++++++++++++++++++++++++++++++
 2 files changed, 48 insertions(+)

diff --git a/include/linux/kasan.h b/include/linux/kasan.h
index 890011071f2b..cd6cdf732378 100644
--- a/include/linux/kasan.h
+++ b/include/linux/kasan.h
@@ -102,6 +102,16 @@ static inline bool kasan_has_integrated_init(void)
 }
 
 #ifdef CONFIG_KASAN
+
+/**
+ * kasan_poison_range - poison the memory range [@addr, @addr + @size)
+ *
+ * The exact behavior is subject to alignment with KASAN_GRANULE_SIZE, defined
+ * in <mm/kasan/kasan.h>: if @start is unaligned, the initial partial granule
+ * at the beginning of the range is only poisoned if CONFIG_KASAN_GENERIC=y.
+ */
+int kasan_poison_range(const void *addr, size_t size);
+
 void __kasan_unpoison_range(const void *addr, size_t size);
 static __always_inline void kasan_unpoison_range(const void *addr, size_t size)
 {
@@ -402,6 +412,7 @@ static __always_inline bool kasan_check_byte(const void *addr)
 
 #else /* CONFIG_KASAN */
 
+static inline int kasan_poison_range(const void *start, size_t size) { return 0; }
 static inline void kasan_unpoison_range(const void *address, size_t size) {}
 static inline void kasan_poison_pages(struct page *page, unsigned int order,
 				      bool init) {}
diff --git a/mm/kasan/common.c b/mm/kasan/common.c
index 9142964ab9c9..c83579ef37c6 100644
--- a/mm/kasan/common.c
+++ b/mm/kasan/common.c
@@ -570,3 +570,40 @@ bool __kasan_check_byte(const void *address, unsigned long ip)
 	}
 	return true;
 }
+
+int kasan_poison_range(const void *addr, size_t size)
+{
+	uintptr_t start_addr = (uintptr_t)addr;
+	uintptr_t head_granule_start;
+	uintptr_t poison_body_start;
+	uintptr_t poison_body_end;
+	size_t head_prefix_size;
+	uintptr_t end_addr;
+
+	if (!kasan_enabled())
+		return 0;
+
+	end_addr = start_addr + size;
+	if (WARN_ON(end_addr % KASAN_GRANULE_SIZE))
+		return -EINVAL;
+
+	if (WARN_ON(start_addr >= end_addr))
+		return -EINVAL;
+
+	head_granule_start = ALIGN_DOWN(start_addr, KASAN_GRANULE_SIZE);
+	head_prefix_size = start_addr - head_granule_start;
+
+	if (IS_ENABLED(CONFIG_KASAN_GENERIC) && head_prefix_size > 0)
+		kasan_poison_last_granule((void *)head_granule_start,
+					  head_prefix_size);
+
+	poison_body_start = ALIGN(start_addr, KASAN_GRANULE_SIZE);
+	poison_body_end = end_addr;
+
+	if (poison_body_start < poison_body_end)
+		kasan_poison((void *)poison_body_start,
+			     poison_body_end - poison_body_start,
+			     KASAN_SLAB_REDZONE, false);
+	return 0;
+}
+EXPORT_SYMBOL(kasan_poison_range);
-- 
2.51.0



^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 02/10] kfuzztest: add user-facing API and data structures
  2025-12-04 14:12 [PATCH v3 00/10] KFuzzTest: a new kernel fuzzing framework Ethan Graham
  2025-12-04 14:12 ` [PATCH 01/10] mm/kasan: implement kasan_poison_range Ethan Graham
@ 2025-12-04 14:12 ` Ethan Graham
  2025-12-04 14:12 ` [PATCH 03/10] kfuzztest: introduce the FUZZ_TEST_SIMPLE macro Ethan Graham
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 21+ messages in thread
From: Ethan Graham @ 2025-12-04 14:12 UTC (permalink / raw)
  To: ethan.w.s.graham, glider
  Cc: andreyknvl, andy, andy.shevchenko, brauner, brendan.higgins,
	davem, davidgow, dhowells, dvyukov, elver, herbert, ignat, jack,
	jannh, johannes, kasan-dev, kees, kunit-dev, linux-crypto,
	linux-kernel, linux-mm, lukas, rmoar, shuah, sj, tarasmadan,
	Ethan Graham

From: Ethan Graham <ethangraham@google.com>

Add the foundational user-facing components for the KFuzzTest framework.
This includes the main API header <linux/kfuzztest.h>, the Kconfig
option to enable the feature, and the required linker script changes
which introduce three new ELF sections in vmlinux.

Note that KFuzzTest is intended strictly for debug builds only, and
should never be enabled in a production build. The fact that it exposes
internal kernel functions and state directly to userspace may constitute
a serious security vulnerability if used for any reason other than
testing.

The header defines:
- The FUZZ_TEST() macro for creating test targets.
- The data structures required for the binary serialization format,
  which allows passing complex inputs from userspace.
- The metadata structures for test targets, constraints and annotations,
  which are placed in dedicated ELF sections (.kfuzztest_*) for
  discovery.

This patch only adds the public interface and build integration; no
runtime logic is included.

Signed-off-by: Ethan Graham <ethangraham@google.com>
Signed-off-by: Ethan Graham <ethan.w.s.graham@gmail.com>
Reviewed-by: Alexander Potapenko <glider@google.com>

---
PR v3:
- Reorder definitions in kfuzztest.h for better flow and readability.
- Introduce __KFUZZTEST_CONSTRAINT macro in preparation for the
  introduction of the FUZZ_TEST_SIMPLE macro in the following patch,
  which uses it for manually emitting constraint metadata.
PR v1:
- Move KFuzzTest metadata definitions to generic vmlinux linkage so that
  the framework isn't bound to x86_64.
- Return -EFAULT when simple_write_to_buffer returns a value not equal
  to the input length in the main FUZZ_TEST macro.
- Enforce a maximum input size of 64KiB in the main FUZZ_TEST macro,
  returning -EINVAL when it isn't respected.
- Refactor KFUZZTEST_ANNOTATION_* macros.
- Taint the kernel with TAINT_TEST inside the FUZZ_TEST macro when a
  fuzz target is invoked for the first time.
---
---
 include/asm-generic/vmlinux.lds.h |  22 +-
 include/linux/kfuzztest.h         | 486 ++++++++++++++++++++++++++++++
 lib/Kconfig.debug                 |   1 +
 lib/kfuzztest/Kconfig             |  20 ++
 4 files changed, 528 insertions(+), 1 deletion(-)
 create mode 100644 include/linux/kfuzztest.h
 create mode 100644 lib/kfuzztest/Kconfig

diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index ae2d2359b79e..9afe569d013b 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -373,7 +373,8 @@ defined(CONFIG_AUTOFDO_CLANG) || defined(CONFIG_PROPELLER_CLANG)
 	TRACE_PRINTKS()							\
 	BPF_RAW_TP()							\
 	TRACEPOINT_STR()						\
-	KUNIT_TABLE()
+	KUNIT_TABLE()							\
+	KFUZZTEST_TABLE()
 
 /*
  * Data section helpers
@@ -966,6 +967,25 @@ defined(CONFIG_AUTOFDO_CLANG) || defined(CONFIG_PROPELLER_CLANG)
 		BOUNDED_SECTION_POST_LABEL(.kunit_init_test_suites, \
 				__kunit_init_suites, _start, _end)
 
+#ifdef CONFIG_KFUZZTEST
+#define KFUZZTEST_TABLE()						\
+	. = ALIGN(PAGE_SIZE);						\
+	__kfuzztest_targets_start = .;					\
+	KEEP(*(.kfuzztest_target));					\
+	__kfuzztest_targets_end = .;					\
+	. = ALIGN(PAGE_SIZE);						\
+	__kfuzztest_constraints_start = .;				\
+	KEEP(*(.kfuzztest_constraint));					\
+	__kfuzztest_constraints_end = .;				\
+	. = ALIGN(PAGE_SIZE);						\
+	__kfuzztest_annotations_start = .;				\
+	KEEP(*(.kfuzztest_annotation));					\
+	__kfuzztest_annotations_end = .;
+
+#else /* CONFIG_KFUZZTEST */
+#define KFUZZTEST_TABLE()
+#endif /* CONFIG_KFUZZTEST */
+
 #ifdef CONFIG_BLK_DEV_INITRD
 #define INIT_RAM_FS							\
 	. = ALIGN(4);							\
diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h
new file mode 100644
index 000000000000..1839fcfeabf5
--- /dev/null
+++ b/include/linux/kfuzztest.h
@@ -0,0 +1,486 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * The Kernel Fuzz Testing Framework (KFuzzTest) API for defining fuzz targets
+ * for internal kernel functions.
+ *
+ * For more information please see Documentation/dev-tools/kfuzztest.rst.
+ *
+ * Copyright 2025 Google LLC
+ */
+#ifndef KFUZZTEST_H
+#define KFUZZTEST_H
+
+#include <linux/fs.h>
+#include <linux/printk.h>
+#include <linux/types.h>
+
+#define KFUZZTEST_HEADER_MAGIC (0xBFACE)
+#define KFUZZTEST_V0 (0)
+#define KFUZZTEST_REGIONID_NULL U32_MAX
+#define KFUZZTEST_MAX_INPUT_SIZE (PAGE_SIZE * 16)
+/**
+ * The end of the input should be padded by at least this number of bytes as
+ * it is poisoned to detect out of bounds accesses at the end of the last
+ * region.
+ */
+#define KFUZZTEST_POISON_SIZE 0x8
+
+/**
+ * @brief The KFuzzTest Input Serialization Format
+ *
+ * KFuzzTest receives its input from userspace as a single binary blob. This
+ * format allows for the serialization of complex, pointer-rich C structures
+ * into a flat buffer that can be safely passed into the kernel. This format
+ * requires only a single copy from userspace into a kernel buffer, and no
+ * further kernel allocations. Pointers are patched internally using a "region"
+ * system where each region corresponds to some pointed-to data.
+ *
+ * Regions should be padded to respect alignment constraints of their underlying
+ * types, and should be followed by at least 8 bytes of padding. These padded
+ * regions are poisoned by KFuzzTest to ensure that KASAN catches OOB accesses.
+ *
+ * The format consists of a header and three main components:
+ * 1. An 8-byte header: Contains KFUZZTEST_MAGIC in the first 4 bytes, and the
+ *	version number in the subsequent 4 bytes. This ensures backwards
+ *	compatibility in the event of future format changes.
+ * 2. A reloc_region_array: Defines the memory layout of the target structure
+ *	by partitioning the payload into logical regions. Each logical region
+ *	should contain the byte representation of the type that it represents,
+ *	including any necessary padding. The region descriptors should be
+ *	ordered by offset ascending.
+ * 3. A reloc_table: Provides "linking" instructions that tell the kernel how
+ *	to patch pointer fields to point to the correct regions. By design,
+ *	the first region (index 0) is passed as input into a FUZZ_TEST.
+ * 4. A Payload: The raw binary data for the target structure and its associated
+ *	buffers. This should be aligned to the maximum alignment of all
+ *	regions to satisfy alignment requirements of the input types, but this
+ *	isn't checked by the parser.
+ *
+ * For a detailed specification of the binary layout see the full documentation
+ * at: Documentation/dev-tools/kfuzztest.rst
+ */
+
+/**
+ * struct reloc_region - single contiguous memory region in the payload
+ *
+ * @offset: The byte offset of this region from the start of the payload, which
+ *	should be aligned to the alignment requirements of the region's
+ *	underlying type.
+ * @size: The size of this region in bytes.
+ */
+struct reloc_region {
+	uint32_t offset;
+	uint32_t size;
+};
+
+/**
+ * struct reloc_region_array - array of regions in an input
+ *
+ * @num_regions: The total number of regions defined.
+ * @regions: A flexible array of `num_regions` region descriptors.
+ */
+struct reloc_region_array {
+	uint32_t num_regions;
+	struct reloc_region regions[];
+};
+
+/**
+ * struct reloc_entry - a single pointer to be patched in an input
+ *
+ * @region_id: The index of the region in the `reloc_region_array` that
+ *	contains the pointer.
+ * @region_offset: The start offset of the pointer inside of the region.
+ * @value: contains the index of the pointee region, or KFUZZTEST_REGIONID_NULL
+ *	if the pointer is NULL.
+ */
+struct reloc_entry {
+	uint32_t region_id;
+	uint32_t region_offset;
+	uint32_t value;
+};
+
+/**
+ * struct reloc_table - array of relocations required by an input
+ *
+ * @num_entries: the number of pointer relocations.
+ * @padding_size: the number of padded bytes between the last relocation in
+ *	entries, and the start of the payload data. This should be at least
+ *	8 bytes, as it is used for poisoning.
+ * @entries: array of relocations.
+ */
+struct reloc_table {
+	uint32_t num_entries;
+	uint32_t padding_size;
+	struct reloc_entry entries[];
+};
+
+/**
+ * kfuzztest_parse_and_relocate - validate and relocate a KFuzzTest input
+ *
+ * @input:      A buffer containing the serialized input for a fuzz target.
+ * @input_size: the size in bytes of the @input buffer.
+ * @arg_ret:    return pointer for the test case's input structure.
+ */
+int kfuzztest_parse_and_relocate(void *input, size_t input_size, void **arg_ret);
+
+enum kfuzztest_constraint_type {
+	EXPECT_EQ,
+	EXPECT_NE,
+	EXPECT_LT,
+	EXPECT_LE,
+	EXPECT_GT,
+	EXPECT_GE,
+	EXPECT_IN_RANGE,
+};
+
+/**
+ * struct kfuzztest_constraint - a metadata record for a domain constraint
+ *
+ * Domain constraints are rules about the input data that must be satisfied for
+ * a fuzz test to proceed. While they are enforced in the kernel with a runtime
+ * check, they are primarily intended as a discoverable contract for userspace
+ * fuzzers.
+ *
+ * Instances of this struct are generated by the KFUZZTEST_EXPECT_* macros
+ * and placed into the read-only ".kfuzztest_constraint" ELF section of the
+ * vmlinux binary. A fuzzer can parse this section to learn about the
+ * constraints and generate valid inputs more intelligently.
+ *
+ * For an example of how these constraints are used within a fuzz test, see the
+ * documentation for the FUZZ_TEST() macro.
+ *
+ * @input_type: The name of the input struct type, without the leading
+ *	"struct ".
+ * @field_name: The name of the field within the struct that this constraint
+ *	applies to.
+ * @value1: The primary value used in the comparison (e.g., the upper
+ *	bound for EXPECT_LE).
+ * @value2: The secondary value, used only for multi-value comparisons
+ *	(e.g., the upper bound for EXPECT_IN_RANGE).
+ * @type: The type of the constraint.
+ */
+struct kfuzztest_constraint {
+	const char *input_type;
+	const char *field_name;
+	uintptr_t value1;
+	uintptr_t value2;
+	enum kfuzztest_constraint_type type;
+} __aligned(64);
+
+
+#define __KFUZZTEST_CONSTRAINT(arg_type, field, val1, val2, tpe)				\
+	static struct kfuzztest_constraint __constraint_##arg_type##_##field				\
+		__section(".kfuzztest_constraint") __used = {						\
+			.input_type = "struct " #arg_type,						\
+			.field_name = #field,								\
+			.value1 = (uintptr_t)val1,							\
+			.value2 = (uintptr_t)val2,							\
+			.type = tpe,									\
+		}
+
+#define __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val1, val2, tpe, predicate)				\
+	do {													\
+		__KFUZZTEST_CONSTRAINT(arg_type, field, val1, val2, tpe);				\
+		if (!(predicate))										\
+			return;											\
+	} while (0)
+
+/**
+ * KFUZZTEST_EXPECT_EQ - constrain a field to be equal to a value
+ *
+ * @arg_type: name of the input structure, without the leading "struct ".
+ * @field: some field that is comparable
+ * @val: a value of the same type as @arg_type.@field
+ */
+#define KFUZZTEST_EXPECT_EQ(arg_type, field, val)	\
+	__KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_EQ, arg->field == val)
+
+/**
+ * KFUZZTEST_EXPECT_NE - constrain a field to be not equal to a value
+ *
+ * @arg_type: name of the input structure, without the leading "struct ".
+ * @field: some field that is comparable.
+ * @val: a value of the same type as @arg_type.@field.
+ */
+#define KFUZZTEST_EXPECT_NE(arg_type, field, val)	\
+	__KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_NE, arg->field != val)
+
+/**
+ * KFUZZTEST_EXPECT_LT - constrain a field to be less than a value
+ *
+ * @arg_type: name of the input structure, without the leading "struct ".
+ * @field: some field that is comparable.
+ * @val: a value of the same type as @arg_type.@field.
+ */
+#define KFUZZTEST_EXPECT_LT(arg_type, field, val)	\
+	__KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_LT, arg->field < val)
+
+/**
+ * KFUZZTEST_EXPECT_LE - constrain a field to be less than or equal to a value
+ *
+ * @arg_type: name of the input structure, without the leading "struct ".
+ * @field: some field that is comparable.
+ * @val: a value of the same type as @arg_type.@field.
+ */
+#define KFUZZTEST_EXPECT_LE(arg_type, field, val)	\
+		__KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_LE, arg->field <= val)
+
+/**
+ * KFUZZTEST_EXPECT_GT - constrain a field to be greater than a value
+ *
+ * @arg_type: name of the input structure, without the leading "struct ".
+ * @field: some field that is comparable.
+ * @val: a value of the same type as @arg_type.@field.
+ */
+#define KFUZZTEST_EXPECT_GT(arg_type, field, val)	\
+	__KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_GT, arg->field > val)
+
+/**
+ * KFUZZTEST_EXPECT_GE - constrain a field to be greater than or equal to a value
+ *
+ * @arg_type: name of the input structure, without the leading "struct ".
+ * @field: some field that is comparable.
+ * @val: a value of the same type as @arg_type.@field.
+ */
+#define KFUZZTEST_EXPECT_GE(arg_type, field, val)	\
+	__KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_GE, arg->field >= val)
+
+/**
+ * KFUZZTEST_EXPECT_NOT_NULL - constrain a pointer field to be non-NULL
+ *
+ * @arg_type: name of the input structure, without the leading "struct ".
+ * @field: a pointer field.
+ */
+#define KFUZZTEST_EXPECT_NOT_NULL(arg_type, field) KFUZZTEST_EXPECT_NE(arg_type, field, NULL)
+
+/**
+ * KFUZZTEST_EXPECT_IN_RANGE - constrain a field to be within a range
+ *
+ * @arg_type: name of the input structure, without the leading "struct ".
+ * @field: some field that is comparable.
+ * @lower_bound: a lower bound of the same type as @arg_type.@field.
+ * @upper_bound: an upper bound of the same type as @arg_type.@field.
+ */
+#define KFUZZTEST_EXPECT_IN_RANGE(arg_type, field, lower_bound, upper_bound)		\
+	__KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, lower_bound, upper_bound,	\
+			EXPECT_IN_RANGE, arg->field >= lower_bound && arg->field <= upper_bound)
+
+/**
+ * Annotations express attributes about structure fields that can't be easily
+ * or safely verified at runtime. They are intended as hints to the fuzzing
+ * engine to help it generate more semantically correct and effective inputs.
+ * Unlike constraints, annotations do not add any runtime checks and do not
+ * cause a test to exit early.
+ *
+ * For example, a `char *` field could be a raw byte buffer or a C-style
+ * null-terminated string. A fuzzer that is aware of this distinction can avoid
+ * creating inputs that would cause trivial, uninteresting crashes from reading
+ * past the end of a non-null-terminated buffer.
+ */
+enum kfuzztest_annotation_attribute {
+	ATTRIBUTE_LEN,
+	ATTRIBUTE_STRING,
+	ATTRIBUTE_ARRAY,
+};
+
+/**
+ * struct kfuzztest_annotation - a metadata record for a fuzzer hint
+ *
+ * This struct captures a single hint about a field in the input structure.
+ * Instances are generated by the KFUZZTEST_ANNOTATE_* macros and are placed
+ * into the read-only ".kfuzztest_annotation" ELF section of the vmlinux binary.
+ *
+ * A userspace fuzzer can parse this section to understand the semantic
+ * relationships between fields (e.g., which field is a length for which
+ * buffer) and the expected format of the data (e.g., a null-terminated
+ * string). This allows the fuzzer to be much more intelligent during input
+ * generation and mutation.
+ *
+ * For an example of how annotations are used within a fuzz test, see the
+ * documentation for the FUZZ_TEST() macro.
+ *
+ * @input_type: The name of the input struct type.
+ * @field_name: The name of the field being annotated (e.g., the data
+ *	buffer field).
+ * @linked_field_name: For annotations that link two fields (like
+ *	ATTRIBUTE_LEN), this is the name of the related field (e.g., the
+ *	length field). For others, this may be unused.
+ * @attrib: The type of the annotation hint.
+ */
+struct kfuzztest_annotation {
+	const char *input_type;
+	const char *field_name;
+	const char *linked_field_name;
+	enum kfuzztest_annotation_attribute attrib;
+} __aligned(32);
+
+#define __KFUZZTEST_ANNOTATE(arg_type, field, linked_field, attribute)						\
+	static struct kfuzztest_annotation __annotation_##arg_type##_##field __section(".kfuzztest_annotation")	\
+		__used = {											\
+			.input_type = "struct " #arg_type,							\
+			.field_name = #field,									\
+			.linked_field_name = #linked_field,							\
+			.attrib = attribute,									\
+		}
+
+/**
+ * KFUZZTEST_ANNOTATE_STRING - annotate a char* field as a C string
+ *
+ * We define a C string as a sequence of non-zero characters followed by exactly
+ * one null terminator.
+ *
+ * @arg_type: name of the input structure, without the leading "struct ".
+ * @field: the name of the field to annotate.
+ */
+#define KFUZZTEST_ANNOTATE_STRING(arg_type, field) __KFUZZTEST_ANNOTATE(arg_type, field, NULL, ATTRIBUTE_STRING)
+
+/**
+ * KFUZZTEST_ANNOTATE_ARRAY - annotate a pointer as an array
+ *
+ * We define an array as a contiguous memory region containing zero or more
+ * elements of the same type.
+ *
+ * @arg_type: name of the input structure, without the leading "struct ".
+ * @field: the name of the field to annotate.
+ */
+#define KFUZZTEST_ANNOTATE_ARRAY(arg_type, field) __KFUZZTEST_ANNOTATE(arg_type, field, NULL, ATTRIBUTE_ARRAY)
+
+/**
+ * KFUZZTEST_ANNOTATE_LEN - annotate a field as the length of another
+ *
+ * This expresses the relationship `arg_type.field == len(linked_field)`, where
+ * `linked_field` is an array.
+ *
+ * @arg_type: name of the input structure, without the leading "struct ".
+ * @field: the name of the field to annotate.
+ * @linked_field: the name of an array field with length @field.
+ */
+#define KFUZZTEST_ANNOTATE_LEN(arg_type, field, linked_field) \
+	__KFUZZTEST_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN)
+
+
+/*
+ * Dump some information on the parsed headers and payload. Can be useful for
+ * debugging inputs when writing an encoder for the KFuzzTest input format.
+ */
+__attribute__((unused)) static inline void kfuzztest_debug_header(struct reloc_region_array *regions,
+								  struct reloc_table *rt, void *payload_start,
+								  void *payload_end)
+{
+	uint32_t i;
+
+	pr_info("regions: { num_regions = %u } @ %px", regions->num_regions, regions);
+	for (i = 0; i < regions->num_regions; i++) {
+		pr_info("  region_%u: { start: 0x%x, size: 0x%x }", i, regions->regions[i].offset,
+			regions->regions[i].size);
+	}
+
+	pr_info("reloc_table: { num_entries = %u, padding = %u } @ offset 0x%tx", rt->num_entries, rt->padding_size,
+		(char *)rt - (char *)regions);
+	for (i = 0; i < rt->num_entries; i++) {
+		pr_info("  reloc_%u: { src: %u, offset: 0x%x, dst: %u }", i, rt->entries[i].region_id,
+			rt->entries[i].region_offset, rt->entries[i].value);
+	}
+
+	pr_info("payload: [0x%lx, 0x%tx)", (char *)payload_start - (char *)regions,
+		(char *)payload_end - (char *)regions);
+}
+
+/* Increments a global counter after a successful invocation. */
+void record_invocation(void);
+
+/* Common code for receiving inputs from userspace. */
+int kfuzztest_write_cb_common(struct file *filp, const char __user *buf, size_t len, loff_t *off, void **test_buffer);
+
+struct kfuzztest_target {
+	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);
+} __aligned(32);
+
+/**
+ * FUZZ_TEST - defines a KFuzzTest target
+ *
+ * @test_name: The unique identifier for the fuzz test, which is used to name
+ *	the debugfs entry, e.g., /sys/kernel/debug/kfuzztest/@test_name.
+ * @test_arg_type: The struct type that defines the inputs for the test. This
+ *	must be the full struct type (e.g., "struct my_inputs"), not a typedef.
+ *
+ * Context:
+ * This macro is the primary entry point for the KFuzzTest framework. It
+ * generates all the necessary boilerplate for a fuzz test, including:
+ *   - A static `struct kfuzztest_target` instance that is placed in a
+ *	dedicated ELF section for discovery by userspace tools.
+ *   - A `debugfs` write callback that handles receiving serialized data from
+ *	a fuzzer, parsing it, and "hydrating" it into a valid C struct.
+ *   - A function stub where the developer places the test logic.
+ *
+ * User-Provided Logic:
+ * The developer must provide the body of the fuzz test logic within the curly
+ * braces following the macro invocation. Within this scope, the framework
+ * provides the `arg` variable, which is a pointer of type `@test_arg_type *`
+ * to the fully hydrated input structure. All pointer fields within this struct
+ * have been relocated and are valid kernel pointers. This is the primary
+ * variable to use for accessing fuzzing inputs.
+ *
+ * Example Usage:
+ *
+ * // 1. The kernel function we want to fuzz.
+ * int process_data(const char *data, size_t len);
+ *
+ * // 2. Define a struct to hold all inputs for the function.
+ * struct process_data_inputs {
+ *	const char *data;
+ *	size_t len;
+ * };
+ *
+ * // 3. Define the fuzz test using the FUZZ_TEST macro.
+ * FUZZ_TEST(process_data_fuzzer, struct process_data_inputs)
+ * {
+ *	int ret;
+ *	// Use KFUZZTEST_EXPECT_* to enforce preconditions.
+ *	// The test will exit early if data is NULL.
+ *	KFUZZTEST_EXPECT_NOT_NULL(process_data_inputs, data);
+ *
+ *	// Use KFUZZTEST_ANNOTATE_* to provide hints to the fuzzer.
+ *	// This links the 'len' field to the 'data' buffer.
+ *	KFUZZTEST_ANNOTATE_LEN(process_data_inputs, len, data);
+ *
+ *	// Call the function under test using the 'arg' variable. OOB memory
+ *	// accesses will be caught by KASAN, but the user can also choose to
+ *	// validate the return value and log any failures.
+ *	ret = process_data(arg->data, arg->len);
+ * }
+ */
+#define FUZZ_TEST(test_name, test_arg_type)									\
+	static ssize_t kfuzztest_write_cb_##test_name(struct file *filp, const char __user *buf, size_t len,	\
+						      loff_t *off);						\
+	static void kfuzztest_logic_##test_name(test_arg_type *arg);						\
+	static const struct kfuzztest_target __fuzz_test__##test_name __section(".kfuzztest_target") __used = {	\
+		.name = #test_name,										\
+		.arg_type_name = #test_arg_type,								\
+		.write_input_cb = kfuzztest_write_cb_##test_name,						\
+	};													\
+	static ssize_t kfuzztest_write_cb_##test_name(struct file *filp, const char __user *buf, size_t len,	\
+						      loff_t *off)						\
+	{													\
+		test_arg_type *arg;										\
+		void *buffer;											\
+		int ret;											\
+		ret = kfuzztest_write_cb_common(filp, buf, len, off, &buffer);					\
+		if (ret < 0)											\
+			goto fail_early;									\
+		ret = kfuzztest_parse_and_relocate(buffer, len, (void **)&arg);					\
+		if (ret < 0)											\
+			goto fail_late;										\
+		kfuzztest_logic_##test_name(arg);								\
+		record_invocation();										\
+		ret = len;											\
+fail_late:													\
+		kfree(buffer);											\
+fail_early:													\
+		return ret;											\
+	}													\
+	static void kfuzztest_logic_##test_name(test_arg_type *arg)
+
+#endif /* KFUZZTEST_H */
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index dc0e0c6ed075..49a1748b9f24 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -1947,6 +1947,7 @@ endmenu
 menu "Kernel Testing and Coverage"
 
 source "lib/kunit/Kconfig"
+source "lib/kfuzztest/Kconfig"
 
 config NOTIFIER_ERROR_INJECTION
 	tristate "Notifier error injection"
diff --git a/lib/kfuzztest/Kconfig b/lib/kfuzztest/Kconfig
new file mode 100644
index 000000000000..f9fb5abf8d27
--- /dev/null
+++ b/lib/kfuzztest/Kconfig
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config KFUZZTEST
+	bool "KFuzzTest - enable support for internal fuzz targets"
+	depends on DEBUG_FS && DEBUG_KERNEL
+	help
+	  Enables support for the kernel fuzz testing framework (KFuzzTest), an
+	  interface for exposing internal kernel functions to a userspace fuzzing
+	  engine. KFuzzTest targets are exposed via a debugfs interface that
+	  accepts serialized userspace inputs, and is designed to make it easier
+	  to fuzz deeply nested kernel code that is hard to reach from the system
+	  call boundary. Using a simple macro-based API, developers can add a new
+	  fuzz target with minimal boilerplate code.
+
+	  It is strongly recommended to also enable CONFIG_KASAN for byte-accurate
+	  out-of-bounds detection, as KFuzzTest was designed with this in mind. It
+	  is also recommended to enable CONFIG_KCOV for coverage guided fuzzing.
+
+	  WARNING: This exposes internal kernel functions directly to userspace
+	  and must NEVER be enabled in production builds.
-- 
2.51.0



^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 03/10] kfuzztest: introduce the FUZZ_TEST_SIMPLE macro
  2025-12-04 14:12 [PATCH v3 00/10] KFuzzTest: a new kernel fuzzing framework Ethan Graham
  2025-12-04 14:12 ` [PATCH 01/10] mm/kasan: implement kasan_poison_range Ethan Graham
  2025-12-04 14:12 ` [PATCH 02/10] kfuzztest: add user-facing API and data structures Ethan Graham
@ 2025-12-04 14:12 ` Ethan Graham
  2025-12-04 14:12 ` [PATCH 05/10] tools: add kfuzztest-bridge utility Ethan Graham
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 21+ messages in thread
From: Ethan Graham @ 2025-12-04 14:12 UTC (permalink / raw)
  To: ethan.w.s.graham, glider
  Cc: andreyknvl, andy, andy.shevchenko, brauner, brendan.higgins,
	davem, davidgow, dhowells, dvyukov, elver, herbert, ignat, jack,
	jannh, johannes, kasan-dev, kees, kunit-dev, linux-crypto,
	linux-kernel, linux-mm, lukas, rmoar, shuah, sj, tarasmadan

The serialization format required by a KFuzzTest target defined with the
FUZZ_TEST macro is overkill for simpler cases, in particular the very
common pattern of kernel interfaces taking a (data, datalen) pair.

Introduce the FUZZ_TEST_SIMPLE for defining simple targets that accept
a simpler binary interface without any required serialization. The aim
is to make simple targets compatible with a wide variety of userspace
fuzzing engines out of the box.

A FUZZ_TEST_SIMPLE target also defines an equivalent FUZZ_TEST macro in
its expansion maintaining compatibility with the default KFuzzTest
interface, using a shared `struct kfuzztest_simple_arg` as input type.
In essence, the following equivalence holds:

FUZZ_TEST_SIMPLE(test) === FUZZ_TEST(test, struct kfuzztest_simple_arg)

Constraints and annotation metadata for `struct kfuzztest_simple_arg` is
defined statically in the header file to avoid duplicate definitions in
the compiled vmlinux image.

Signed-off-by: Ethan Graham <ethan.w.s.graham@gmail.com>
---
 include/asm-generic/vmlinux.lds.h |  4 ++
 include/linux/kfuzztest.h         | 87 +++++++++++++++++++++++++++++++
 2 files changed, 91 insertions(+)

diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index 9afe569d013b..2736dd41fba0 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -974,6 +974,10 @@ defined(CONFIG_AUTOFDO_CLANG) || defined(CONFIG_PROPELLER_CLANG)
 	KEEP(*(.kfuzztest_target));					\
 	__kfuzztest_targets_end = .;					\
 	. = ALIGN(PAGE_SIZE);						\
+	__kfuzztest_simple_targets_start = .;				\
+	KEEP(*(.kfuzztest_simple_target));				\
+	__kfuzztest_simple_targets_end = .;				\
+	. = ALIGN(PAGE_SIZE);						\
 	__kfuzztest_constraints_start = .;				\
 	KEEP(*(.kfuzztest_constraint));					\
 	__kfuzztest_constraints_end = .;				\
diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h
index 1839fcfeabf5..284142fa4300 100644
--- a/include/linux/kfuzztest.h
+++ b/include/linux/kfuzztest.h
@@ -483,4 +483,91 @@ fail_early:													\
 	}													\
 	static void kfuzztest_logic_##test_name(test_arg_type *arg)
 
+struct kfuzztest_simple_target {
+	const char *name;
+	ssize_t (*write_input_cb)(struct file *filp, const char __user *buf, size_t len, loff_t *off);
+} __aligned(32);
+
+struct kfuzztest_simple_arg {
+	char *data;
+	size_t datalen;
+};
+
+/* Define constraint and annotation metadata for reused kfuzztest_simple_arg. */
+__KFUZZTEST_CONSTRAINT(kfuzztest_simple_arg, data, NULL, 0x0, EXPECT_NE);
+__KFUZZTEST_ANNOTATE(kfuzztest_simple_arg, data, NULL, ATTRIBUTE_ARRAY);
+__KFUZZTEST_ANNOTATE(kfuzztest_simple_arg, datalen, data, ATTRIBUTE_LEN);
+
+/**
+ * FUZZ_TEST_SIMPLE - defines a simple KFuzzTest target
+ *
+ * @test_name: the unique identifier for the fuzz test, which is used to name
+ *	the debugfs entry.
+ *
+ * This macro function nearly identically to the standard FUZZ_TEST target, the
+ * key difference being that a simple fuzz target is constrained to inputs of
+ * the form `(char *data, size_t datalen)` - a common pattern in kernel APIs.
+ *
+ * The FUZZ_TEST_SIMPLE macro expands to define an equivalent FUZZ_TEST,
+ * effectively creating two debugfs input files for the fuzz target. In essence,
+ * on top of creating an input file under kfuzztest/@test_name/input, a new
+ * simple input file is created under kfuzztest/@test_name/input_simple. This
+ * debugfs file takes raw byte buffers as input and doesn't require any special
+ * serialization.
+ *
+ * User-provided Logic:
+ * The developer must provide the body of the fuzz test logic within the curly
+ * braces following the macro invocation. Within this scope, the framework
+ * provides the `data` and `datalen` variables, where `datalen == len(data)`.
+ *
+ * Example Usage:
+ *
+ * // 1. The kernel function that we wnat to fuzz.
+ * int process_data(const char *data, size_t datalen);
+ *
+ * // 2. Define a fuzz target using the FUZZ_TEST_SIMPLE macro.
+ * FUZZ_TEST_SIMPLE(test_process_data)
+ * {
+ *	// Call the function under test using the `data` and `datalen`
+ *	// variables.
+ *	process_data(data, datalen);
+ * }
+ *
+ */
+#define FUZZ_TEST_SIMPLE(test_name)											\
+	static ssize_t kfuzztest_simple_write_cb_##test_name(struct file *filp, const char __user *buf, size_t len,	\
+							     loff_t *off);						\
+	static void kfuzztest_simple_logic_##test_name(char *data, size_t datalen);					\
+	static const struct kfuzztest_simple_target __fuzz_test_simple__##test_name __section(				\
+		".kfuzztest_simple_target") __used = {									\
+		.name = #test_name,											\
+		.write_input_cb = kfuzztest_simple_write_cb_##test_name,						\
+	};														\
+	FUZZ_TEST(test_name, struct kfuzztest_simple_arg)								\
+	{														\
+		/* We don't use the KFUZZTEST_EXPECT macro to define the
+		 * non-null constraint on `arg->data` as we only want metadata
+		 * to be emitted once, so we enforce it here manually. */						\
+		if (arg->data == NULL)											\
+			return;												\
+		kfuzztest_simple_logic_##test_name(arg->data, arg->datalen);						\
+	}														\
+	static ssize_t kfuzztest_simple_write_cb_##test_name(struct file *filp, const char __user *buf, size_t len,	\
+							     loff_t *off)						\
+	{														\
+		void *buffer;												\
+		int ret;												\
+															\
+		ret = kfuzztest_write_cb_common(filp, buf, len, off, &buffer);						\
+		if (ret < 0)												\
+			goto out;											\
+		kfuzztest_simple_logic_##test_name(buffer, len);							\
+		record_invocation();											\
+		ret = len;												\
+		kfree(buffer);												\
+out:															\
+		return ret;												\
+	}														\
+	static void kfuzztest_simple_logic_##test_name(char *data, size_t datalen)
+
 #endif /* KFUZZTEST_H */
-- 
2.51.0



^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 05/10] tools: add kfuzztest-bridge utility
  2025-12-04 14:12 [PATCH v3 00/10] KFuzzTest: a new kernel fuzzing framework Ethan Graham
                   ` (2 preceding siblings ...)
  2025-12-04 14:12 ` [PATCH 03/10] kfuzztest: introduce the FUZZ_TEST_SIMPLE macro Ethan Graham
@ 2025-12-04 14:12 ` Ethan Graham
  2025-12-04 14:12 ` [PATCH 06/10] kfuzztest: add ReST documentation Ethan Graham
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 21+ messages in thread
From: Ethan Graham @ 2025-12-04 14:12 UTC (permalink / raw)
  To: ethan.w.s.graham, glider
  Cc: andreyknvl, andy, andy.shevchenko, brauner, brendan.higgins,
	davem, davidgow, dhowells, dvyukov, elver, herbert, ignat, jack,
	jannh, johannes, kasan-dev, kees, kunit-dev, linux-crypto,
	linux-kernel, linux-mm, lukas, rmoar, shuah, sj, tarasmadan,
	Ethan Graham

From: Ethan Graham <ethangraham@google.com>

Introduce the kfuzztest-bridge tool, a userspace utility for sending
structured inputs to KFuzzTest harnesses via debugfs.

The bridge takes a textual description of the expected input format, a
file containing random bytes, and the name of the target fuzz test. It
parses the description, encodes the random data into the binary format
expected by the kernel, and writes the result to the corresponding
debugfs entry.

This allows for both simple manual testing and integration with
userspace fuzzing engines. For example, it can be used for smoke testing
by providing data from /dev/urandom, or act as a bridge for blob-based
fuzzers (e.g., AFL) to target KFuzzTest harnesses.

Signed-off-by: Ethan Graham <ethangraham@google.com>
Signed-off-by: Ethan Graham <ethan.w.s.graham@gmail.com>
Reviewed-by: Alexander Potapenko <glider@google.com>

---
PR v2:
- Move kfuzztest-bridge tool under tools/testing, as suggested by
  SeongJae Park.
- Cleanup several resource leaks that were pointed out by Alexander
  Potapenko.
PR v1:
- Add additional context in header comment of kfuzztest-bridge/parser.c.
- Add some missing NULL checks.
- Refactor skip_whitespace() function in input_lexer.c.
- Use ctx->minalign to compute correct region alignment, which is read
  from /sys/kernel/debug/kfuzztest/_config/minalign.
---
---
 tools/Makefile                                |  18 +-
 tools/testing/kfuzztest-bridge/.gitignore     |   2 +
 tools/testing/kfuzztest-bridge/Build          |   6 +
 tools/testing/kfuzztest-bridge/Makefile       |  49 ++
 tools/testing/kfuzztest-bridge/bridge.c       | 115 +++++
 tools/testing/kfuzztest-bridge/byte_buffer.c  |  85 ++++
 tools/testing/kfuzztest-bridge/byte_buffer.h  |  31 ++
 tools/testing/kfuzztest-bridge/encoder.c      | 390 ++++++++++++++++
 tools/testing/kfuzztest-bridge/encoder.h      |  16 +
 tools/testing/kfuzztest-bridge/input_lexer.c  | 256 +++++++++++
 tools/testing/kfuzztest-bridge/input_lexer.h  |  58 +++
 tools/testing/kfuzztest-bridge/input_parser.c | 423 ++++++++++++++++++
 tools/testing/kfuzztest-bridge/input_parser.h |  82 ++++
 tools/testing/kfuzztest-bridge/rand_stream.c  |  77 ++++
 tools/testing/kfuzztest-bridge/rand_stream.h  |  57 +++
 15 files changed, 1662 insertions(+), 3 deletions(-)
 create mode 100644 tools/testing/kfuzztest-bridge/.gitignore
 create mode 100644 tools/testing/kfuzztest-bridge/Build
 create mode 100644 tools/testing/kfuzztest-bridge/Makefile
 create mode 100644 tools/testing/kfuzztest-bridge/bridge.c
 create mode 100644 tools/testing/kfuzztest-bridge/byte_buffer.c
 create mode 100644 tools/testing/kfuzztest-bridge/byte_buffer.h
 create mode 100644 tools/testing/kfuzztest-bridge/encoder.c
 create mode 100644 tools/testing/kfuzztest-bridge/encoder.h
 create mode 100644 tools/testing/kfuzztest-bridge/input_lexer.c
 create mode 100644 tools/testing/kfuzztest-bridge/input_lexer.h
 create mode 100644 tools/testing/kfuzztest-bridge/input_parser.c
 create mode 100644 tools/testing/kfuzztest-bridge/input_parser.h
 create mode 100644 tools/testing/kfuzztest-bridge/rand_stream.c
 create mode 100644 tools/testing/kfuzztest-bridge/rand_stream.h

diff --git a/tools/Makefile b/tools/Makefile
index c31cbbd12c45..dfb0cd19aeb9 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -21,6 +21,7 @@ help:
 	@echo '  hv                     - tools used when in Hyper-V clients'
 	@echo '  iio                    - IIO tools'
 	@echo '  intel-speed-select     - Intel Speed Select tool'
+	@echo '  kfuzztest-bridge       - KFuzzTest userspace utility'
 	@echo '  kvm_stat               - top-like utility for displaying kvm statistics'
 	@echo '  leds                   - LEDs  tools'
 	@echo '  nolibc                 - nolibc headers testing and installation'
@@ -98,6 +99,9 @@ sched_ext: FORCE
 selftests: FORCE
 	$(call descend,testing/$@)
 
+kfuzztest-bridge: FORCE
+	$(call descend,testing/kfuzztest-bridge)
+
 thermal: FORCE
 	$(call descend,lib/$@)
 
@@ -126,7 +130,8 @@ all: acpi counter cpupower gpio hv firewire \
 		perf selftests bootconfig spi turbostat usb \
 		virtio mm bpf x86_energy_perf_policy \
 		tmon freefall iio objtool kvm_stat wmi \
-		debugging tracing thermal thermometer thermal-engine ynl
+		debugging tracing thermal thermometer thermal-engine ynl \
+		kfuzztest-bridge
 
 acpi_install:
 	$(call descend,power/$(@:_install=),install)
@@ -140,6 +145,9 @@ counter_install firewire_install gpio_install hv_install iio_install perf_instal
 selftests_install:
 	$(call descend,testing/$(@:_install=),install)
 
+kfuzztest-bridge_install:
+	$(call descend,testing/kfuzztest-bridge,install)
+
 thermal_install:
 	$(call descend,lib/$(@:_install=),install)
 
@@ -170,7 +178,8 @@ install: acpi_install counter_install cpupower_install gpio_install \
 		virtio_install mm_install bpf_install x86_energy_perf_policy_install \
 		tmon_install freefall_install objtool_install kvm_stat_install \
 		wmi_install debugging_install intel-speed-select_install \
-		tracing_install thermometer_install thermal-engine_install ynl_install
+		tracing_install thermometer_install thermal-engine_install ynl_install \
+		kfuzztest-bridge_install
 
 acpi_clean:
 	$(call descend,power/acpi,clean)
@@ -200,6 +209,9 @@ sched_ext_clean:
 selftests_clean:
 	$(call descend,testing/$(@:_clean=),clean)
 
+kfuzztest-bridge_clean:
+	$(call descend,testing/kfuzztest-bridge,clean)
+
 thermal_clean:
 	$(call descend,lib/thermal,clean)
 
@@ -230,6 +242,6 @@ clean: acpi_clean counter_clean cpupower_clean hv_clean firewire_clean \
 		freefall_clean build_clean libbpf_clean libsubcmd_clean \
 		gpio_clean objtool_clean leds_clean wmi_clean firmware_clean debugging_clean \
 		intel-speed-select_clean tracing_clean thermal_clean thermometer_clean thermal-engine_clean \
-		sched_ext_clean ynl_clean
+		sched_ext_clean ynl_clean kfuzztest-bridge_clean
 
 .PHONY: FORCE
diff --git a/tools/testing/kfuzztest-bridge/.gitignore b/tools/testing/kfuzztest-bridge/.gitignore
new file mode 100644
index 000000000000..4aa9fb0d44e2
--- /dev/null
+++ b/tools/testing/kfuzztest-bridge/.gitignore
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+kfuzztest-bridge
diff --git a/tools/testing/kfuzztest-bridge/Build b/tools/testing/kfuzztest-bridge/Build
new file mode 100644
index 000000000000..d07341a226d6
--- /dev/null
+++ b/tools/testing/kfuzztest-bridge/Build
@@ -0,0 +1,6 @@
+kfuzztest-bridge-y += bridge.o
+kfuzztest-bridge-y += byte_buffer.o
+kfuzztest-bridge-y += encoder.o
+kfuzztest-bridge-y += input_lexer.o
+kfuzztest-bridge-y += input_parser.o
+kfuzztest-bridge-y += rand_stream.o
diff --git a/tools/testing/kfuzztest-bridge/Makefile b/tools/testing/kfuzztest-bridge/Makefile
new file mode 100644
index 000000000000..6e110bdeaee5
--- /dev/null
+++ b/tools/testing/kfuzztest-bridge/Makefile
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: GPL-2.0
+# Makefile for KFuzzTest-Bridge
+include ../../scripts/Makefile.include
+
+bindir ?= /usr/bin
+
+ifeq ($(srctree),)
+srctree := $(patsubst %/,%,$(dir $(CURDIR)))
+srctree := $(patsubst %/,%,$(dir $(srctree)))
+srctree := $(patsubst %/,%,$(dir $(srctree)))
+endif
+
+MAKEFLAGS += -r
+
+override CFLAGS += -O2 -g
+override CFLAGS += -Wall -Wextra
+override CFLAGS += -D_GNU_SOURCE
+override CFLAGS += -I$(OUTPUT)include -I$(srctree)/tools/include
+
+ALL_TARGETS := kfuzztest-bridge
+ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS))
+
+KFUZZTEST_BRIDGE_IN := $(OUTPUT)kfuzztest-bridge-in.o
+KFUZZTEST_BRIDGE    := $(OUTPUT)kfuzztest-bridge
+
+all: $(ALL_PROGRAMS)
+
+export srctree OUTPUT CC LD CFLAGS
+include $(srctree)/tools/build/Makefile.include
+
+$(KFUZZTEST_BRIDGE_IN): FORCE
+	$(Q)$(MAKE) $(build)=kfuzztest-bridge
+
+$(KFUZZTEST_BRIDGE): $(KFUZZTEST_BRIDGE_IN)
+	$(QUIET_LINK)$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
+
+clean:
+	rm -f $(ALL_PROGRAMS)
+	find $(or $(OUTPUT),.) -name '*.o' -delete -o -name '\.*.d' -delete -o -name '\.*.o.cmd' -delete
+
+install: $(ALL_PROGRAMS)
+	install -d -m 755 $(DESTDIR)$(bindir);		\
+	for program in $(ALL_PROGRAMS); do		\
+		install $$program $(DESTDIR)$(bindir);	\
+	done
+
+FORCE:
+
+.PHONY: all install clean FORCE prepare
diff --git a/tools/testing/kfuzztest-bridge/bridge.c b/tools/testing/kfuzztest-bridge/bridge.c
new file mode 100644
index 000000000000..aec0eb4e9ff7
--- /dev/null
+++ b/tools/testing/kfuzztest-bridge/bridge.c
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KFuzzTest tool for sending inputs into a KFuzzTest harness
+ *
+ * Copyright 2025 Google LLC
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "byte_buffer.h"
+#include "encoder.h"
+#include "input_lexer.h"
+#include "input_parser.h"
+#include "rand_stream.h"
+
+static int invoke_kfuzztest_target(const char *target_name, const char *data, ssize_t data_size)
+{
+	ssize_t bytes_written;
+	char *buf = NULL;
+	int ret;
+	int fd;
+
+	if (asprintf(&buf, "/sys/kernel/debug/kfuzztest/%s/input", target_name) < 0)
+		return -ENOMEM;
+
+	fd = openat(AT_FDCWD, buf, O_WRONLY, 0);
+	if (fd < 0) {
+		ret = -errno;
+		goto out_free;
+	}
+
+	/*
+	 * A KFuzzTest target's debugfs handler expects the entire input to be
+	 * written in a single contiguous blob. Treat partial writes as errors.
+	 */
+	bytes_written = write(fd, data, data_size);
+	if (bytes_written != data_size) {
+		ret = (bytes_written < 0) ? -errno : -EIO;
+		goto out_close;
+	}
+	ret = 0;
+
+out_close:
+	if (close(fd) != 0 && ret == 0)
+		ret = -errno;
+out_free:
+	free(buf);
+	return ret;
+}
+
+static int invoke_one(const char *input_fmt, const char *fuzz_target, const char *input_filepath)
+{
+	struct ast_node *ast_prog;
+	struct byte_buffer *bb;
+	struct rand_stream *rs;
+	struct token **tokens;
+	size_t num_tokens;
+	size_t num_bytes;
+	int err;
+
+	err = tokenize(input_fmt, &tokens, &num_tokens);
+	if (err) {
+		fprintf(stderr, "tokenization failed: %s\n", strerror(-err));
+		return err;
+	}
+
+	err = parse(tokens, num_tokens, &ast_prog);
+	if (err) {
+		fprintf(stderr, "parsing failed: %s\n", strerror(-err));
+		goto cleanup_tokens;
+	}
+
+	rs = new_rand_stream(input_filepath, 1024);
+	if (!rs) {
+		err = -ENOMEM;
+		goto cleanup_ast;
+	}
+
+	err = encode(ast_prog, rs, &num_bytes, &bb);
+	if (err == STREAM_EOF) {
+		fprintf(stderr, "encoding failed: reached EOF in %s\n", input_filepath);
+		err = -EINVAL;
+		goto cleanup_rs;
+	} else if (err) {
+		fprintf(stderr, "encoding failed: %s\n", strerror(-err));
+		goto cleanup_rs;
+	}
+
+	err = invoke_kfuzztest_target(fuzz_target, bb->buffer, (ssize_t)num_bytes);
+	if (err)
+		fprintf(stderr, "invocation failed: %s\n", strerror(-err));
+
+	destroy_byte_buffer(bb);
+cleanup_rs:
+	destroy_rand_stream(rs);
+cleanup_ast:
+	destroy_ast_node(ast_prog);
+cleanup_tokens:
+	destroy_tokens(tokens, num_tokens);
+	return err;
+}
+
+int main(int argc, char *argv[])
+{
+	if (argc != 4) {
+		printf("Usage: %s <input-description> <fuzz-target-name> <input-file>\n", argv[0]);
+		printf("For more detailed information see Documentation/dev-tools/kfuzztest.rst\n");
+		return 1;
+	}
+
+	return invoke_one(argv[1], argv[2], argv[3]);
+}
diff --git a/tools/testing/kfuzztest-bridge/byte_buffer.c b/tools/testing/kfuzztest-bridge/byte_buffer.c
new file mode 100644
index 000000000000..1974dbf3862e
--- /dev/null
+++ b/tools/testing/kfuzztest-bridge/byte_buffer.c
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A simple byte buffer implementation for encoding binary data
+ *
+ * Copyright 2025 Google LLC
+ */
+#include <asm-generic/errno-base.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "byte_buffer.h"
+
+struct byte_buffer *new_byte_buffer(size_t initial_size)
+{
+	struct byte_buffer *ret;
+	size_t alloc_size = initial_size >= 8 ? initial_size : 8;
+
+	ret = malloc(sizeof(*ret));
+	if (!ret)
+		return NULL;
+
+	ret->alloc_size = alloc_size;
+	ret->buffer = malloc(alloc_size);
+	if (!ret->buffer) {
+		free(ret);
+		return NULL;
+	}
+	ret->num_bytes = 0;
+	return ret;
+}
+
+void destroy_byte_buffer(struct byte_buffer *buf)
+{
+	free(buf->buffer);
+	free(buf);
+}
+
+int append_bytes(struct byte_buffer *buf, const char *bytes, size_t num_bytes)
+{
+	size_t req_size;
+	size_t new_size;
+	char *new_ptr;
+
+	req_size = buf->num_bytes + num_bytes;
+	new_size = buf->alloc_size;
+
+	while (req_size > new_size)
+		new_size *= 2;
+	if (new_size != buf->alloc_size) {
+		new_ptr = realloc(buf->buffer, new_size);
+		if (!new_ptr)
+			return -ENOMEM;
+		buf->buffer = new_ptr;
+		buf->alloc_size = new_size;
+	}
+	memcpy(buf->buffer + buf->num_bytes, bytes, num_bytes);
+	buf->num_bytes += num_bytes;
+	return 0;
+}
+
+int append_byte(struct byte_buffer *buf, char c)
+{
+	return append_bytes(buf, &c, 1);
+}
+
+int encode_le(struct byte_buffer *buf, uint64_t value, size_t byte_width)
+{
+	size_t i;
+	int ret;
+
+	for (i = 0; i < byte_width; ++i)
+		if ((ret = append_byte(buf, (uint8_t)((value >> (i * 8)) & 0xFF))))
+			return ret;
+	return 0;
+}
+
+int pad(struct byte_buffer *buf, size_t num_padding)
+{
+	int ret;
+	size_t i;
+	for (i = 0; i < num_padding; i++)
+		if ((ret = append_byte(buf, 0)))
+			return ret;
+	return 0;
+}
diff --git a/tools/testing/kfuzztest-bridge/byte_buffer.h b/tools/testing/kfuzztest-bridge/byte_buffer.h
new file mode 100644
index 000000000000..6a31bfb5e78f
--- /dev/null
+++ b/tools/testing/kfuzztest-bridge/byte_buffer.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A simple byte buffer implementation for encoding binary data
+ *
+ * Copyright 2025 Google LLC
+ */
+#ifndef KFUZZTEST_BRIDGE_BYTE_BUFFER_H
+#define KFUZZTEST_BRIDGE_BYTE_BUFFER_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+struct byte_buffer {
+	char *buffer;
+	size_t num_bytes;
+	size_t alloc_size;
+};
+
+struct byte_buffer *new_byte_buffer(size_t initial_size);
+
+void destroy_byte_buffer(struct byte_buffer *buf);
+
+int append_bytes(struct byte_buffer *buf, const char *bytes, size_t num_bytes);
+
+int append_byte(struct byte_buffer *buf, char c);
+
+int encode_le(struct byte_buffer *buf, uint64_t value, size_t byte_width);
+
+int pad(struct byte_buffer *buf, size_t num_padding);
+
+#endif /* KFUZZTEST_BRIDGE_BYTE_BUFFER_H */
diff --git a/tools/testing/kfuzztest-bridge/encoder.c b/tools/testing/kfuzztest-bridge/encoder.c
new file mode 100644
index 000000000000..11ff5bd589d3
--- /dev/null
+++ b/tools/testing/kfuzztest-bridge/encoder.c
@@ -0,0 +1,390 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Encoder for KFuzzTest binary input format
+ *
+ * Copyright 2025 Google LLC
+ */
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "byte_buffer.h"
+#include "input_parser.h"
+#include "rand_stream.h"
+
+#define KFUZZTEST_MAGIC 0xBFACE
+#define KFUZZTEST_PROTO_VERSION 0
+
+/* 
+ * The KFuzzTest binary input format requires at least 8 bytes of padding
+ * at the head and tail of every region.
+ */
+#define KFUZZTEST_POISON_SIZE 8
+
+#define BUFSIZE_SMALL 32
+#define BUFSIZE_LARGE 128
+
+struct region_info {
+	const char *name;
+	uint32_t offset;
+	uint32_t size;
+};
+
+struct reloc_info {
+	uint32_t src_reg;
+	uint32_t offset;
+	uint32_t dst_reg;
+};
+
+struct encoder_ctx {
+	struct byte_buffer *payload;
+	struct rand_stream *rand;
+
+	struct region_info *regions;
+	size_t num_regions;
+
+	struct reloc_info *relocations;
+	size_t num_relocations;
+
+	size_t minalign;
+	size_t reg_offset;
+	int curr_reg;
+};
+
+static void cleanup_ctx(struct encoder_ctx *ctx)
+{
+	if (ctx->regions)
+		free(ctx->regions);
+	if (ctx->relocations)
+		free(ctx->relocations);
+	if (ctx->payload)
+		destroy_byte_buffer(ctx->payload);
+}
+
+static int read_minalign(struct encoder_ctx *ctx)
+{
+	const char *minalign_file = "/sys/kernel/debug/kfuzztest/_config/minalign";
+	char buffer[64 + 1] = { 0 };
+	int ret = 0;
+
+	FILE *f = fopen(minalign_file, "r");
+	if (!f)
+		return -ENOENT;
+
+	fread(&buffer, 1, sizeof(buffer) - 1, f);
+	if (ferror(f))
+		return ferror(f);
+
+	/*
+	 * atoi returns 0 on error. Since we expect a strictly positive
+	 * minalign value on all architectures, any non-positive value
+	 * represents an error.
+	 */
+	ret = atoi(buffer);
+	if (ret <= 0) {
+		fclose(f);
+		return -EINVAL;
+	}
+	ctx->minalign = ret;
+	fclose(f);
+	return 0;
+}
+
+static int pad_payload(struct encoder_ctx *ctx, size_t amount)
+{
+	int ret;
+
+	if ((ret = pad(ctx->payload, amount)))
+		return ret;
+	ctx->reg_offset += amount;
+	return 0;
+}
+
+static int align_payload(struct encoder_ctx *ctx, size_t alignment)
+{
+	size_t pad_amount = ROUND_UP_TO_MULTIPLE(ctx->payload->num_bytes, alignment) - ctx->payload->num_bytes;
+	return pad_payload(ctx, pad_amount);
+}
+
+static int lookup_reg(struct encoder_ctx *ctx, const char *name)
+{
+	size_t i;
+
+	for (i = 0; i < ctx->num_regions; i++) {
+		if (strcmp(ctx->regions[i].name, name) == 0)
+			return i;
+	}
+	return -ENOENT;
+}
+
+static int add_reloc(struct encoder_ctx *ctx, struct reloc_info reloc)
+{
+	void *new_ptr = realloc(ctx->relocations, (ctx->num_relocations + 1) * sizeof(struct reloc_info));
+	if (!new_ptr)
+		return -ENOMEM;
+
+	ctx->relocations = new_ptr;
+	ctx->relocations[ctx->num_relocations] = reloc;
+	ctx->num_relocations++;
+	return 0;
+}
+
+static int build_region_map(struct encoder_ctx *ctx, struct ast_node *top_level)
+{
+	struct ast_program *prog;
+	struct ast_node *reg;
+	size_t i;
+
+	if (top_level->type != NODE_PROGRAM)
+		return -EINVAL;
+
+	prog = &top_level->data.program;
+	ctx->regions = malloc(prog->num_members * sizeof(struct region_info));
+	if (!ctx->regions)
+		return -ENOMEM;
+
+	ctx->num_regions = prog->num_members;
+	for (i = 0; i < ctx->num_regions; i++) {
+		reg = prog->members[i];
+		/* Offset is determined after the second pass. */
+		ctx->regions[i] = (struct region_info){
+			.name = reg->data.region.name,
+			.size = node_size(reg),
+		};
+	}
+	return 0;
+}
+/**
+ * Encodes a value node as little-endian. A value node is one that has no
+ * children, and can therefore be directly written into the payload.
+ */
+static int encode_value_le(struct encoder_ctx *ctx, struct ast_node *node)
+{
+	size_t array_size;
+	char rand_char;
+	size_t length;
+	size_t i;
+	int reg;
+	int ret;
+
+	switch (node->type) {
+	case NODE_ARRAY:
+		array_size = node->data.array.num_elems * node->data.array.elem_size;
+		for (i = 0; i < array_size; i++) {
+			if ((ret = next_byte(ctx->rand, &rand_char)))
+				return ret;
+			if ((ret = append_byte(ctx->payload, rand_char)))
+				return ret;
+		}
+		ctx->reg_offset += array_size;
+		if (node->data.array.null_terminated) {
+			if ((ret = pad_payload(ctx, 1)))
+				return ret;
+			ctx->reg_offset++;
+		}
+		break;
+	case NODE_LENGTH:
+		reg = lookup_reg(ctx, node->data.length.length_of);
+		if (reg < 0)
+			return reg;
+		length = ctx->regions[reg].size;
+		if ((ret = encode_le(ctx->payload, length, node->data.length.byte_width)))
+			return ret;
+		ctx->reg_offset += node->data.length.byte_width;
+		break;
+	case NODE_PRIMITIVE:
+		for (i = 0; i < node->data.primitive.byte_width; i++) {
+			if ((ret = next_byte(ctx->rand, &rand_char)))
+				return ret;
+			if ((ret = append_byte(ctx->payload, rand_char)))
+				return ret;
+		}
+		ctx->reg_offset += node->data.primitive.byte_width;
+		break;
+	case NODE_POINTER:
+		reg = lookup_reg(ctx, node->data.pointer.points_to);
+		if (reg < 0)
+			return reg;
+		if ((ret = add_reloc(ctx, (struct reloc_info){ .src_reg = ctx->curr_reg,
+							       .offset = ctx->reg_offset,
+							       .dst_reg = reg })))
+			return ret;
+		/* Placeholder pointer value, as pointers are patched by KFuzzTest anyways. */
+		if ((ret = encode_le(ctx->payload, UINTPTR_MAX, sizeof(uintptr_t))))
+			return ret;
+		ctx->reg_offset += sizeof(uintptr_t);
+		break;
+	case NODE_PROGRAM:
+	case NODE_REGION:
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int encode_region(struct encoder_ctx *ctx, struct ast_region *reg)
+{
+	struct ast_node *child;
+	size_t i;
+	int ret;
+
+	ctx->reg_offset = 0;
+	for (i = 0; i < reg->num_members; i++) {
+		child = reg->members[i];
+		if ((ret = align_payload(ctx, node_alignment(child))))
+			return ret;
+		if ((ret = encode_value_le(ctx, child)))
+			return ret;
+	}
+	return 0;
+}
+
+static int encode_payload(struct encoder_ctx *ctx, struct ast_node *top_level)
+{
+	struct ast_node *reg;
+	size_t i;
+	int ret;
+
+	for (i = 0; i < ctx->num_regions; i++) {
+		reg = top_level->data.program.members[i];
+		if ((ret = align_payload(ctx, MAX(ctx->minalign, node_alignment(reg)))))
+			return ret;
+
+		ctx->curr_reg = i;
+		ctx->regions[i].offset = ctx->payload->num_bytes;
+		if ((ret = encode_region(ctx, &reg->data.region)))
+			return ret;
+		if ((ret = pad_payload(ctx, KFUZZTEST_POISON_SIZE)))
+			return ret;
+	}
+	return align_payload(ctx, ctx->minalign);
+}
+
+static int encode_region_array(struct encoder_ctx *ctx, struct byte_buffer **ret)
+{
+	struct byte_buffer *reg_array;
+	struct region_info info;
+	int retcode;
+	size_t i;
+
+	reg_array = new_byte_buffer(BUFSIZE_SMALL);
+	if (!reg_array)
+		return -ENOMEM;
+
+	if ((retcode = encode_le(reg_array, ctx->num_regions, sizeof(uint32_t))))
+		goto fail;
+
+	for (i = 0; i < ctx->num_regions; i++) {
+		info = ctx->regions[i];
+		if ((retcode = encode_le(reg_array, info.offset, sizeof(uint32_t))))
+			goto fail;
+		if ((retcode = encode_le(reg_array, info.size, sizeof(uint32_t))))
+			goto fail;
+	}
+	*ret = reg_array;
+	return 0;
+
+fail:
+	destroy_byte_buffer(reg_array);
+	return retcode;
+}
+
+static int encode_reloc_table(struct encoder_ctx *ctx, size_t padding_amount, struct byte_buffer **ret)
+{
+	struct byte_buffer *reloc_table;
+	struct reloc_info info;
+	int retcode;
+	size_t i;
+
+	reloc_table = new_byte_buffer(BUFSIZE_SMALL);
+	if (!reloc_table)
+		return -ENOMEM;
+
+	if ((retcode = encode_le(reloc_table, ctx->num_relocations, sizeof(uint32_t))) ||
+	    (retcode = encode_le(reloc_table, padding_amount, sizeof(uint32_t))))
+		goto fail;
+
+	for (i = 0; i < ctx->num_relocations; i++) {
+		info = ctx->relocations[i];
+		if ((retcode = encode_le(reloc_table, info.src_reg, sizeof(uint32_t))) ||
+		    (retcode = encode_le(reloc_table, info.offset, sizeof(uint32_t))) ||
+		    (retcode = encode_le(reloc_table, info.dst_reg, sizeof(uint32_t))))
+			goto fail;
+	}
+	pad(reloc_table, padding_amount);
+	*ret = reloc_table;
+	return 0;
+
+fail:
+	destroy_byte_buffer(reloc_table);
+	return retcode;
+}
+
+static size_t reloc_table_size(struct encoder_ctx *ctx)
+{
+	return 2 * sizeof(uint32_t) + 3 * ctx->num_relocations * sizeof(uint32_t);
+}
+
+int encode(struct ast_node *top_level, struct rand_stream *r, size_t *num_bytes, struct byte_buffer **ret)
+{
+	struct byte_buffer *region_array = NULL;
+	struct byte_buffer *final_buffer = NULL;
+	struct byte_buffer *reloc_table = NULL;
+	size_t header_size;
+	int alignment;
+	int retcode;
+
+	struct encoder_ctx ctx = { 0 };
+	if ((retcode = read_minalign(&ctx)))
+		return retcode;
+
+	if ((retcode = build_region_map(&ctx, top_level)))
+		goto fail;
+
+	ctx.rand = r;
+	ctx.payload = new_byte_buffer(BUFSIZE_SMALL);
+	if (!ctx.payload) {
+		retcode = -ENOMEM;
+		goto fail;
+	}
+	if ((retcode = encode_payload(&ctx, top_level)))
+		goto fail;
+
+	if ((retcode = encode_region_array(&ctx, &region_array)))
+		goto fail;
+
+	header_size = sizeof(uint64_t) + region_array->num_bytes + reloc_table_size(&ctx);
+	alignment = node_alignment(top_level);
+	if ((retcode = encode_reloc_table(
+		     &ctx, ROUND_UP_TO_MULTIPLE(header_size + KFUZZTEST_POISON_SIZE, alignment) - header_size,
+		     &reloc_table)))
+		goto fail;
+
+	final_buffer = new_byte_buffer(BUFSIZE_LARGE);
+	if (!final_buffer) {
+		retcode = -ENOMEM;
+		goto fail;
+	}
+
+	if ((retcode = encode_le(final_buffer, KFUZZTEST_MAGIC, sizeof(uint32_t))) ||
+	    (retcode = encode_le(final_buffer, KFUZZTEST_PROTO_VERSION, sizeof(uint32_t))) ||
+	    (retcode = append_bytes(final_buffer, region_array->buffer, region_array->num_bytes)) ||
+	    (retcode = append_bytes(final_buffer, reloc_table->buffer, reloc_table->num_bytes)) ||
+	    (retcode = append_bytes(final_buffer, ctx.payload->buffer, ctx.payload->num_bytes))) {
+		destroy_byte_buffer(final_buffer);
+		goto fail;
+	}
+
+	*num_bytes = final_buffer->num_bytes;
+	*ret = final_buffer;
+
+fail:
+	if (region_array)
+		destroy_byte_buffer(region_array);
+	if (reloc_table)
+		destroy_byte_buffer(reloc_table);
+	cleanup_ctx(&ctx);
+	return retcode;
+}
diff --git a/tools/testing/kfuzztest-bridge/encoder.h b/tools/testing/kfuzztest-bridge/encoder.h
new file mode 100644
index 000000000000..73f8c4b7893c
--- /dev/null
+++ b/tools/testing/kfuzztest-bridge/encoder.h
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Encoder for KFuzzTest binary input format
+ *
+ * Copyright 2025 Google LLC
+ */
+#ifndef KFUZZTEST_BRIDGE_ENCODER_H
+#define KFUZZTEST_BRIDGE_ENCODER_H
+
+#include "input_parser.h"
+#include "rand_stream.h"
+#include "byte_buffer.h"
+
+int encode(struct ast_node *top_level, struct rand_stream *r, size_t *num_bytes, struct byte_buffer **ret);
+
+#endif /* KFUZZTEST_BRIDGE_ENCODER_H */
diff --git a/tools/testing/kfuzztest-bridge/input_lexer.c b/tools/testing/kfuzztest-bridge/input_lexer.c
new file mode 100644
index 000000000000..d0a3e352a265
--- /dev/null
+++ b/tools/testing/kfuzztest-bridge/input_lexer.c
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Parser for KFuzzTest textual input format
+ *
+ * Copyright 2025 Google LLC
+ */
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "input_lexer.h"
+
+struct keyword_map {
+	const char *keyword;
+	enum token_type type;
+};
+
+static struct keyword_map keywords[] = {
+	{ "ptr", TOKEN_KEYWORD_PTR }, { "arr", TOKEN_KEYWORD_ARR },
+	{ "len", TOKEN_KEYWORD_LEN }, { "str", TOKEN_KEYWORD_STR },
+	{ "u8", TOKEN_KEYWORD_U8 },   { "u16", TOKEN_KEYWORD_U16 },
+	{ "u32", TOKEN_KEYWORD_U32 }, { "u64", TOKEN_KEYWORD_U64 },
+};
+
+static struct token *make_token(enum token_type type, size_t position)
+{
+	struct token *ret = calloc(1, sizeof(*ret));
+	ret->position = position;
+	ret->type = type;
+	return ret;
+}
+
+void destroy_tokens(struct token **tokens, size_t num_tokens)
+{
+	size_t i;
+
+	if (!tokens)
+		return;
+
+	for (i = 0; i < num_tokens; i++)
+		if (tokens[i])
+			free(tokens[i]);
+	free(tokens);
+}
+
+struct lexer {
+	const char *start;
+	const char *current;
+	size_t position;
+};
+
+static char advance(struct lexer *l)
+{
+	l->current++;
+	l->position++;
+	return l->current[-1];
+}
+
+static void retreat(struct lexer *l)
+{
+	l->position--;
+	l->current--;
+}
+
+static char peek(struct lexer *l)
+{
+	return *l->current;
+}
+
+static bool is_digit(char c)
+{
+	return c >= '0' && c <= '9';
+}
+
+static bool is_alpha(char c)
+{
+	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+}
+
+static bool is_whitespace(char c)
+{
+	switch (c) {
+	case ' ':
+	case '\r':
+	case '\t':
+	case '\n':
+		return true;
+	default:
+		return false;
+	}
+}
+
+static void skip_whitespace(struct lexer *l)
+{
+	while (is_whitespace(peek(l)))
+		advance(l);
+}
+
+static struct token *number(struct lexer *l)
+{
+	struct token *tok;
+	uint64_t value;
+	while (is_digit(peek(l)))
+		advance(l);
+	value = strtoull(l->start, NULL, 10);
+	tok = make_token(TOKEN_INTEGER, l->position);
+	tok->data.integer = value;
+	return tok;
+}
+
+static enum token_type check_keyword(struct lexer *l, const char *keyword,
+				     enum token_type type)
+{
+	size_t len = strlen(keyword);
+
+	if (((size_t)(l->current - l->start) == len) &&
+	    strncmp(l->start, keyword, len) == 0)
+		return type;
+	return TOKEN_IDENTIFIER;
+}
+
+static struct token *identifier(struct lexer *l)
+{
+	enum token_type type = TOKEN_IDENTIFIER;
+	struct token *tok;
+	size_t i;
+
+	while (is_digit(peek(l)) || is_alpha(peek(l)) || peek(l) == '_')
+		advance(l);
+
+	for (i = 0; i < ARRAY_SIZE(keywords); i++) {
+		if (check_keyword(l, keywords[i].keyword, keywords[i].type) !=
+		    TOKEN_IDENTIFIER) {
+			type = keywords[i].type;
+			break;
+		}
+	}
+
+	tok = make_token(type, l->position);
+	if (!tok)
+		return NULL;
+	if (type == TOKEN_IDENTIFIER) {
+		tok->data.identifier.start = l->start;
+		tok->data.identifier.length = l->current - l->start;
+	}
+	return tok;
+}
+
+static struct token *scan_token(struct lexer *l)
+{
+	char c;
+	skip_whitespace(l);
+
+	l->start = l->current;
+	c = peek(l);
+
+	if (c == '\0')
+		return make_token(TOKEN_EOF, l->position);
+
+	advance(l);
+	switch (c) {
+	case '{':
+		return make_token(TOKEN_LBRACE, l->position);
+	case '}':
+		return make_token(TOKEN_RBRACE, l->position);
+	case '[':
+		return make_token(TOKEN_LBRACKET, l->position);
+	case ']':
+		return make_token(TOKEN_RBRACKET, l->position);
+	case ',':
+		return make_token(TOKEN_COMMA, l->position);
+	case ';':
+		return make_token(TOKEN_SEMICOLON, l->position);
+	default:
+		retreat(l);
+		if (is_digit(c))
+			return number(l);
+		if (is_alpha(c) || c == '_')
+			return identifier(l);
+		return make_token(TOKEN_ERROR, l->position);
+	}
+}
+
+int primitive_byte_width(enum token_type type)
+{
+	switch (type) {
+	case TOKEN_KEYWORD_U8:
+		return 1;
+	case TOKEN_KEYWORD_U16:
+		return 2;
+	case TOKEN_KEYWORD_U32:
+		return 4;
+	case TOKEN_KEYWORD_U64:
+		return 8;
+	default:
+		return 0;
+	}
+}
+
+int tokenize(const char *input, struct token ***tokens, size_t *num_tokens)
+{
+	struct lexer l = { .start = input, .current = input };
+	struct token **ret_tokens;
+	size_t token_arr_size;
+	size_t token_count;
+	struct token *tok;
+	void *tmp;
+	int err;
+
+	token_arr_size = 128;
+	ret_tokens = calloc(token_arr_size, sizeof(struct token *));
+	if (!ret_tokens)
+		return -ENOMEM;
+
+	token_count = 0;
+	do {
+		tok = scan_token(&l);
+		if (!tok) {
+			err = -ENOMEM;
+			goto failure;
+		}
+
+		if (token_count == token_arr_size) {
+			token_arr_size *= 2;
+			tmp = realloc(ret_tokens, token_arr_size);
+			if (!tmp) {
+				err = -ENOMEM;
+				goto failure;
+			}
+			ret_tokens = tmp;
+		}
+
+		ret_tokens[token_count] = tok;
+		if (tok->type == TOKEN_ERROR) {
+			err = -EINVAL;
+			goto failure;
+		}
+		token_count++;
+	} while (tok->type != TOKEN_EOF);
+
+	*tokens = ret_tokens;
+	*num_tokens = token_count;
+	return 0;
+
+failure:
+	destroy_tokens(ret_tokens, token_count);
+	return err;
+}
+
+bool is_primitive(struct token *tok)
+{
+	return tok->type >= TOKEN_KEYWORD_U8 && tok->type <= TOKEN_KEYWORD_U64;
+}
diff --git a/tools/testing/kfuzztest-bridge/input_lexer.h b/tools/testing/kfuzztest-bridge/input_lexer.h
new file mode 100644
index 000000000000..40814493c24d
--- /dev/null
+++ b/tools/testing/kfuzztest-bridge/input_lexer.h
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Lexer for KFuzzTest textual input format
+ *
+ * Copyright 2025 Google LLC
+ */
+#ifndef KFUZZTEST_BRIDGE_INPUT_LEXER_H
+#define KFUZZTEST_BRIDGE_INPUT_LEXER_H
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
+
+enum token_type {
+	TOKEN_LBRACE,
+	TOKEN_RBRACE,
+	TOKEN_LBRACKET,
+	TOKEN_RBRACKET,
+	TOKEN_COMMA,
+	TOKEN_SEMICOLON,
+
+	TOKEN_KEYWORD_PTR,
+	TOKEN_KEYWORD_ARR,
+	TOKEN_KEYWORD_LEN,
+	TOKEN_KEYWORD_STR,
+	TOKEN_KEYWORD_U8,
+	TOKEN_KEYWORD_U16,
+	TOKEN_KEYWORD_U32,
+	TOKEN_KEYWORD_U64,
+
+	TOKEN_IDENTIFIER,
+	TOKEN_INTEGER,
+
+	TOKEN_EOF,
+	TOKEN_ERROR,
+};
+
+struct token {
+	enum token_type type;
+	union {
+		uint64_t integer;
+		struct {
+			const char *start;
+			size_t length;
+		} identifier;
+	} data;
+	int position;
+};
+
+int tokenize(const char *input, struct token ***tokens, size_t *num_tokens);
+void destroy_tokens(struct token **tokens, size_t num_tokens);
+
+bool is_primitive(struct token *tok);
+int primitive_byte_width(enum token_type type);
+
+#endif /* KFUZZTEST_BRIDGE_INPUT_LEXER_H */
diff --git a/tools/testing/kfuzztest-bridge/input_parser.c b/tools/testing/kfuzztest-bridge/input_parser.c
new file mode 100644
index 000000000000..b1fd8ba5217e
--- /dev/null
+++ b/tools/testing/kfuzztest-bridge/input_parser.c
@@ -0,0 +1,423 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Parser for the KFuzzTest textual input format
+ *
+ * This file implements a parser for a simple DSL used to describe C-like data
+ * structures. This format allows the kfuzztest-bridge tool to encode a random
+ * byte stream into the structured binary format expected by a KFuzzTest
+ * harness.
+ *
+ * The format consists of semicolon-separated "regions," which are analogous to
+ * C structs. For example:
+ *
+ * "my_struct { ptr[buf] len[buf, u64] }; buf { arr[u8, 42] };"
+ *
+ * This describes a `my_struct` region that contains a pointer to a `buf` region
+ * and its corresponding length encoded over 8 bytes, where `buf` itself
+ * contains a 42-byte array.
+ *
+ * Copyright 2025 Google LLC
+ */
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "input_lexer.h"
+#include "input_parser.h"
+
+static struct token *peek(struct parser *p)
+{
+	return p->tokens[p->curr_token];
+}
+
+static struct token *advance(struct parser *p)
+{
+	struct token *tok;
+	if (p->curr_token >= p->token_count)
+		return NULL;
+	tok = peek(p);
+	p->curr_token++;
+	return tok;
+}
+
+static struct token *consume(struct parser *p, enum token_type type, const char *err_msg)
+{
+	if (peek(p)->type != type) {
+		printf("parser failure at position %d: %s\n", peek(p)->position, err_msg);
+		return NULL;
+	}
+	return advance(p);
+}
+
+static bool match(struct parser *p, enum token_type t)
+{
+	struct token *tok = peek(p);
+	return tok->type == t;
+}
+
+static int parse_primitive(struct parser *p, struct ast_node **node_ret)
+{
+	struct ast_node *ret;
+	struct token *tok;
+	int byte_width;
+
+	tok = advance(p);
+	byte_width = primitive_byte_width(tok->type);
+	if (!byte_width)
+		return -EINVAL;
+
+	ret = malloc(sizeof(*ret));
+	if (!ret)
+		return -ENOMEM;
+
+	ret->type = NODE_PRIMITIVE;
+	ret->data.primitive.byte_width = byte_width;
+	*node_ret = ret;
+	return 0;
+}
+
+static int parse_ptr(struct parser *p, struct ast_node **node_ret)
+{
+	const char *points_to;
+	struct ast_node *ret;
+	struct token *tok;
+	if (!consume(p, TOKEN_KEYWORD_PTR, "expected 'ptr'"))
+		return -EINVAL;
+	if (!consume(p, TOKEN_LBRACKET, "expected '['"))
+		return -EINVAL;
+
+	tok = consume(p, TOKEN_IDENTIFIER, "expected identifier");
+	if (!tok)
+		return -EINVAL;
+
+	if (!consume(p, TOKEN_RBRACKET, "expected ']'"))
+		return -EINVAL;
+
+	ret = malloc(sizeof(*ret));
+	ret->type = NODE_POINTER;
+
+	points_to = strndup(tok->data.identifier.start, tok->data.identifier.length);
+	if (!points_to) {
+		free(ret);
+		return -EINVAL;
+	}
+
+	ret->data.pointer.points_to = points_to;
+	*node_ret = ret;
+	return 0;
+}
+
+static int parse_arr(struct parser *p, struct ast_node **node_ret)
+{
+	struct token *type, *num_elems;
+	struct ast_node *ret;
+
+	if (!consume(p, TOKEN_KEYWORD_ARR, "expected 'arr'") || !consume(p, TOKEN_LBRACKET, "expected '['"))
+		return -EINVAL;
+
+	type = advance(p);
+	if (!is_primitive(type))
+		return -EINVAL;
+
+	if (!consume(p, TOKEN_COMMA, "expected ','"))
+		return -EINVAL;
+
+	num_elems = consume(p, TOKEN_INTEGER, "expected integer");
+	if (!num_elems)
+		return -EINVAL;
+
+	if (!consume(p, TOKEN_RBRACKET, "expected ']'"))
+		return -EINVAL;
+
+	ret = malloc(sizeof(*ret));
+	if (!ret)
+		return -ENOMEM;
+
+	ret->type = NODE_ARRAY;
+	ret->data.array.num_elems = num_elems->data.integer;
+	ret->data.array.elem_size = primitive_byte_width(type->type);
+	ret->data.array.null_terminated = false;
+	*node_ret = ret;
+	return 0;
+}
+
+static int parse_str(struct parser *p, struct ast_node **node_ret)
+{
+	struct ast_node *ret;
+	struct token *len;
+
+	if (!consume(p, TOKEN_KEYWORD_STR, "expected 'str'") || !consume(p, TOKEN_LBRACKET, "expected '['"))
+		return -EINVAL;
+
+	len = consume(p, TOKEN_INTEGER, "expected integer");
+	if (!len)
+		return -EINVAL;
+
+	if (!consume(p, TOKEN_RBRACKET, "expected ']'"))
+		return -EINVAL;
+
+	ret = malloc(sizeof(*ret));
+	if (!ret)
+		return -ENOMEM;
+
+	/* A string is the susbet of byte arrays that are null-terminated. */
+	ret->type = NODE_ARRAY;
+	ret->data.array.num_elems = len->data.integer;
+	ret->data.array.elem_size = sizeof(char);
+	ret->data.array.null_terminated = true;
+	*node_ret = ret;
+	return 0;
+}
+
+static int parse_len(struct parser *p, struct ast_node **node_ret)
+{
+	struct token *type, *len;
+	const char *length_of;
+	struct ast_node *ret;
+
+	if (!consume(p, TOKEN_KEYWORD_LEN, "expected 'len'") || !consume(p, TOKEN_LBRACKET, "expected '['"))
+		return -EINVAL;
+
+	len = advance(p);
+	if (len->type != TOKEN_IDENTIFIER)
+		return -EINVAL;
+
+	if (!consume(p, TOKEN_COMMA, "expected ','"))
+		return -EINVAL;
+
+	type = advance(p);
+	if (!is_primitive(type))
+		return -EINVAL;
+
+	if (!consume(p, TOKEN_RBRACKET, "expected ']'"))
+		return -EINVAL;
+
+	ret = malloc(sizeof(*ret));
+	if (!ret)
+		return -ENOMEM;
+
+	length_of = strndup(len->data.identifier.start, len->data.identifier.length);
+	if (!length_of) {
+		free(ret);
+		return -ENOMEM;
+	}
+
+	ret->type = NODE_LENGTH;
+	ret->data.length.length_of = length_of;
+	ret->data.length.byte_width = primitive_byte_width(type->type);
+
+	*node_ret = ret;
+	return 0;
+}
+
+static int parse_type(struct parser *p, struct ast_node **node_ret)
+{
+	if (is_primitive(peek(p)))
+		return parse_primitive(p, node_ret);
+
+	if (peek(p)->type == TOKEN_KEYWORD_PTR)
+		return parse_ptr(p, node_ret);
+
+	if (peek(p)->type == TOKEN_KEYWORD_ARR)
+		return parse_arr(p, node_ret);
+
+	if (peek(p)->type == TOKEN_KEYWORD_STR)
+		return parse_str(p, node_ret);
+
+	if (peek(p)->type == TOKEN_KEYWORD_LEN)
+		return parse_len(p, node_ret);
+
+	return -EINVAL;
+}
+
+static int parse_region(struct parser *p, struct ast_node **node_ret)
+{
+	struct token *tok, *identifier;
+	struct ast_region *region;
+	struct ast_node *node;
+	struct ast_node *ret;
+	void *new_ptr;
+	int err;
+
+	identifier = consume(p, TOKEN_IDENTIFIER, "expected identifier");
+	if (!identifier)
+		return -EINVAL;
+
+	ret = malloc(sizeof(*ret));
+	if (!ret)
+		return -ENOMEM;
+
+	tok = consume(p, TOKEN_LBRACE, "expected '{'");
+	if (!tok) {
+		err = -EINVAL;
+		goto fail_early;
+	}
+
+	region = &ret->data.region;
+	region->name = strndup(identifier->data.identifier.start, identifier->data.identifier.length);
+	if (!region->name) {
+		err = -ENOMEM;
+		goto fail_early;
+	}
+
+	region->num_members = 0;
+	while (!match(p, TOKEN_RBRACE)) {
+		err = parse_type(p, &node);
+		if (err)
+			goto fail;
+		new_ptr = realloc(region->members, (region->num_members + 1) * sizeof(struct ast_node *));
+		if (!new_ptr) {
+			err = -ENOMEM;
+			goto fail;
+		}
+		region->num_members++;
+		region->members = new_ptr;
+		region->members[region->num_members - 1] = node;
+	}
+
+	if (!consume(p, TOKEN_RBRACE, "expected '}'") || !consume(p, TOKEN_SEMICOLON, "expected ';'")) {
+		err = -EINVAL;
+		goto fail;
+	}
+
+	ret->type = NODE_REGION;
+	*node_ret = ret;
+	return 0;
+
+fail:
+	destroy_ast_node(ret);
+	return err;
+
+fail_early:
+	free(ret);
+	return err;
+}
+
+static int parse_program(struct parser *p, struct ast_node **node_ret)
+{
+	struct ast_program *prog;
+	struct ast_node *reg;
+	struct ast_node *ret;
+	void *new_ptr;
+	int err;
+
+	ret = malloc(sizeof(*ret));
+	if (!ret)
+		return -ENOMEM;
+	ret->type = NODE_PROGRAM;
+
+	prog = &ret->data.program;
+	prog->num_members = 0;
+	prog->members = NULL;
+	while (!match(p, TOKEN_EOF)) {
+		err = parse_region(p, &reg);
+		if (err)
+			goto fail;
+
+		new_ptr = realloc(prog->members, ++prog->num_members * sizeof(struct ast_node *));
+		if (!new_ptr) {
+			err = -ENOMEM;
+			goto fail;
+		}
+		prog->members = new_ptr;
+		prog->members[prog->num_members - 1] = reg;
+	}
+
+	*node_ret = ret;
+	return 0;
+
+fail:
+	destroy_ast_node(ret);
+	return err;
+}
+
+size_t node_alignment(struct ast_node *node)
+{
+	size_t max_alignment = 1;
+	size_t i;
+
+	switch (node->type) {
+	case NODE_PROGRAM:
+		for (i = 0; i < node->data.program.num_members; i++)
+			max_alignment = MAX(max_alignment, node_alignment(node->data.program.members[i]));
+		return max_alignment;
+	case NODE_REGION:
+		for (i = 0; i < node->data.region.num_members; i++)
+			max_alignment = MAX(max_alignment, node_alignment(node->data.region.members[i]));
+		return max_alignment;
+	case NODE_ARRAY:
+		return node->data.array.elem_size;
+	case NODE_LENGTH:
+		return node->data.length.byte_width;
+	case NODE_PRIMITIVE:
+		/* Primitives are aligned to their size. */
+		return node->data.primitive.byte_width;
+	case NODE_POINTER:
+		return sizeof(uintptr_t);
+	}
+
+	/* Anything should be at least 1-byte-aligned. */
+	return 1;
+}
+
+size_t node_size(struct ast_node *node)
+{
+	size_t total = 0;
+	size_t i;
+
+	switch (node->type) {
+	case NODE_PROGRAM:
+		for (i = 0; i < node->data.program.num_members; i++)
+			total += node_size(node->data.program.members[i]);
+		return total;
+	case NODE_REGION:
+		for (i = 0; i < node->data.region.num_members; i++) {
+			/* Account for padding within region. */
+			total = ROUND_UP_TO_MULTIPLE(total, node_alignment(node->data.region.members[i]));
+			total += node_size(node->data.region.members[i]);
+		}
+		return total;
+	case NODE_ARRAY:
+		return node->data.array.elem_size * node->data.array.num_elems +
+		       (node->data.array.null_terminated ? 1 : 0);
+	case NODE_LENGTH:
+		return node->data.length.byte_width;
+	case NODE_PRIMITIVE:
+		return node->data.primitive.byte_width;
+	case NODE_POINTER:
+		return sizeof(uintptr_t);
+	}
+	return 0;
+}
+
+int parse(struct token **tokens, size_t token_count, struct ast_node **node_ret)
+{
+	struct parser p = { .tokens = tokens, .token_count = token_count, .curr_token = 0 };
+	return parse_program(&p, node_ret);
+}
+
+void destroy_ast_node(struct ast_node *node)
+{
+	size_t i;
+
+	switch (node->type) {
+	case NODE_PROGRAM:
+		for (i = 0; i < node->data.program.num_members; i++)
+			destroy_ast_node(node->data.program.members[i]);
+		break;
+	case NODE_REGION:
+		for (i = 0; i < node->data.region.num_members; i++)
+			destroy_ast_node(node->data.region.members[i]);
+		free((void *)node->data.region.name);
+		break;
+	case NODE_LENGTH:
+		free((void *)node->data.length.length_of);
+		break;
+	case NODE_POINTER:
+		free((void *)node->data.pointer.points_to);
+		break;
+	default:
+		break;
+	}
+	free(node);
+}
diff --git a/tools/testing/kfuzztest-bridge/input_parser.h b/tools/testing/kfuzztest-bridge/input_parser.h
new file mode 100644
index 000000000000..5f444b40f672
--- /dev/null
+++ b/tools/testing/kfuzztest-bridge/input_parser.h
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Parser for KFuzzTest textual input format
+ *
+ * Copyright 2025 Google LLC
+ */
+#ifndef KFUZZTEST_BRIDGE_INPUT_PARSER_H
+#define KFUZZTEST_BRIDGE_INPUT_PARSER_H
+
+#include <stdlib.h>
+
+/* Rounds x up to the nearest multiple of n. */
+#define ROUND_UP_TO_MULTIPLE(x, n) (((n) == 0) ? (0) : (((x) + (n) - 1) / (n)) * (n))
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+enum ast_node_type {
+	NODE_PROGRAM,
+	NODE_REGION,
+	NODE_ARRAY,
+	NODE_LENGTH,
+	NODE_PRIMITIVE,
+	NODE_POINTER,
+};
+
+struct ast_node; /* Forward declaration. */
+
+struct ast_program {
+	struct ast_node **members;
+	size_t num_members;
+};
+
+struct ast_region {
+	const char *name;
+	struct ast_node **members;
+	size_t num_members;
+};
+
+struct ast_array {
+	int elem_size;
+	int null_terminated; /* True iff the array should always end with 0. */
+	size_t num_elems;
+};
+
+struct ast_length {
+	size_t byte_width;
+	const char *length_of;
+};
+
+struct ast_primitive {
+	size_t byte_width;
+};
+
+struct ast_pointer {
+	const char *points_to;
+};
+
+struct ast_node {
+	enum ast_node_type type;
+	union {
+		struct ast_program program;
+		struct ast_region region;
+		struct ast_array array;
+		struct ast_length length;
+		struct ast_primitive primitive;
+		struct ast_pointer pointer;
+	} data;
+};
+
+struct parser {
+	struct token **tokens;
+	size_t token_count;
+	size_t curr_token;
+};
+
+int parse(struct token **tokens, size_t token_count, struct ast_node **node_ret);
+void destroy_ast_node(struct ast_node *node);
+
+size_t node_size(struct ast_node *node);
+size_t node_alignment(struct ast_node *node);
+
+#endif /* KFUZZTEST_BRIDGE_INPUT_PARSER_H */
diff --git a/tools/testing/kfuzztest-bridge/rand_stream.c b/tools/testing/kfuzztest-bridge/rand_stream.c
new file mode 100644
index 000000000000..bca6b3de5aad
--- /dev/null
+++ b/tools/testing/kfuzztest-bridge/rand_stream.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Implements a cached file-reader for iterating over a byte stream of
+ * pseudo-random data
+ *
+ * Copyright 2025 Google LLC
+ */
+#include "rand_stream.h"
+
+static int refill(struct rand_stream *rs)
+{
+	rs->valid_bytes = fread(rs->buffer, sizeof(char), rs->buffer_size, rs->source);
+	rs->buffer_pos = 0;
+	if (rs->valid_bytes != rs->buffer_size && ferror(rs->source))
+		return ferror(rs->source);
+	return 0;
+}
+
+struct rand_stream *new_rand_stream(const char *path_to_file, size_t cache_size)
+{
+	struct rand_stream *rs;
+
+	rs = malloc(sizeof(*rs));
+	if (!rs)
+		return NULL;
+
+	rs->valid_bytes = 0;
+	rs->source = fopen(path_to_file, "rb");
+	if (!rs->source) {
+		free(rs);
+		return NULL;
+	}
+
+	if (fseek(rs->source, 0, SEEK_END)) {
+		fclose(rs->source);
+		free(rs);
+		return NULL;
+	}
+	rs->source_size = ftell(rs->source);
+
+	if (fseek(rs->source, 0, SEEK_SET)) {
+		fclose(rs->source);
+		free(rs);
+		return NULL;
+	}
+
+	rs->buffer = malloc(cache_size);
+	if (!rs->buffer) {
+		fclose(rs->source);
+		free(rs);
+		return NULL;
+	}
+	rs->buffer_size = cache_size;
+	return rs;
+}
+
+void destroy_rand_stream(struct rand_stream *rs)
+{
+	fclose(rs->source);
+	free(rs->buffer);
+	free(rs);
+}
+
+int next_byte(struct rand_stream *rs, char *ret)
+{
+	int res;
+
+	if (rs->buffer_pos >= rs->valid_bytes) {
+		res = refill(rs);
+		if (res)
+			return res;
+		if (rs->valid_bytes == 0)
+			return STREAM_EOF;
+	}
+	*ret = rs->buffer[rs->buffer_pos++];
+	return 0;
+}
diff --git a/tools/testing/kfuzztest-bridge/rand_stream.h b/tools/testing/kfuzztest-bridge/rand_stream.h
new file mode 100644
index 000000000000..acb3271d30ca
--- /dev/null
+++ b/tools/testing/kfuzztest-bridge/rand_stream.h
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Implements a cached file-reader for iterating over a byte stream of
+ * pseudo-random data
+ *
+ * Copyright 2025 Google LLC
+ */
+#ifndef KFUZZTEST_BRIDGE_RAND_STREAM_H
+#define KFUZZTEST_BRIDGE_RAND_STREAM_H
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#define STREAM_EOF 1
+
+/**
+ * struct rand_stream - a buffered bytestream reader
+ *
+ * Reads and returns bytes from a file, using buffered pre-fetching to amortize
+ * the cost of reads.
+ */
+struct rand_stream {
+	FILE *source;
+	size_t source_size;
+	char *buffer;
+	size_t buffer_size;
+	size_t buffer_pos;
+	size_t valid_bytes;
+};
+
+/**
+ * new_rand_stream - return a new struct rand_stream
+ *
+ * @path_to_file: source of the output byte stream.
+ * @cache_size: size of the read-ahead cache in bytes.
+ */
+struct rand_stream *new_rand_stream(const char *path_to_file, size_t cache_size);
+
+/**
+ * destroy_rand_stream - clean up a rand stream's resources
+ *
+ * @rs: a struct rand_stream
+ */
+void destroy_rand_stream(struct rand_stream *rs);
+
+/**
+ * next_byte - return the next byte from a struct rand_stream
+ *
+ * @rs: an initialized struct rand_stream.
+ * @ret: return pointer.
+ *
+ * @return 0 on success or a negative value on failure.
+ *
+ */
+int next_byte(struct rand_stream *rs, char *ret);
+
+#endif /* KFUZZTEST_BRIDGE_RAND_STREAM_H */
-- 
2.51.0



^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 06/10] kfuzztest: add ReST documentation
  2025-12-04 14:12 [PATCH v3 00/10] KFuzzTest: a new kernel fuzzing framework Ethan Graham
                   ` (3 preceding siblings ...)
  2025-12-04 14:12 ` [PATCH 05/10] tools: add kfuzztest-bridge utility Ethan Graham
@ 2025-12-04 14:12 ` Ethan Graham
  2025-12-04 14:12 ` [PATCH 07/10] kfuzztest: add KFuzzTest sample fuzz targets Ethan Graham
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 21+ messages in thread
From: Ethan Graham @ 2025-12-04 14:12 UTC (permalink / raw)
  To: ethan.w.s.graham, glider
  Cc: andreyknvl, andy, andy.shevchenko, brauner, brendan.higgins,
	davem, davidgow, dhowells, dvyukov, elver, herbert, ignat, jack,
	jannh, johannes, kasan-dev, kees, kunit-dev, linux-crypto,
	linux-kernel, linux-mm, lukas, rmoar, shuah, sj, tarasmadan,
	Ethan Graham

From: Ethan Graham <ethangraham@google.com>

Add Documentation/dev-tools/kfuzztest.rst and reference it in the
dev-tools index.

Signed-off-by: Ethan Graham <ethangraham@google.com>
Signed-off-by: Ethan Graham <ethan.w.s.graham@gmail.com>
Acked-by: Alexander Potapenko <glider@google.com>

---
PR v3:
- Document newly introduced FUZZ_TEST_SIMPLE targets.
- Rework the flow in several sections.
PR v2:
- Update documentation to reflect new location of kfuzztest-bridge,
  under tools/testing.
PR v1:
- Fix some typos and reword some sections.
- Correct kfuzztest-bridge grammar description.
- Reference documentation in kfuzztest-bridge/input_parser.c header
  comment.
RFC v2:
- Add documentation for kfuzztest-bridge tool introduced in patch 4.
---
---
 Documentation/dev-tools/index.rst             |   1 +
 Documentation/dev-tools/kfuzztest.rst         | 491 ++++++++++++++++++
 tools/testing/kfuzztest-bridge/input_parser.c |   2 +
 3 files changed, 494 insertions(+)
 create mode 100644 Documentation/dev-tools/kfuzztest.rst

diff --git a/Documentation/dev-tools/index.rst b/Documentation/dev-tools/index.rst
index 65c54b27a60b..00ccc4da003b 100644
--- a/Documentation/dev-tools/index.rst
+++ b/Documentation/dev-tools/index.rst
@@ -32,6 +32,7 @@ Documentation/process/debugging/index.rst
    kfence
    kselftest
    kunit/index
+   kfuzztest
    ktap
    checkuapi
    gpio-sloppy-logic-analyzer
diff --git a/Documentation/dev-tools/kfuzztest.rst b/Documentation/dev-tools/kfuzztest.rst
new file mode 100644
index 000000000000..61f877e8bb10
--- /dev/null
+++ b/Documentation/dev-tools/kfuzztest.rst
@@ -0,0 +1,491 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. Copyright 2025 Google LLC
+
+=========================================
+Kernel Fuzz Testing Framework (KFuzzTest)
+=========================================
+
+Overview
+========
+
+The Kernel Fuzz Testing Framework (KFuzzTest) is a framework designed to expose
+internal kernel functions to a userspace fuzzing engine.
+
+It is intended for testing stateless or low-state functions that are difficult
+to reach from the system call interface, such as routines involved in file
+format parsing or complex data transformations. This provides a method for
+in-situ fuzzing of kernel code without requiring that it be built as a separate
+userspace library or that its dependencies be stubbed out.
+
+The framework consists of four main components:
+
+1.  An API, based on the ``FUZZ_TEST`` and ``FUZZ_TEST_SIMPLE`` macros, for
+    defining test targets directly in the kernel tree.
+2.  A binary serialization format for passing complex, pointer-rich data
+    structures from userspace to the kernel.
+3.  A ``debugfs`` interface through which a userspace fuzzer submits
+    serialized test inputs.
+4.  Metadata embedded in dedicated ELF sections of the ``vmlinux`` binary to
+    allow for the discovery of available fuzz targets by external tooling.
+
+.. warning::
+   KFuzzTest is a debugging and testing tool. It exposes internal kernel
+   functions to userspace with minimal sanitization and is designed for
+   use in controlled test environments only. It must **NEVER** be enabled
+   in production kernels.
+
+Supported Architectures
+=======================
+
+KFuzzTest is designed for generic architecture support. It has only been
+explicitly tested on x86_64.
+
+Usage
+=====
+
+To enable KFuzzTest, configure the kernel with::
+
+	CONFIG_KFUZZTEST=y
+
+which depends on ``CONFIG_DEBUGFS`` for receiving userspace inputs, and
+``CONFIG_DEBUG_KERNEL`` as an additional guardrail for preventing KFuzzTest
+from finding its way into a production build accidentally.
+
+The KFuzzTest sample fuzz targets can be built in with
+``CONFIG_SAMPLE_KFUZZTEST``.
+
+KFuzzTest currently only supports targets that are built into the kernel, as the
+core module's startup process discovers fuzz targets from a dedicated ELF
+section during startup. Furthermore, constraints and annotations emit metadata
+that can be scanned from a ``vmlinux`` binary by a userspace fuzzing engine.
+
+Declaring a KFuzzTest target
+----------------------------
+
+A fuzz target should be defined in a .c file. The recommended place to define
+this is under the subsystem's ``/tests`` directory in a ``<file-name>_kfuzz.c``
+file, following the convention used by KUnit. The only strict requirement is
+that the function being fuzzed is visible to the fuzz target.
+
+KFuzzTest provides two macros for defining a target, depending on the complexity
+of the input for the function being fuzzed.
+
+- ``FUZZ_TEST`` for complex, structure-aware fuzzing of functions that take
+  pointers, nested structures, or other complex inputs.
+- ``FUZZ_TEST_SIMPLE`` for the common case of fuzzing a function that accepts
+  a simple data buffer and length.
+
+Non-trivial Fuzz Targets (``FUZZ_TEST``)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+For functions with more complex arguments such as nested structs, multiple
+pointers, or where fine-grained control over inputs it needed, use the
+``FUZZ_TEST`` macro.
+
+Complex fuzz targets enable structure-aware fuzzing, but require more setup,
+including the definition a ``struct`` wrapping its parameters, and optional
+metadata for the fuzzer.
+
+Defining a fuzz target involves three main parts: defining an input structure,
+writing the test body using the ``FUZZ_TEST`` macro, and optionally adding
+metadata for the fuzzer.
+
+The following example illustrates the 6-step process for this macro.
+
+.. code-block:: c
+
+	/* 1. The kernel function that we want to fuzz. */
+	int func(const char *str, char *data, size_t datalen);
+
+	/*
+	 * 2. Define a struct to model the inputs for the function under test.
+	 *    Each field corresponds to an argument needed by the function.
+	 */
+	struct func_inputs {
+		const char *str;
+		char *data;
+		size_t datalen;
+	};
+
+	/*
+	 * 3. Define the fuzz target using the FUZZ_TEST macro.
+	 *    The first parameter is a unique name for the target.
+	 *    The second parameter is the input struct defined above.
+	 */
+	FUZZ_TEST(test_func, struct func_inputs)
+	{
+		/*
+		 * Within this body, the `arg` variable is a pointer to a
+		 * fully initialized `struct func_inputs`.
+		 */
+
+		/*
+		 * 4. (Optional) Add constraints to define preconditions.
+		 *    This check ensures `arg->str` and `arg->data` are non-NULL. If
+		 *    the conditions are not met, the test exits early. This also
+		 *    creates metadata to inform the fuzzing engine.
+		 */
+		KFUZZTEST_EXPECT_NOT_NULL(func_inputs, str);
+		KFUZZTEST_EXPECT_NOT_NULL(func_inputs, data);
+
+		/*
+		 * 5. (Optional) Add annotations to provide semantic hints to the
+		 *    fuzzer. These annotations inform the fuzzer that `str` is a
+		 *    null-terminated string, that `data` is a pointer to an array
+		 *    (i.e., not a pointer to a single value), and that the `len` field
+		 *    is the length of the buffer pointed to by `data`.
+		 *    Annotations do not add any runtime checks.
+		 */
+		KFUZZTEST_ANNOTATE_STRING(func_inputs, str);
+		KFUZZTEST_ANNOTATE_LEN(func_inputs, datalen, data);
+		KFUZZTEST_ANNOTATE_ARRAY(func_inputs, data);
+
+		/*
+		 * 6. Call the kernel function with the provided inputs.
+		 *    Memory errors like out-of-bounds accesses on 'arg->data' will
+		 *    be detected by KASAN or other memory error detection tools.
+		 */
+		func(arg->str, arg->data, arg->datalen);
+	}
+
+A ``FUZZ_TEST`` creates a debugfs file under
+`/sys/kernel/debug/kfuzztest/<test-name>/input` that accepts inputs from a
+fuzzing engine.
+
+KFuzzTest provides two families of macros to improve the quality of fuzzing:
+
+- ``KFUZZTEST_EXPECT_*``: These macros define constraints, which are
+  preconditions that must be true for the test to proceed. They are enforced
+  with a runtime check in the kernel. If a check fails, the current test run is
+  aborted. This metadata helps the userspace fuzzer avoid generating invalid
+  inputs.
+
+- ``KFUZZTEST_ANNOTATE_*``: These macros define annotations, which are purely
+  semantic hints for the fuzzer. They do not add any runtime checks and exist
+  only to help the fuzzer generate more intelligent and structurally correct
+  inputs. For example, KFUZZTEST_ANNOTATE_LEN links a size field to a pointer
+  field, which is a common pattern in C APIs.
+
+A fuzzing engine that aims to effectively fuzz these targets must implement
+the following:
+
+- Serialized inputs following the format introduced in the
+  `Input Serialization`_ section.
+- ``vmlinux`` metadata parsing to become aware of domain constraints and
+  annotations.
+
+Simple Fuzz Targets (``FUZZ_TEST_SIMPLE``)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+For the common case of defining a fuzz target for a function that accepts a
+buffer and its length (e.g., ``(const char *data, size_t datalen)``), the
+``FUZZ_TEST_SIMPLE`` macro should be used.
+
+This macro simplifies test creation by providing ``data`` and ``datalen``
+variables to the test body.
+
+.. code-block:: c
+
+	/* 1. The kernel function that we want to fuzz. */
+	int process_data(const char *data, size_t len);
+
+	/* 2. Define the fuzz target with the FUZZ_TEST_SIMPLE macro. */
+	FUZZ_TEST_SIMPLE(test_process_data)
+	{
+		/* 3. Call the kernel function with the provided input. */
+		process_data(data, datalen);
+	}
+
+A ``FUZZ_TEST_SIMPLE`` target creates two debugfs files in its directory
+(``/sys/kernel/debug/kfuzztest/<test-name>``):
+
+- ``input_simple``: A simplified interface. Writing a raw byte blob to this
+  file will invoke the fuzz target, passing the blob as ``(data, datalen)``.
+- ``input``: Accepts the serialization format described in the
+  `Input Serialization`_ section.
+
+The ``input_simple`` file makes it much easier to integrate with userspace
+fuzzers (e.g., LibFuzzer, AFL++, honggfuzz) without requiring any knowledge
+of KFuzzTest's serialization format or constraint system.
+
+A LibFuzzer harness may look like so:
+
+.. code-block:: c
+
+    /* Path to the simple target's input file */
+    const char *filepath = "/sys/kernel/debug/kfuzztest/test_process_data/input_simple";
+
+    extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
+        FILE *f = fopen(filepath, "w");
+        if (!f) {
+            return 0; /* Fuzzer should not stop. */
+        }
+        /* Write the raw fuzzer input directly. */
+        fwrite(Data, 1, Size, f);
+        fclose(f);
+        return 0;
+    }
+
+Note that despite it being very simple for a fuzzing engine to fuzz simple
+KFuzzTest targets, kernel coverage collection is key for the effectiveness
+of a coverage-guided fuzzer - this is outside of KFuzzTest's scope.
+
+Metadata
+--------
+
+Macros ``FUZZ_TEST``, ``FUZZ_TEST_SIMPLE``, ``KFUZZTEST_EXPECT_*`` and
+``KFUZZTEST_ANNOTATE_*`` embed metadata into several sections within the main
+``.data`` section of the final ``vmlinux`` binary; ``.kfuzztest_target``,
+``.kfuzztest_simple_target``, ``.kfuzztest_constraint`` and
+``.kfuzztest_annotation`` respectively.
+
+Note that simple targets defined with the ``FUZZ_TEST_SIMPLE`` macro implicitly
+define a ``FUZZ_TEST`` to maintain compatibility with fuzzers that assume
+structured inputs, allowing both target types to be treated as one and the same.
+
+The metadata regions serve a few purposes:
+
+1. The core module uses the ``.kfuzztest_target`` section at boot to discover
+   every ``FUZZ_TEST`` instance and create its ``debugfs`` directory and
+   ``input`` file.
+2. If a ``.kfuzztest_simple_target`` is defined for a given fuzz test, an
+   additional ``input_simple`` file is created in the target's ``debugfs``
+   directory to accept inputs that don't require complex serialization.
+3. Userspace fuzzers can read this metadata from the ``vmlinux`` binary to
+   discover targets and learn about their rules and structure in order to
+   generate correct and effective inputs.
+
+The metadata in the ``.kfuzztest_*`` sections consists of arrays of fixed-size C
+structs (e.g., ``struct kfuzztest_target``). Fields within these structs that
+are pointers, such as ``name`` or ``arg_type_name``, contain addresses that
+point to other locations in the ``vmlinux`` binary. A userspace tool that
+parses the ``vmlinux`` ELF file must resolve these pointers to read the data
+that they reference. For example, to get a target's name, a tool must:
+
+1. Read the ``struct kfuzztest_target`` from the ``.kfuzztest_target`` section.
+2. Read the address in the ``.name`` field.
+3. Use that address to locate and read null-terminated string from its position
+   elsewhere in the binary (e.g., ``.rodata``).
+
+Tooling Dependencies
+--------------------
+
+For userspace tools to parse the ``vmlinux`` binary and make use of emitted
+KFuzzTest metadata, the kernel must be compiled with DWARF debug information.
+This is required for tools to understand the layout of C structs, resolve type
+information, and correctly interpret constraints and annotations.
+
+When using KFuzzTest with automated fuzzing tools, either
+``CONFIG_DEBUG_INFO_DWARF4`` or ``CONFIG_DEBUG_INFO_DWARF5`` should be enabled.
+
+Input Serialization
+===================
+
+``FUZZ_TEST`` macros accept serialized inputs representing nested data with
+pointers. This section describes the input format for non-trivial inputs.
+
+KFuzzTest targets receive their inputs from userspace via a write to a dedicated
+debugfs file ``/sys/kernel/debug/kfuzztest/<test-name>/input``.
+
+The data written to this file must be a single binary blob that follows a
+specific serialization format. This format is designed to allow complex,
+pointer-rich C structures to be represented in a flat buffer, requiring only a
+single kernel allocation and copy from userspace.
+
+An input is first prefixed by an 8-byte header containing a magic value in the
+first four bytes, defined as ``KFUZZTEST_HEADER_MAGIC`` in
+`<include/linux/kfuzztest.h>``, and a version number in the subsequent four
+bytes.
+
+Version 0
+---------
+
+In version 0 (i.e., when the version number in the 8-byte header is equal to 0),
+the input format consists of three main parts laid out sequentially: a region
+array, a relocation table, and the payload.::
+
+    +----------------+---------------------+-----------+----------------+
+    |  region array  |  relocation table   |  padding  |    payload     |
+    +----------------+---------------------+-----------+----------------+
+
+Region Array
+^^^^^^^^^^^^
+
+This component is a header that describes how the raw data in the Payload is
+partitioned into logical memory regions. It consists of a count of regions
+followed by an array of ``struct reloc_region``, where each entry defines a
+single region with its size and offset from the start of the payload.
+
+.. code-block:: c
+
+	struct reloc_region {
+		uint32_t offset;
+		uint32_t size;
+	};
+
+	struct reloc_region_array {
+		uint32_t num_regions;
+		struct reloc_region regions[];
+	};
+
+By convention, region 0 represents the top-level input struct that is passed
+as the arg variable to the ``FUZZ_TEST`` body. Subsequent regions typically
+represent data buffers or structs pointed to by fields within that struct.
+Region array entries must be ordered by ascending offset, and must not overlap
+with one another.
+
+Relocation Table
+^^^^^^^^^^^^^^^^
+
+The relocation table contains the instructions for the kernel to "hydrate" the
+payload by patching pointer fields. It contains an array of
+``struct reloc_entry`` items. Each entry acts as a linking instruction,
+specifying:
+
+- The location of a pointer that needs to be patched (identified by a region
+  ID and an offset within that region).
+
+- The target region that the pointer should point to (identified by the
+  target's region ID) or ``KFUZZTEST_REGIONID_NULL`` if the pointer is ``NULL``.
+
+This table also specifies the amount of padding between its end and the start
+of the payload, which should be at least 8 bytes.
+
+.. code-block:: c
+
+	struct reloc_entry {
+		uint32_t region_id;
+		uint32_t region_offset;
+		uint32_t value;
+	};
+
+	struct reloc_table {
+		uint32_t num_entries;
+		uint32_t padding_size;
+		struct reloc_entry entries[];
+    };
+
+Payload
+^^^^^^^
+
+The payload contains the raw binary data for all regions, concatenated together
+according to their specified offsets.
+
+- Region specific alignment: The data for each individual region must start at
+  an offset that is aligned to its own C type's requirements. For example, a
+  ``uint64_t`` must begin on an 8-byte boundary.
+
+- Minimum alignment: The offset of each region, as well as the beginning of the
+  payload, must also be a multiple of the overall minimum alignment value. This
+  value is determined by the greater of ``ARCH_KMALLOC_MINALIGN`` and
+  ``KASAN_GRANULE_SIZE`` (which is represented by ``KFUZZTEST_POISON_SIZE`` in
+  ``/include/linux/kfuzztest.h``). This minimum alignment ensures that all
+  function inputs respect C calling conventions.
+
+- Padding: The space between the end of one region's data and the beginning of
+  the next must be sufficient for padding. The padding must also be at least
+  the same minimum alignment value mentioned above. This is crucial for KASAN
+  builds, as it allows KFuzzTest to poison this unused space enabling precise
+  detection of out-of-bounds memory accesses between adjacent buffers.
+
+The minimum alignment value is architecture-dependent and is exposed to
+userspace via the read-only file
+``/sys/kernel/debug/kfuzztest/_config/minalign``. The framework relies on
+userspace tooling to construct the payload correctly, adhering to all three of
+these rules for every region.
+
+KFuzzTest Bridge Tool
+=====================
+
+The ``kfuzztest-bridge`` program is a userspace utility that encodes a random
+byte stream into the structured binary format expected by a KFuzzTest harness.
+It allows users to describe the target's input structure textually, making it
+easy to perform smoke tests or connect harnesses to blob-based fuzzing engines.
+
+This tool is intended to be simple, both in usage and implementation. Its
+structure and DSL are sufficient for simpler use-cases. For more advanced
+coverage-guided fuzzing it is recommended to use
+`syzkaller <https://github.com/google/syzkaller>` which implements deeper
+support for KFuzzTest targets.
+
+Usage
+-----
+
+The tool can be built with ``make tools/testing/kfuzztest-bridge``. In the case
+of libc incompatibilities, the tool will have to be linked statically or built
+on the target system.
+
+Example:
+
+.. code-block:: sh
+
+    ./tools/testing/kfuzztest-bridge \
+        "foo { u32 ptr[bar] }; bar { ptr[data] len[data, u64]}; data { arr[u8, 42] };" \
+        "my-fuzz-target" /dev/urandom
+
+The command takes three arguments
+
+1.  A string describing the input structure (see `Textual Format`_ sub-section).
+2.  The name of the target test, which corresponds to its directory in
+    ``/sys/kernel/debug/kfuzztest/``.
+3.  A path to a file providing a stream of random data, such as
+    ``/dev/urandom``.
+
+The structure string in the example corresponds to the following C data
+structures:
+
+.. code-block:: c
+
+	struct foo {
+		u32 a;
+		struct bar *b;
+	};
+
+	struct bar {
+		struct data *d;
+		u64 data_len; /* Equals 42. */
+	};
+
+	struct data {
+		char arr[42];
+	};
+
+Textual Format
+--------------
+
+The textual format is a human-readable representation of the region-based binary
+format used by KFuzzTest. It is described by the following grammar:
+
+.. code-block:: text
+
+	schema     ::= region ( ";" region )* [";"]
+	region     ::= identifier "{" type ( " " type )* "}"
+	type       ::= primitive | pointer | array | length | string
+	primitive  ::= "u8" | "u16" | "u32" | "u64"
+	pointer    ::= "ptr" "[" identifier "]"
+	array      ::= "arr" "[" primitive "," integer "]"
+	length     ::= "len" "[" identifier "," primitive "]"
+	string     ::= "str" "[" integer "]"
+	identifier ::= [a-zA-Z_][a-zA-Z1-9_]*
+	integer    ::= [0-9]+
+
+Pointers must reference a named region.
+
+To fuzz a raw buffer, the buffer must be defined in its own region, as shown
+below:
+
+.. code-block:: c
+
+	struct my_struct {
+		char *buf;
+		size_t buflen;
+	};
+
+This would correspond to the following textual description:
+
+.. code-block:: text
+
+	my_struct { ptr[buf] len[buf, u64] }; buf { arr[u8, n] };
+
+Here, ``n`` is some integer value defining the size of the byte array inside of
+the ``buf`` region.
diff --git a/tools/testing/kfuzztest-bridge/input_parser.c b/tools/testing/kfuzztest-bridge/input_parser.c
index b1fd8ba5217e..feaa59de49d7 100644
--- a/tools/testing/kfuzztest-bridge/input_parser.c
+++ b/tools/testing/kfuzztest-bridge/input_parser.c
@@ -16,6 +16,8 @@
  * and its corresponding length encoded over 8 bytes, where `buf` itself
  * contains a 42-byte array.
  *
+ * The full grammar is documented in Documentation/dev-tools/kfuzztest.rst.
+ *
  * Copyright 2025 Google LLC
  */
 #include <errno.h>
-- 
2.51.0



^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 07/10] kfuzztest: add KFuzzTest sample fuzz targets
  2025-12-04 14:12 [PATCH v3 00/10] KFuzzTest: a new kernel fuzzing framework Ethan Graham
                   ` (4 preceding siblings ...)
  2025-12-04 14:12 ` [PATCH 06/10] kfuzztest: add ReST documentation Ethan Graham
@ 2025-12-04 14:12 ` Ethan Graham
  2025-12-04 14:12 ` [PATCH 08/10] crypto: implement KFuzzTest targets for PKCS7 and RSA parsing Ethan Graham
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 21+ messages in thread
From: Ethan Graham @ 2025-12-04 14:12 UTC (permalink / raw)
  To: ethan.w.s.graham, glider
  Cc: andreyknvl, andy, andy.shevchenko, brauner, brendan.higgins,
	davem, davidgow, dhowells, dvyukov, elver, herbert, ignat, jack,
	jannh, johannes, kasan-dev, kees, kunit-dev, linux-crypto,
	linux-kernel, linux-mm, lukas, rmoar, shuah, sj, tarasmadan,
	Ethan Graham

From: Ethan Graham <ethangraham@google.com>

Add two simple fuzz target samples to demonstrate the KFuzzTest API and
provide basic self-tests for the framework.

These examples showcase how a developer can define a fuzz target using
the FUZZ_TEST(), constraint, and annotation macros, and serve as runtime
sanity checks for the core logic. For example, they test that
out-of-bounds memory accesses into poisoned padding regions are
correctly detected in a KASAN build.

These have been tested by writing syzkaller-generated inputs into their
debugfs 'input' files and verifying that the correct KASAN reports were
triggered.

Signed-off-by: Ethan Graham <ethangraham@google.com>
Signed-off-by: Ethan Graham <ethan.w.s.graham@gmail.com>
Acked-by: Alexander Potapenko <glider@google.com>

---
PR v3:
- Use the FUZZ_TEST_SIMPLE macro in the `underflow_on_buffer` sample
  fuzz target instead of FUZZ_TEST.
PR v2:
- Fix build issues pointed out by the kernel test robot <lkp@intel.com>.
---
---
 samples/Kconfig                               |  7 ++
 samples/Makefile                              |  1 +
 samples/kfuzztest/Makefile                    |  3 +
 samples/kfuzztest/overflow_on_nested_buffer.c | 71 +++++++++++++++++++
 samples/kfuzztest/underflow_on_buffer.c       | 51 +++++++++++++
 5 files changed, 133 insertions(+)
 create mode 100644 samples/kfuzztest/Makefile
 create mode 100644 samples/kfuzztest/overflow_on_nested_buffer.c
 create mode 100644 samples/kfuzztest/underflow_on_buffer.c

diff --git a/samples/Kconfig b/samples/Kconfig
index 6e072a5f1ed8..5209dd9d7a5c 100644
--- a/samples/Kconfig
+++ b/samples/Kconfig
@@ -320,6 +320,13 @@ config SAMPLE_HUNG_TASK
 	  Reading these files with multiple processes triggers hung task
 	  detection by holding locks for a long time (256 seconds).
 
+config SAMPLE_KFUZZTEST
+	bool "Build KFuzzTest sample targets"
+	depends on KFUZZTEST
+	help
+	  Build KFuzzTest sample targets that serve as selftests for input
+	  deserialization and inter-region redzone poisoning logic.
+
 source "samples/rust/Kconfig"
 
 source "samples/damon/Kconfig"
diff --git a/samples/Makefile b/samples/Makefile
index 07641e177bd8..3a0e7f744f44 100644
--- a/samples/Makefile
+++ b/samples/Makefile
@@ -44,4 +44,5 @@ obj-$(CONFIG_SAMPLE_DAMON_WSSE)		+= damon/
 obj-$(CONFIG_SAMPLE_DAMON_PRCL)		+= damon/
 obj-$(CONFIG_SAMPLE_DAMON_MTIER)	+= damon/
 obj-$(CONFIG_SAMPLE_HUNG_TASK)		+= hung_task/
+obj-$(CONFIG_SAMPLE_KFUZZTEST)		+= kfuzztest/
 obj-$(CONFIG_SAMPLE_TSM_MR)		+= tsm-mr/
diff --git a/samples/kfuzztest/Makefile b/samples/kfuzztest/Makefile
new file mode 100644
index 000000000000..4f8709876c9e
--- /dev/null
+++ b/samples/kfuzztest/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_SAMPLE_KFUZZTEST) += overflow_on_nested_buffer.o underflow_on_buffer.o
diff --git a/samples/kfuzztest/overflow_on_nested_buffer.c b/samples/kfuzztest/overflow_on_nested_buffer.c
new file mode 100644
index 000000000000..2f1c3ff9f750
--- /dev/null
+++ b/samples/kfuzztest/overflow_on_nested_buffer.c
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This file contains a KFuzzTest example target that ensures that a buffer
+ * overflow on a nested region triggers a KASAN OOB access report.
+ *
+ * Copyright 2025 Google LLC
+ */
+
+/**
+ * DOC: test_overflow_on_nested_buffer
+ *
+ * This test uses a struct with two distinct dynamically allocated buffers.
+ * It checks that KFuzzTest's memory layout correctly poisons the memory
+ * regions and that KASAN can detect an overflow when reading one byte past the
+ * end of the first buffer (`a`).
+ *
+ * It can be invoked with kfuzztest-bridge using the following command:
+ *
+ * ./kfuzztest-bridge \
+ *   "nested_buffers { ptr[a] len[a, u64] ptr[b] len[b, u64] }; \
+ *   a { arr[u8, 64] }; b { arr[u8, 64] };" \
+ *   "test_overflow_on_nested_buffer" /dev/urandom
+ *
+ * The first argument describes the C struct `nested_buffers` and specifies that
+ * both `a` and `b` are pointers to arrays of 64 bytes.
+ */
+#include <linux/kfuzztest.h>
+
+static void overflow_on_nested_buffer(const char *a, size_t a_len, const char *b, size_t b_len)
+{
+	size_t i;
+	pr_info("a = [%px, %px)", a, a + a_len);
+	pr_info("b = [%px, %px)", b, b + b_len);
+
+	/* Ensure that all bytes in arg->b are accessible. */
+	for (i = 0; i < b_len; i++)
+		READ_ONCE(b[i]);
+	/*
+	 * Check that all bytes in arg->a are accessible, and provoke an OOB on
+	 * the first byte to the right of the buffer which will trigger a KASAN
+	 * report.
+	 */
+	for (i = 0; i <= a_len; i++)
+		READ_ONCE(a[i]);
+}
+
+struct nested_buffers {
+	const char *a;
+	size_t a_len;
+	const char *b;
+	size_t b_len;
+};
+
+/**
+ * The KFuzzTest input format specifies that struct nested buffers should
+ * be expanded as:
+ *
+ * | a | b | pad[8] | *a | pad[8] | *b |
+ *
+ * where the padded regions are poisoned. We expect to trigger a KASAN report by
+ * overflowing one byte into the `a` buffer.
+ */
+FUZZ_TEST(test_overflow_on_nested_buffer, struct nested_buffers)
+{
+	KFUZZTEST_EXPECT_NOT_NULL(nested_buffers, a);
+	KFUZZTEST_EXPECT_NOT_NULL(nested_buffers, b);
+	KFUZZTEST_ANNOTATE_LEN(nested_buffers, a_len, a);
+	KFUZZTEST_ANNOTATE_LEN(nested_buffers, b_len, b);
+
+	overflow_on_nested_buffer(arg->a, arg->a_len, arg->b, arg->b_len);
+}
diff --git a/samples/kfuzztest/underflow_on_buffer.c b/samples/kfuzztest/underflow_on_buffer.c
new file mode 100644
index 000000000000..b2f5ff467334
--- /dev/null
+++ b/samples/kfuzztest/underflow_on_buffer.c
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This file contains a KFuzzTest example target that ensures that a buffer
+ * underflow on a region triggers a KASAN OOB access report.
+ *
+ * Copyright 2025 Google LLC
+ */
+
+/**
+ * DOC: test_underflow_on_buffer
+ *
+ * This test ensures that the region between the metadata struct and the
+ * dynamically allocated buffer is poisoned. It provokes a one-byte underflow
+ * on the buffer, which should be caught by KASAN.
+ *
+ * It can be invoked with kfuzztest-bridge using the following command:
+ *
+ * ./kfuzztest-bridge \
+ *   "some_buffer { ptr[buf] len[buf, u64]}; buf { arr[u8, 128] };" \
+ *   "test_underflow_on_buffer" /dev/urandom
+ *
+ * The first argument describes the C struct `some_buffer` and specifies that
+ * `buf` is a pointer to an array of 128 bytes. The second argument is the test
+ * name, and the third is a seed file.
+ */
+#include <linux/kfuzztest.h>
+
+static void underflow_on_buffer(char *buf, size_t buflen)
+{
+	size_t i;
+
+	pr_info("buf = [%px, %px)", buf, buf + buflen);
+
+	/* First ensure that all bytes in arg->b are accessible. */
+	for (i = 0; i < buflen; i++)
+		READ_ONCE(buf[i]);
+	/*
+	 * Provoke a buffer overflow on the first byte preceding b, triggering
+	 * a KASAN report.
+	 */
+	READ_ONCE(*((char *)buf - 1));
+}
+
+/**
+ * Tests that the region between struct some_buffer and the expanded *buf field
+ * is correctly poisoned by accessing the first byte before *buf.
+ */
+FUZZ_TEST_SIMPLE(test_underflow_on_buffer)
+{
+	underflow_on_buffer(data, datalen);
+}
-- 
2.51.0



^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 08/10] crypto: implement KFuzzTest targets for PKCS7 and RSA parsing
  2025-12-04 14:12 [PATCH v3 00/10] KFuzzTest: a new kernel fuzzing framework Ethan Graham
                   ` (5 preceding siblings ...)
  2025-12-04 14:12 ` [PATCH 07/10] kfuzztest: add KFuzzTest sample fuzz targets Ethan Graham
@ 2025-12-04 14:12 ` Ethan Graham
  2025-12-04 14:12 ` [PATCH 09/10] drivers/auxdisplay: add a KFuzzTest for parse_xy() Ethan Graham
  2025-12-04 14:12 ` [PATCH 10/10] MAINTAINERS: add maintainer information for KFuzzTest Ethan Graham
  8 siblings, 0 replies; 21+ messages in thread
From: Ethan Graham @ 2025-12-04 14:12 UTC (permalink / raw)
  To: ethan.w.s.graham, glider
  Cc: andreyknvl, andy, andy.shevchenko, brauner, brendan.higgins,
	davem, davidgow, dhowells, dvyukov, elver, herbert, ignat, jack,
	jannh, johannes, kasan-dev, kees, kunit-dev, linux-crypto,
	linux-kernel, linux-mm, lukas, rmoar, shuah, sj, tarasmadan,
	Ethan Graham

From: Ethan Graham <ethangraham@google.com>

Add KFuzzTest targets for pkcs7_parse_message, rsa_parse_pub_key, and
rsa_parse_priv_key to serve as real-world examples of how the framework
is used.

These functions are ideal candidates for KFuzzTest as they perform
complex parsing of user-controlled data but are not directly exposed at
the syscall boundary. This makes them difficult to exercise with
traditional fuzzing tools and showcases the primary strength of the
KFuzzTest framework: providing an interface to fuzz internal functions.

To validate the effectiveness of the framework on these new targets, we
injected two artificial bugs and let syzkaller fuzz the targets in an
attempt to catch them.

The first of these was calling the asn1 decoder with an incorrect input
from pkcs7_parse_message, like so:

- ret = asn1_ber_decoder(&pkcs7_decoder, ctx, data, datalen);
+ ret = asn1_ber_decoder(&pkcs7_decoder, ctx, data, datalen + 1);

The second was bug deeper inside of asn1_ber_decoder itself, like so:

- for (len = 0; n > 0; n--)
+ for (len = 0; n >= 0; n--)

syzkaller was able to trigger these bugs, and the associated KASAN
slab-out-of-bounds reports, within seconds.

The targets are defined within crypto/asymmetric-keys/tests.

Signed-off-by: Ethan Graham <ethangraham@google.com>
Signed-off-by: Ethan Graham <ethan.w.s.graham@gmail.com>
Reviewed-by: Ignat Korchagin <ignat@cloudflare.com>

---
PR v3:
- Use the FUZZ_TEST_SIMPLE macro for all introduced fuzz targets as
  they each take `(data, datalen)` pairs. This also removes the need for
  explicit constraints and annotations which become implicit.
PR v2:
- Make fuzz targets also depend on the KConfig options needed for the
  functions they are fuzzing, CONFIG_PKCS7_MESSAGE_PARSER and
  CONFIG_CRYPTO_RSA respectively.
- Fix build issues pointed out by the kernel test robot <lkp@intel.com>.
- Account for return value of pkcs7_parse_message, and free resources if
  the function call succeeds.
PR v1:
- Change the fuzz target build to depend on CONFIG_KFUZZTEST=y,
  eliminating the need for a separate config option for each individual
  file as suggested by Ignat Korchagin.
- Remove KFUZZTEST_EXPECT_LE on the length of the `key` field inside of
  the fuzz targets. A maximum length is now set inside of the core input
  parsing logic.
RFC v2:
- Move KFuzzTest targets outside of the source files into dedicated
  _kfuzz.c files under /crypto/asymmetric_keys/tests/ as suggested by
  Ignat Korchagin and Eric Biggers.
---
---
 crypto/asymmetric_keys/Makefile               |  2 ++
 crypto/asymmetric_keys/tests/Makefile         |  4 ++++
 crypto/asymmetric_keys/tests/pkcs7_kfuzz.c    | 17 ++++++++++++++++
 .../asymmetric_keys/tests/rsa_helper_kfuzz.c  | 20 +++++++++++++++++++
 4 files changed, 43 insertions(+)
 create mode 100644 crypto/asymmetric_keys/tests/Makefile
 create mode 100644 crypto/asymmetric_keys/tests/pkcs7_kfuzz.c
 create mode 100644 crypto/asymmetric_keys/tests/rsa_helper_kfuzz.c

diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile
index bc65d3b98dcb..77b825aee6b2 100644
--- a/crypto/asymmetric_keys/Makefile
+++ b/crypto/asymmetric_keys/Makefile
@@ -67,6 +67,8 @@ obj-$(CONFIG_PKCS7_TEST_KEY) += pkcs7_test_key.o
 pkcs7_test_key-y := \
 	pkcs7_key_type.o
 
+obj-y += tests/
+
 #
 # Signed PE binary-wrapped key handling
 #
diff --git a/crypto/asymmetric_keys/tests/Makefile b/crypto/asymmetric_keys/tests/Makefile
new file mode 100644
index 000000000000..023d6a65fb89
--- /dev/null
+++ b/crypto/asymmetric_keys/tests/Makefile
@@ -0,0 +1,4 @@
+pkcs7-kfuzz-y := $(and $(CONFIG_KFUZZTEST),$(CONFIG_PKCS7_MESSAGE_PARSER))
+rsa-helper-kfuzz-y := $(and $(CONFIG_KFUZZTEST),$(CONFIG_CRYPTO_RSA))
+obj-$(pkcs7-kfuzz-y) += pkcs7_kfuzz.o
+obj-$(rsa-helper-kfuzz-y) += rsa_helper_kfuzz.o
diff --git a/crypto/asymmetric_keys/tests/pkcs7_kfuzz.c b/crypto/asymmetric_keys/tests/pkcs7_kfuzz.c
new file mode 100644
index 000000000000..345f99990653
--- /dev/null
+++ b/crypto/asymmetric_keys/tests/pkcs7_kfuzz.c
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * PKCS#7 parser KFuzzTest target
+ *
+ * Copyright 2025 Google LLC
+ */
+#include <crypto/pkcs7.h>
+#include <linux/kfuzztest.h>
+
+FUZZ_TEST_SIMPLE(test_pkcs7_parse_message)
+{
+	struct pkcs7_message *msg;
+
+	msg = pkcs7_parse_message(data, datalen);
+	if (msg && !IS_ERR(msg))
+		kfree(msg);
+}
diff --git a/crypto/asymmetric_keys/tests/rsa_helper_kfuzz.c b/crypto/asymmetric_keys/tests/rsa_helper_kfuzz.c
new file mode 100644
index 000000000000..dd434f1a21ed
--- /dev/null
+++ b/crypto/asymmetric_keys/tests/rsa_helper_kfuzz.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * RSA key extract helper KFuzzTest targets
+ *
+ * Copyright 2025 Google LLC
+ */
+#include <linux/kfuzztest.h>
+#include <crypto/internal/rsa.h>
+
+FUZZ_TEST_SIMPLE(test_rsa_parse_pub_key)
+{
+	struct rsa_key out;
+	rsa_parse_pub_key(&out, data, datalen);
+}
+
+FUZZ_TEST_SIMPLE(test_rsa_parse_priv_key)
+{
+	struct rsa_key out;
+	rsa_parse_priv_key(&out, data, datalen);
+}
-- 
2.51.0



^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 09/10] drivers/auxdisplay: add a KFuzzTest for parse_xy()
  2025-12-04 14:12 [PATCH v3 00/10] KFuzzTest: a new kernel fuzzing framework Ethan Graham
                   ` (6 preceding siblings ...)
  2025-12-04 14:12 ` [PATCH 08/10] crypto: implement KFuzzTest targets for PKCS7 and RSA parsing Ethan Graham
@ 2025-12-04 14:12 ` Ethan Graham
  2025-12-04 15:26   ` Andy Shevchenko
  2025-12-04 14:12 ` [PATCH 10/10] MAINTAINERS: add maintainer information for KFuzzTest Ethan Graham
  8 siblings, 1 reply; 21+ messages in thread
From: Ethan Graham @ 2025-12-04 14:12 UTC (permalink / raw)
  To: ethan.w.s.graham, glider
  Cc: andreyknvl, andy, andy.shevchenko, brauner, brendan.higgins,
	davem, davidgow, dhowells, dvyukov, elver, herbert, ignat, jack,
	jannh, johannes, kasan-dev, kees, kunit-dev, linux-crypto,
	linux-kernel, linux-mm, lukas, rmoar, shuah, sj, tarasmadan,
	Ethan Graham

From: Ethan Graham <ethangraham@google.com>

Add a KFuzzTest fuzzer for the parse_xy() function, located in a new
file under /drivers/auxdisplay/tests.

To validate the correctness and effectiveness of this KFuzzTest target,
a bug was injected into parse_xy() like so:

drivers/auxdisplay/charlcd.c:179
- s = p;
+ s = p + 1;

Although a simple off-by-one bug, it requires a specific input sequence
in order to trigger it, thus demonstrating the power of pairing
KFuzzTest with a coverage-guided fuzzer like syzkaller.

Signed-off-by: Ethan Graham <ethangraham@google.com>
Signed-off-by: Ethan Graham <ethan.w.s.graham@gmail.com>
Acked-by: Alexander Potapenko <glider@google.com>

---
PR v3:
- Remove conditional inclusion of charlcd_kfuzz.c from charlcd.c, as
  requested by Andy Shevchenko.
- Update auxdisplay Makefile to conditionally build charlcd_kfuzz.c when
  CONFIG_KFUZZTEST=y, as suggested by Lukas Wunner and Andy Shevchenko.
- Foward declare parse_xy in charlcd_kfuzz.c.
---
---
 drivers/auxdisplay/Makefile              |  3 +++
 drivers/auxdisplay/tests/charlcd_kfuzz.c | 22 ++++++++++++++++++++++
 2 files changed, 25 insertions(+)
 create mode 100644 drivers/auxdisplay/tests/charlcd_kfuzz.c

diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile
index f5c13ed1cd4f..af00b0a173de 100644
--- a/drivers/auxdisplay/Makefile
+++ b/drivers/auxdisplay/Makefile
@@ -6,6 +6,9 @@
 obj-$(CONFIG_ARM_CHARLCD)	+= arm-charlcd.o
 obj-$(CONFIG_CFAG12864B)	+= cfag12864b.o cfag12864bfb.o
 obj-$(CONFIG_CHARLCD)		+= charlcd.o
+ifeq ($(CONFIG_KFUZZTEST),y)
+CFLAGS_charlcd.o += -include $(src)/tests/charlcd_kfuzz.c
+endif
 obj-$(CONFIG_HD44780_COMMON)	+= hd44780_common.o
 obj-$(CONFIG_HD44780)		+= hd44780.o
 obj-$(CONFIG_HT16K33)		+= ht16k33.o
diff --git a/drivers/auxdisplay/tests/charlcd_kfuzz.c b/drivers/auxdisplay/tests/charlcd_kfuzz.c
new file mode 100644
index 000000000000..3adf510f4356
--- /dev/null
+++ b/drivers/auxdisplay/tests/charlcd_kfuzz.c
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * charlcd KFuzzTest target
+ *
+ * Copyright 2025 Google LLC
+ */
+#include <linux/kfuzztest.h>
+
+struct parse_xy_arg {
+	const char *s;
+};
+
+static bool parse_xy(const char *s, unsigned long *x, unsigned long *y);
+
+FUZZ_TEST(test_parse_xy, struct parse_xy_arg)
+{
+	unsigned long x, y;
+
+	KFUZZTEST_EXPECT_NOT_NULL(parse_xy_arg, s);
+	KFUZZTEST_ANNOTATE_STRING(parse_xy_arg, s);
+	parse_xy(arg->s, &x, &y);
+}
-- 
2.51.0



^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 10/10] MAINTAINERS: add maintainer information for KFuzzTest
  2025-12-04 14:12 [PATCH v3 00/10] KFuzzTest: a new kernel fuzzing framework Ethan Graham
                   ` (7 preceding siblings ...)
  2025-12-04 14:12 ` [PATCH 09/10] drivers/auxdisplay: add a KFuzzTest for parse_xy() Ethan Graham
@ 2025-12-04 14:12 ` Ethan Graham
  8 siblings, 0 replies; 21+ messages in thread
From: Ethan Graham @ 2025-12-04 14:12 UTC (permalink / raw)
  To: ethan.w.s.graham, glider
  Cc: andreyknvl, andy, andy.shevchenko, brauner, brendan.higgins,
	davem, davidgow, dhowells, dvyukov, elver, herbert, ignat, jack,
	jannh, johannes, kasan-dev, kees, kunit-dev, linux-crypto,
	linux-kernel, linux-mm, lukas, rmoar, shuah, sj, tarasmadan,
	Ethan Graham

From: Ethan Graham <ethangraham@google.com>

Add myself as maintainer and Alexander Potapenko as reviewer for
KFuzzTest.

Signed-off-by: Ethan Graham <ethangraham@google.com>
Signed-off-by: Ethan Graham <ethan.w.s.graham@gmail.com>
Acked-by: Alexander Potapenko <glider@google.com>

---
PR v3:
- Update MAINTAINERS to reflect the correct location of kfuzztest-bridge
  under tools/testing as pointed out by SeongJae Park.
---
---
 MAINTAINERS | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 6dcfbd11efef..3ad357477f92 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13641,6 +13641,14 @@ F:	include/linux/kfifo.h
 F:	lib/kfifo.c
 F:	samples/kfifo/
 
+KFUZZTEST
+M:  Ethan Graham <ethan.w.s.graham@gmail.com>
+R:  Alexander Potapenko <glider@google.com>
+F:  include/linux/kfuzztest.h
+F:  lib/kfuzztest/
+F:  Documentation/dev-tools/kfuzztest.rst
+F:  tools/testing/kfuzztest-bridge/
+
 KGDB / KDB /debug_core
 M:	Jason Wessel <jason.wessel@windriver.com>
 M:	Daniel Thompson <danielt@kernel.org>
-- 
2.51.0



^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 01/10] mm/kasan: implement kasan_poison_range
  2025-12-04 14:12 ` [PATCH 01/10] mm/kasan: implement kasan_poison_range Ethan Graham
@ 2025-12-04 15:17   ` Andrey Konovalov
  2025-12-04 15:31     ` Andy Shevchenko
  0 siblings, 1 reply; 21+ messages in thread
From: Andrey Konovalov @ 2025-12-04 15:17 UTC (permalink / raw)
  To: Ethan Graham
  Cc: glider, andy, andy.shevchenko, brauner, brendan.higgins, davem,
	davidgow, dhowells, dvyukov, elver, herbert, ignat, jack, jannh,
	johannes, kasan-dev, kees, kunit-dev, linux-crypto, linux-kernel,
	linux-mm, lukas, rmoar, shuah, sj, tarasmadan, Ethan Graham

On Thu, Dec 4, 2025 at 3:13 PM Ethan Graham <ethan.w.s.graham@gmail.com> wrote:
>
> From: Ethan Graham <ethangraham@google.com>
>
> Introduce a new helper function, kasan_poison_range(), to encapsulate
> the logic for poisoning an arbitrary memory range of a given size, and
> expose it publically in <include/linux/kasan.h>.
>
> This is a preparatory change for the upcoming KFuzzTest patches, which
> requires the ability to poison the inter-region padding in its input
> buffers.
>
> No functional change to any other subsystem is intended by this commit.
>
> Signed-off-by: Ethan Graham <ethangraham@google.com>
> Signed-off-by: Ethan Graham <ethan.w.s.graham@gmail.com>
> Reviewed-by: Alexander Potapenko <glider@google.com>
>
> ---
> PR v3:
> - Move kasan_poison_range into mm/kasan/common.c so that it is built
>   with HW_TAGS mode enabled.
> - Add a runtime check for kasan_enabled() in kasan_poison_range.
> - Add two WARN_ON()s in kasan_poison_range when the input is invalid.
> PR v1:
> - Enforce KASAN_GRANULE_SIZE alignment for the end of the range in
>   kasan_poison_range(), and return -EINVAL when this isn't respected.
> ---
> ---
>  include/linux/kasan.h | 11 +++++++++++
>  mm/kasan/common.c     | 37 +++++++++++++++++++++++++++++++++++++
>  2 files changed, 48 insertions(+)
>
> diff --git a/include/linux/kasan.h b/include/linux/kasan.h
> index 890011071f2b..cd6cdf732378 100644
> --- a/include/linux/kasan.h
> +++ b/include/linux/kasan.h
> @@ -102,6 +102,16 @@ static inline bool kasan_has_integrated_init(void)
>  }
>
>  #ifdef CONFIG_KASAN
> +
> +/**
> + * kasan_poison_range - poison the memory range [@addr, @addr + @size)
> + *
> + * The exact behavior is subject to alignment with KASAN_GRANULE_SIZE, defined
> + * in <mm/kasan/kasan.h>: if @start is unaligned, the initial partial granule
> + * at the beginning of the range is only poisoned if CONFIG_KASAN_GENERIC=y.

You can also mention that @addr + @size must be aligned.

> + */
> +int kasan_poison_range(const void *addr, size_t size);
> +
>  void __kasan_unpoison_range(const void *addr, size_t size);
>  static __always_inline void kasan_unpoison_range(const void *addr, size_t size)
>  {
> @@ -402,6 +412,7 @@ static __always_inline bool kasan_check_byte(const void *addr)
>
>  #else /* CONFIG_KASAN */
>
> +static inline int kasan_poison_range(const void *start, size_t size) { return 0; }
>  static inline void kasan_unpoison_range(const void *address, size_t size) {}
>  static inline void kasan_poison_pages(struct page *page, unsigned int order,
>                                       bool init) {}
> diff --git a/mm/kasan/common.c b/mm/kasan/common.c
> index 9142964ab9c9..c83579ef37c6 100644
> --- a/mm/kasan/common.c
> +++ b/mm/kasan/common.c
> @@ -570,3 +570,40 @@ bool __kasan_check_byte(const void *address, unsigned long ip)
>         }
>         return true;
>  }
> +
> +int kasan_poison_range(const void *addr, size_t size)
> +{
> +       uintptr_t start_addr = (uintptr_t)addr;
> +       uintptr_t head_granule_start;
> +       uintptr_t poison_body_start;
> +       uintptr_t poison_body_end;
> +       size_t head_prefix_size;
> +       uintptr_t end_addr;
> +
> +       if (!kasan_enabled())
> +               return 0;

Please move this check to include/linux/kasan.h; see how
kasan_unpoison_range() is implemented. Otherwise eventually these
checks start creeping into lower level functions and the logic of
checking when and whether KASAN is enabled becomes a mess.

> +
> +       end_addr = start_addr + size;
> +       if (WARN_ON(end_addr % KASAN_GRANULE_SIZE))
> +               return -EINVAL;
> +
> +       if (WARN_ON(start_addr >= end_addr))
> +               return -EINVAL;
> +
> +       head_granule_start = ALIGN_DOWN(start_addr, KASAN_GRANULE_SIZE);
> +       head_prefix_size = start_addr - head_granule_start;
> +
> +       if (IS_ENABLED(CONFIG_KASAN_GENERIC) && head_prefix_size > 0)
> +               kasan_poison_last_granule((void *)head_granule_start,
> +                                         head_prefix_size);

As I mentioned before, please rename kasan_poison_last_granule() to
kasan_poison_granule() (or maybe even kasan_poison_partial_granule?).
Here the granule being poisoned is not the last one.


> +
> +       poison_body_start = ALIGN(start_addr, KASAN_GRANULE_SIZE);
> +       poison_body_end = end_addr;
> +
> +       if (poison_body_start < poison_body_end)
> +               kasan_poison((void *)poison_body_start,
> +                            poison_body_end - poison_body_start,
> +                            KASAN_SLAB_REDZONE, false);
> +       return 0;
> +}
> +EXPORT_SYMBOL(kasan_poison_range);
> --
> 2.51.0
>


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 09/10] drivers/auxdisplay: add a KFuzzTest for parse_xy()
  2025-12-04 14:12 ` [PATCH 09/10] drivers/auxdisplay: add a KFuzzTest for parse_xy() Ethan Graham
@ 2025-12-04 15:26   ` Andy Shevchenko
  2025-12-04 15:28     ` Andy Shevchenko
  2025-12-04 15:32     ` Marco Elver
  0 siblings, 2 replies; 21+ messages in thread
From: Andy Shevchenko @ 2025-12-04 15:26 UTC (permalink / raw)
  To: Ethan Graham
  Cc: glider, andreyknvl, andy, brauner, brendan.higgins, davem,
	davidgow, dhowells, dvyukov, elver, herbert, ignat, jack, jannh,
	johannes, kasan-dev, kees, kunit-dev, linux-crypto, linux-kernel,
	linux-mm, lukas, rmoar, shuah, sj, tarasmadan, Ethan Graham

On Thu, Dec 4, 2025 at 4:13 PM Ethan Graham <ethan.w.s.graham@gmail.com> wrote:
>
> From: Ethan Graham <ethangraham@google.com>
>
> Add a KFuzzTest fuzzer for the parse_xy() function, located in a new
> file under /drivers/auxdisplay/tests.

drivers/...

(no leading /)

> To validate the correctness and effectiveness of this KFuzzTest target,
> a bug was injected into parse_xy() like so:
>
> drivers/auxdisplay/charlcd.c:179
> - s = p;
> + s = p + 1;
>
> Although a simple off-by-one bug, it requires a specific input sequence
> in order to trigger it, thus demonstrating the power of pairing
> KFuzzTest with a coverage-guided fuzzer like syzkaller.

fuzzers

> Signed-off-by: Ethan Graham <ethangraham@google.com>
> Signed-off-by: Ethan Graham <ethan.w.s.graham@gmail.com>

I believe one of two SoBs is enough.

> Acked-by: Alexander Potapenko <glider@google.com>

...

> --- a/drivers/auxdisplay/Makefile
> +++ b/drivers/auxdisplay/Makefile
> @@ -6,6 +6,9 @@
>  obj-$(CONFIG_ARM_CHARLCD)      += arm-charlcd.o
>  obj-$(CONFIG_CFAG12864B)       += cfag12864b.o cfag12864bfb.o
>  obj-$(CONFIG_CHARLCD)          += charlcd.o
> +ifeq ($(CONFIG_KFUZZTEST),y)
> +CFLAGS_charlcd.o += -include $(src)/tests/charlcd_kfuzz.c
> +endif
>  obj-$(CONFIG_HD44780_COMMON)   += hd44780_common.o
>  obj-$(CONFIG_HD44780)          += hd44780.o
>  obj-$(CONFIG_HT16K33)          += ht16k33.o

Yes, this level of intrusion is fine to me.

...

> +++ b/drivers/auxdisplay/tests/charlcd_kfuzz.c

So, this will require it to be expanded each time we want to add
coverage. Can this be actually generated based on the C
(preprocessed?) level of prototypes listed? Ideally I would like to
see only some small meta-data and then the fuzzer should create the
object based on the profile of the module.

Input like:

bool parse_xy(const char *s $nonnull$, unsigned long *x $nonnull$,
unsigned long *y $nonnull$)
Or even with the expected ranges, and then you can generate a code
that tests the behaviour inside given ranges and outside, including
invalid input, etc.

But okay, the below seems not too big enough.

> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * charlcd KFuzzTest target
> + *
> + * Copyright 2025 Google LLC
> + */
> +#include <linux/kfuzztest.h>
> +
> +struct parse_xy_arg {
> +       const char *s;
> +};

> +static bool parse_xy(const char *s, unsigned long *x, unsigned long *y);

Is it still needed?

I mean, can we make sure that include in this case works as tail one
and not head, because otherwise we would need to add the respective
includes, i.e. for bool type here, which is missing. Also I *hope&
that kfuzztest.h is NOT Yet Another Include EVERYTHING type of
headers. Otherwise it breaks the whole idea behind modularity of the
headers.

> +FUZZ_TEST(test_parse_xy, struct parse_xy_arg)
> +{
> +       unsigned long x, y;
> +
> +       KFUZZTEST_EXPECT_NOT_NULL(parse_xy_arg, s);
> +       KFUZZTEST_ANNOTATE_STRING(parse_xy_arg, s);
> +       parse_xy(arg->s, &x, &y);
> +}


-- 
With Best Regards,
Andy Shevchenko


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 09/10] drivers/auxdisplay: add a KFuzzTest for parse_xy()
  2025-12-04 15:26   ` Andy Shevchenko
@ 2025-12-04 15:28     ` Andy Shevchenko
  2025-12-04 15:32     ` Marco Elver
  1 sibling, 0 replies; 21+ messages in thread
From: Andy Shevchenko @ 2025-12-04 15:28 UTC (permalink / raw)
  To: Ethan Graham
  Cc: glider, andreyknvl, andy, brauner, brendan.higgins, davem,
	davidgow, dhowells, dvyukov, elver, herbert, ignat, jack, jannh,
	johannes, kasan-dev, kees, kunit-dev, linux-crypto, linux-kernel,
	linux-mm, lukas, rmoar, shuah, sj, tarasmadan

On Thu, Dec 4, 2025 at 5:26 PM Andy Shevchenko
<andy.shevchenko@gmail.com> wrote:
> On Thu, Dec 4, 2025 at 4:13 PM Ethan Graham <ethan.w.s.graham@gmail.com> wrote:

> > From: Ethan Graham <ethangraham@google.com>

OK, this bounces. Please update the series to make sure you have no
dead addresses in it.

-- 
With Best Regards,
Andy Shevchenko


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 01/10] mm/kasan: implement kasan_poison_range
  2025-12-04 15:17   ` Andrey Konovalov
@ 2025-12-04 15:31     ` Andy Shevchenko
  0 siblings, 0 replies; 21+ messages in thread
From: Andy Shevchenko @ 2025-12-04 15:31 UTC (permalink / raw)
  To: Andrey Konovalov
  Cc: Ethan Graham, glider, andy, brauner, brendan.higgins, davem,
	davidgow, dhowells, dvyukov, elver, herbert, ignat, jack, jannh,
	johannes, kasan-dev, kees, kunit-dev, linux-crypto, linux-kernel,
	linux-mm, lukas, shuah, sj, tarasmadan

On Thu, Dec 4, 2025 at 5:17 PM Andrey Konovalov <andreyknvl@gmail.com> wrote:
> On Thu, Dec 4, 2025 at 3:13 PM Ethan Graham <ethan.w.s.graham@gmail.com> wrote:

> > Introduce a new helper function, kasan_poison_range(), to encapsulate
> > the logic for poisoning an arbitrary memory range of a given size, and
> > expose it publically in <include/linux/kasan.h>.

publicly

> > This is a preparatory change for the upcoming KFuzzTest patches, which
> > requires the ability to poison the inter-region padding in its input
> > buffers.
> >
> > No functional change to any other subsystem is intended by this commit.

...

> > +/**
> > + * kasan_poison_range - poison the memory range [@addr, @addr + @size)
> > + *
> > + * The exact behavior is subject to alignment with KASAN_GRANULE_SIZE, defined
> > + * in <mm/kasan/kasan.h>: if @start is unaligned, the initial partial granule
> > + * at the beginning of the range is only poisoned if CONFIG_KASAN_GENERIC=y.
>
> You can also mention that @addr + @size must be aligned.
>
> > + */
> > +int kasan_poison_range(const void *addr, size_t size);

And also run a kernel-doc with all warnings enabled and fix the
descriptions respectively.

-- 
With Best Regards,
Andy Shevchenko


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 09/10] drivers/auxdisplay: add a KFuzzTest for parse_xy()
  2025-12-04 15:26   ` Andy Shevchenko
  2025-12-04 15:28     ` Andy Shevchenko
@ 2025-12-04 15:32     ` Marco Elver
  2025-12-04 15:34       ` Andy Shevchenko
  1 sibling, 1 reply; 21+ messages in thread
From: Marco Elver @ 2025-12-04 15:32 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Ethan Graham, glider, andreyknvl, andy, brauner, brendan.higgins,
	davem, davidgow, dhowells, dvyukov, herbert, ignat, jack, jannh,
	johannes, kasan-dev, kees, kunit-dev, linux-crypto, linux-kernel,
	linux-mm, lukas, rmoar, shuah, sj, tarasmadan, Ethan Graham

On Thu, 4 Dec 2025 at 16:26, Andy Shevchenko <andy.shevchenko@gmail.com> wrote:
[..]
> > Signed-off-by: Ethan Graham <ethangraham@google.com>
> > Signed-off-by: Ethan Graham <ethan.w.s.graham@gmail.com>
>
> I believe one of two SoBs is enough.

Per my interpretation of
https://docs.kernel.org/process/submitting-patches.html#developer-s-certificate-of-origin-1-1
it's required where the affiliation/identity of the author has
changed; it's as if another developer picked up the series and
continues improving it.


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 09/10] drivers/auxdisplay: add a KFuzzTest for parse_xy()
  2025-12-04 15:32     ` Marco Elver
@ 2025-12-04 15:34       ` Andy Shevchenko
  2025-12-04 15:35         ` Marco Elver
  0 siblings, 1 reply; 21+ messages in thread
From: Andy Shevchenko @ 2025-12-04 15:34 UTC (permalink / raw)
  To: Marco Elver
  Cc: Ethan Graham, glider, andreyknvl, andy, brauner, brendan.higgins,
	davem, davidgow, dhowells, dvyukov, herbert, ignat, jack, jannh,
	johannes, kasan-dev, kees, kunit-dev, linux-crypto, linux-kernel,
	linux-mm, lukas, shuah, sj, tarasmadan

On Thu, Dec 4, 2025 at 5:33 PM Marco Elver <elver@google.com> wrote:
> On Thu, 4 Dec 2025 at 16:26, Andy Shevchenko <andy.shevchenko@gmail.com> wrote:

[..]

> > > Signed-off-by: Ethan Graham <ethangraham@google.com>
> > > Signed-off-by: Ethan Graham <ethan.w.s.graham@gmail.com>
> >
> > I believe one of two SoBs is enough.
>
> Per my interpretation of
> https://docs.kernel.org/process/submitting-patches.html#developer-s-certificate-of-origin-1-1
> it's required where the affiliation/identity of the author has
> changed; it's as if another developer picked up the series and
> continues improving it.

Since the original address does not exist, the Originally-by: or free
text in the commit message / cover letter should be enough.

-- 
With Best Regards,
Andy Shevchenko


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 09/10] drivers/auxdisplay: add a KFuzzTest for parse_xy()
  2025-12-04 15:34       ` Andy Shevchenko
@ 2025-12-04 15:35         ` Marco Elver
  2025-12-04 15:42           ` Marco Elver
  2025-12-04 17:10           ` Andy Shevchenko
  0 siblings, 2 replies; 21+ messages in thread
From: Marco Elver @ 2025-12-04 15:35 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Ethan Graham, glider, andreyknvl, andy, brauner, brendan.higgins,
	davem, davidgow, dhowells, dvyukov, herbert, ignat, jack, jannh,
	johannes, kasan-dev, kees, kunit-dev, linux-crypto, linux-kernel,
	linux-mm, lukas, shuah, sj, tarasmadan

On Thu, 4 Dec 2025 at 16:34, Andy Shevchenko <andy.shevchenko@gmail.com> wrote:
>
> On Thu, Dec 4, 2025 at 5:33 PM Marco Elver <elver@google.com> wrote:
> > On Thu, 4 Dec 2025 at 16:26, Andy Shevchenko <andy.shevchenko@gmail.com> wrote:
>
> [..]
>
> > > > Signed-off-by: Ethan Graham <ethangraham@google.com>
> > > > Signed-off-by: Ethan Graham <ethan.w.s.graham@gmail.com>
> > >
> > > I believe one of two SoBs is enough.
> >
> > Per my interpretation of
> > https://docs.kernel.org/process/submitting-patches.html#developer-s-certificate-of-origin-1-1
> > it's required where the affiliation/identity of the author has
> > changed; it's as if another developer picked up the series and
> > continues improving it.
>
> Since the original address does not exist, the Originally-by: or free
> text in the commit message / cover letter should be enough.

The original copyright still applies, and the SOB captures that.


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 09/10] drivers/auxdisplay: add a KFuzzTest for parse_xy()
  2025-12-04 15:35         ` Marco Elver
@ 2025-12-04 15:42           ` Marco Elver
  2025-12-04 15:56             ` Greg Kroah-Hartman
  2025-12-04 17:10           ` Andy Shevchenko
  1 sibling, 1 reply; 21+ messages in thread
From: Marco Elver @ 2025-12-04 15:42 UTC (permalink / raw)
  To: Andy Shevchenko, Greg Kroah-Hartman
  Cc: Ethan Graham, glider, andreyknvl, andy, brauner, brendan.higgins,
	davem, davidgow, dhowells, dvyukov, herbert, ignat, jack, jannh,
	johannes, kasan-dev, kees, kunit-dev, linux-crypto, linux-kernel,
	linux-mm, lukas, shuah, sj, tarasmadan

On Thu, 4 Dec 2025 at 16:35, Marco Elver <elver@google.com> wrote:
> On Thu, 4 Dec 2025 at 16:34, Andy Shevchenko <andy.shevchenko@gmail.com> wrote:
> >
> > On Thu, Dec 4, 2025 at 5:33 PM Marco Elver <elver@google.com> wrote:
> > > On Thu, 4 Dec 2025 at 16:26, Andy Shevchenko <andy.shevchenko@gmail.com> wrote:
> >
> > [..]
> >
> > > > > Signed-off-by: Ethan Graham <ethangraham@google.com>
> > > > > Signed-off-by: Ethan Graham <ethan.w.s.graham@gmail.com>
> > > >
> > > > I believe one of two SoBs is enough.
> > >
> > > Per my interpretation of
> > > https://docs.kernel.org/process/submitting-patches.html#developer-s-certificate-of-origin-1-1
> > > it's required where the affiliation/identity of the author has
> > > changed; it's as if another developer picked up the series and
> > > continues improving it.
> >
> > Since the original address does not exist, the Originally-by: or free
> > text in the commit message / cover letter should be enough.
>
> The original copyright still applies, and the SOB captures that.

+Cc Greg - who might be able to shed a light on tricky cases like this.

tldr; Ethan left Google, but continues to develop series in personal
capacity. Question about double-SOB requirement above.

Thanks,
-- Marco


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 09/10] drivers/auxdisplay: add a KFuzzTest for parse_xy()
  2025-12-04 15:42           ` Marco Elver
@ 2025-12-04 15:56             ` Greg Kroah-Hartman
  0 siblings, 0 replies; 21+ messages in thread
From: Greg Kroah-Hartman @ 2025-12-04 15:56 UTC (permalink / raw)
  To: Marco Elver
  Cc: Andy Shevchenko, Ethan Graham, glider, andreyknvl, andy, brauner,
	brendan.higgins, davem, davidgow, dhowells, dvyukov, herbert,
	ignat, jack, jannh, johannes, kasan-dev, kees, kunit-dev,
	linux-crypto, linux-kernel, linux-mm, lukas, shuah, sj,
	tarasmadan

On Thu, Dec 04, 2025 at 04:42:37PM +0100, Marco Elver wrote:
> On Thu, 4 Dec 2025 at 16:35, Marco Elver <elver@google.com> wrote:
> > On Thu, 4 Dec 2025 at 16:34, Andy Shevchenko <andy.shevchenko@gmail.com> wrote:
> > >
> > > On Thu, Dec 4, 2025 at 5:33 PM Marco Elver <elver@google.com> wrote:
> > > > On Thu, 4 Dec 2025 at 16:26, Andy Shevchenko <andy.shevchenko@gmail.com> wrote:
> > >
> > > [..]
> > >
> > > > > > Signed-off-by: Ethan Graham <ethangraham@google.com>
> > > > > > Signed-off-by: Ethan Graham <ethan.w.s.graham@gmail.com>
> > > > >
> > > > > I believe one of two SoBs is enough.
> > > >
> > > > Per my interpretation of
> > > > https://docs.kernel.org/process/submitting-patches.html#developer-s-certificate-of-origin-1-1
> > > > it's required where the affiliation/identity of the author has
> > > > changed; it's as if another developer picked up the series and
> > > > continues improving it.
> > >
> > > Since the original address does not exist, the Originally-by: or free
> > > text in the commit message / cover letter should be enough.
> >
> > The original copyright still applies, and the SOB captures that.
> 
> +Cc Greg - who might be able to shed a light on tricky cases like this.
> 
> tldr; Ethan left Google, but continues to develop series in personal
> capacity. Question about double-SOB requirement above.

It's the same natural person, so only 1 is needed.

thanks,

greg k-h


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 09/10] drivers/auxdisplay: add a KFuzzTest for parse_xy()
  2025-12-04 15:35         ` Marco Elver
  2025-12-04 15:42           ` Marco Elver
@ 2025-12-04 17:10           ` Andy Shevchenko
  2025-12-04 21:38             ` Ethan Graham
  1 sibling, 1 reply; 21+ messages in thread
From: Andy Shevchenko @ 2025-12-04 17:10 UTC (permalink / raw)
  To: Marco Elver
  Cc: Ethan Graham, glider, andreyknvl, andy, brauner, brendan.higgins,
	davem, davidgow, dhowells, dvyukov, herbert, ignat, jack, jannh,
	johannes, kasan-dev, kees, kunit-dev, linux-crypto, linux-kernel,
	linux-mm, lukas, shuah, sj, tarasmadan

On Thu, Dec 4, 2025 at 5:36 PM Marco Elver <elver@google.com> wrote:
> On Thu, 4 Dec 2025 at 16:34, Andy Shevchenko <andy.shevchenko@gmail.com> wrote:
> > On Thu, Dec 4, 2025 at 5:33 PM Marco Elver <elver@google.com> wrote:
> > > On Thu, 4 Dec 2025 at 16:26, Andy Shevchenko <andy.shevchenko@gmail.com> wrote:

[..]

> > > > > Signed-off-by: Ethan Graham <ethangraham@google.com>
> > > > > Signed-off-by: Ethan Graham <ethan.w.s.graham@gmail.com>
> > > >
> > > > I believe one of two SoBs is enough.
> > >
> > > Per my interpretation of
> > > https://docs.kernel.org/process/submitting-patches.html#developer-s-certificate-of-origin-1-1
> > > it's required where the affiliation/identity of the author has
> > > changed; it's as if another developer picked up the series and
> > > continues improving it.
> >
> > Since the original address does not exist, the Originally-by: or free
> > text in the commit message / cover letter should be enough.
>
> The original copyright still applies, and the SOB captures that.

The problem is that you put a non-existing person there. Make sure
emails are not bouncing and I will not object (however, I just saw
Greg's reply).

-- 
With Best Regards,
Andy Shevchenko


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 09/10] drivers/auxdisplay: add a KFuzzTest for parse_xy()
  2025-12-04 17:10           ` Andy Shevchenko
@ 2025-12-04 21:38             ` Ethan Graham
  0 siblings, 0 replies; 21+ messages in thread
From: Ethan Graham @ 2025-12-04 21:38 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Marco Elver, glider, andreyknvl, andy, brauner, brendan.higgins,
	davem, davidgow, dhowells, dvyukov, herbert, ignat, jack, jannh,
	johannes, kasan-dev, kees, kunit-dev, linux-crypto, linux-kernel,
	linux-mm, lukas, shuah, sj, tarasmadan

On Thu, Dec 4, 2025 at 6:10 PM Andy Shevchenko
<andy.shevchenko@gmail.com> wrote:
>
> On Thu, Dec 4, 2025 at 5:36 PM Marco Elver <elver@google.com> wrote:
> > On Thu, 4 Dec 2025 at 16:34, Andy Shevchenko <andy.shevchenko@gmail.com> wrote:
> > > On Thu, Dec 4, 2025 at 5:33 PM Marco Elver <elver@google.com> wrote:
> > > > On Thu, 4 Dec 2025 at 16:26, Andy Shevchenko <andy.shevchenko@gmail.com> wrote:
>
> [..]
>
> > > > > > Signed-off-by: Ethan Graham <ethangraham@google.com>
> > > > > > Signed-off-by: Ethan Graham <ethan.w.s.graham@gmail.com>
> > > > >
> > > > > I believe one of two SoBs is enough.
> > > >
> > > > Per my interpretation of
> > > > https://docs.kernel.org/process/submitting-patches.html#developer-s-certificate-of-origin-1-1
> > > > it's required where the affiliation/identity of the author has
> > > > changed; it's as if another developer picked up the series and
> > > > continues improving it.
> > >
> > > Since the original address does not exist, the Originally-by: or free
> > > text in the commit message / cover letter should be enough.
> >
> > The original copyright still applies, and the SOB captures that.
>
> The problem is that you put a non-existing person there. Make sure
> emails are not bouncing and I will not object (however, I just saw
> Greg's reply).

Understood. I'll stick to the single SoB in the next version as Greg
suggested.

This address is permanent, so there won't be any bouncing issues.


^ permalink raw reply	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2025-12-04 21:39 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-12-04 14:12 [PATCH v3 00/10] KFuzzTest: a new kernel fuzzing framework Ethan Graham
2025-12-04 14:12 ` [PATCH 01/10] mm/kasan: implement kasan_poison_range Ethan Graham
2025-12-04 15:17   ` Andrey Konovalov
2025-12-04 15:31     ` Andy Shevchenko
2025-12-04 14:12 ` [PATCH 02/10] kfuzztest: add user-facing API and data structures Ethan Graham
2025-12-04 14:12 ` [PATCH 03/10] kfuzztest: introduce the FUZZ_TEST_SIMPLE macro Ethan Graham
2025-12-04 14:12 ` [PATCH 05/10] tools: add kfuzztest-bridge utility Ethan Graham
2025-12-04 14:12 ` [PATCH 06/10] kfuzztest: add ReST documentation Ethan Graham
2025-12-04 14:12 ` [PATCH 07/10] kfuzztest: add KFuzzTest sample fuzz targets Ethan Graham
2025-12-04 14:12 ` [PATCH 08/10] crypto: implement KFuzzTest targets for PKCS7 and RSA parsing Ethan Graham
2025-12-04 14:12 ` [PATCH 09/10] drivers/auxdisplay: add a KFuzzTest for parse_xy() Ethan Graham
2025-12-04 15:26   ` Andy Shevchenko
2025-12-04 15:28     ` Andy Shevchenko
2025-12-04 15:32     ` Marco Elver
2025-12-04 15:34       ` Andy Shevchenko
2025-12-04 15:35         ` Marco Elver
2025-12-04 15:42           ` Marco Elver
2025-12-04 15:56             ` Greg Kroah-Hartman
2025-12-04 17:10           ` Andy Shevchenko
2025-12-04 21:38             ` Ethan Graham
2025-12-04 14:12 ` [PATCH 10/10] MAINTAINERS: add maintainer information for KFuzzTest Ethan Graham

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox