Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
f44cc6c
mm/kasan: expose poisoning functions for external modules
Aug 8, 2025
8fdc2e6
kfuzztest: add user-facing API and data structures
Aug 8, 2025
27b335e
kfuzztest: implement core module and input processing
Aug 8, 2025
67b5651
kfuzztest: add ReST documentation
Aug 8, 2025
90aaeb0
kfuzztest: add example fuzz targets
ethangraham2001 Aug 8, 2025
980b073
kfuzztest: refactor as-per Marco's suggestions
ethangraham2001 Aug 12, 2025
08f3393
kfuzztest: expose KASAN API instead of defining custom function
ethangraham2001 Aug 12, 2025
113cca0
kfuzztest: return error when magic is incorrect
ethangraham2001 Aug 12, 2025
97a7e5d
kfuzztest: move static assertions to .c file, update attribute syntax
ethangraham2001 Aug 12, 2025
2734f42
kfuzztest: remove = 0 from enums
ethangraham2001 Aug 12, 2025
be6900b
kfuzztest: move KConfig in KConfig.debug and to kfuzztest dir
ethangraham2001 Aug 12, 2025
fc89ccf
kfuzztest: update docs to use kernel style comments
ethangraham2001 Aug 12, 2025
d4c7928
kfuzztest: move examples /lib/kfuzztest to /samples
ethangraham2001 Aug 12, 2025
05b59b5
Documentation: move kfuzzetest position in index
ethangraham2001 Aug 12, 2025
072d945
kfuzztest: add example fuzz targets for real kernel functions
ethangraham2001 Aug 12, 2025
fbf6555
kfuzztest: remove a redundant check on input length
ethangraham2001 Aug 12, 2025
ed22eef
kfuzztest: add kerneldoc comments for annotations and constraints
ethangraham2001 Aug 12, 2025
48888de
kfuzztest: update how 8-byte version header is accessed in input
ethangraham2001 Aug 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Documentation/dev-tools/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Documentation/process/debugging/index.rst
kfence
kselftest
kunit/index
kfuzztest
ktap
checkuapi
gpio-sloppy-logic-analyzer
Expand Down
239 changes: 239 additions & 0 deletions Documentation/dev-tools/kfuzztest.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
.. 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`` macro, 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 currently only supported for 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 code that is built into the kernel, as the
core module's startup process discovers fuzz targets, constraints, and
annotations from a dedicated ELF section during startup.

Declaring a KFuzzTest target
----------------------------

A fuzz target is defined directly in a .c file, typically alongside the function
being tested. This process 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 how to create a fuzz target for a function
``int process_data(const char *data, size_t len)``.

.. code-block:: c

/*
* 1. Define a struct to model the inputs for the function under test.
* Each field corresponds to an argument needed by the function.
*/
struct process_data_inputs {
const char *data;
size_t len;
};

/*
* 2. 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_process_data, struct process_data_inputs)
{
/*
* Within this body, the 'arg' variable is a pointer to a
* fully initialized 'struct process_data_inputs'.
*/

/*
* 3. (Optional) Add constraints to define preconditions.
* This check ensures 'arg->data' is not NULL. If the condition
* is not met, the test exits early. This also creates metadata
* to inform the fuzzer.
*/
KFUZZTEST_EXPECT_NOT_NULL(process_data_inputs, data);

/*
* 4. (Optional) Add annotations to provide semantic hints.
* This annotation informs the fuzzer that the 'len' field
* is the length of the buffer pointed to by 'data'.
* Annotations do not add any runtime checks.
*/
KFUZZTEST_ANNOTATE_LEN(process_data_inputs, len, data);

/*
* 5. 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.
*/
process_data(arg->data, arg->len);
}

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.

Input Format
============

KFuzzTest targets receive their inputs from userspace via a write to a dedicated
debugfs ``/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 pointed to by fields within that struct. Region array
entries must be ordered by offset ascending, and must not overlap with one
another.

To satisfy C language alignment requirements and prevent potential hardware
faults, the memory address of each region's data must be correctly aligned for
the type it represents. The framework allocates a base buffer that is suitably
aligned for any C type. Therefore, the userspace tool that generates the input
is responsible for calculating each region's offset within the payload to ensure
this alignment is maintained.

Relocation Table
^^^^^^^^^^^^^^^^

The relocation table provides 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.

- Alignment: The start of the payload must be aligned to the most restrictive
alignment requirement of all its constituent regions. The framework ensures
that each region within the payload is then placed at an offset that respects
its own type's alignment.

