diff --git a/Documentation/dev-tools/index.rst b/Documentation/dev-tools/index.rst index 65c54b27a60b8d..ff453ac6292c9d 100644 --- a/Documentation/dev-tools/index.rst +++ b/Documentation/dev-tools/index.rst @@ -30,6 +30,7 @@ Documentation/process/debugging/index.rst kmemleak kcsan kfence + kfuzztest kselftest kunit/index ktap diff --git a/Documentation/dev-tools/kfuzztest.rst b/Documentation/dev-tools/kfuzztest.rst new file mode 100644 index 00000000000000..92afad2a93c6b5 --- /dev/null +++ b/Documentation/dev-tools/kfuzztest.rst @@ -0,0 +1,187 @@ +.. 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. + +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//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. + +The 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. + +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. + +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``. diff --git a/arch/x86/kernel/vmlinux.lds.S b/arch/x86/kernel/vmlinux.lds.S index 4fa0be732af10f..484e3e1ffb9f9e 100644 --- a/arch/x86/kernel/vmlinux.lds.S +++ b/arch/x86/kernel/vmlinux.lds.S @@ -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_ */ @@ -199,6 +219,8 @@ SECTIONS CONSTRUCTORS KEXEC_RELOCATE_KERNEL + KFUZZTEST_TABLE + /* rarely changed data like cpu maps */ READ_MOSTLY_DATA(INTERNODE_CACHE_BYTES) diff --git a/include/linux/kasan.h b/include/linux/kasan.h index 890011071f2b14..508d8a2d5ad613 100644 --- a/include/linux/kasan.h +++ b/include/linux/kasan.h @@ -126,6 +126,34 @@ static __always_inline bool kasan_unpoison_pages(struct page *page, return false; } +#ifdef CONFIG_KASAN_GENERIC + +/** + * kasan_poison_last_granule - mark the last granule of the memory range as + * inaccessible + * @address: range start address, must be aligned to KASAN_GRANULE_SIZE + * @size: range size + * + * This function is only available for the generic mode, as it's the only mode + * that has partially poisoned memory granules. + */ +void kasan_poison_last_granule(const void *address, size_t size); + +#else /* CONFIG_KASAN_GENERIC */ + +static inline void kasan_poison_last_granule(const void *address, size_t size) { } + +#endif /* CONFIG_KASAN_GENERIC */ + +/** + * kasan_poison - mark the memory range as inaccessible + * @addr: range start address, must be aligned to KASAN_GRANULE_SIZE + * @size: range size, must be aligned to KASAN_GRANULE_SIZE + * @value: value that's written to metadata for the range + * @init: whether to initialize the memory range (only for hardware tag-based) + */ +void kasan_poison(const void *addr, size_t size, u8 value, bool init); + void __kasan_poison_slab(struct slab *slab); static __always_inline void kasan_poison_slab(struct slab *slab) { diff --git a/include/linux/kfuzztest.h b/include/linux/kfuzztest.h new file mode 100644 index 00000000000000..067935060c5e45 --- /dev/null +++ b/include/linux/kfuzztest.h @@ -0,0 +1,537 @@ +// 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 +#include +#include + +/** + * @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 kenrel 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 three main components: + * 1. 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. + * 2. 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. + * 3. A Payload: The raw binary data for the 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_entry - 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_input - parse and validate a KFuzzTest input + * + * @input: A buffer containing the serialized test case. + * @input_size: The size in bytes of the @input buffer. + * @ret_regions: On success, updated to point to the relocation region array + * within the @input buffer. + * @ret_reloc_table: On success, updated to point to the relocation table + * within the @input buffer. + * @ret_payload_start: On success, updated to point to the start of the data + * payload within the @input buffer. + * @ret_payload_end: On success, updated to point to the first byte after the + * end of the data payload. + * + * Returns: 0 on success, or a negative error code if the input is corrupted. + */ +int __kfuzztest_parse_input(void *input, size_t input_size, + struct reloc_region_array **ret_regions, + struct reloc_table **ret_reloc_table, + void **ret_payload_start, void **ret_payload_end); + +/** + * __kfuzztest_relocate - resolve relocations in a serialized payload + * + * @regions: The relocation region array parsed from the input. + * @rt: The relocation table parsed from the input. + * @payload_start: A pointer to the start of the data payload. + * @payload_end: A pointer to the first byte after the end of the payload. + * + * This function iterates through the region array and relocation table to + * patch the pointers inside of the payload, reconstructing pointer-pointee + * relationships between the logical regions of the fuzz driver input. For + * each entry in @rt, it calculates the address of a pointer field within the + * payload and sets it to the start address of its target region, or a NULL + * pointer if marked with KFUZZTEST_REGIONID_NULL. + * + * The padded areas between each region are poisoned with a KASAN slab redzone + * to enable the detection of byte-accurate OOB accesses in the fuzz logic. + * + * Returns: 0 on success, or a negative error code if the relocation data is + * found to be corrupt (e.g., invalid pointers). + * + * NOTE: this function only performs basic input validation. Full input + * validation is handled during parsing by __kfuzztest_parse_input. + */ +int __kfuzztest_relocate(struct reloc_region_array *regions, + struct reloc_table *rt, void *payload_start, + void *payload_end); + +/* + * 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%lx", + 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%lx)", + (char *)payload_start - (char *)regions, + (char *)payload_end - (char *)regions); +} + +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); + +/* + * Enforce a fixed struct size to ensure a consistent stride when iterating + * over the array of these structs in the dedicated ELF section. + */ +static_assert(sizeof(struct kfuzztest_target) == 32, + "struct kfuzztest_target should have size 32"); + +/* clang-format off */ +/** + * 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/kftf/@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 following variables: + * + * - `arg`: 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. + * + * - `regions`: A pointer of type `struct reloc_region_array *`. This is an + * advanced feature that allows access to the raw region metadata, which + * can be useful for checking the actual allocated size of a buffer via + * `KFUZZTEST_REGION_SIZE(n)`. + * + * 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, struct reloc_region_array *regions); \ + const struct kfuzztest_target __fuzz_test__##test_name __attribute__(( \ + __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) \ + { \ + void *payload_start, *payload_end, *buffer; \ + struct reloc_region_array *regions; \ + struct reloc_table *rt; \ + test_arg_type *arg; \ + int ret; \ + \ + buffer = kmalloc(len, GFP_KERNEL); \ + if (!buffer) \ + return -ENOMEM; \ + ret = simple_write_to_buffer(buffer, len, off, buf, len); \ + if (ret < 0) \ + goto out; \ + ret = __kfuzztest_parse_input(buffer, len, ®ions, &rt, \ + &payload_start, &payload_end); \ + if (ret) \ + goto out; \ + __kfuzztest_debug_header(regions, rt, payload_start, \ + payload_end); \ + ret = __kfuzztest_relocate(regions, rt, payload_start, \ + payload_end); \ + if (ret) \ + goto out; \ + /* Call the fuzz logic on the provided written input. */ \ + arg = (test_arg_type *)payload_start; \ + kfuzztest_logic_##test_name(arg, regions); \ + ret = len; \ +out: \ + kfree(buffer); \ + return ret; \ + } \ + static void kfuzztest_logic_##test_name( \ + test_arg_type *arg, struct reloc_region_array *regions) + +enum kfuzztest_constraint_type { + EXPECT_EQ = 0, + 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); + +/* + * Enforce a fixed struct size to ensure a consistent stride when iterating + * over the array of these structs in the dedicated ELF section. + */ +static_assert(sizeof(struct kfuzztest_constraint) == 64, + "struct kfuzztest_constraint should have size 64"); + +#define __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val1, val2, tpe) \ + static struct kfuzztest_constraint __constraint_##arg_type##_##f \ + __attribute__((__section__(".kfuzztest_constraint"), \ + __used__)) = { \ + .input_type = "struct " #arg_type, \ + .field_name = #field, \ + .value1 = (uintptr_t)val1, \ + .value2 = (uintptr_t)val2, \ + .type = tpe, \ + } + +#define KFUZZTEST_EXPECT_EQ(arg_type, field, val) \ + do { \ + if (arg->field != val) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, \ + val, 0x0, EXPECT_EQ); \ + } while (0) + +#define KFUZZTEST_EXPECT_NE(arg_type, field, val) \ + do { \ + if (arg->field == val) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, \ + 0x0, EXPECT_NE); \ + } while (0) + +#define KFUZZTEST_EXPECT_LT(arg_type, field, val) \ + do { \ + if (arg->field >= val) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, \ + 0x0, EXPECT_LT); \ + } while (0) + +#define KFUZZTEST_EXPECT_LE(arg_type, field, val) \ + do { \ + if (arg->field > val) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, \ + 0x0, EXPECT_LE); \ + } while (0) + +#define KFUZZTEST_EXPECT_GT(arg_type, field, val) \ + do { \ + if (arg->field <= val) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, \ + 0x0, EXPECT_GT) \ + } while (0) + +#define KFUZZTEST_EXPECT_GE(arg_type, field, val) \ + do { \ + if (arg->field < val) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, \ + 0x0, EXPECT_GE)` \ + } while (0) + +#define KFUZZTEST_EXPECT_NOT_NULL(arg_type, field) \ + KFUZZTEST_EXPECT_NE(arg_type, field, 0x0) + +#define KFUZZTEST_EXPECT_IN_RANGE(arg_type, field, lower_bound, upper_bound) \ + do { \ + if (arg->field < lower_bound || arg->field > upper_bound) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, lower_bound, \ + upper_bound, EXPECT_IN_RANGE) \ + } while (0) + +/** + * 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 : uint8_t { + ATTRIBUTE_LEN = 0, + 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); + +/* + * Enforce a fixed struct size to ensure a consistent stride when iterating + * over the array of these structs in the dedicated ELF section. + */ +static_assert(sizeof(struct kfuzztest_annotation) == 32, + "struct kfuzztest_annotation should have size 32"); + +#define __KFUZZTEST_ANNOTATE(arg_type, field, linked_field, attribute) \ + static struct kfuzztest_annotation __annotation_##arg_type##_##field \ + __attribute__((__section__(".kfuzztest_annotation"), \ + __used__)) = { \ + .input_type = "struct " #arg_type, \ + .field_name = #field, \ + .linked_field_name = #linked_field, \ + .attrib = attribute, \ + } + +/** + * Annotates a char* field as a string, which is the subset of char arrays that + * are null-terminated. + */ +#define KFUZZTEST_ANNOTATE_STRING(arg_type, field) \ + __KFUZZTEST_ANNOTATE(arg_type, field, NULL, ATTRIBUTE_STRING) + +/** + * Annotates a pointer field as an array, which is a contiguous memory region + * containing zero or more elements of the same type. + */ +#define KFUZZTEST_ANNOTATE_ARRAY(arg_type, field) \ + __KFUZZTEST_ANNOTATE(arg_type, field, NULL, ATTRIBUTE_ARRAY) + +/** + * Annotates arg_type.field as the length of arg_type.linked_field + */ +#define KFUZZTEST_ANNOTATE_LEN(arg_type, field, linked_field) \ + __KFUZZTEST_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) + +#define KFUZZTEST_REGIONID_NULL U32_MAX + +/** Get the size of a payload region from within a FUZZ_TEST body */ +#define KFUZZTEST_REGION_SIZE(n) \ + ((n) < (regions->num_regions) ? (regions->regions[n].size) : 0) + +/* + * FIXME: These are both defined in `mm/kasan/kasan.h`, but the build breaks + * if we define them in `include/linux/kasan.h` Since these values are unlikely + * to change, we redefine them here. + */ +#define __KASAN_SLAB_REDZONE 0xFC +#define __KASAN_GRANULE_SIZE 0x8 + +/** + * 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 __KASAN_GRANULE_SIZE + +#endif /* KFUZZTEST_H */ diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index ebe33181b6e6e0..5a33c34b107469 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1040,6 +1040,7 @@ config MEM_ALLOC_PROFILING_DEBUG source "lib/Kconfig.kasan" source "lib/Kconfig.kfence" source "lib/Kconfig.kmsan" +source "lib/Kconfig.kfuzztest" endmenu # "Memory Debugging" diff --git a/lib/Kconfig.kfuzztest b/lib/Kconfig.kfuzztest new file mode 100644 index 00000000000000..6364ca8e78b168 --- /dev/null +++ b/lib/Kconfig.kfuzztest @@ -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 + 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. diff --git a/lib/Makefile b/lib/Makefile index c38582f187dd81..511c44ef4b19e9 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -354,6 +354,8 @@ obj-$(CONFIG_GENERIC_LIB_CMPDI2) += cmpdi2.o obj-$(CONFIG_GENERIC_LIB_UCMPDI2) += ucmpdi2.o obj-$(CONFIG_OBJAGG) += objagg.o +obj-$(CONFIG_KFUZZTEST) += kfuzztest/ + # pldmfw library obj-$(CONFIG_PLDMFW) += pldmfw/ diff --git a/lib/kfuzztest/Makefile b/lib/kfuzztest/Makefile new file mode 100644 index 00000000000000..ddf8ab63219ff4 --- /dev/null +++ b/lib/kfuzztest/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_KFUZZTEST) += kfuzztest.o +kfuzztest-objs := main.o relocations.o parse.o examples.o diff --git a/lib/kfuzztest/examples.c b/lib/kfuzztest/examples.c new file mode 100644 index 00000000000000..6c709d25818fda --- /dev/null +++ b/lib/kfuzztest/examples.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file contains some KFuzzTest target examples. + * + * Copyright 2025 Google LLC + */ +#include + +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) +{ + size_t i; + + 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); + + pr_info("a = [%px, %px)", arg->a, arg->a + KFUZZTEST_REGION_SIZE(1)); + pr_info("b = [%px, %px)", arg->b, arg->b + KFUZZTEST_REGION_SIZE(2)); + + /* Ensure that all bytes in arg->b are accessible. */ + for (i = 0; i < arg->b_len; i++) + READ_ONCE(arg->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 <= arg->a_len; i++) + READ_ONCE(arg->a[i]); +} + +struct some_buffer { + char *buf; + size_t buflen; +}; + +/** + * 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(test_underflow_on_buffer, struct some_buffer) +{ + size_t i; + + KFUZZTEST_EXPECT_NOT_NULL(some_buffer, buf); + KFUZZTEST_ANNOTATE_LEN(some_buffer, buflen, buf); + + pr_info("buf = [%px, %px)", arg->buf, arg->buf + arg->buflen); + + /* First ensure that all bytes in arg->b are accessible. */ + for (i = 0; i < arg->buflen; i++) + READ_ONCE(arg->buf[i]); + /* + * Provoke a buffer overflow on the first byte preceding b, triggering + * a KASAN report. + */ + READ_ONCE(*((char *)arg->buf - 1)); +} diff --git a/lib/kfuzztest/main.c b/lib/kfuzztest/main.c new file mode 100644 index 00000000000000..fccda1319fb0d4 --- /dev/null +++ b/lib/kfuzztest/main.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KFuzzTest core module initialization and debugfs interface. + * + * Copyright 2025 Google LLC + */ +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ethan Graham "); +MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFuzzTest)"); + +extern const struct kfuzztest_target __kfuzztest_targets_start[]; +extern const struct kfuzztest_target __kfuzztest_targets_end[]; + +/** + * struct kfuzztest_dentry - A container for a debugfs dentry and its fops. + * @dentry: Pointer to the created debugfs dentry. + * @fops: The file_operations struct associated with this dentry. + * + * This simplifies state management by keeping a file's dentry and its + * operations bundled together. + */ +struct kfuzztest_dentry { + struct dentry *dentry; + struct file_operations fops; +}; + +/** + * struct kfuzztest_debugfs_state - Per-test-case debugfs state. + * @test_dir: The top-level debugfs directory for a single test case, e.g., + * /sys/kernel/debug/kfuzztest//. + * @input_dentry: The state for the "input" file, which is write-only. + * + * Wraps all debugfs components created for a single test case. + */ +struct kfuzztest_debugfs_state { + struct dentry *target_dir; + struct kfuzztest_dentry input_dentry; +}; + +/** + * struct kfuzztest_simple_fuzzer_state - Global state for the KFTF module. + * @kfuzztest_dir: The root debugfs directory, /sys/kernel/debug/kfuzztest/. + * @debugfs_state: A statically sized array holding the state for each + * registered test case. + */ +struct kfuzztest_state { + struct file_operations fops; + struct dentry *kfuzztest_dir; + struct kfuzztest_debugfs_state *debugfs_state; +}; + +/* Global static variable to hold all state for the module. */ +static struct kfuzztest_state state; + +const umode_t KFUZZTEST_INPUT_PERMS = 0222; + +/** + * kfuzztest_init - Initializes the debug filesystem for KFuzzTest. + * + * Each registered test in the ".kfuzztest" section gets its own subdirectory + * under "/sys/kernel/debug/kfuzztest/" with one files: + * - input: write-only file to send input to the fuzz driver + * + * Returns: + * 0 on success. + * -ENODEV or other error codes if debugfs creation fails. + */ +static int __init kfuzztest_init(void) +{ + const struct kfuzztest_target *targ; + int ret = 0; + int i = 0; + size_t num_test_cases; + + num_test_cases = __kfuzztest_targets_end - __kfuzztest_targets_start; + + state.debugfs_state = + kzalloc(num_test_cases * sizeof(struct kfuzztest_debugfs_state), + GFP_KERNEL); + if (!state.debugfs_state) + return -ENOMEM; + + /* Create the main "kfuzztest" directory in /sys/kernel/debug. */ + state.kfuzztest_dir = debugfs_create_dir("kfuzztest", NULL); + if (!state.kfuzztest_dir) { + pr_warn("KFuzzTest: could not create debugfs"); + return -ENODEV; + } + + if (IS_ERR(state.kfuzztest_dir)) { + state.kfuzztest_dir = NULL; + return PTR_ERR(state.kfuzztest_dir); + } + + for (targ = __kfuzztest_targets_start; targ < __kfuzztest_targets_end; + targ++, i++) { + /* Create debugfs directory for the target. */ + state.debugfs_state[i].target_dir = + debugfs_create_dir(targ->name, state.kfuzztest_dir); + + if (!state.debugfs_state[i].target_dir) { + ret = -ENOMEM; + goto cleanup_failure; + } else if (IS_ERR(state.debugfs_state[i].target_dir)) { + ret = PTR_ERR(state.debugfs_state[i].target_dir); + goto cleanup_failure; + } + + /* Create an input file under the target's directory. */ + state.debugfs_state[i].input_dentry.fops = + (struct file_operations){ + .owner = THIS_MODULE, + .write = targ->write_input_cb, + }; + state.debugfs_state[i].input_dentry.dentry = + debugfs_create_file( + "input", KFUZZTEST_INPUT_PERMS, + state.debugfs_state[i].target_dir, NULL, + &state.debugfs_state[i].input_dentry.fops); + if (!state.debugfs_state[i].input_dentry.dentry) { + ret = -ENOMEM; + goto cleanup_failure; + } else if (IS_ERR(state.debugfs_state[i].input_dentry.dentry)) { + ret = PTR_ERR( + state.debugfs_state[i].input_dentry.dentry); + goto cleanup_failure; + } + + pr_info("KFuzzTest: registered target %s", targ->name); + } + + return 0; + +cleanup_failure: + debugfs_remove_recursive(state.kfuzztest_dir); + return ret; +} + +static void __exit kfuzztest_exit(void) +{ + pr_info("KFuzzTest: exiting"); + if (!state.kfuzztest_dir) + return; + + debugfs_remove_recursive(state.kfuzztest_dir); + state.kfuzztest_dir = NULL; + + if (state.debugfs_state) { + kfree(state.debugfs_state); + state.debugfs_state = NULL; + } +} + +module_init(kfuzztest_init); +module_exit(kfuzztest_exit); diff --git a/lib/kfuzztest/parse.c b/lib/kfuzztest/parse.c new file mode 100644 index 00000000000000..f19c8f1853e58a --- /dev/null +++ b/lib/kfuzztest/parse.c @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * KFuzzTest input parsing and validation. + * + * Copyright 2025 Google LLC + */ +#include + +static bool __kfuzztest_input_is_valid(struct reloc_region_array *regions, + struct reloc_table *rt, + void *payload_start, void *payload_end) +{ + size_t payload_size = (char *)payload_end - (char *)payload_start; + struct reloc_region reg, next_reg; + size_t usable_payload_size; + uint32_t region_end_offset; + struct reloc_entry reloc; + uint32_t i; + + if ((char *)payload_start > (char *)payload_end) + return false; + if (payload_size < KFUZZTEST_POISON_SIZE) + return false; + usable_payload_size = payload_size - KFUZZTEST_POISON_SIZE; + + for (i = 0; i < regions->num_regions; i++) { + reg = regions->regions[i]; + if (check_add_overflow(reg.offset, reg.size, + ®ion_end_offset)) + return false; + if ((size_t)region_end_offset > usable_payload_size) + return false; + + if (i < regions->num_regions - 1) { + next_reg = regions->regions[i + 1]; + if (reg.offset > next_reg.offset) + return false; + /* + * Enforce the minimum poisonable gap between + * consecutive regions. + */ + if (reg.offset + reg.size + KFUZZTEST_POISON_SIZE > + next_reg.offset) + return false; + } + } + + if (rt->padding_size < KFUZZTEST_POISON_SIZE) { + pr_info("validation failed because rt->padding_size = %u", + rt->padding_size); + return false; + } + + for (i = 0; i < rt->num_entries; i++) { + reloc = rt->entries[i]; + if (reloc.region_id >= regions->num_regions) + return false; + if (reloc.value != KFUZZTEST_REGIONID_NULL && + reloc.value >= regions->num_regions) + return false; + + reg = regions->regions[reloc.region_id]; + if (reloc.region_offset % (sizeof(uintptr_t)) || + reloc.region_offset + sizeof(uintptr_t) > reg.size) + return false; + } + + return true; +} + +int __kfuzztest_parse_input(void *input, size_t input_size, + struct reloc_region_array **ret_regions, + struct reloc_table **ret_reloc_table, + void **ret_payload_start, void **ret_payload_end) +{ + size_t reloc_entries_size, reloc_regions_size; + size_t reloc_table_size, regions_size; + struct reloc_region_array *regions; + void *payload_end, *payload_start; + struct reloc_table *rt; + size_t curr_offset = 0; + + if (input_size < + sizeof(struct reloc_region_array) + sizeof(struct reloc_table)) + return -EINVAL; + + regions = input; + if (check_mul_overflow(regions->num_regions, + sizeof(struct reloc_region), + &reloc_regions_size)) + return -EINVAL; + if (check_add_overflow(sizeof(*regions), reloc_regions_size, + ®ions_size)) + return -EINVAL; + + curr_offset = regions_size; + if (curr_offset > input_size) + return -EINVAL; + if (input_size - curr_offset < sizeof(struct reloc_table)) + return -EINVAL; + + rt = (struct reloc_table *)((char *)input + curr_offset); + + if (check_mul_overflow((size_t)rt->num_entries, + sizeof(struct reloc_entry), &reloc_entries_size)) + return -EINVAL; + if (check_add_overflow(sizeof(*rt), reloc_entries_size, + &reloc_table_size)) + return -EINVAL; + if (check_add_overflow(reloc_table_size, rt->padding_size, + &reloc_table_size)) + return -EINVAL; + + if (check_add_overflow(curr_offset, reloc_table_size, &curr_offset)) + return -EINVAL; + if (curr_offset > input_size) + return -EINVAL; + + payload_start = (char *)input + curr_offset; + payload_end = (char *)input + input_size; + + if (!__kfuzztest_input_is_valid(regions, rt, payload_start, + payload_end)) + return -EINVAL; + + *ret_regions = regions; + *ret_reloc_table = rt; + *ret_payload_start = payload_start; + *ret_payload_end = payload_end; + return 0; +} diff --git a/lib/kfuzztest/relocations.c b/lib/kfuzztest/relocations.c new file mode 100644 index 00000000000000..ed188a25076721 --- /dev/null +++ b/lib/kfuzztest/relocations.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KFuzzTest input relocation and memory poisoning. + * + * Copyright 2025 Google LLC + */ +#include +#include + +#ifdef CONFIG_KASAN + +/** + * kfuzztest_poison_range - poison the memory range [start, end) + * + * The exact behavior is subject to alignment with KASAN's 8-byte granule size: + * + * - 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 @end. Any remaining bytes in a final partial granule are ignored. + */ +static void kfuzztest_poison_range(void *start, void *end) +{ + uintptr_t end_addr = ALIGN_DOWN((uintptr_t)end, __KASAN_GRANULE_SIZE); + uintptr_t start_addr = (uintptr_t)start; + uintptr_t head_granule_start; + uintptr_t poison_body_start; + uintptr_t poison_body_end; + size_t head_prefix_size; + + if (start_addr >= end_addr) + return; + + 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 = ALIGN_DOWN(end_addr, __KASAN_GRANULE_SIZE); + + if (poison_body_start < poison_body_end) + kasan_poison((void *)poison_body_start, + poison_body_end - poison_body_start, + __KASAN_SLAB_REDZONE, false); +} + +#else /* CONFIG_KASAN */ + +static inline void kfuzztest_poison_range(void *, void *) {} + +#endif /* CONFIG_KASAN */ + +int __kfuzztest_relocate(struct reloc_region_array *regions, + struct reloc_table *rt, void *payload_start, + void *payload_end) +{ + struct reloc_region reg, src, dst; + void *poison_start, *poison_end; + uintptr_t *ptr_location; + struct reloc_entry re; + size_t i; + + /* Patch pointers. */ + for (i = 0; i < rt->num_entries; i++) { + re = rt->entries[i]; + src = regions->regions[re.region_id]; + ptr_location = (uintptr_t *)((char *)payload_start + + src.offset + re.region_offset); + if (re.value == KFUZZTEST_REGIONID_NULL) + *ptr_location = (uintptr_t)NULL; + else if (re.value < regions->num_regions) { + dst = regions->regions[re.value]; + *ptr_location = + (uintptr_t)((char *)payload_start + dst.offset); + } else + return -EINVAL; + } + + /* Poison the padding between regions. */ + for (i = 0; i < regions->num_regions; i++) { + reg = regions->regions[i]; + + /* Points to the beginning of the inter-region padding */ + poison_start = payload_start + reg.offset + reg.size; + if (i < regions->num_regions - 1) + poison_end = + payload_start + regions->regions[i + 1].offset; + else + poison_end = payload_end; + + if ((char *)poison_end > (char *)payload_end) + return -EINVAL; + + kfuzztest_poison_range(poison_start, poison_end); + } + + /* Poison the padded area preceding the payload. */ + kfuzztest_poison_range((char *)payload_start - rt->padding_size, + payload_start); + return 0; +} diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h index 129178be5e6492..c4a478f2d22e42 100644 --- a/mm/kasan/kasan.h +++ b/mm/kasan/kasan.h @@ -499,15 +499,6 @@ static inline bool kasan_byte_accessible(const void *addr) #else /* CONFIG_KASAN_HW_TAGS */ -/** - * kasan_poison - mark the memory range as inaccessible - * @addr: range start address, must be aligned to KASAN_GRANULE_SIZE - * @size: range size, must be aligned to KASAN_GRANULE_SIZE - * @value: value that's written to metadata for the range - * @init: whether to initialize the memory range (only for hardware tag-based) - */ -void kasan_poison(const void *addr, size_t size, u8 value, bool init); - /** * kasan_unpoison - mark the memory range as accessible * @addr: range start address, must be aligned to KASAN_GRANULE_SIZE @@ -525,25 +516,6 @@ bool kasan_byte_accessible(const void *addr); #endif /* CONFIG_KASAN_HW_TAGS */ -#ifdef CONFIG_KASAN_GENERIC - -/** - * kasan_poison_last_granule - mark the last granule of the memory range as - * inaccessible - * @address: range start address, must be aligned to KASAN_GRANULE_SIZE - * @size: range size - * - * This function is only available for the generic mode, as it's the only mode - * that has partially poisoned memory granules. - */ -void kasan_poison_last_granule(const void *address, size_t size); - -#else /* CONFIG_KASAN_GENERIC */ - -static inline void kasan_poison_last_granule(const void *address, size_t size) { } - -#endif /* CONFIG_KASAN_GENERIC */ - #ifndef kasan_arch_is_ready static inline bool kasan_arch_is_ready(void) { return true; } #elif !defined(CONFIG_KASAN_GENERIC) || !defined(CONFIG_KASAN_OUTLINE)