summaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-21 12:47:25 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-21 12:47:25 -0700
commit2f0f6b0773be0a1ec475097ae54848eea42adc7d (patch)
tree63dc254caea8a8a76d0889305317cab33119289d /kernel
parent27a59c0251e5d1f41a3a6fbae7c4dc478c34d919 (diff)
parent3dfaae04243cde460d82dfc2a7dd0bb6664d20ae (diff)
Merge tag 'modules-7.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/modules/linux
Pull module updates from Petr Pavlu: - Remove unnecessary module::args. Nowadays, no parameter-handling code points into the module::args buffer. The last user of module::args in xtensa/simdisk is updated and the data is then removed - Add Rust support for boolean parameters. This will initially be used by the Rust null block driver - Fix clearing the current charp parameter value when setting a new one fails due to an allocation failure - Improve the debugging code for kmod (request_module()) duplicates. Fix a potential use-after-free when waiting on a duplicate request and make several general improvements to the code - Fix the symbol size returned when looking up a data symbol through kallsyms - Smaller fixes and cleanups * tag 'modules-7.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/modules/linux: params: fix charp corruption on allocation failure module: validate string table section types module/dups: Clean up includes module/dups: Use strcmp() to compare module names module/dups: Use scope-based cleanup helpers module/dups: Avoid unnecessary kmod_dup_req allocations module/dups: Fix use-after-free in kmod_dup_req lifetime handling module/dups: Inform duplicate requests about the result directly rust: module_param: support bool parameters rust: module_param: return value by copy from `value` module: Remove unnecessary module::args xtensa/simdisk: Avoid referring to module::args module: Remove unused DISCARD_EH_FRAME definition from module.lds.S module: procfs: use matching type for accumulator in module_total_size() module: use strscpy() to copy module names in stats and dup tracking params: fix path of /sys/module/XYZ/parameters/ in comment module/kallsyms: fix nextval for data symbol lookup
Diffstat (limited to 'kernel')
-rw-r--r--kernel/module/dups.c169
-rw-r--r--kernel/module/kallsyms.c24
-rw-r--r--kernel/module/main.c29
-rw-r--r--kernel/module/procfs.c2
-rw-r--r--kernel/module/stats.c2
-rw-r--r--kernel/params.c16
6 files changed, 125 insertions, 117 deletions
diff --git a/kernel/module/dups.c b/kernel/module/dups.c
index 1d720a5311ba..c9be9df6a65d 100644
--- a/kernel/module/dups.c
+++ b/kernel/module/dups.c
@@ -7,29 +7,22 @@
#define pr_fmt(fmt) "module: " fmt
+#include <linux/bug.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/container_of.h>
+#include <linux/list.h>
+#include <linux/lockdep.h>
#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/param.h>
+#include <linux/printk.h>
+#include <linux/refcount.h>
#include <linux/sched.h>
-#include <linux/sched/task.h>
-#include <linux/binfmts.h>
-#include <linux/syscalls.h>
-#include <linux/unistd.h>
-#include <linux/kmod.h>
#include <linux/slab.h>
-#include <linux/completion.h>
-#include <linux/cred.h>
-#include <linux/file.h>
+#include <linux/string.h>
#include <linux/workqueue.h>
-#include <linux/security.h>
-#include <linux/mount.h>
-#include <linux/kernel.h>
-#include <linux/init.h>
-#include <linux/resource.h>
-#include <linux/notifier.h>
-#include <linux/suspend.h>
-#include <linux/rwsem.h>
-#include <linux/ptrace.h>
-#include <linux/async.h>
-#include <linux/uaccess.h>
#include "internal.h"
@@ -38,32 +31,42 @@
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;
- struct work_struct complete_work;
struct delayed_work delete_work;
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);
+}
+
+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;
- list_for_each_entry_rcu(kmod_req, &dup_kmod_reqs, list,
- lockdep_is_held(&kmod_dup_mutex)) {
- if (strlen(kmod_req->name) == strlen(module_name) &&
- !memcmp(kmod_req->name, module_name, strlen(module_name))) {
+ lockdep_assert_held(&kmod_dup_mutex);
+
+ list_for_each_entry(kmod_req, &dup_kmod_reqs, list) {
+ if (!strcmp(kmod_req->name, module_name))
return kmod_req;
- }
- }
+ }
return NULL;
}
@@ -86,58 +89,40 @@ 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_rcu(&kmod_req->list);
- synchronize_rcu();
- mutex_unlock(&kmod_dup_mutex);
- kfree(kmod_req);
+ scoped_guard(mutex, &kmod_dup_mutex)
+ list_del(&kmod_req->list);
+
+ put_kmod_req(kmod_req);
}
-static void kmod_dup_request_complete(struct work_struct *work)
+static struct kmod_dup_req *alloc_kmod_req(const char *module_name)
{
- struct kmod_dup_req *kmod_req;
+ struct kmod_dup_req *kmod_req = kzalloc_obj(*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);
+ if (!kmod_req)
+ return NULL;
- /*
- * 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);
+ 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 __free(put_kmod_req) = NULL;
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;
-
- memcpy(new_kmod_req->name, module_name, strlen(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);
+ scoped_guard(mutex, &kmod_dup_mutex) {
+ struct kmod_dup_req *new_kmod_req;
- mutex_lock(&kmod_dup_mutex);
+ kmod_req = kmod_dup_request_lookup(module_name);
+ if (kmod_req) {
+ get_kmod_req(kmod_req);
+ break;
+ }
- kmod_req = kmod_dup_request_lookup(module_name);
- if (!kmod_req) {
/*
* If the first request that came through for a module
* was with request_module_nowait() we cannot wait for it
@@ -150,9 +135,7 @@ 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;
}
@@ -161,14 +144,14 @@ 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);
- mutex_unlock(&kmod_dup_mutex);
+ new_kmod_req = alloc_kmod_req(module_name);
+ if (!new_kmod_req)
+ return false;
+ list_add(&new_kmod_req->list, &dup_kmod_reqs);
return false;
}
- 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
@@ -214,7 +197,6 @@ bool kmod_dup_request_exists_wait(char *module_name, bool wait, int *dup_ret)
/* Now the duplicate request has the same exact return value as the first request */
*dup_ret = kmod_req->dup_ret;
-
return true;
}
@@ -222,26 +204,29 @@ 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.
+ */
+ 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 = kmod_dup_request_lookup(module_name);
- if (!kmod_req)
- goto out;
+ kmod_req->dup_ret = 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);
-
-out:
- mutex_unlock(&kmod_dup_mutex);
+ queue_delayed_work(system_dfl_wq, &kmod_req->delete_work, 60 * HZ);
}
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;
+ }
+ }
- nextval = (unsigned long)mod_mem->base + mod_mem->size;
+ if (!mod_mem)
+ return NULL;
- bestval = kallsyms_symbol_value(&kallsyms->symtab[best]);
+ /* Initialize bounds within memory region the address belongs to. */
+ nextval = (unsigned long)mod_mem->base + mod_mem->size;
+ bestval = (unsigned long)mod_mem->base - 1;
/*
* Scan for closest preceding symbol, and next symbol. (ELF
diff --git a/kernel/module/main.c b/kernel/module/main.c
index 46dd8d25a605..d0e1e0bd2ad0 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);
@@ -2011,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
@@ -2038,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
@@ -2204,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.
*/
@@ -2218,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;
}
@@ -3425,7 +3436,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 +3534,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 +3557,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 +3567,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 +3610,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:
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;
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);
diff --git a/kernel/params.c b/kernel/params.c
index a668863a4bb6..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;
}
@@ -538,7 +540,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)