From 0e9f090a4e9bfae5a190ccf89eab3bf14f6b0f96 Mon Sep 17 00:00:00 2001 From: Stanislaw Gruszka Date: Fri, 27 Mar 2026 12:00:04 +0100 Subject: [PATCH 01/17] module/kallsyms: fix nextval for data symbol lookup The symbol lookup code assumes the queried address resides in either MOD_TEXT or MOD_INIT_TEXT. This breaks for addresses in other module memory regions (e.g. rodata or data), resulting in incorrect upper bounds and wrong symbol size. Select the module memory region the address belongs to instead of hardcoding text sections. Also initialize the lower bound to the start of that region, as searching from address 0 is unnecessary. Cc: stable@vger.kernel.org Signed-off-by: Stanislaw Gruszka Reviewed-by: Petr Pavlu Signed-off-by: Petr Pavlu --- kernel/module/kallsyms.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/kernel/module/kallsyms.c b/kernel/module/kallsyms.c index 0fc11e45df9b..f23126d804b2 100644 --- a/kernel/module/kallsyms.c +++ b/kernel/module/kallsyms.c @@ -258,17 +258,25 @@ static const char *find_kallsyms_symbol(struct module *mod, unsigned int i, best = 0; unsigned long nextval, bestval; struct mod_kallsyms *kallsyms = rcu_dereference(mod->kallsyms); - struct module_memory *mod_mem; + struct module_memory *mod_mem = NULL; - /* At worse, next value is at end of module */ - if (within_module_init(addr, mod)) - mod_mem = &mod->mem[MOD_INIT_TEXT]; - else - mod_mem = &mod->mem[MOD_TEXT]; + for_each_mod_mem_type(type) { +#ifndef CONFIG_KALLSYMS_ALL + if (!mod_mem_type_is_text(type)) + continue; +#endif + if (within_module_mem_type(addr, mod, type)) { + mod_mem = &mod->mem[type]; + break; + } + } + if (!mod_mem) + return NULL; + + /* Initialize bounds within memory region the address belongs to. */ nextval = (unsigned long)mod_mem->base + mod_mem->size; - - bestval = kallsyms_symbol_value(&kallsyms->symtab[best]); + bestval = (unsigned long)mod_mem->base - 1; /* * Scan for closest preceding symbol, and next symbol. (ELF From df8de94eb3dce16ac9b7b2696efe9b11aed4f3a5 Mon Sep 17 00:00:00 2001 From: Zenghui Yu Date: Sun, 21 Jun 2026 16:22:23 +0800 Subject: [PATCH 02/17] params: fix path of /sys/module/XYZ/parameters/ in comment The comment wrongly references to /sys/modules/XYZ/parameters/ directory instead of /sys/module/XYZ/parameters/. Fix it. Signed-off-by: Zenghui Yu Reviewed-by: Aaron Tomlin Acked-by: Randy Dunlap Signed-off-by: Petr Pavlu --- kernel/params.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/params.c b/kernel/params.c index a668863a4bb6..3456b104efc9 100644 --- a/kernel/params.c +++ b/kernel/params.c @@ -538,7 +538,7 @@ const struct kernel_param_ops param_ops_string = { }; EXPORT_SYMBOL(param_ops_string); -/* sysfs output in /sys/modules/XYZ/parameters/ */ +/* sysfs output in /sys/module/XYZ/parameters/ */ #define to_module_attr(n) container_of_const(n, struct module_attribute, attr) #define to_module_kobject(n) container_of(n, struct module_kobject, kobj) From 93c29ebd1622fb0670701e1c1b3a978a5cac08b7 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Chaudhary Date: Thu, 4 Jun 2026 23:15:02 +0530 Subject: [PATCH 03/17] module: use strscpy() to copy module names in stats and dup tracking Both try_add_failed_module() and kmod_dup_request_exists_wait() use memcpy() with strlen() to copy module names into fixed-size char[MODULE_NAME_LEN] buffers. Neither performs a bounds check on the copy. Current callers always pass names originating from mod->name (itself char[MODULE_NAME_LEN]), so this is not exploitable today. However both functions accept a plain const char * with no documented length contract, making them latent buffer overflows if a future caller passes a longer string. Replace memcpy() with strscpy() in both sites, which bounds the copy to MODULE_NAME_LEN and always NUL-terminates. Signed-off-by: Naveen Kumar Chaudhary Reviewed-by: Petr Pavlu Signed-off-by: Petr Pavlu --- kernel/module/dups.c | 2 +- kernel/module/stats.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/module/dups.c b/kernel/module/dups.c index 1d720a5311ba..8b6f8f177de4 100644 --- a/kernel/module/dups.c +++ b/kernel/module/dups.c @@ -129,7 +129,7 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret) if (!new_kmod_req) return false; - memcpy(new_kmod_req->name, module_name, strlen(module_name)); + strscpy(new_kmod_req->name, module_name); INIT_WORK(&new_kmod_req->complete_work, kmod_dup_request_complete); INIT_DELAYED_WORK(&new_kmod_req->delete_work, kmod_dup_request_delete); init_completion(&new_kmod_req->first_req_done); diff --git a/kernel/module/stats.c b/kernel/module/stats.c index 3a9672f93a8e..78b59bc05387 100644 --- a/kernel/module/stats.c +++ b/kernel/module/stats.c @@ -253,7 +253,7 @@ int try_add_failed_module(const char *name, enum fail_dup_mod_reason reason) mod_fail = kzalloc_obj(*mod_fail); if (!mod_fail) return -ENOMEM; - memcpy(mod_fail->name, name, strlen(name)); + strscpy(mod_fail->name, name); __set_bit(reason, &mod_fail->dup_fail_mask); atomic_long_inc(&mod_fail->count); list_add_rcu(&mod_fail->list, &dup_failed_modules); From a3126c746ddfe1cc3f6eee70475a33906e173ece Mon Sep 17 00:00:00 2001 From: Naveen Kumar Chaudhary Date: Sun, 7 Jun 2026 09:48:58 +0530 Subject: [PATCH 04/17] module: procfs: use matching type for accumulator in module_total_size() module_total_size() returns unsigned int but uses a signed int accumulator. While the result is numerically correct, the type mismatch is misleading. Change the accumulator to unsigned int to match the return type. Signed-off-by: Naveen Kumar Chaudhary Reviewed-by: Sami Tolvanen [ppavlu: correct the commit title] Signed-off-by: Petr Pavlu --- kernel/module/procfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/module/procfs.c b/kernel/module/procfs.c index 0a4841e88adb..90712aa9dd13 100644 --- a/kernel/module/procfs.c +++ b/kernel/module/procfs.c @@ -64,7 +64,7 @@ static void m_stop(struct seq_file *m, void *p) static unsigned int module_total_size(struct module *mod) { - int size = 0; + unsigned int size = 0; for_each_mod_mem_type(type) size += mod->mem[type].size; From a347812c2ef0fafef587d2580be145d8101a0137 Mon Sep 17 00:00:00 2001 From: Petr Pavlu Date: Fri, 12 Jun 2026 15:38:28 +0200 Subject: [PATCH 05/17] module: Remove unused DISCARD_EH_FRAME definition from module.lds.S The linker script scripts/module.lds.S contains an unused DISCARD_EH_FRAME definition introduced by commit 68c76ad4a957 ("arm64: unwind: add asynchronous unwind tables to kernel and modules"). As shown in an earlier version of that patch [1], DISCARD_EH_FRAME was meant to be used by SANITIZER_DISCARDS in the same file, as follows: -# define SANITIZER_DISCARDS *(.eh_frame) +# define SANITIZER_DISCARDS DISCARD_EH_FRAME However, in the meantime, SANITIZER_DISCARDS was removed entirely from module.lds.S by commit 89245600941e ("cfi: Switch to -fsanitize=kcfi"). Eventually, the mentioned commit 68c76ad4a957 only added the new DISCARD_EH_FRAME definition to this file without actually using it. The file include/asm-generic/vmlinux.lds.h contains a similar DISCARD_EH_FRAME definition for vmlinux to discard .eh_frame sections that may be present when CONFIG_GCOV_KERNEL, CONFIG_KASAN_GENERIC or CONFIG_KCSAN is enabled. Testing these options on arm64 with LLVM 19.1 did not show any unexpected .eh_frame sections in modules. Remove the unused DISCARD_EH_FRAME definition from scripts/module.lds.S. Link: https://lore.kernel.org/linux-arm-kernel/20220701152724.3343599-2-ardb@kernel.org/ [1] Reviewed-by: Sami Tolvanen Signed-off-by: Petr Pavlu --- scripts/module.lds.S | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/module.lds.S b/scripts/module.lds.S index b62683061d79..d0f200428957 100644 --- a/scripts/module.lds.S +++ b/scripts/module.lds.S @@ -3,11 +3,6 @@ * Archs are free to supply their own linker scripts. ld will * combine them automatically. */ -#ifdef CONFIG_UNWIND_TABLES -#define DISCARD_EH_FRAME -#else -#define DISCARD_EH_FRAME *(.eh_frame) -#endif #include From 391ef7a88e8fef92ec472db14422468be477f0bb Mon Sep 17 00:00:00 2001 From: Petr Pavlu Date: Thu, 4 Jun 2026 13:04:45 +0200 Subject: [PATCH 06/17] xtensa/simdisk: Avoid referring to module::args When simdisk support is built as a loadable module, simdisk_param_set_filename() receives a pointer into module::args and stores each filename pointer as is. In preparation for removing module::args, update the simdisk.filename parameter code to copy the provided string. This is somewhat complicated by the fact that simdisk support can also be built-in, in which case the parameters are parsed during early boot before slab is available. In that case, the command line itself is preserved for the lifetime of the kernel, so continue storing the incoming pointer directly. Reviewed-by: Max Filippov Signed-off-by: Petr Pavlu --- arch/xtensa/platforms/iss/simdisk.c | 38 +++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/arch/xtensa/platforms/iss/simdisk.c b/arch/xtensa/platforms/iss/simdisk.c index 7c7a2aa749f8..a2ddb49c269d 100644 --- a/arch/xtensa/platforms/iss/simdisk.c +++ b/arch/xtensa/platforms/iss/simdisk.c @@ -41,7 +41,7 @@ module_param(simdisk_count, int, S_IRUGO); MODULE_PARM_DESC(simdisk_count, "Number of simdisk units."); static int n_files; -static const char *filename[MAX_SIMDISK_COUNT] = { +static char *filename[MAX_SIMDISK_COUNT] = { #ifdef CONFIG_SIMDISK0_FILENAME CONFIG_SIMDISK0_FILENAME, #ifdef CONFIG_SIMDISK1_FILENAME @@ -50,20 +50,48 @@ static const char *filename[MAX_SIMDISK_COUNT] = { #endif }; +/* + * The simdisk code can be built either into the kernel or as a loadable module. + * When built-in, CONFIG_SIMDISK{0,1}_FILENAME can be used to specify the + * initial simdisk filenames and additional filenames can be provided on the + * kernel command line. These arguments are parsed during early boot when slab + * is not yet available, but the command line itself is preserved for the + * lifetime of the kernel, so the incoming pointer is stored directly. + * When built as a loadable module, each value is copied with kstrdup() and all + * allocated memory is freed in simdisk_param_free_filename() when the module is + * unloaded. + */ static int simdisk_param_set_filename(const char *val, const struct kernel_param *kp) { - if (n_files < ARRAY_SIZE(filename)) - filename[n_files++] = val; - else + char *str; + + if (n_files >= ARRAY_SIZE(filename)) return -EINVAL; + +#ifdef MODULE + str = kstrdup(val, GFP_KERNEL); + if (!str) + return -ENOMEM; +#else + str = (char *)val; +#endif + + filename[n_files++] = str; return 0; } +static void simdisk_param_free_filename(void *arg) +{ + for (int i = 0; i < n_files; i++) + kfree(filename[i]); +} + static const struct kernel_param_ops simdisk_param_ops_filename = { .set = simdisk_param_set_filename, + .free = simdisk_param_free_filename, }; -module_param_cb(filename, &simdisk_param_ops_filename, &n_files, 0); +module_param_cb(filename, &simdisk_param_ops_filename, NULL, 0); MODULE_PARM_DESC(filename, "Backing storage filename."); static int simdisk_major = SIMDISK_MAJOR; From 6b722d1ec0384d2479ae453c994c6546c6093687 Mon Sep 17 00:00:00 2001 From: Petr Pavlu Date: Thu, 4 Jun 2026 13:04:46 +0200 Subject: [PATCH 07/17] module: Remove unnecessary module::args Historically, various parameter-handling code kept pointers into module::args, most notably the charp support. However, in 2009, commit e180a6b7759a ("param: fix charp parameters set via sysfs") changed charp parameters to kstrdup() the input string as well. As a result, module::args now mostly wastes memory. The last users that still pointed into module::args have now been cleaned up, so remove this data. Reviewed-by: Aaron Tomlin Signed-off-by: Petr Pavlu --- include/linux/module.h | 4 ---- kernel/module/main.c | 15 ++++++++------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/include/linux/module.h b/include/linux/module.h index 7566815fabbe..96cc98568eea 100644 --- a/include/linux/module.h +++ b/include/linux/module.h @@ -477,10 +477,6 @@ struct module { struct module_notes_attrs *notes_attrs; #endif - /* The command line arguments (may be mangled). People like - keeping pointers to this stuff */ - char *args; - #ifdef CONFIG_SMP /* Per-cpu data. */ void __percpu *percpu; diff --git a/kernel/module/main.c b/kernel/module/main.c index 46dd8d25a605..528690ba160b 100644 --- a/kernel/module/main.c +++ b/kernel/module/main.c @@ -1458,7 +1458,6 @@ static void free_module(struct module *mod) /* This may be empty, but that's OK */ module_arch_freeing_init(mod); - kfree(mod->args); percpu_modfree(mod); free_mod_mem(mod); @@ -3425,7 +3424,7 @@ static int load_module(struct load_info *info, const char __user *uargs, struct module *mod; bool module_allocated = false; long err = 0; - char *after_dashes; + char *args = NULL, *after_dashes; /* * Do the signature check (if any) first. All that @@ -3523,9 +3522,9 @@ static int load_module(struct load_info *info, const char __user *uargs, flush_module_icache(mod); /* Now copy in args */ - mod->args = strndup_user(uargs, ~0UL >> 1); - if (IS_ERR(mod->args)) { - err = PTR_ERR(mod->args); + args = strndup_user(uargs, ~0UL >> 1); + if (IS_ERR(args)) { + err = PTR_ERR(args); goto free_arch_cleanup; } @@ -3546,7 +3545,7 @@ static int load_module(struct load_info *info, const char __user *uargs, mod->async_probe_requested = async_probe; /* Module is ready to execute: parsing args may do that. */ - after_dashes = parse_args(mod->name, mod->args, mod->kp, mod->num_kp, + after_dashes = parse_args(mod->name, args, mod->kp, mod->num_kp, -32768, 32767, mod, unknown_module_param_cb); if (IS_ERR(after_dashes)) { @@ -3556,6 +3555,8 @@ static int load_module(struct load_info *info, const char __user *uargs, pr_warn("%s: parameters '%s' after `--' ignored\n", mod->name, after_dashes); } + kfree(args); + args = NULL; /* Link in to sysfs. */ err = mod_sysfs_setup(mod, info, mod->kp, mod->num_kp); @@ -3597,7 +3598,7 @@ static int load_module(struct load_info *info, const char __user *uargs, ddebug_cleanup: ftrace_release_mod(mod); synchronize_rcu(); - kfree(mod->args); + kfree(args); free_arch_cleanup: module_arch_cleanup(mod); free_modinfo: From 327d44754c54060baf9679ac073a21839912be16 Mon Sep 17 00:00:00 2001 From: Andreas Hindborg Date: Mon, 1 Jun 2026 12:17:05 +0200 Subject: [PATCH 08/17] rust: module_param: return value by copy from `value` For `Copy` parameter types it is more ergonomic to retrieve the parameter value by copy than through a shared reference. Change `ModuleParamAccess::value` to return `T` by copy when `T: Copy`, and rename the previous reference-returning accessor to `value_ref`. Update the in-tree caller in `rust_minimal`. Suggested-by: Alice Ryhl Signed-off-by: Andreas Hindborg Reviewed-by: Petr Pavlu Reviewed-by: Gary Guo Signed-off-by: Petr Pavlu --- rust/kernel/module_param.rs | 18 +++++++++++++++++- samples/rust/rust_minimal.rs | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/rust/kernel/module_param.rs b/rust/kernel/module_param.rs index 6541af218390..a1724c890566 100644 --- a/rust/kernel/module_param.rs +++ b/rust/kernel/module_param.rs @@ -130,10 +130,26 @@ pub const fn new(default: T) -> Self { } } + /// Get a copy of the parameter value. + /// + /// Returns the value supplied at module load time, or the default value + /// if the parameter has not been set. + #[inline] + pub fn value(&self) -> T + where + T: Copy, + { + self.value.copy().unwrap_or(self.default) + } + /// Get a shared reference to the parameter value. + /// + /// Returns a reference to the value supplied at module load time, or a + /// reference to the default value if the parameter has not been set. // Note: When sysfs access to parameters are enabled, we have to pass in a // held lock guard here. - pub fn value(&self) -> &T { + #[inline] + pub fn value_ref(&self) -> &T { self.value.as_ref().unwrap_or(&self.default) } diff --git a/samples/rust/rust_minimal.rs b/samples/rust/rust_minimal.rs index 8eb9583571d7..60d03df6cd80 100644 --- a/samples/rust/rust_minimal.rs +++ b/samples/rust/rust_minimal.rs @@ -28,7 +28,7 @@ fn init(_module: &'static ThisModule) -> Result { pr_info!("Am I built-in? {}\n", !cfg!(MODULE)); pr_info!( "test_parameter: {}\n", - *module_parameters::test_parameter.value() + module_parameters::test_parameter.value() ); let mut numbers = KVec::new(); From da991304c9afc0f1567033ac743ce27dcc947652 Mon Sep 17 00:00:00 2001 From: Wenzhao Liao Date: Sat, 11 Apr 2026 09:02:54 -0400 Subject: [PATCH 09/17] rust: module_param: support bool parameters Add support for parsing boolean module parameters in the Rust module! macro. Currently, only integer types are supported by the `module_param!` macros. This patch implements the `ModuleParam` trait for `bool` by delegating the string parsing to the existing C implementation via `kstrtobool_bytes()`. It also wires up `PARAM_OPS_BOOL` so that the Rust parameter system correctly links to the C `param_ops_bool` structure. For demonstration and verification, a boolean parameter is added to `samples/rust/rust_minimal.rs`. Support for boolean parameters will initially be used by the Rust null block driver [1]. Link: https://lore.kernel.org/all/20260609-rnull-v6-19-rc5-send-v2-4-82c7404542e2@kernel.org/ [1] Assisted-by: Codex:GPT-5 Signed-off-by: Wenzhao Liao Tested-by: Andreas Hindborg Reviewed-by: Andreas Hindborg Link: https://lore.kernel.org/linux-modules/20260411130254.3510128-1-wenzhaoliao@ruc.edu.cn/ [ppavlu: add motivation to the commit message and rebase the patch] Signed-off-by: Petr Pavlu --- rust/kernel/module_param.rs | 9 ++++++++- rust/macros/lib.rs | 1 + rust/macros/module.rs | 1 + samples/rust/rust_minimal.rs | 8 ++++++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/rust/kernel/module_param.rs b/rust/kernel/module_param.rs index a1724c890566..f9a14765a926 100644 --- a/rust/kernel/module_param.rs +++ b/rust/kernel/module_param.rs @@ -5,7 +5,7 @@ //! C header: [`include/linux/moduleparam.h`](srctree/include/linux/moduleparam.h) use crate::prelude::*; -use crate::str::BStr; +use crate::str::{kstrtobool_bytes, BStr}; use bindings; use kernel::sync::SetOnce; @@ -105,6 +105,12 @@ fn try_from_param_arg(arg: &BStr) -> Result { impl_int_module_param!(isize); impl_int_module_param!(usize); +impl ModuleParam for bool { + fn try_from_param_arg(arg: &BStr) -> Result { + kstrtobool_bytes(arg) + } +} + /// A wrapper for kernel parameters. /// /// This type is instantiated by the [`module!`] macro when module parameters are @@ -195,3 +201,4 @@ macro_rules! make_param_ops { make_param_ops!(PARAM_OPS_U64, u64); make_param_ops!(PARAM_OPS_ISIZE, isize); make_param_ops!(PARAM_OPS_USIZE, usize); +make_param_ops!(PARAM_OPS_BOOL, bool); diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs index 4a48fabbc268..5cff7fa4f111 100644 --- a/rust/macros/lib.rs +++ b/rust/macros/lib.rs @@ -56,6 +56,7 @@ /// - [`u64`] /// - [`isize`] /// - [`usize`] +/// - [`bool`] /// /// C header: [`include/linux/moduleparam.h`](srctree/include/linux/moduleparam.h) /// diff --git a/rust/macros/module.rs b/rust/macros/module.rs index 06c18e207508..025323fb030d 100644 --- a/rust/macros/module.rs +++ b/rust/macros/module.rs @@ -192,6 +192,7 @@ fn param_ops_path(param_type: &str) -> Path { "u64" => parse_quote!(::kernel::module_param::PARAM_OPS_U64), "isize" => parse_quote!(::kernel::module_param::PARAM_OPS_ISIZE), "usize" => parse_quote!(::kernel::module_param::PARAM_OPS_USIZE), + "bool" => parse_quote!(::kernel::module_param::PARAM_OPS_BOOL), t => panic!("Unsupported parameter type {}", t), } } diff --git a/samples/rust/rust_minimal.rs b/samples/rust/rust_minimal.rs index 60d03df6cd80..8c910d314dfa 100644 --- a/samples/rust/rust_minimal.rs +++ b/samples/rust/rust_minimal.rs @@ -15,6 +15,10 @@ default: 1, description: "This parameter has a default of 1", }, + test_bool_parameter: bool { + default: false, + description: "This boolean parameter defaults to false", + }, }, } @@ -30,6 +34,10 @@ fn init(_module: &'static ThisModule) -> Result { "test_parameter: {}\n", module_parameters::test_parameter.value() ); + pr_info!( + "test_bool_parameter: {}\n", + module_parameters::test_bool_parameter.value() + ); let mut numbers = KVec::new(); numbers.push(72, GFP_KERNEL)?; From d258ed8a86bb46bbbbc84fb914478259a1e694a4 Mon Sep 17 00:00:00 2001 From: Petr Pavlu Date: Thu, 6 Aug 2026 11:10:15 +0200 Subject: [PATCH 10/17] module/dups: Inform duplicate requests about the result directly When kmod_dup_request_announce() announces the completion of a request_module() call to duplicate waiters, it queues a work item to invoke kmod_dup_request_complete(), and only that function calls complete_all(). This adds an arbitrary delay that is unnecessary and provides little benefit. Call complete_all() directly from kmod_dup_request_announce() instead. Signed-off-by: Petr Pavlu --- kernel/module/dups.c | 43 +++++++++---------------------------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/kernel/module/dups.c b/kernel/module/dups.c index 8b6f8f177de4..45080f451e5c 100644 --- a/kernel/module/dups.c +++ b/kernel/module/dups.c @@ -48,7 +48,6 @@ struct kmod_dup_req { struct list_head list; char name[MODULE_NAME_LEN]; struct completion first_req_done; - struct work_struct complete_work; struct delayed_work delete_work; int dup_ret; }; @@ -93,29 +92,6 @@ static void kmod_dup_request_delete(struct work_struct *work) kfree(kmod_req); } -static void kmod_dup_request_complete(struct work_struct *work) -{ - struct kmod_dup_req *kmod_req; - - kmod_req = container_of(work, struct kmod_dup_req, complete_work); - - /* - * This will ensure that the kernel will let all the waiters get - * informed its time to check the return value. It's time to - * go home. - */ - complete_all(&kmod_req->first_req_done); - - /* - * Now that we have allowed prior request_module() calls to go on - * with life, let's schedule deleting this entry. We don't have - * to do it right away, but we *eventually* want to do it so to not - * let this linger forever as this is just a boot optimization for - * possible abuses of vmalloc() incurred by finit_module() thrashing. - */ - queue_delayed_work(system_dfl_wq, &kmod_req->delete_work, 60 * HZ); -} - bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret) { struct kmod_dup_req *kmod_req, *new_kmod_req; @@ -130,7 +106,6 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret) return false; strscpy(new_kmod_req->name, module_name); - INIT_WORK(&new_kmod_req->complete_work, kmod_dup_request_complete); INIT_DELAYED_WORK(&new_kmod_req->delete_work, kmod_dup_request_delete); init_completion(&new_kmod_req->first_req_done); @@ -230,17 +205,17 @@ void kmod_dup_request_announce(char *module_name, int ret) kmod_req->dup_ret = ret; + /* Inform all duplicate waiters to check the return value. */ + complete_all(&kmod_req->first_req_done); + /* - * If we complete() here we may allow duplicate threads - * to continue before the first one that submitted the - * request. We're in no rush also, given that each and - * every bounce back to userspace is slow we avoid that - * with a slight delay here. So queueue up the completion - * and let duplicates suffer, just wait a tad bit longer. - * There is no rush. But we also don't want to hold the - * caller up forever or introduce any boot delays. + * Now that we have allowed prior request_module() calls to go on + * with life, let's schedule deleting this entry. We don't have + * to do it right away, but we *eventually* want to do it so to not + * let this linger forever as this is just a boot optimization for + * possible abuses of vmalloc() incurred by finit_module() thrashing. */ - queue_work(system_dfl_wq, &kmod_req->complete_work); + queue_delayed_work(system_dfl_wq, &kmod_req->delete_work, 60 * HZ); out: mutex_unlock(&kmod_dup_mutex); From 5eecb11b543f9f417bcf0dea239ff99c6af65dbd Mon Sep 17 00:00:00 2001 From: Petr Pavlu Date: Thu, 6 Aug 2026 11:10:16 +0200 Subject: [PATCH 11/17] module/dups: Fix use-after-free in kmod_dup_req lifetime handling The kmod dups code uses RCU to ensure that a kmod_dup_req instance is freed only after it is no longer referenced. When releasing an instance, the kmod_dup_request_delete() function removes the kmod_dup_req from the dup_kmod_reqs list, waits via synchronize_rcu() and finally frees it. However, this doesn't work correctly because parallel users referencing the instance in kmod_dup_request_exists_wait() don't enter an RCU read-side critical section. This can result in a use-after-free. The kmod_dup_request_exists_wait() function may need to hold a valid reference to a kmod_dup_req instance across a blocking wait until the corresponding modprobe command completes. This makes it unsuitable for RCU. Fix the issue by changing the lifecycle management of kmod_dup_req to use reference counting. Fixes: 8660484ed1cf ("module: add debugging auto-load duplicate module support") Reviewed-by: Aaron Tomlin Signed-off-by: Petr Pavlu --- kernel/module/dups.c | 56 +++++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/kernel/module/dups.c b/kernel/module/dups.c index 45080f451e5c..db7377229703 100644 --- a/kernel/module/dups.c +++ b/kernel/module/dups.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "internal.h" @@ -38,13 +39,12 @@ static bool enable_dups_trace = IS_ENABLED(CONFIG_MODULE_DEBUG_AUTOLOAD_DUPS_TRACE); module_param(enable_dups_trace, bool_enable_only, 0644); -/* - * Protects dup_kmod_reqs list, adds / removals with RCU. - */ +/* A mutex-protected list of active kmod requests. */ static DEFINE_MUTEX(kmod_dup_mutex); static LIST_HEAD(dup_kmod_reqs); struct kmod_dup_req { + refcount_t refcount; struct list_head list; char name[MODULE_NAME_LEN]; struct completion first_req_done; @@ -52,12 +52,24 @@ struct kmod_dup_req { int dup_ret; }; +static void get_kmod_req(struct kmod_dup_req *kmod_req) +{ + refcount_inc(&kmod_req->refcount); +} + +static void put_kmod_req(struct kmod_dup_req *kmod_req) +{ + if (refcount_dec_and_test(&kmod_req->refcount)) + kfree(kmod_req); +} + static struct kmod_dup_req *kmod_dup_request_lookup(char *module_name) { struct kmod_dup_req *kmod_req; - list_for_each_entry_rcu(kmod_req, &dup_kmod_reqs, list, - lockdep_is_held(&kmod_dup_mutex)) { + lockdep_assert_held(&kmod_dup_mutex); + + list_for_each_entry(kmod_req, &dup_kmod_reqs, list) { if (strlen(kmod_req->name) == strlen(module_name) && !memcmp(kmod_req->name, module_name, strlen(module_name))) { return kmod_req; @@ -86,10 +98,10 @@ static void kmod_dup_request_delete(struct work_struct *work) * just returning 0. */ mutex_lock(&kmod_dup_mutex); - list_del_rcu(&kmod_req->list); - synchronize_rcu(); + list_del(&kmod_req->list); mutex_unlock(&kmod_dup_mutex); - kfree(kmod_req); + + put_kmod_req(kmod_req); } bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret) @@ -105,6 +117,7 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret) if (!new_kmod_req) return false; + refcount_set(&new_kmod_req->refcount, 1); strscpy(new_kmod_req->name, module_name); INIT_DELAYED_WORK(&new_kmod_req->delete_work, kmod_dup_request_delete); init_completion(&new_kmod_req->first_req_done); @@ -136,10 +149,12 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret) * keep tab on duplicates later. */ pr_debug("New request_module() for %s\n", module_name); - list_add_rcu(&new_kmod_req->list, &dup_kmod_reqs); + list_add(&new_kmod_req->list, &dup_kmod_reqs); mutex_unlock(&kmod_dup_mutex); return false; } + + get_kmod_req(kmod_req); mutex_unlock(&kmod_dup_mutex); /* We are dealing with a duplicate request now */ @@ -169,7 +184,7 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret) * calls bail out right away. */ *dup_ret = 0; - return true; + goto out; } /* @@ -184,12 +199,14 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret) TASK_KILLABLE); if (ret) { *dup_ret = ret; - return true; + goto out; } /* Now the duplicate request has the same exact return value as the first request */ *dup_ret = kmod_req->dup_ret; +out: + put_kmod_req(kmod_req); return true; } @@ -199,15 +216,25 @@ void kmod_dup_request_announce(char *module_name, int ret) mutex_lock(&kmod_dup_mutex); + /* + * Look for a kmod_dup_req previously added in + * kmod_dup_request_exists_wait(). Note that a request_module_nowait() + * without its own kmod_dup_req entry can announce a result of + * a concurrent request_module() call. + */ kmod_req = kmod_dup_request_lookup(module_name); - if (!kmod_req) - goto out; + if (!kmod_req || completion_done(&kmod_req->first_req_done)) { + mutex_unlock(&kmod_dup_mutex); + return; + } kmod_req->dup_ret = ret; /* Inform all duplicate waiters to check the return value. */ complete_all(&kmod_req->first_req_done); + mutex_unlock(&kmod_dup_mutex); + /* * Now that we have allowed prior request_module() calls to go on * with life, let's schedule deleting this entry. We don't have @@ -216,7 +243,4 @@ void kmod_dup_request_announce(char *module_name, int ret) * possible abuses of vmalloc() incurred by finit_module() thrashing. */ queue_delayed_work(system_dfl_wq, &kmod_req->delete_work, 60 * HZ); - -out: - mutex_unlock(&kmod_dup_mutex); } From 96e6f752fc25af366de2b3f8aa09c93963d00d2a Mon Sep 17 00:00:00 2001 From: Petr Pavlu Date: Thu, 6 Aug 2026 11:10:17 +0200 Subject: [PATCH 12/17] module/dups: Avoid unnecessary kmod_dup_req allocations The kmod dups code preallocates kmod_dup_req before taking kmod_dup_mutex to avoid allocating memory while holding the lock. This provides little benefit, since the allocation is fast and can safely be done under the lock. On the other hand, it leads to unnecessary allocations when the request turns out to be a duplicate and slightly complicates the code. Allocate kmod_dup_req only when needed and introduce a helper function alloc_kmod_req() to initialize the structure. Reviewed-by: Aaron Tomlin Signed-off-by: Petr Pavlu --- kernel/module/dups.c | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/kernel/module/dups.c b/kernel/module/dups.c index db7377229703..276016051ad2 100644 --- a/kernel/module/dups.c +++ b/kernel/module/dups.c @@ -104,28 +104,31 @@ static void kmod_dup_request_delete(struct work_struct *work) put_kmod_req(kmod_req); } +static struct kmod_dup_req *alloc_kmod_req(const char *module_name) +{ + struct kmod_dup_req *kmod_req = kzalloc_obj(*kmod_req); + + if (!kmod_req) + return NULL; + + refcount_set(&kmod_req->refcount, 1); + strscpy(kmod_req->name, module_name); + INIT_DELAYED_WORK(&kmod_req->delete_work, kmod_dup_request_delete); + init_completion(&kmod_req->first_req_done); + return kmod_req; +} + bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret) { - struct kmod_dup_req *kmod_req, *new_kmod_req; + struct kmod_dup_req *kmod_req; int ret; - /* - * Pre-allocate the entry in case we have to use it later - * to avoid contention with the mutex. - */ - new_kmod_req = kzalloc_obj(*new_kmod_req); - if (!new_kmod_req) - return false; - - refcount_set(&new_kmod_req->refcount, 1); - strscpy(new_kmod_req->name, module_name); - INIT_DELAYED_WORK(&new_kmod_req->delete_work, kmod_dup_request_delete); - init_completion(&new_kmod_req->first_req_done); - mutex_lock(&kmod_dup_mutex); kmod_req = kmod_dup_request_lookup(module_name); if (!kmod_req) { + struct kmod_dup_req *new_kmod_req; + /* * If the first request that came through for a module * was with request_module_nowait() we cannot wait for it @@ -138,7 +141,6 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret) * would benefit from duplicate detection. */ if (!wait) { - kfree(new_kmod_req); pr_debug("New request_module_nowait() for %s -- cannot track duplicates for this request\n", module_name); mutex_unlock(&kmod_dup_mutex); return false; @@ -149,6 +151,11 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret) * keep tab on duplicates later. */ pr_debug("New request_module() for %s\n", module_name); + new_kmod_req = alloc_kmod_req(module_name); + if (!new_kmod_req) { + mutex_unlock(&kmod_dup_mutex); + return false; + } list_add(&new_kmod_req->list, &dup_kmod_reqs); mutex_unlock(&kmod_dup_mutex); return false; @@ -158,7 +165,6 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret) mutex_unlock(&kmod_dup_mutex); /* We are dealing with a duplicate request now */ - kfree(new_kmod_req); /* * To fix these try to use try_then_request_module() instead as that From c85e76535b6d66cf89e6895721d36cf63b233c6a Mon Sep 17 00:00:00 2001 From: Petr Pavlu Date: Thu, 6 Aug 2026 11:10:18 +0200 Subject: [PATCH 13/17] module/dups: Use scope-based cleanup helpers Use scope-based cleanup helpers for kmod_dup_mutex and kmod_req to shorten the code and to clarify where the lock is taken in kmod_dup_request_exists_wait(). Signed-off-by: Petr Pavlu --- kernel/module/dups.c | 58 ++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/kernel/module/dups.c b/kernel/module/dups.c index 276016051ad2..201a7f4c151f 100644 --- a/kernel/module/dups.c +++ b/kernel/module/dups.c @@ -63,6 +63,8 @@ static void put_kmod_req(struct kmod_dup_req *kmod_req) kfree(kmod_req); } +DEFINE_FREE(put_kmod_req, struct kmod_dup_req *, if (_T) put_kmod_req(_T)) + static struct kmod_dup_req *kmod_dup_request_lookup(char *module_name) { struct kmod_dup_req *kmod_req; @@ -97,9 +99,8 @@ static void kmod_dup_request_delete(struct work_struct *work) * kmod. The inneficies there are a call to modprobe and modprobe * just returning 0. */ - mutex_lock(&kmod_dup_mutex); - list_del(&kmod_req->list); - mutex_unlock(&kmod_dup_mutex); + scoped_guard(mutex, &kmod_dup_mutex) + list_del(&kmod_req->list); put_kmod_req(kmod_req); } @@ -120,15 +121,18 @@ static struct kmod_dup_req *alloc_kmod_req(const char *module_name) bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret) { - struct kmod_dup_req *kmod_req; + struct kmod_dup_req *kmod_req __free(put_kmod_req) = NULL; int ret; - mutex_lock(&kmod_dup_mutex); - - kmod_req = kmod_dup_request_lookup(module_name); - if (!kmod_req) { + scoped_guard(mutex, &kmod_dup_mutex) { struct kmod_dup_req *new_kmod_req; + kmod_req = kmod_dup_request_lookup(module_name); + if (kmod_req) { + get_kmod_req(kmod_req); + break; + } + /* * If the first request that came through for a module * was with request_module_nowait() we cannot wait for it @@ -142,7 +146,6 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret) */ if (!wait) { pr_debug("New request_module_nowait() for %s -- cannot track duplicates for this request\n", module_name); - mutex_unlock(&kmod_dup_mutex); return false; } @@ -152,18 +155,12 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret) */ pr_debug("New request_module() for %s\n", module_name); new_kmod_req = alloc_kmod_req(module_name); - if (!new_kmod_req) { - mutex_unlock(&kmod_dup_mutex); + if (!new_kmod_req) return false; - } list_add(&new_kmod_req->list, &dup_kmod_reqs); - mutex_unlock(&kmod_dup_mutex); return false; } - get_kmod_req(kmod_req); - mutex_unlock(&kmod_dup_mutex); - /* We are dealing with a duplicate request now */ /* @@ -190,7 +187,7 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret) * calls bail out right away. */ *dup_ret = 0; - goto out; + return true; } /* @@ -205,14 +202,11 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret) TASK_KILLABLE); if (ret) { *dup_ret = ret; - goto out; + return true; } /* Now the duplicate request has the same exact return value as the first request */ *dup_ret = kmod_req->dup_ret; - -out: - put_kmod_req(kmod_req); return true; } @@ -220,27 +214,23 @@ void kmod_dup_request_announce(char *module_name, int ret) { struct kmod_dup_req *kmod_req; - mutex_lock(&kmod_dup_mutex); - /* * Look for a kmod_dup_req previously added in * kmod_dup_request_exists_wait(). Note that a request_module_nowait() * without its own kmod_dup_req entry can announce a result of * a concurrent request_module() call. */ - kmod_req = kmod_dup_request_lookup(module_name); - if (!kmod_req || completion_done(&kmod_req->first_req_done)) { - mutex_unlock(&kmod_dup_mutex); - return; + scoped_guard(mutex, &kmod_dup_mutex) { + kmod_req = kmod_dup_request_lookup(module_name); + if (!kmod_req || completion_done(&kmod_req->first_req_done)) + return; + + kmod_req->dup_ret = ret; + + /* Inform all duplicate waiters to check the return value. */ + complete_all(&kmod_req->first_req_done); } - kmod_req->dup_ret = ret; - - /* Inform all duplicate waiters to check the return value. */ - complete_all(&kmod_req->first_req_done); - - mutex_unlock(&kmod_dup_mutex); - /* * Now that we have allowed prior request_module() calls to go on * with life, let's schedule deleting this entry. We don't have From cc6e79b2080da3310dbf9749c6ee61ae1641ec70 Mon Sep 17 00:00:00 2001 From: Petr Pavlu Date: Thu, 6 Aug 2026 11:10:19 +0200 Subject: [PATCH 14/17] module/dups: Use strcmp() to compare module names Use strcmp() instead of strlen()+memcmp() to compare module names in kmod_dup_request_lookup(), since all strings are NUL-terminated. Reviewed-by: Aaron Tomlin Signed-off-by: Petr Pavlu --- kernel/module/dups.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/kernel/module/dups.c b/kernel/module/dups.c index 201a7f4c151f..fa3a4e623543 100644 --- a/kernel/module/dups.c +++ b/kernel/module/dups.c @@ -72,11 +72,9 @@ static struct kmod_dup_req *kmod_dup_request_lookup(char *module_name) lockdep_assert_held(&kmod_dup_mutex); list_for_each_entry(kmod_req, &dup_kmod_reqs, list) { - if (strlen(kmod_req->name) == strlen(module_name) && - !memcmp(kmod_req->name, module_name, strlen(module_name))) { + if (!strcmp(kmod_req->name, module_name)) return kmod_req; - } - } + } return NULL; } From 2a7ecc5fbde76fd48f37c2878e52c50ae8928e0a Mon Sep 17 00:00:00 2001 From: Petr Pavlu Date: Thu, 6 Aug 2026 11:10:20 +0200 Subject: [PATCH 15/17] module/dups: Clean up includes The kernel/module/dups.c file relies on the following definitions and associated functions: * module_param() -> linux/moduleparam.h, * DEFINE_MUTEX() -> linux/mutex.h, * LIST_HEAD(), list_for_each_entry(), ... -> linux/list.h, * refcount_t, refcount_inc(), ... -> linux/refcount.h, * MODULE_NAME_LEN -> linux/module.h, * completion, complete_all(), ... -> linux/completion.h, * delayed_work, work_struct, ... -> linux/workqueue.h, * lockdep_assert_held() -> linux/lockdep.h, * strcmp(), memcpy() -> linux/string.h, * container_of() -> linux/container_of.h, * DEFINE_FREE(), __free(), scoped_guard() -> linux/cleanup.h, * kzalloc_obj(), kfree() -> linux/slab.h, * pr_debug(), pr_warn() -> linux/printk.h, * WARN() -> linux/bug.h, * TASK_KILLABLE -> linux/sched.h, * HZ -> linux/param.h. Update the file's include list accordingly. Signed-off-by: Petr Pavlu --- kernel/module/dups.c | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/kernel/module/dups.c b/kernel/module/dups.c index fa3a4e623543..c9be9df6a65d 100644 --- a/kernel/module/dups.c +++ b/kernel/module/dups.c @@ -7,30 +7,22 @@ #define pr_fmt(fmt) "module: " fmt -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include "internal.h" From 9a5ff45689329835f874cefe5174e577d141d423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thi=C3=A9baud=20Weksteen?= Date: Wed, 8 Jul 2026 11:21:07 +1000 Subject: [PATCH 16/17] module: validate string table section types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In elf_validity_cache_sechdrs, section sizes and offsets are validated, unless the section type is SHT_NULL or SHT_NOBITS. Later, elf_validity_cache_secstrings and elf_validity_cache_index_str access the section name table (.shstrtab) and symbol string table (.strtab) headers without first ensuring that their types are SHT_STRTAB. If a section type is SHT_NULL or SHT_NOBITS, sh_offset has not been validated and may reference out-of-bounds memory when dereferenced in elf_validity_cache_secstrings or elf_validity_cache_strtab. Validate that both string section headers are of type SHT_STRTAB before caching them. Cc: stable@vger.kernel.org Signed-off-by: ThiƩbaud Weksteen Reviewed-by: Aaron Tomlin Reviewed-by: Petr Pavlu Signed-off-by: Petr Pavlu --- kernel/module/main.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/kernel/module/main.c b/kernel/module/main.c index 528690ba160b..d0e1e0bd2ad0 100644 --- a/kernel/module/main.c +++ b/kernel/module/main.c @@ -2010,6 +2010,7 @@ static int elf_validity_cache_sechdrs(struct load_info *info) * Specifically checks: * * * Section name table index is inbounds of section headers + * * Section name table type is SHT_STRTAB * * Section name table is not empty * * Section name table is NUL terminated * * All section name offsets are inbounds of the section @@ -2037,6 +2038,11 @@ static int elf_validity_cache_secstrings(struct load_info *info) strhdr = &info->sechdrs[info->hdr->e_shstrndx]; + if (strhdr->sh_type != SHT_STRTAB) { + pr_err("Invalid ELF section name table type: %u\n", strhdr->sh_type); + return -ENOEXEC; + } + /* * The section name table must be NUL-terminated, as required * by the spec. This makes strcmp and pr_* calls that access @@ -2203,7 +2209,7 @@ static int elf_validity_cache_index_sym(struct load_info *info) * Must have &load_info->index.sym populated. * * Looks at the symbol table's associated string table, makes sure it is - * in-bounds, and caches it. + * in-bounds and of type SHT_STRTAB, and caches it. * * Return: %0 if valid, %-ENOEXEC on failure. */ @@ -2217,6 +2223,12 @@ static int elf_validity_cache_index_str(struct load_info *info) return -ENOEXEC; } + if (info->sechdrs[str_idx].sh_type != SHT_STRTAB) { + pr_err("Invalid ELF symbol string table type: %u\n", + info->sechdrs[str_idx].sh_type); + return -ENOEXEC; + } + info->index.str = str_idx; return 0; } From 3dfaae04243cde460d82dfc2a7dd0bb6664d20ae Mon Sep 17 00:00:00 2001 From: Jiacheng Yu Date: Wed, 29 Jul 2026 12:32:43 +0000 Subject: [PATCH 17/17] params: fix charp corruption on allocation failure param_set_charp() stores charp parameters in allocated memory after slab is available, and releases the previous value when the parameter is updated. The previous value is released before the replacement allocation succeeds. If kmalloc_parameter() fails, the setter returns -ENOMEM with the parameter left as NULL. Failing zswap's compressor update before zswap is initialized can later trigger: BUG: kernel NULL pointer dereference, address: 0000000000000000 RIP: 0010:strcmp+0x10/0x30 Call Trace: zswap_setup+0x3b1/0x490 zswap_enabled_param_set+0x5b/0xa0 param_attr_store+0x93/0xe0 module_attr_store+0x1c/0x30 kernfs_fop_write_iter+0x116/0x1f0 Allocate and copy the replacement first, then replace the parameter value only after allocation succeeds. Fixes: e180a6b7759a ("param: fix charp parameters set via sysfs") Cc: stable@vger.kernel.org Signed-off-by: Jiacheng Yu Reviewed-by: Petr Pavlu Signed-off-by: Petr Pavlu --- kernel/params.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/kernel/params.c b/kernel/params.c index 3456b104efc9..a1ff4bfc9165 100644 --- a/kernel/params.c +++ b/kernel/params.c @@ -261,6 +261,7 @@ EXPORT_SYMBOL_GPL(param_set_uint_minmax); int param_set_charp(const char *val, const struct kernel_param *kp) { + char *tmp; size_t len, maxlen = 1024; len = strnlen(val, maxlen + 1); @@ -269,19 +270,20 @@ int param_set_charp(const char *val, const struct kernel_param *kp) return -ENOSPC; } - maybe_kfree_parameter(*(char **)kp->arg); - /* * This is a hack. We can't kmalloc() in early boot, and we * don't need to; this mangled commandline is preserved. */ if (slab_is_available()) { - *(char **)kp->arg = kmalloc_parameter(len + 1); - if (!*(char **)kp->arg) + tmp = kmalloc_parameter(len + 1); + if (!tmp) return -ENOMEM; - strcpy(*(char **)kp->arg, val); + memcpy(tmp, val, len + 1); } else - *(const char **)kp->arg = val; + tmp = (char *)val; + + maybe_kfree_parameter(*(char **)kp->arg); + *(char **)kp->arg = tmp; return 0; }