- Padding and Poisoning: The space between the end of one region's data and the
beginning of the next must be sufficient for padding. In KASAN builds,
KFuzzTest poisons this unused padding, allowing for precise detection of
out-of-bounds memory accesses between adjacent buffers. This padding should
be at least ``KFUZZTEST_POISON_SIZE`` bytes as defined in
`include/linux/kfuzztest.h``.
22 changes: 22 additions & 0 deletions arch/x86/kernel/vmlinux.lds.S
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,26 @@ ASSERT(__relocate_kernel_end - __relocate_kernel_start <= KEXEC_CONTROL_CODE_MAX
#else
#define KEXEC_RELOCATE_KERNEL
#endif

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

PHDRS {
text PT_LOAD FLAGS(5); /* R_E */
data PT_LOAD FLAGS(6); /* RW_ */
Expand Down Expand Up @@ -199,6 +219,8 @@ SECTIONS
CONSTRUCTORS
KEXEC_RELOCATE_KERNEL

KFUZZTEST_TABLE

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

Expand Down
15 changes: 15 additions & 0 deletions crypto/asymmetric_keys/pkcs7_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <linux/err.h>
#include <linux/oid_registry.h>
#include <crypto/public_key.h>
#include <linux/kfuzztest.h>
#include "pkcs7_parser.h"
#include "pkcs7.asn1.h"

Expand Down Expand Up @@ -169,6 +170,20 @@ struct pkcs7_message *pkcs7_parse_message(const void *data, size_t datalen)
}
EXPORT_SYMBOL_GPL(pkcs7_parse_message);

struct pkcs7_parse_message_arg {
const void *data;
size_t datalen;
};

FUZZ_TEST(test_pkcs7_parse_message, struct pkcs7_parse_message_arg)
{
KFUZZTEST_EXPECT_NOT_NULL(pkcs7_parse_message_arg, data);
KFUZZTEST_ANNOTATE_LEN(pkcs7_parse_message_arg, datalen, data);
KFUZZTEST_EXPECT_LE(pkcs7_parse_message_arg, datalen, 16 * PAGE_SIZE);

pkcs7_parse_message(arg->data, arg->datalen);
}

/**
* pkcs7_get_content_data - Get access to the PKCS#7 content
* @pkcs7: The preparsed PKCS#7 message to access
Expand Down
29 changes: 29 additions & 0 deletions crypto/rsa_helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <linux/export.h>
#include <linux/err.h>
#include <linux/fips.h>
#include <linux/kfuzztest.h>
#include <crypto/internal/rsa.h>
#include "rsapubkey.asn1.h"
#include "rsaprivkey.asn1.h"
Expand Down Expand Up @@ -166,6 +167,20 @@ int rsa_parse_pub_key(struct rsa_key *rsa_key, const void *key,
}
EXPORT_SYMBOL_GPL(rsa_parse_pub_key);

struct rsa_parse_pub_key_arg {
const void *key;
size_t key_len;
};

FUZZ_TEST(test_rsa_parse_pub_key, struct rsa_parse_pub_key_arg)
{
KFUZZTEST_EXPECT_NOT_NULL(rsa_parse_pub_key_arg, key);
KFUZZTEST_EXPECT_LE(rsa_parse_pub_key_arg, key_len, 16 * PAGE_SIZE);

struct rsa_key out;
rsa_parse_pub_key(&out, arg->key, arg->key_len);
}

/**
* rsa_parse_priv_key() - decodes the BER encoded buffer and stores in the
* provided struct rsa_key, pointers to the raw key
Expand All @@ -184,3 +199,17 @@ int rsa_parse_priv_key(struct rsa_key *rsa_key, const void *key,
return asn1_ber_decoder(&rsaprivkey_decoder, rsa_key, key, key_len);
}
EXPORT_SYMBOL_GPL(rsa_parse_priv_key);

struct rsa_parse_priv_key_arg {
const void *key;
size_t key_len;
};

FUZZ_TEST(test_rsa_parse_priv_key, struct rsa_parse_priv_key_arg)
{
KFUZZTEST_EXPECT_NOT_NULL(rsa_parse_priv_key_arg, key);
KFUZZTEST_EXPECT_LE(rsa_parse_priv_key_arg, key_len, 16 * PAGE_SIZE);

struct rsa_key out;
rsa_parse_priv_key(&out, arg->key, arg->key_len);
}
16 changes: 16 additions & 0 deletions include/linux/kasan.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,21 @@ static inline bool kasan_has_integrated_init(void)
}

#ifdef CONFIG_KASAN

/**
* kasan_poison_range - poison the memory range [start, start + 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 is enabled.
* - The poisoning of the range only extends up to the last full granule before
* the end of the range. Any remaining bytes in a final partial granule are
* ignored.
*/
void kasan_poison_range(const void *start, 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)
{
Expand Down Expand Up @@ -402,6 +417,7 @@ static __always_inline bool kasan_check_byte(const void *addr)

#else /* CONFIG_KASAN */

static inline void kasan_poison_range(const void *start, size_t size) {}
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) {}
Expand Down
Loading