diff --git a/Documentation/dev-tools/kfuzztest.rst b/Documentation/dev-tools/kfuzztest.rst new file mode 100644 index 00000000000000..5e96eddbfda63e --- /dev/null +++ b/Documentation/dev-tools/kfuzztest.rst @@ -0,0 +1,181 @@ +.. 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. + +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 fdcca6b4b2f44e..484e3e1ffb9f9e 100644 --- a/arch/x86/kernel/vmlinux.lds.S +++ b/arch/x86/kernel/vmlinux.lds.S @@ -113,6 +113,7 @@ ASSERT(__relocate_kernel_end - __relocate_kernel_start <= KEXEC_CONTROL_CODE_MAX #define KEXEC_RELOCATE_KERNEL #endif +#ifdef CONFIG_KFUZZTEST #define KFUZZTEST_TABLE \ . = ALIGN(PAGE_SIZE); \ __kfuzztest_targets_start = .; \ @@ -125,7 +126,11 @@ ASSERT(__relocate_kernel_end - __relocate_kernel_start <= KEXEC_CONTROL_CODE_MAX . = ALIGN(PAGE_SIZE); \ __kfuzztest_annotations_start = .; \ KEEP(*(.kfuzztest_annotation)); \ - __kfuzztest_annotations_end = .; \ + __kfuzztest_annotations_end = .; + +#else /* CONFIG_KFUZZTEST */ +#define KFUZZTEST_TABLE +#endif /* CONFIG_KFUZZTEST */ PHDRS { text PT_LOAD FLAGS(5); /* R_E */ diff --git a/include/linux/kasan.h b/include/linux/kasan.h index 890011071f2b14..345ab363c7ba3e 100644 --- a/include/linux/kasan.h +++ b/include/linux/kasan.h @@ -126,6 +126,29 @@ 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 */ + +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 index cae69cd9eb9c5c..3879acb18ccbb5 100644 --- a/include/linux/kfuzztest.h +++ b/include/linux/kfuzztest.h @@ -1,13 +1,183 @@ +// 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 +#include -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Ethan Graham "); -MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFuzzTest)"); +/** + * @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 + */ -static void *kfuzztest_parse_input(void *input, size_t input_size); +/** + * 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; @@ -15,149 +185,158 @@ struct kfuzztest_target { ssize_t (*write_input_cb)(struct file *filp, const char __user *buf, size_t len, loff_t *off); } __attribute__((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"); -__attribute__((unused)) static int -write_input_cb_common(struct file *filp, const char __user *buf, size_t len, - loff_t *off, void *arg, size_t arg_size) -{ - if (len != arg_size) { - return -EINVAL; - } - if (simple_write_to_buffer((void *)arg, arg_size, off, buf, len) < 0) { - return -EFAULT; - } - return 0; -} - /** - * FUZZ_TEST - defines a KFuzzTest target. + * 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. * - * @test_name: Name of the fuzz target, which is used to create the associated - * debufs entries. - * @test_arg_type: the input type of fuzz target. This should always be a - * struct type even when fuzzing with a single input parameter in order - * to take advantage of the domain constraint and annotation systems. See - * usage example below. + * 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. * - * This macro generates all of the necessary boilerplate for a KFuzzTest - * driver, which is placed in a dedicated ".kfuzztest_target" that is used by - * the KFuzzTest module and can be read by a fuzzing engine. + * - `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)`. * - * For each test, this macro generates - * - A buffer to receive input through the debugfs entry - * - A mutex to protect the input buffer - * - A `struct kfuzztest_target` instance + * Example Usage: * - * Example usage: + * // 1. The kernel function we want to fuzz. + * int process_data(const char *data, size_t len); * - * // Assume that we are fuzzing some function func(T1 param1, ... TN paramN). - * // Define input type of the fuzz target. This should be always be a struct. - * struct test_arg_type { - * T1 arg1; - * ... - * TN argn; + * // 2. Define a struct to hold all inputs for the function. + * struct process_data_inputs { + * const char *data; + * size_t len; * }; * - * // Define the test case. - * FUZZ_TEST(test_func, struct test_arg_type) + * // 3. Define the fuzz test using the FUZZ_TEST macro. + * FUZZ_TEST(process_data_fuzzer, struct process_data_inputs) * { - * int ret; - * // arg is provided by the macro, and is of type struct test_arg_type. - * ret = func(arg.arg1, ..., arg.argn); - * // Validate the return value if testing for correctness. - * if (ret != expected_value) { - * KFUZZTEST_REPORT_BUG("Unexpected return value"); - * } + * 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 _write_callback_##test_name(struct file *filp, \ - const char __user *buf, \ - size_t len, loff_t *off); \ - static void _fuzz_test_logic_##test_name(test_arg_type *arg); \ + 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 = _write_callback_##test_name, \ + .write_input_cb = kfuzztest_write_cb_##test_name, \ }; \ - /* Invoked when data is written into the target's input file. */ \ - static ssize_t _write_callback_##test_name(struct file *filp, \ - const char __user *buf, \ - size_t len, loff_t *off) \ + static ssize_t kfuzztest_write_cb_##test_name(struct file *filp, \ + const char __user *buf, \ + size_t len, loff_t *off) \ { \ - int err; \ - void *buffer = kmalloc(len, GFP_KERNEL); \ - if (!buffer || IS_ERR(buffer)) \ - return PTR_ERR(buffer); \ - err = write_input_cb_common(filp, buf, len, off, buffer, len); \ - if (err != 0) { \ - kfree(buffer); \ - return err; \ - } \ - void *payload = kfuzztest_parse_input(buffer, len); \ - if (!payload) { \ - kfree(buffer); \ - return -1; \ - } \ - test_arg_type *arg = payload; \ - /* Call the user's logic on the provided written input. */ \ - _fuzz_test_logic_##test_name(arg); \ + 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; \ + 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); \ - kfree(payload); \ - return len; \ + return ret; \ } \ - static void _fuzz_test_logic_##test_name(test_arg_type *arg) + static void kfuzztest_logic_##test_name( \ + test_arg_type *arg, struct reloc_region_array *regions) -/** - * Reports a bug with a predictable prefix so that it can be parsed by a - * fuzzing driver. - */ -#define KFUZZTEST_REPORT_BUG(msg, fmt) pr_warn("bug: " #msg, fmt) - -enum kfuzztest_constraint_type : uint8_t { +enum kfuzztest_constraint_type { EXPECT_EQ = 0, EXPECT_NE, + EXPECT_LT, EXPECT_LE, EXPECT_GT, + EXPECT_GE, EXPECT_IN_RANGE, }; /** - * Domain constraints are used to restrict the values that the fuzz driver - * accepts, enforcing early exit when not satisfied. Domain constraints are - * encoded in vmlinux under the `__kfuzztest_constraint` section. A good - * fuzzing engine should be aware of these domain constraints during input - * generation and mutation. - * - * struct kfuzztest_constraint defines a domain constraint for a structure - * field. + * struct kfuzztest_constraint - a metadata record for a domain constraint * - * @input_type: the name of the input (a struct name) - * @field_name: the name of the field that this domain constraint applies to - * @value1: used in all comparisons - * @value2: only used in comparisons that require multiple values, e.g. range - * constraints - * @type: the type of the constraint, enumerated above + * 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. * - * Example usage: + * 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. * - * struct foo { - * struct bar *a; - * int b - * }; + * For an example of how these constraints are used within a fuzz test, see the + * documentation for the FUZZ_TEST() macro. * - * FUZZ_TEST(test_name, struct foo) - * { - * // Early exit if foo.a == NULL. - * KFUZZTEST_EXPECT_NOT_NULL(foo, a); - * // Early exit if foo < 23 || foo > 42 - * KFUZZTEST_EXPECT_IN_RANGE(foo, b, 23, 42); - * // User-defined fuzz logic. - * } + * @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; @@ -167,6 +346,10 @@ struct kfuzztest_constraint { enum kfuzztest_constraint_type type; } __attribute__((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"); @@ -191,6 +374,11 @@ static_assert(sizeof(struct kfuzztest_constraint) == 64, return; \ __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_NE) +#define KFUZZTEST_EXPECT_LT(arg_type, field, val) \ + if (arg->field >= val) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_LT) + #define KFUZZTEST_EXPECT_LE(arg_type, field, val) \ if (arg->field > val) \ return; \ @@ -201,6 +389,11 @@ static_assert(sizeof(struct kfuzztest_constraint) == 64, return; \ __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_GT) +#define KFUZZTEST_EXPECT_GE(arg_type, field, val) \ + if (arg->field < val) \ + return; \ + __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_GE) + #define KFUZZTEST_EXPECT_NOT_NULL(arg_type, field) \ KFUZZTEST_EXPECT_NE(arg_type, field, 0x0) @@ -210,19 +403,17 @@ static_assert(sizeof(struct kfuzztest_constraint) == 64, __KFUZZTEST_DEFINE_CONSTRAINT(arg_type, field, lower_bound, \ upper_bound, EXPECT_IN_RANGE) -#define KFUZZTEST_EXPECT_LEN(expected_len, actual_len) \ - if ((expected_len) != (actual_len)) \ - return; - /** * Annotations express attributes about structure fields that can't be easily - * verified at runtime, and are intended as a hint to the fuzzing engine. + * 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* could either be a raw byte buffer or a string, where - * the latter is null terminated. If a function accepts a null-terminated - * string without a length and is passed an arbitrary byte buffer, we - * may get false positive KASAN reports, for example. However, verifying that - * the char buffer is null-termined could itself trigger a memory overflow. + * 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, @@ -230,6 +421,30 @@ enum kfuzztest_annotation_attribute : uint8_t { 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; @@ -237,6 +452,13 @@ struct kfuzztest_annotation { enum kfuzztest_annotation_attribute attrib; } __attribute__((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"), \ @@ -267,124 +489,25 @@ struct kfuzztest_annotation { #define KFUZZTEST_ANNOTATE_LEN(arg_type, field, linked_field) \ __KFUZZTEST_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN) -struct reloc_entry { - uintptr_t pointer; /* Offset from the beginning of the payload. */ - uintptr_t value; /* Offset between the pointer and the pointed-to address */ -}; +#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) /* - * How many integers of padding in the relocation table between the header - * information and the relocation entries + * 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 RELOC_TABLE_PADDING 2 - -struct reloc_table { - int num_entries; - uint32_t max_alignment; - int padding[RELOC_TABLE_PADDING]; - struct reloc_entry entries[]; -}; -static_assert(offsetof(struct reloc_table, entries) % - sizeof(struct reloc_entry) == - 0); +#define __KASAN_SLAB_REDZONE 0xFC +#define __KASAN_GRANULE_SIZE 0x8 /** - * The relocation table format encodes pointer values as a relative offset from - * the location of the pointer. A relative offset of zero could indicate that - * the pointer points to its own address, which is valid. We encode a null - * pointer as 0xFF...FF as adding this value to any address would result in an - * overflow anyways, and is therefore invalid in any other circumstance. + * 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. */ -static const uintptr_t nullPtr = (uintptr_t)-1; - -__attribute__((unused)) static void *kfuzztest_parse_input(void *input, - size_t input_size) -{ - size_t i; - void *payload_start, *out; - uintptr_t *ptr_location; - size_t payload_len, alloc_size, entries_size, header_size; - struct reloc_table *rt; - struct reloc_entry re; - if (input_size > KMALLOC_MAX_SIZE) - return NULL; - - if (input_size < sizeof(struct reloc_table)) { - pr_warn("got misformed input in %s\n", __FUNCTION__); - return NULL; - } - rt = input; - - if (check_mul_overflow(rt->num_entries, sizeof(struct reloc_entry), - &entries_size)) - return NULL; - header_size = offsetof(struct reloc_table, entries) + entries_size; - if (header_size > input_size) - return NULL; - - payload_start = (char *)input + header_size; - if (payload_start >= input + input_size) - return NULL; - - if (!is_power_of_2(rt->max_alignment) || rt->max_alignment > PAGE_SIZE) - return NULL; - - payload_len = input_size - (payload_start - input); - - /* - * Check input for out-of-bounds pointers before before allocating - * aligned output buffer. - */ - for (i = 0; i < rt->num_entries; i++) { - re = rt->entries[i]; - if (re.pointer > payload_len || - re.pointer + sizeof(uintptr_t) > payload_len) - return NULL; - - if (re.value == nullPtr) - continue; - - /* We don't know the size of the data that is being pointed and - * therefor cannot make any stricter assertions. For example, - * if we enforce: - * re.pointer + re.value + sizeof(uintptr_t) < payload_len - * then we cannot write values with bytesize < 8. - */ - if (re.pointer + re.value >= payload_len) - return NULL; - } - - /* - * To guarantee correct alignment of structures within the payload, we - * allocate a new buffer that is aligned to the next power of two - * greater than either the size of the payload + 1 or the maximum - * alignment of the nested structures. We add one to the payload length - * and call kzalloc to ensure that the payload is padded by trailing - * zeros to prevent false-positives on non-null terminated strings. - */ - alloc_size = - MAX(roundup_pow_of_two(payload_len + 1), rt->max_alignment); - out = kzalloc(alloc_size, GFP_KERNEL); - if (!out) - return NULL; - - memcpy(out, payload_start, payload_len); - - /* - * Iterate through entries in the relocation table and patch the - * pointers. - */ - for (i = 0; i < rt->num_entries; i++) { - re = rt->entries[i]; - ptr_location = (uintptr_t *)(out + re.pointer); - if (re.value == nullPtr) { - *ptr_location = (uintptr_t)NULL; - } else { - *ptr_location = (uintptr_t)ptr_location + re.value; - } - } - - return out; -} +#define KFUZZTEST_POISON_SIZE __KASAN_GRANULE_SIZE #endif /* KFUZZTEST_H */ diff --git a/lib/Kconfig.kfuzztest b/lib/Kconfig.kfuzztest index 0fb000fe305c6a..7a20f5325a3d44 100644 --- a/lib/Kconfig.kfuzztest +++ b/lib/Kconfig.kfuzztest @@ -1,5 +1,17 @@ # SPDX-License-Identifier: GPL-2.0-only config KFUZZTEST - bool "Enable Kernel Fuzz Testing Framework (KFuzzTest)" - depends on DEBUG_FS + 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. diff --git a/lib/kfuzztest/Makefile b/lib/kfuzztest/Makefile index ddaab873324c2a..af7fd847b89e30 100644 --- a/lib/kfuzztest/Makefile +++ b/lib/kfuzztest/Makefile @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: GPL-2.0-only +# SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_KFUZZTEST) += kfuzztest.o -kfuzztest-objs := kfuzztest_main.o +kfuzztest-objs := kfuzztest_main.o relocations.o parse.o kfuzztest_examples.o diff --git a/lib/kfuzztest/kfuzztest_examples.c b/lib/kfuzztest/kfuzztest_examples.c new file mode 100644 index 00000000000000..6c709d25818fda --- /dev/null +++ b/lib/kfuzztest/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/kfuzztest_main.c b/lib/kfuzztest/kfuzztest_main.c index f0adc62c9882ad..fccda1319fb0d4 100644 --- a/lib/kfuzztest/kfuzztest_main.c +++ b/lib/kfuzztest/kfuzztest_main.c @@ -1,16 +1,19 @@ -/* SPDX-License-Identifier: GPL-2.0 */ +// SPDX-License-Identifier: GPL-2.0 /* - * Kernel Fuzz Testing Framework (KFuzzTest) - Core Module + * KFuzzTest core module initialization and debugfs interface. * - * This module is responsible for discovering and initializing all fuzz test - * cases defined using the FUZZ_TEST() macro. It creates a debugfs interface - * under /sys/kernel/debug/kfuzztest/ for userspace to interact with each test. + * 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[]; @@ -53,15 +56,9 @@ struct kfuzztest_state { }; /* Global static variable to hold all state for the module. */ -static struct kfuzztest_state st; +static struct kfuzztest_state state; -/** - * Default file permissions for the debugfs entries. - * 0222: World-writable for the 'input' file. - * - * XXX: should formally define what the permissions should be on these files. - */ -const umode_t kfuzztest_flags_w = 0222; +const umode_t KFUZZTEST_INPUT_PERMS = 0222; /** * kfuzztest_init - Initializes the debug filesystem for KFuzzTest. @@ -83,80 +80,80 @@ static int __init kfuzztest_init(void) num_test_cases = __kfuzztest_targets_end - __kfuzztest_targets_start; - st.debugfs_state = - kmalloc(num_test_cases * sizeof(struct kfuzztest_debugfs_state), + state.debugfs_state = + kzalloc(num_test_cases * sizeof(struct kfuzztest_debugfs_state), GFP_KERNEL); - if (!st.debugfs_state) + if (!state.debugfs_state) return -ENOMEM; - else if (IS_ERR(st.debugfs_state)) - return PTR_ERR(st.debugfs_state); /* Create the main "kfuzztest" directory in /sys/kernel/debug. */ - st.kfuzztest_dir = debugfs_create_dir("kfuzztest", NULL); - if (!st.kfuzztest_dir) { + state.kfuzztest_dir = debugfs_create_dir("kfuzztest", NULL); + if (!state.kfuzztest_dir) { pr_warn("KFuzzTest: could not create debugfs"); return -ENODEV; } - if (IS_ERR(st.kfuzztest_dir)) { - st.kfuzztest_dir = NULL; - return PTR_ERR(st.kfuzztest_dir); + 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. */ - st.debugfs_state[i].target_dir = - debugfs_create_dir(targ->name, st.kfuzztest_dir); + state.debugfs_state[i].target_dir = + debugfs_create_dir(targ->name, state.kfuzztest_dir); - if (!st.debugfs_state[i].target_dir) { + if (!state.debugfs_state[i].target_dir) { ret = -ENOMEM; goto cleanup_failure; - } else if (IS_ERR(st.debugfs_state[i].target_dir)) { - ret = PTR_ERR(st.debugfs_state[i].target_dir); + } 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. */ - st.debugfs_state[i].input_dentry.fops = + state.debugfs_state[i].input_dentry.fops = (struct file_operations){ .owner = THIS_MODULE, .write = targ->write_input_cb, }; - st.debugfs_state[i].input_dentry.dentry = debugfs_create_file( - "input", kfuzztest_flags_w, - st.debugfs_state[i].target_dir, NULL, - &st.debugfs_state[i].input_dentry.fops); - if (!st.debugfs_state[i].input_dentry.dentry) { + 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(st.debugfs_state[i].input_dentry.dentry)) { - ret = PTR_ERR(st.debugfs_state[i].input_dentry.dentry); + } 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\n", targ->name); + pr_info("KFuzzTest: registered target %s", targ->name); } return 0; cleanup_failure: - debugfs_remove_recursive(st.kfuzztest_dir); + debugfs_remove_recursive(state.kfuzztest_dir); return ret; } static void __exit kfuzztest_exit(void) { - pr_info("KFuzzTest: exiting\n"); - if (!st.kfuzztest_dir) + pr_info("KFuzzTest: exiting"); + if (!state.kfuzztest_dir) return; - debugfs_remove_recursive(st.kfuzztest_dir); - st.kfuzztest_dir = NULL; + debugfs_remove_recursive(state.kfuzztest_dir); + state.kfuzztest_dir = NULL; - if (st.debugfs_state) { - kfree(st.debugfs_state); - st.debugfs_state = NULL; + if (state.debugfs_state) { + kfree(state.debugfs_state); + state.debugfs_state = NULL; } } 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)