From: Ethan Graham <ethangraham@google.com>
Add a KFuzzTest target for the load_script function to serve as a
real-world example of the framework's usage.
The load_script function is responsible for parsing the shebang line
(`#!`) of script files. This makes it an excellent candidate for
KFuzzTest, as it involves parsing user-controlled data within the
binary loading path, which is not directly exposed as a system call.
The provided fuzz target in fs/tests/binfmt_script_kfuzz.c illustrates
how to fuzz a function that requires more involved setup - here, we only
let the fuzzer generate input for the `buf` field of struct linux_bprm,
and manually set the other fields with sensible values inside of the
FUZZ_TEST body.
To demonstrate the effectiveness of the fuzz target, a buffer overflow
bug was injected in the load_script function like so:
- buf_end = bprm->buf + sizeof(bprm->buf) - 1;
+ buf_end = bprm->buf + sizeof(bprm->buf) + 1;
Which was caught in around 40 seconds by syzkaller simultaneously
fuzzing four other targets, a realistic use case where targets are
continuously fuzzed. It also requires that the fuzzer be smart enough to
generate an input starting with `#!`.
While this bug is shallow, the fact that the bug is caught quickly and
with minimal additional code can potentially be a source of confidence
when modifying existing implementations or writing new functions.
Signed-off-by: Ethan Graham <ethangraham@google.com>
---
fs/binfmt_script.c | 8 ++++++
fs/tests/binfmt_script_kfuzz.c | 51 ++++++++++++++++++++++++++++++++++
2 files changed, 59 insertions(+)
create mode 100644 fs/tests/binfmt_script_kfuzz.c
diff --git a/fs/binfmt_script.c b/fs/binfmt_script.c
index 637daf6e4d45..c09f224d6d7e 100644
--- a/fs/binfmt_script.c
+++ b/fs/binfmt_script.c
@@ -157,3 +157,11 @@ core_initcall(init_script_binfmt);
module_exit(exit_script_binfmt);
MODULE_DESCRIPTION("Kernel support for scripts starting with #!");
MODULE_LICENSE("GPL");
+
+/*
+ * When CONFIG_KFUZZTEST is enabled, we include this _kfuzz.c file to ensure
+ * that KFuzzTest targets are built.
+ */
+#ifdef CONFIG_KFUZZTEST
+#include "tests/binfmt_script_kfuzz.c"
+#endif /* CONFIG_KFUZZTEST */
diff --git a/fs/tests/binfmt_script_kfuzz.c b/fs/tests/binfmt_script_kfuzz.c
new file mode 100644
index 000000000000..9db2fb5a7f66
--- /dev/null
+++ b/fs/tests/binfmt_script_kfuzz.c
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * binfmt_script loader KFuzzTest target
+ *
+ * Copyright 2025 Google LLC
+ */
+#include <linux/binfmts.h>
+#include <linux/kfuzztest.h>
+#include <linux/slab.h>
+#include <linux/sched/mm.h>
+
+struct load_script_arg {
+ char buf[BINPRM_BUF_SIZE];
+};
+
+FUZZ_TEST(test_load_script, struct load_script_arg)
+{
+ struct linux_binprm bprm = {};
+ char *arg_page;
+
+ arg_page = (char *)get_zeroed_page(GFP_KERNEL);
+ if (!arg_page)
+ return;
+
+ memcpy(bprm.buf, arg->buf, sizeof(bprm.buf));
+ /*
+ * `load_script` calls remove_arg_zero, which expects argc != 0. A
+ * static value of 1 is sufficient for fuzzing.
+ */
+ bprm.argc = 1;
+ bprm.p = (unsigned long)arg_page + PAGE_SIZE;
+ bprm.filename = "fuzz_script";
+ bprm.interp = bprm.filename;
+
+ bprm.mm = mm_alloc();
+ if (!bprm.mm) {
+ free_page((unsigned long)arg_page);
+ return;
+ }
+
+ /*
+ * Call the target function. We expect it to fail and return an error
+ * (e.g., at open_exec), which is fine. The goal is to survive the
+ * initial parsing logic without crashing.
+ */
+ load_script(&bprm);
+
+ if (bprm.mm)
+ mmput(bprm.mm);
+ free_page((unsigned long)arg_page);
+}
--
2.51.0.384.g4c02a37b29-goog
Hi Ethan, kernel test robot noticed the following build warnings: [auto build test WARNING on akpm-mm/mm-nonmm-unstable] [also build test WARNING on herbert-cryptodev-2.6/master herbert-crypto-2.6/master linus/master v6.17-rc6 next-20250916] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Ethan-Graham/mm-kasan-implement-kasan_poison_range/20250916-210448 base: https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm.git mm-nonmm-unstable patch link: https://lore.kernel.org/r/20250916090109.91132-10-ethan.w.s.graham%40gmail.com patch subject: [PATCH v1 09/10] fs/binfmt_script: add KFuzzTest target for load_script config: i386-randconfig-013-20250917 (https://download.01.org/0day-ci/archive/20250917/202509171240.sw10iAf6-lkp@intel.com/config) compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261) reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250917/202509171240.sw10iAf6-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202509171240.sw10iAf6-lkp@intel.com/ All warnings (new ones prefixed by >>): In file included from fs/binfmt_script.c:166: In file included from fs/tests/binfmt_script_kfuzz.c:8: >> include/linux/kfuzztest.h:135:3: warning: format specifies type 'unsigned long' but the argument has type 'int' [-Wformat] 134 | pr_info("reloc_table: { num_entries = %u, padding = %u } @ offset 0x%lx", rt->num_entries, rt->padding_size, | ~~~ | %x 135 | (char *)rt - (char *)regions); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/printk.h:585:34: note: expanded from macro 'pr_info' 585 | printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__) | ~~~ ^~~~~~~~~~~ include/linux/printk.h:512:60: note: expanded from macro 'printk' 512 | #define printk(fmt, ...) printk_index_wrap(_printk, fmt, ##__VA_ARGS__) | ~~~ ^~~~~~~~~~~ include/linux/printk.h:484:19: note: expanded from macro 'printk_index_wrap' 484 | _p_func(_fmt, ##__VA_ARGS__); \ | ~~~~ ^~~~~~~~~~~ In file included from fs/binfmt_script.c:166: In file included from fs/tests/binfmt_script_kfuzz.c:8: include/linux/kfuzztest.h:141:37: warning: format specifies type 'unsigned long' but the argument has type 'int' [-Wformat] 141 | pr_info("payload: [0x%lx, 0x%lx)", (char *)payload_start - (char *)regions, | ~~~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | %x include/linux/printk.h:585:34: note: expanded from macro 'pr_info' 585 | printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__) | ~~~ ^~~~~~~~~~~ include/linux/printk.h:512:60: note: expanded from macro 'printk' 512 | #define printk(fmt, ...) printk_index_wrap(_printk, fmt, ##__VA_ARGS__) | ~~~ ^~~~~~~~~~~ include/linux/printk.h:484:19: note: expanded from macro 'printk_index_wrap' 484 | _p_func(_fmt, ##__VA_ARGS__); \ | ~~~~ ^~~~~~~~~~~ In file included from fs/binfmt_script.c:166: In file included from fs/tests/binfmt_script_kfuzz.c:8: include/linux/kfuzztest.h:142:3: warning: format specifies type 'unsigned long' but the argument has type 'int' [-Wformat] 141 | pr_info("payload: [0x%lx, 0x%lx)", (char *)payload_start - (char *)regions, | ~~~ | %x 142 | (char *)payload_end - (char *)regions); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/printk.h:585:34: note: expanded from macro 'pr_info' 585 | printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__) | ~~~ ^~~~~~~~~~~ include/linux/printk.h:512:60: note: expanded from macro 'printk' 512 | #define printk(fmt, ...) printk_index_wrap(_printk, fmt, ##__VA_ARGS__) | ~~~ ^~~~~~~~~~~ include/linux/printk.h:484:19: note: expanded from macro 'printk_index_wrap' 484 | _p_func(_fmt, ##__VA_ARGS__); \ | ~~~~ ^~~~~~~~~~~ 3 warnings generated. vim +135 include/linux/kfuzztest.h 6f8f11abdf5c57 Ethan Graham 2025-09-16 117 6f8f11abdf5c57 Ethan Graham 2025-09-16 118 /* 6f8f11abdf5c57 Ethan Graham 2025-09-16 119 * Dump some information on the parsed headers and payload. Can be useful for 6f8f11abdf5c57 Ethan Graham 2025-09-16 120 * debugging inputs when writing an encoder for the KFuzzTest input format. 6f8f11abdf5c57 Ethan Graham 2025-09-16 121 */ 6f8f11abdf5c57 Ethan Graham 2025-09-16 122 __attribute__((unused)) static inline void kfuzztest_debug_header(struct reloc_region_array *regions, 6f8f11abdf5c57 Ethan Graham 2025-09-16 123 struct reloc_table *rt, void *payload_start, 6f8f11abdf5c57 Ethan Graham 2025-09-16 124 void *payload_end) 6f8f11abdf5c57 Ethan Graham 2025-09-16 125 { 6f8f11abdf5c57 Ethan Graham 2025-09-16 126 uint32_t i; 6f8f11abdf5c57 Ethan Graham 2025-09-16 127 6f8f11abdf5c57 Ethan Graham 2025-09-16 128 pr_info("regions: { num_regions = %u } @ %px", regions->num_regions, regions); 6f8f11abdf5c57 Ethan Graham 2025-09-16 129 for (i = 0; i < regions->num_regions; i++) { 6f8f11abdf5c57 Ethan Graham 2025-09-16 130 pr_info(" region_%u: { start: 0x%x, size: 0x%x }", i, regions->regions[i].offset, 6f8f11abdf5c57 Ethan Graham 2025-09-16 131 regions->regions[i].size); 6f8f11abdf5c57 Ethan Graham 2025-09-16 132 } 6f8f11abdf5c57 Ethan Graham 2025-09-16 133 6f8f11abdf5c57 Ethan Graham 2025-09-16 134 pr_info("reloc_table: { num_entries = %u, padding = %u } @ offset 0x%lx", rt->num_entries, rt->padding_size, 6f8f11abdf5c57 Ethan Graham 2025-09-16 @135 (char *)rt - (char *)regions); 6f8f11abdf5c57 Ethan Graham 2025-09-16 136 for (i = 0; i < rt->num_entries; i++) { 6f8f11abdf5c57 Ethan Graham 2025-09-16 137 pr_info(" reloc_%u: { src: %u, offset: 0x%x, dst: %u }", i, rt->entries[i].region_id, 6f8f11abdf5c57 Ethan Graham 2025-09-16 138 rt->entries[i].region_offset, rt->entries[i].value); 6f8f11abdf5c57 Ethan Graham 2025-09-16 139 } 6f8f11abdf5c57 Ethan Graham 2025-09-16 140 6f8f11abdf5c57 Ethan Graham 2025-09-16 141 pr_info("payload: [0x%lx, 0x%lx)", (char *)payload_start - (char *)regions, 6f8f11abdf5c57 Ethan Graham 2025-09-16 142 (char *)payload_end - (char *)regions); 6f8f11abdf5c57 Ethan Graham 2025-09-16 143 } 6f8f11abdf5c57 Ethan Graham 2025-09-16 144 -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
© 2016 - 2025 Red Hat, Inc.