summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2024-11-27 10:20:50 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2024-11-27 10:20:50 -0800
commitb5361254c9027c2b3730be1bebcdb37eed42e9a5 (patch)
tree908e432853cb6aa173a09f2c4f129674d88dab89
parent7d4050728c83aa63828494ad0f4d0eb4faf5f97a (diff)
parent2466b31201424ccb7eda00277222302a4d6576cb (diff)
Merge tag 'modules-6.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/modules/linux
Pull modules updates from Luis Chamberlain: - The whole caching of module code into huge pages by Mike Rapoport is going in through Andrew Morton's tree due to some other code dependencies. That's really the biggest highlight for Linux kernel modules in this release. With it we share huge pages for modules, starting off with x86. Expect to see that soon through Andrew! - Helge Deller addressed some lingering low hanging fruit alignment enhancements by. It is worth pointing out that from his old patch series I dropped his vmlinux.lds.h change at Masahiro's request as he would prefer this to be specified in asm code [0]. [0] https://lore.kernel.org/all/20240129192644.3359978-5-mcgrof@kernel.org/T/#m9efef5e700fbecd28b7afb462c15eed8ba78ef5a - Matthew Maurer and Sami Tolvanen have been tag teaming to help get us closer to a modversions for Rust. In this cycle we take in quite a lot of the refactoring for ELF validation. I expect modversions for Rust will be merged by v6.14 as that code is mostly ready now. - Adds a new modules selftests: kallsyms which helps us tests find_symbol() and the limits of kallsyms on Linux today. - We have a realtime mailing list to kernel-ci testing for modules now which relies and combines patchwork, kpd and kdevops: https://patchwork.kernel.org/project/linux-modules/list/ https://github.com/linux-kdevops/kdevops/blob/main/docs/kernel-ci/README.md https://github.com/linux-kdevops/kdevops/blob/main/docs/kernel-ci/kernel-ci-kpd.md https://github.com/linux-kdevops/kdevops/blob/main/docs/kernel-ci/linux-modules-kdevops-ci.md If you want to help avoid Linux kernel modules regressions, now its simple, just add a new Linux modules sefltests under tools/testing/selftests/module/ That is it. All new selftests will be used and leveraged automatically by the CI. * tag 'modules-6.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/modules/linux: tests/module/gen_test_kallsyms.sh: use 0 value for variables scripts: Remove export_report.pl selftests: kallsyms: add MODULE_DESCRIPTION selftests: add new kallsyms selftests module: Reformat struct for code style module: Additional validation in elf_validity_cache_strtab module: Factor out elf_validity_cache_strtab module: Group section index calculations together module: Factor out elf_validity_cache_index_str module: Factor out elf_validity_cache_index_sym module: Factor out elf_validity_cache_index_mod module: Factor out elf_validity_cache_index_info module: Factor out elf_validity_cache_secstrings module: Factor out elf_validity_cache_sechdrs module: Factor out elf_validity_ehdr module: Take const arg in validate_section_offset modules: Add missing entry for __ex_table modules: Ensure 64-bit alignment on __ksymtab_* sections
-rw-r--r--Makefile6
-rw-r--r--kernel/module/internal.h7
-rw-r--r--kernel/module/main.c569
-rw-r--r--lib/Kconfig.debug105
-rw-r--r--lib/Makefile1
-rw-r--r--lib/tests/Makefile1
-rw-r--r--lib/tests/module/.gitignore4
-rw-r--r--lib/tests/module/Makefile15
-rwxr-xr-xlib/tests/module/gen_test_kallsyms.sh129
-rwxr-xr-xscripts/export_report.pl186
-rw-r--r--scripts/module.lds.S9
-rw-r--r--tools/testing/selftests/module/Makefile12
-rw-r--r--tools/testing/selftests/module/config3
-rwxr-xr-xtools/testing/selftests/module/find_symbol.sh81
14 files changed, 785 insertions, 343 deletions
diff --git a/Makefile b/Makefile
index 3be2bda148a9..8129de0b214f 100644
--- a/Makefile
+++ b/Makefile
@@ -1610,7 +1610,6 @@ help:
@echo ' with a stack size larger than MINSTACKSIZE (default: 100)'
@echo ' versioncheck - Sanity check on version.h usage'
@echo ' includecheck - Check for duplicate included header files'
- @echo ' export_report - List the usages of all exported symbols'
@echo ' headerdep - Detect inclusion cycles in headers'
@echo ' coccicheck - Check with Coccinelle'
@echo ' clang-analyzer - Check with clang static analyzer'
@@ -2023,7 +2022,7 @@ endif
# Scripts to check various things for consistency
# ---------------------------------------------------------------------------
-PHONY += includecheck versioncheck coccicheck export_report
+PHONY += includecheck versioncheck coccicheck
includecheck:
find $(srctree)/* $(RCS_FIND_IGNORE) \
@@ -2038,9 +2037,6 @@ versioncheck:
coccicheck:
$(Q)$(BASH) $(srctree)/scripts/$@
-export_report:
- $(PERL) $(srctree)/scripts/export_report.pl
-
PHONY += checkstack kernelrelease kernelversion image_name
# UML needs a little special treatment here. It wants to use the host
diff --git a/kernel/module/internal.h b/kernel/module/internal.h
index 2ebece8a789f..daef2be83902 100644
--- a/kernel/module/internal.h
+++ b/kernel/module/internal.h
@@ -80,7 +80,12 @@ struct load_info {
unsigned int used_pages;
#endif
struct {
- unsigned int sym, str, mod, vers, info, pcpu;
+ unsigned int sym;
+ unsigned int str;
+ unsigned int mod;
+ unsigned int vers;
+ unsigned int info;
+ unsigned int pcpu;
} index;
};
diff --git a/kernel/module/main.c b/kernel/module/main.c
index d2e1b8976c7b..5399c182b3cb 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -195,6 +195,38 @@ static unsigned int find_sec(const struct load_info *info, const char *name)
return 0;
}
+/**
+ * find_any_unique_sec() - Find a unique section index by name
+ * @info: Load info for the module to scan
+ * @name: Name of the section we're looking for
+ *
+ * Locates a unique section by name. Ignores SHF_ALLOC.
+ *
+ * Return: Section index if found uniquely, zero if absent, negative count
+ * of total instances if multiple were found.
+ */
+static int find_any_unique_sec(const struct load_info *info, const char *name)
+{
+ unsigned int idx;
+ unsigned int count = 0;
+ int i;
+
+ for (i = 1; i < info->hdr->e_shnum; i++) {
+ if (strcmp(info->secstrings + info->sechdrs[i].sh_name,
+ name) == 0) {
+ count++;
+ idx = i;
+ }
+ }
+ if (count == 1) {
+ return idx;
+ } else if (count == 0) {
+ return 0;
+ } else {
+ return -count;
+ }
+}
+
/* Find a module section, or NULL. */
static void *section_addr(const struct load_info *info, const char *name)
{
@@ -1679,7 +1711,7 @@ bool __weak module_exit_section(const char *name)
return strstarts(name, ".exit");
}
-static int validate_section_offset(struct load_info *info, Elf_Shdr *shdr)
+static int validate_section_offset(const struct load_info *info, Elf_Shdr *shdr)
{
#if defined(CONFIG_64BIT)
unsigned long long secend;
@@ -1698,62 +1730,80 @@ static int validate_section_offset(struct load_info *info, Elf_Shdr *shdr)
return 0;
}
-/*
- * Check userspace passed ELF module against our expectations, and cache
- * useful variables for further processing as we go.
- *
- * This does basic validity checks against section offsets and sizes, the
- * section name string table, and the indices used for it (sh_name).
+/**
+ * elf_validity_ehdr() - Checks an ELF header for module validity
+ * @info: Load info containing the ELF header to check
*
- * As a last step, since we're already checking the ELF sections we cache
- * useful variables which will be used later for our convenience:
+ * Checks whether an ELF header could belong to a valid module. Checks:
*
- * o pointers to section headers
- * o cache the modinfo symbol section
- * o cache the string symbol section
- * o cache the module section
+ * * ELF header is within the data the user provided
+ * * ELF magic is present
+ * * It is relocatable (not final linked, not core file, etc.)
+ * * The header's machine type matches what the architecture expects.
+ * * Optional arch-specific hook for other properties
+ * - module_elf_check_arch() is currently only used by PPC to check
+ * ELF ABI version, but may be used by others in the future.
*
- * As a last step we set info->mod to the temporary copy of the module in
- * info->hdr. The final one will be allocated in move_module(). Any
- * modifications we make to our copy of the module will be carried over
- * to the final minted module.
+ * Return: %0 if valid, %-ENOEXEC on failure.
*/
-static int elf_validity_cache_copy(struct load_info *info, int flags)
+static int elf_validity_ehdr(const struct load_info *info)
{
- unsigned int i;
- Elf_Shdr *shdr, *strhdr;
- int err;
- unsigned int num_mod_secs = 0, mod_idx;
- unsigned int num_info_secs = 0, info_idx;
- unsigned int num_sym_secs = 0, sym_idx;
-
if (info->len < sizeof(*(info->hdr))) {
pr_err("Invalid ELF header len %lu\n", info->len);
- goto no_exec;
+ return -ENOEXEC;
}
-
if (memcmp(info->hdr->e_ident, ELFMAG, SELFMAG) != 0) {
pr_err("Invalid ELF header magic: != %s\n", ELFMAG);
- goto no_exec;
+ return -ENOEXEC;
}
if (info->hdr->e_type != ET_REL) {
pr_err("Invalid ELF header type: %u != %u\n",
info->hdr->e_type, ET_REL);
- goto no_exec;
+ return -ENOEXEC;
}
if (!elf_check_arch(info->hdr)) {
pr_err("Invalid architecture in ELF header: %u\n",
info->hdr->e_machine);
- goto no_exec;
+ return -ENOEXEC;
}
if (!module_elf_check_arch(info->hdr)) {
pr_err("Invalid module architecture in ELF header: %u\n",
info->hdr->e_machine);
- goto no_exec;
+ return -ENOEXEC;
}
+ return 0;
+}
+
+/**
+ * elf_validity_cache_sechdrs() - Cache section headers if valid
+ * @info: Load info to compute section headers from
+ *
+ * Checks:
+ *
+ * * ELF header is valid (see elf_validity_ehdr())
+ * * Section headers are the size we expect
+ * * Section array fits in the user provided data
+ * * Section index 0 is NULL
+ * * Section contents are inbounds
+ *
+ * Then updates @info with a &load_info->sechdrs pointer if valid.
+ *
+ * Return: %0 if valid, negative error code if validation failed.
+ */
+static int elf_validity_cache_sechdrs(struct load_info *info)
+{
+ Elf_Shdr *sechdrs;
+ Elf_Shdr *shdr;
+ int i;
+ int err;
+
+ err = elf_validity_ehdr(info);
+ if (err < 0)
+ return err;
+
if (info->hdr->e_shentsize != sizeof(Elf_Shdr)) {
pr_err("Invalid ELF section header size\n");
- goto no_exec;
+ return -ENOEXEC;
}
/*
@@ -1765,10 +1815,66 @@ static int elf_validity_cache_copy(struct load_info *info, int flags)
|| (info->hdr->e_shnum * sizeof(Elf_Shdr) >
info->len - info->hdr->e_shoff)) {
pr_err("Invalid ELF section header overflow\n");
- goto no_exec;
+ return -ENOEXEC;
}
- info->sechdrs = (void *)info->hdr + info->hdr->e_shoff;
+ sechdrs = (void *)info->hdr + info->hdr->e_shoff;
+
+ /*
+ * The code assumes that section 0 has a length of zero and
+ * an addr of zero, so check for it.
+ */
+ if (sechdrs[0].sh_type != SHT_NULL
+ || sechdrs[0].sh_size != 0
+ || sechdrs[0].sh_addr != 0) {
+ pr_err("ELF Spec violation: section 0 type(%d)!=SH_NULL or non-zero len or addr\n",
+ sechdrs[0].sh_type);
+ return -ENOEXEC;
+ }
+
+ /* Validate contents are inbounds */
+ for (i = 1; i < info->hdr->e_shnum; i++) {
+ shdr = &sechdrs[i];
+ switch (shdr->sh_type) {
+ case SHT_NULL:
+ case SHT_NOBITS:
+ /* No contents, offset/size don't mean anything */
+ continue;
+ default:
+ err = validate_section_offset(info, shdr);
+ if (err < 0) {
+ pr_err("Invalid ELF section in module (section %u type %u)\n",
+ i, shdr->sh_type);
+ return err;
+ }
+ }
+ }
+
+ info->sechdrs = sechdrs;
+
+ return 0;
+}
+
+/**
+ * elf_validity_cache_secstrings() - Caches section names if valid
+ * @info: Load info to cache section names from. Must have valid sechdrs.
+ *
+ * Specifically checks:
+ *
+ * * Section name table index is inbounds of section headers
+ * * Section name table is not empty
+ * * Section name table is NUL terminated
+ * * All section name offsets are inbounds of the section
+ *
+ * Then updates @info with a &load_info->secstrings pointer if valid.
+ *
+ * Return: %0 if valid, negative error code if validation failed.
+ */
+static int elf_validity_cache_secstrings(struct load_info *info)
+{
+ Elf_Shdr *strhdr, *shdr;
+ char *secstrings;
+ int i;
/*
* Verify if the section name table index is valid.
@@ -1778,165 +1884,234 @@ static int elf_validity_cache_copy(struct load_info *info, int flags)
pr_err("Invalid ELF section name index: %d || e_shstrndx (%d) >= e_shnum (%d)\n",
info->hdr->e_shstrndx, info->hdr->e_shstrndx,
info->hdr->e_shnum);
- goto no_exec;
+ return -ENOEXEC;
}
strhdr = &info->sechdrs[info->hdr->e_shstrndx];
- err = validate_section_offset(info, strhdr);
- if (err < 0) {
- pr_err("Invalid ELF section hdr(type %u)\n", strhdr->sh_type);
- return err;
- }
/*
* The section name table must be NUL-terminated, as required
* by the spec. This makes strcmp and pr_* calls that access
* strings in the section safe.
*/
- info->secstrings = (void *)info->hdr + strhdr->sh_offset;
+ secstrings = (void *)info->hdr + strhdr->sh_offset;
if (strhdr->sh_size == 0) {
pr_err("empty section name table\n");
- goto no_exec;
+ return -ENOEXEC;
}
- if (info->secstrings[strhdr->sh_size - 1] != '\0') {
+ if (secstrings[strhdr->sh_size - 1] != '\0') {
pr_err("ELF Spec violation: section name table isn't null terminated\n");
- goto no_exec;
- }
-
- /*
- * The code assumes that section 0 has a length of zero and
- * an addr of zero, so check for it.
- */
- if (info->sechdrs[0].sh_type != SHT_NULL
- || info->sechdrs[0].sh_size != 0
- || info->sechdrs[0].sh_addr != 0) {
- pr_err("ELF Spec violation: section 0 type(%d)!=SH_NULL or non-zero len or addr\n",
- info->sechdrs[0].sh_type);
- goto no_exec;
+ return -ENOEXEC;
}
- for (i = 1; i < info->hdr->e_shnum; i++) {
+ for (i = 0; i < info->hdr->e_shnum; i++) {
shdr = &info->sechdrs[i];
- switch (shdr->sh_type) {
- case SHT_NULL:
- case SHT_NOBITS:
+ /* SHT_NULL means sh_name has an undefined value */
+ if (shdr->sh_type == SHT_NULL)
continue;
- case SHT_SYMTAB:
- if (shdr->sh_link == SHN_UNDEF
- || shdr->sh_link >= info->hdr->e_shnum) {
- pr_err("Invalid ELF sh_link!=SHN_UNDEF(%d) or (sh_link(%d) >= hdr->e_shnum(%d)\n",
- shdr->sh_link, shdr->sh_link,
- info->hdr->e_shnum);
- goto no_exec;
- }
- num_sym_secs++;
- sym_idx = i;
- fallthrough;
- default:
- err = validate_section_offset(info, shdr);
- if (err < 0) {
- pr_err("Invalid ELF section in module (section %u type %u)\n",
- i, shdr->sh_type);
- return err;
- }
- if (strcmp(info->secstrings + shdr->sh_name,
- ".gnu.linkonce.this_module") == 0) {
- num_mod_secs++;
- mod_idx = i;
- } else if (strcmp(info->secstrings + shdr->sh_name,
- ".modinfo") == 0) {
- num_info_secs++;
- info_idx = i;
- }
-
- if (shdr->sh_flags & SHF_ALLOC) {
- if (shdr->sh_name >= strhdr->sh_size) {
- pr_err("Invalid ELF section name in module (section %u type %u)\n",
- i, shdr->sh_type);
- return -ENOEXEC;
- }
- }
- break;
+ if (shdr->sh_name >= strhdr->sh_size) {
+ pr_err("Invalid ELF section name in module (section %u type %u)\n",
+ i, shdr->sh_type);
+ return -ENOEXEC;
}
}
- if (num_info_secs > 1) {
+ info->secstrings = secstrings;
+ return 0;
+}
+
+/**
+ * elf_validity_cache_index_info() - Validate and cache modinfo section
+ * @info: Load info to populate the modinfo index on.
+ * Must have &load_info->sechdrs and &load_info->secstrings populated
+ *
+ * Checks that if there is a .modinfo section, it is unique.
+ * Then, it caches its index in &load_info->index.info.
+ * Finally, it tries to populate the name to improve error messages.
+ *
+ * Return: %0 if valid, %-ENOEXEC if multiple modinfo sections were found.
+ */
+static int elf_validity_cache_index_info(struct load_info *info)
+{
+ int info_idx;
+
+ info_idx = find_any_unique_sec(info, ".modinfo");
+
+ if (info_idx == 0)
+ /* Early return, no .modinfo */
+ return 0;
+
+ if (info_idx < 0) {
pr_err("Only one .modinfo section must exist.\n");
- goto no_exec;
- } else if (num_info_secs == 1) {
- /* Try to find a name early so we can log errors with a module name */
- info->index.info = info_idx;
- info->name = get_modinfo(info, "name");
+ return -ENOEXEC;
}
- if (num_sym_secs != 1) {
- pr_warn("%s: module has no symbols (stripped?)\n",
- info->name ?: "(missing .modinfo section or name field)");
- goto no_exec;
- }
+ info->index.info = info_idx;
+ /* Try to find a name early so we can log errors with a module name */
+ info->name = get_modinfo(info, "name");
- /* Sets internal symbols and strings. */
- info->index.sym = sym_idx;
- shdr = &info->sechdrs[sym_idx];
- info->index.str = shdr->sh_link;
- info->strtab = (char *)info->hdr + info->sechdrs[info->index.str].sh_offset;
+ return 0;
+}
- /*
- * The ".gnu.linkonce.this_module" ELF section is special. It is
- * what modpost uses to refer to __this_module and let's use rely
- * on THIS_MODULE to point to &__this_module properly. The kernel's
- * modpost declares it on each modules's *.mod.c file. If the struct
- * module of the kernel changes a full kernel rebuild is required.
- *
- * We have a few expectaions for this special section, the following
- * code validates all this for us:
- *
- * o Only one section must exist
- * o We expect the kernel to always have to allocate it: SHF_ALLOC
- * o The section size must match the kernel's run time's struct module
- * size
- */
- if (num_mod_secs != 1) {
- pr_err("module %s: Only one .gnu.linkonce.this_module section must exist.\n",
+/**
+ * elf_validity_cache_index_mod() - Validates and caches this_module section
+ * @info: Load info to cache this_module on.
+ * Must have &load_info->sechdrs and &load_info->secstrings populated
+ *
+ * The ".gnu.linkonce.this_module" ELF section is special. It is what modpost
+ * uses to refer to __this_module and let's use rely on THIS_MODULE to point
+ * to &__this_module properly. The kernel's modpost declares it on each
+ * modules's *.mod.c file. If the struct module of the kernel changes a full
+ * kernel rebuild is required.
+ *
+ * We have a few expectations for this special section, this function
+ * validates all this for us:
+ *
+ * * The section has contents
+ * * The section is unique
+ * * We expect the kernel to always have to allocate it: SHF_ALLOC
+ * * The section size must match the kernel's run time's struct module
+ * size
+ *
+ * If all checks pass, the index will be cached in &load_info->index.mod
+ *
+ * Return: %0 on validation success, %-ENOEXEC on failure
+ */
+static int elf_validity_cache_index_mod(struct load_info *info)
+{
+ Elf_Shdr *shdr;
+ int mod_idx;
+
+ mod_idx = find_any_unique_sec(info, ".gnu.linkonce.this_module");
+ if (mod_idx <= 0) {
+ pr_err("module %s: Exactly one .gnu.linkonce.this_module section must exist.\n",
info->name ?: "(missing .modinfo section or name field)");
- goto no_exec;
+ return -ENOEXEC;
}
shdr = &info->sechdrs[mod_idx];
- /*
- * This is already implied on the switch above, however let's be
- * pedantic about it.
- */
if (shdr->sh_type == SHT_NOBITS) {
pr_err("module %s: .gnu.linkonce.this_module section must have a size set\n",
info->name ?: "(missing .modinfo section or name field)");
- goto no_exec;
+ return -ENOEXEC;
}
if (!(shdr->sh_flags & SHF_ALLOC)) {
pr_err("module %s: .gnu.linkonce.this_module must occupy memory during process execution\n",
info->name ?: "(missing .modinfo section or name field)");
- goto no_exec;
+ return -ENOEXEC;
}
if (shdr->sh_size != sizeof(struct module)) {
pr_err("module %s: .gnu.linkonce.this_module section size must match the kernel's built struct module size at run time\n",
info->name ?: "(missing .modinfo section or name field)");
- goto no_exec;
+ return -ENOEXEC;
}
info->index.mod = mod_idx;
- /* This is temporary: point mod into copy of data. */
- info->mod = (void *)info->hdr + shdr->sh_offset;
+ return 0;
+}
- /*
- * If we didn't load the .modinfo 'name' field earlier, fall back to
- * on-disk struct mod 'name' field.
- */
- if (!info->name)
- info->name = info->mod->name;
+/**
+ * elf_validity_cache_index_sym() - Validate and cache symtab index
+ * @info: Load info to cache symtab index in.
+ * Must have &load_info->sechdrs and &load_info->secstrings populated.
+ *
+ * Checks that there is exactly one symbol table, then caches its index in
+ * &load_info->index.sym.
+ *
+ * Return: %0 if valid, %-ENOEXEC on failure.
+ */
+static int elf_validity_cache_index_sym(struct load_info *info)
+{
+ unsigned int sym_idx;
+ unsigned int num_sym_secs = 0;
+ int i;
+
+ for (i = 1; i < info->hdr->e_shnum; i++) {
+ if (info->sechdrs[i].sh_type == SHT_SYMTAB) {
+ num_sym_secs++;
+ sym_idx = i;
+ }
+ }
+
+ if (num_sym_secs != 1) {
+ pr_warn("%s: module has no symbols (stripped?)\n",
+ info->name ?: "(missing .modinfo section or name field)");
+ return -ENOEXEC;
+ }
+
+ info->index.sym = sym_idx;
+
+ return 0;
+}
+
+/**
+ * elf_validity_cache_index_str() - Validate and cache strtab index
+ * @info: Load info to cache strtab index in.
+ * Must have &load_info->sechdrs and &load_info->secstrings populated.
+ * 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.
+ *
+ * Return: %0 if valid, %-ENOEXEC on failure.
+ */
+static int elf_validity_cache_index_str(struct load_info *info)
+{
+ unsigned int str_idx = info->sechdrs[info->index.sym].sh_link;
+
+ if (str_idx == SHN_UNDEF || str_idx >= info->hdr->e_shnum) {
+ pr_err("Invalid ELF sh_link!=SHN_UNDEF(%d) or (sh_link(%d) >= hdr->e_shnum(%d)\n",
+ str_idx, str_idx, info->hdr->e_shnum);
+ return -ENOEXEC;
+ }
+
+ info->index.str = str_idx;
+ return 0;
+}
+
+/**
+ * elf_validity_cache_index() - Resolve, validate, cache section indices
+ * @info: Load info to read from and update.
+ * &load_info->sechdrs and &load_info->secstrings must be populated.
+ * @flags: Load flags, relevant to suppress version loading, see
+ * uapi/linux/module.h
+ *
+ * Populates &load_info->index, validating as it goes.
+ * See child functions for per-field validation:
+ *
+ * * elf_validity_cache_index_info()
+ * * elf_validity_cache_index_mod()
+ * * elf_validity_cache_index_sym()
+ * * elf_validity_cache_index_str()
+ *
+ * If versioning is not suppressed via flags, load the version index from
+ * a section called "__versions" with no validation.
+ *
+ * If CONFIG_SMP is enabled, load the percpu section by name with no
+ * validation.
+ *
+ * Return: 0 on success, negative error code if an index failed validation.
+ */
+static int elf_validity_cache_index(struct load_info *info, int flags)
+{
+ int err;
+
+ err = elf_validity_cache_index_info(info);
+ if (err < 0)
+ return err;
+ err = elf_validity_cache_index_mod(info);
+ if (err < 0)
+ return err;
+ err = elf_validity_cache_index_sym(info);
+ if (err < 0)
+ return err;
+ err = elf_validity_cache_index_str(info);
+ if (err < 0)
+ return err;
if (flags & MODULE_INIT_IGNORE_MODVERSIONS)
info->index.vers = 0; /* Pretend no __versions section! */
@@ -1946,9 +2121,109 @@ static int elf_validity_cache_copy(struct load_info *info, int flags)
info->index.pcpu = find_pcpusec(info);
return 0;
+}
-no_exec:
- return -ENOEXEC;
+/**
+ * elf_validity_cache_strtab() - Validate and cache symbol string table
+ * @info: Load info to read from and update.
+ * Must have &load_info->sechdrs and &load_info->secstrings populated.
+ * Must have &load_info->index populated.
+ *
+ * Checks:
+ *
+ * * The string table is not empty.
+ * * The string table starts and ends with NUL (required by ELF spec).
+ * * Every &Elf_Sym->st_name offset in the symbol table is inbounds of the
+ * string table.
+ *
+ * And caches the pointer as &load_info->strtab in @info.
+ *
+ * Return: 0 on success, negative error code if a check failed.
+ */
+static int elf_validity_cache_strtab(struct load_info *info)
+{
+ Elf_Shdr *str_shdr = &info->sechdrs[info->index.str];
+ Elf_Shdr *sym_shdr = &info->sechdrs[info->index.sym];
+ char *strtab = (char *)info->hdr + str_shdr->sh_offset;
+ Elf_Sym *syms = (void *)info->hdr + sym_shdr->sh_offset;
+ int i;
+
+ if (str_shdr->sh_size == 0) {
+ pr_err("empty symbol string table\n");
+ return -ENOEXEC;
+ }
+ if (strtab[0] != '\0') {
+ pr_err("symbol string table missing leading NUL\n");
+ return -ENOEXEC;
+ }
+ if (strtab[str_shdr->sh_size - 1] != '\0') {
+ pr_err("symbol string table isn't NUL terminated\n");
+ return -ENOEXEC;
+ }
+
+ /*
+ * Now that we know strtab is correctly structured, check symbol
+ * starts are inbounds before they're used later.
+ */
+ for (i = 0; i < sym_shdr->sh_size / sizeof(*syms); i++) {
+ if (syms[i].st_name >= str_shdr->sh_size) {
+ pr_err("symbol name out of bounds in string table");
+ return -ENOEXEC;
+ }
+ }
+
+ info->strtab = strtab;
+ return 0;
+}
+
+/*
+ * Check userspace passed ELF module against our expectations, and cache
+ * useful variables for further processing as we go.
+ *
+ * This does basic validity checks against section offsets and sizes, the
+ * section name string table, and the indices used for it (sh_name).
+ *
+ * As a last step, since we're already checking the ELF sections we cache
+ * useful variables which will be used later for our convenience:
+ *
+ * o pointers to section headers
+ * o cache the modinfo symbol section
+ * o cache the string symbol section
+ * o cache the module section
+ *
+ * As a last step we set info->mod to the temporary copy of the module in
+ * info->hdr. The final one will be allocated in move_module(). Any
+ * modifications we make to our copy of the module will be carried over
+ * to the final minted module.
+ */
+static int elf_validity_cache_copy(struct load_info *info, int flags)
+{
+ int err;
+
+ err = elf_validity_cache_sechdrs(info);
+ if (err < 0)
+ return err;
+ err = elf_validity_cache_secstrings(info);
+ if (err < 0)
+ return err;
+ err = elf_validity_cache_index(info, flags);
+ if (err < 0)
+ return err;
+ err = elf_validity_cache_strtab(info);
+ if (err < 0)
+ return err;
+
+ /* This is temporary: point mod into copy of data. */
+ info->mod = (void *)info->hdr + info->sechdrs[info->index.mod].sh_offset;
+
+ /*
+ * If we didn't load the .modinfo 'name' field earlier, fall back to
+ * on-disk struct mod 'name' field.
+ */
+ if (!info->name)
+ info->name = info->mod->name;
+
+ return 0;
}
#define COPY_CHUNK_SIZE (16*PAGE_SIZE)
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 1e37c62e8595..f340017585c5 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2920,6 +2920,111 @@ config TEST_KMOD
If unsure, say N.
+config TEST_RUNTIME
+ bool
+
+config TEST_RUNTIME_MODULE
+ bool
+
+config TEST_KALLSYMS
+ tristate "module kallsyms find_symbol() test"
+ depends on m
+ select TEST_RUNTIME
+ select TEST_RUNTIME_MODULE
+ select TEST_KALLSYMS_A
+ select TEST_KALLSYMS_B
+ select TEST_KALLSYMS_C
+ select TEST_KALLSYMS_D
+ help
+ This allows us to stress test find_symbol() through the kallsyms
+ used to place symbols on the kernel ELF kallsyms and modules kallsyms
+ where we place kernel symbols such as exported symbols.
+
+ We have four test modules:
+
+ A: has KALLSYSMS_NUMSYMS exported symbols
+ B: uses one of A's symbols
+ C: adds KALLSYMS_SCALE_FACTOR * KALLSYSMS_NUMSYMS exported
+ D: adds 2 * the symbols than C
+
+ We stress test find_symbol() through two means:
+
+ 1) Upon load of B it will trigger simplify_symbols() to look for the
+ one symbol it uses from the module A with tons of symbols. This is an
+ indirect way for us to have B call resolve_symbol_wait() upon module
+ load. This will eventually call find_symbol() which will eventually
+ try to find the symbols used with find_exported_symbol_in_section().
+ find_exported_symbol_in_section() uses bsearch() so a binary search
+ for each symbol. Binary search will at worst be O(log(n)) so the
+ larger TEST_MODULE_KALLSYSMS the worse the search.
+
+ 2) The selftests should load C first, before B. Upon B's load towards
+ the end right before we call module B's init routine we get
+ complete_formation() called on the module. That will first check
+ for duplicate symbols with the call to verify_exported_symbols().
+ That is when we'll force iteration on module C's insane symbol list.
+ Since it has 10 * KALLSYMS_NUMSYMS it means we can first test
+ just loading B without C. The amount of time it takes to load C Vs
+ B can give us an idea of the impact growth of the symbol space and
+ give us projection. Module A only uses one symbol from B so to allow
+ this scaling in module C to be proportional, if it used more symbols
+ then the first test would be doing more and increasing just the
+ search space would be slightly different. The last module, module D
+ will just increase the search space by twice the number of symbols in
+ C so to allow for full projects.
+
+ tools/testing/selftests/module/find_symbol.sh
+
+ The current defaults will incur a build delay of about 7 minutes
+ on an x86_64 with only 8 cores. Enable this only if you want to
+ stress test find_symbol() with thousands of symbols. At the same
+ time this is also useful to test building modules with thousands of
+ symbols, and if BTF is enabled this also stress tests adding BTF
+ information for each module. Currently enabling many more symbols
+ will segfault the build system.
+
+ If unsure, say N.
+
+if TEST_KALLSYMS
+
+config TEST_KALLSYMS_A
+ tristate
+ depends on m
+
+config TEST_KALLSYMS_B
+ tristate
+ depends on m
+
+config TEST_KALLSYMS_C
+ tristate
+ depends on m
+
+config TEST_KALLSYMS_D
+ tristate
+ depends on m
+
+config TEST_KALLSYMS_NUMSYMS
+ int "test kallsyms number of symbols"
+ default 100
+ help
+ The number of symbols to create on TEST_KALLSYMS_A, only one of which
+ module TEST_KALLSYMS_B will use. This also will be used
+ for how many symbols TEST_KALLSYMS_C will have, scaled up by
+ TEST_KALLSYMS_SCALE_FACTOR. Note that setting this to 10,000 will
+ trigger a segfault today, don't use anything close to it unless
+ you are aware that this should not be used for automated build tests.
+
+config TEST_KALLSYMS_SCALE_FACTOR
+ int "test kallsyms scale factor"
+ default 8
+ help
+ How many more unusued symbols will TEST_KALLSYSMS_C have than
+ TEST_KALLSYMS_A. If 8, then module C will have 8 * syms
+ than module A. Then TEST_KALLSYMS_D will have double the amount
+ of symbols than C so to allow projections.
+
+endif # TEST_KALLSYMS
+
config TEST_DEBUG_VIRTUAL
tristate "Test CONFIG_DEBUG_VIRTUAL feature"
depends on DEBUG_VIRTUAL
diff --git a/lib/Makefile b/lib/Makefile
index 8000f2270462..a8155c972f02 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -98,6 +98,7 @@ obj-$(CONFIG_TEST_XARRAY) += test_xarray.o
obj-$(CONFIG_TEST_MAPLE_TREE) += test_maple_tree.o
obj-$(CONFIG_TEST_PARMAN) += test_parman.o
obj-$(CONFIG_TEST_KMOD) += test_kmod.o
+obj-$(CONFIG_TEST_RUNTIME) += tests/
obj-$(CONFIG_TEST_DEBUG_VIRTUAL) += test_debug_virtual.o
obj-$(CONFIG_TEST_MEMCAT_P) += test_memcat_p.o
obj-$(CONFIG_TEST_OBJAGG) += test_objagg.o
diff --git a/lib/tests/Makefile b/lib/tests/Makefile
new file mode 100644
index 000000000000..8e4f42cb9c54
--- /dev/null
+++ b/lib/tests/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_TEST_RUNTIME_MODULE) += module/
diff --git a/lib/tests/module/.gitignore b/lib/tests/module/.gitignore
new file mode 100644
index 000000000000..8be7891b250f
--- /dev/null
+++ b/lib/tests/module/.gitignore
@@ -0,0 +1,4 @@
+test_kallsyms_a.c
+test_kallsyms_b.c
+test_kallsyms_c.c
+test_kallsyms_d.c
diff --git a/lib/tests/module/Makefile b/lib/tests/module/Makefile
new file mode 100644
index 000000000000..af5c27b996cb
--- /dev/null
+++ b/lib/tests/module/Makefile
@@ -0,0 +1,15 @@
+obj-$(CONFIG_TEST_KALLSYMS_A) += test_kallsyms_a.o
+obj-$(CONFIG_TEST_KALLSYMS_B) += test_kallsyms_b.o
+obj-$(CONFIG_TEST_KALLSYMS_C) += test_kallsyms_c.o
+obj-$(CONFIG_TEST_KALLSYMS_D) += test_kallsyms_d.o
+
+$(obj)/%.c: FORCE
+ @$(kecho) " GEN $@"
+ $(Q)$(srctree)/lib/tests/module/gen_test_kallsyms.sh $@\
+ $(CONFIG_TEST_KALLSYMS_NUMSYMS) \
+ $(CONFIG_TEST_KALLSYMS_SCALE_FACTOR)
+
+clean-files += test_kallsyms_a.c
+clean-files += test_kallsyms_b.c
+clean-files += test_kallsyms_c.c
+clean-files += test_kallsyms_d.c
diff --git a/lib/tests/module/gen_test_kallsyms.sh b/lib/tests/module/gen_test_kallsyms.sh
new file mode 100755
index 000000000000..3f2c626350ad
--- /dev/null
+++ b/lib/tests/module/gen_test_kallsyms.sh
@@ -0,0 +1,129 @@
+#!/bin/bash
+
+TARGET=$(basename $1)
+DIR=lib/tests/module
+TARGET="$DIR/$TARGET"
+NUM_SYMS=$2
+SCALE_FACTOR=$3
+TEST_TYPE=$(echo $TARGET | sed -e 's|lib/tests/module/test_kallsyms_||g')
+TEST_TYPE=$(echo $TEST_TYPE | sed -e 's|.c||g')
+
+gen_template_module_header()
+{
+ cat <<____END_MODULE
+// SPDX-License-Identifier: GPL-2.0-or-later OR copyleft-next-0.3.1
+/*
+ * Copyright (C) 2023 Luis Chamberlain <mcgrof@kernel.org>
+ *
+ * Automatically generated code for testing, do not edit manually.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/printk.h>
+
+____END_MODULE
+}
+
+gen_num_syms()
+{
+ PREFIX=$1
+ NUM=$2
+ for i in $(seq 1 $NUM); do
+ printf "int auto_test_%s_%010d = 0;\n" $PREFIX $i
+ printf "EXPORT_SYMBOL_GPL(auto_test_%s_%010d);\n" $PREFIX $i
+ done
+ echo
+}
+
+gen_template_module_data_a()
+{
+ gen_num_syms a $1
+ cat <<____END_MODULE
+static int auto_runtime_test(void)
+{
+ return 0;
+}
+
+____END_MODULE
+}
+
+gen_template_module_data_b()
+{
+ printf "\nextern int auto_test_a_%010d;\n\n" 28
+ echo "static int auto_runtime_test(void)"
+ echo "{"
+ printf "\nreturn auto_test_a_%010d;\n" 28
+ echo "}"
+}
+
+gen_template_module_data_c()
+{
+ gen_num_syms c $1
+ cat <<____END_MODULE
+static int auto_runtime_test(void)
+{
+ return 0;
+}
+
+____END_MODULE
+}
+
+gen_template_module_data_d()
+{
+ gen_num_syms d $1
+ cat <<____END_MODULE
+static int auto_runtime_test(void)
+{
+ return 0;
+}
+
+____END_MODULE
+}
+
+gen_template_module_exit()
+{
+ cat <<____END_MODULE
+static int __init auto_test_module_init(void)
+{
+ return auto_runtime_test();
+}
+module_init(auto_test_module_init);
+
+static void __exit auto_test_module_exit(void)
+{
+}
+module_exit(auto_test_module_exit);
+
+MODULE_AUTHOR("Luis Chamberlain <mcgrof@kernel.org>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Test module for kallsyms");
+____END_MODULE
+}
+
+case $TEST_TYPE in
+ a)
+ gen_template_module_header > $TARGET
+ gen_template_module_data_a $NUM_SYMS >> $TARGET
+ gen_template_module_exit >> $TARGET
+ ;;
+ b)
+ gen_template_module_header > $TARGET
+ gen_template_module_data_b >> $TARGET
+ gen_template_module_exit >> $TARGET
+ ;;
+ c)
+ gen_template_module_header > $TARGET
+ gen_template_module_data_c $((NUM_SYMS * SCALE_FACTOR)) >> $TARGET
+ gen_template_module_exit >> $TARGET
+ ;;
+ d)
+ gen_template_module_header > $TARGET
+ gen_template_module_data_d $((NUM_SYMS * SCALE_FACTOR * 2)) >> $TARGET
+ gen_template_module_exit >> $TARGET
+ ;;
+ *)
+ ;;
+esac
diff --git a/scripts/export_report.pl b/scripts/export_report.pl
deleted file mode 100755
index feb3d5542a62..000000000000
--- a/scripts/export_report.pl
+++ /dev/null
@@ -1,186 +0,0 @@
-#!/usr/bin/env perl
-# SPDX-License-Identifier: GPL-2.0-only
-#
-# (C) Copyright IBM Corporation 2006.
-# Author : Ram Pai (linuxram@us.ibm.com)
-#
-# Usage: export_report.pl -k Module.symvers [-o report_file ] -f *.mod.c
-#
-
-use warnings;
-use Getopt::Std;
-use strict;
-
-sub numerically {
- my $no1 = (split /\s+/, $a)[1];
- my $no2 = (split /\s+/, $b)[1];
- return $no1 <=> $no2;
-}
-
-sub alphabetically {
- my ($module1, $value1) = @{$a};
- my ($module2, $value2) = @{$b};
- return $value1 <=> $value2 || $module2 cmp $module1;
-}
-
-sub print_depends_on {
- my ($href) = @_;
- print "\n";
- for my $mod (sort keys %$href) {
- my $list = $href->{$mod};
- print "\t$mod:\n";
- foreach my $sym (sort numerically @{$list}) {
- my ($symbol, $no) = split /\s+/, $sym;
- printf("\t\t%-25s\n", $symbol);
- }
- print "\n";
- }
- print "\n";
- print "~"x80 , "\n";
-}
-
-sub usage {
- print "Usage: @_ -h -k Module.symvers [ -o outputfile ] \n",
- "\t-f: treat all the non-option argument as .mod.c files. ",
- "Recommend using this as the last option\n",
- "\t-h: print detailed help\n",
- "\t-k: the path to Module.symvers file. By default uses ",
- "the file from the current directory\n",
- "\t-o outputfile: output the report to outputfile\n";
- exit 0;
-}
-
-sub collectcfiles {
- my @file;
- open my $fh, '< modules.order' or die "cannot open modules.order: $!\n";
- while (<$fh>) {
- s/\.ko$/.mod.c/;
- push (@file, $_)
- }
- close($fh);
- chomp @file;
- return @file;
-}
-
-my (%SYMBOL, %MODULE, %opt, @allcfiles);
-
-if (not getopts('hk:o:f',\%opt) or defined $opt{'h'}) {
- usage($0);
-}
-
-if (defined $opt{'f'}) {
- @allcfiles = @ARGV;
-} else {
- @allcfiles = collectcfiles();
-}
-
-if (not defined $opt{'k'}) {
- $opt{'k'} = "Module.symvers";
-}
-
-open (my $module_symvers, '<', $opt{'k'})
- or die "Sorry, cannot open $opt{'k'}: $!\n";
-
-if (defined $opt{'o'}) {
- open (my $out, '>', $opt{'o'})
- or die "Sorry, cannot open $opt{'o'} $!\n";
-
- select $out;
-}
-
-#
-# collect all the symbols and their attributes from the
-# Module.symvers file
-#
-while ( <$module_symvers> ) {
- chomp;
- my (undef, $symbol, $module, $gpl, $namespace) = split('\t');
- $SYMBOL { $symbol } = [ $module , "0" , $symbol, $gpl];
-}
-close($module_symvers);
-
-#
-# collect the usage count of each symbol.
-#
-my $modversion_warnings = 0;
-
-foreach my $thismod (@allcfiles) {
- my $module;
-
- unless (open ($module, '<', $thismod)) {
- warn "Sorry, cannot open $thismod: $!\n";
- next;
- }
-
- my $state=0;
- while ( <$module> ) {
- chomp;
- if ($state == 0) {
- $state = 1 if ($_ =~ /static const struct modversion_info/);
- next;
- }
- if ($state == 1) {
- $state = 2 if ($_ =~ /__attribute__\(\(section\("__versions"\)\)\)/);
- next;
- }
- if ($state == 2) {
- if ( $_ !~ /0x[0-9a-f]+,/ ) {
- next;
- }
- my $sym = (split /([,"])/,)[4];
- my ($module, $value, $symbol, $gpl) = @{$SYMBOL{$sym}};
- $SYMBOL{ $sym } = [ $module, $value+1, $symbol, $gpl];
- push(@{$MODULE{$thismod}} , $sym);
- }
- }
- if ($state != 2) {
- warn "WARNING:$thismod is not built with CONFIG_MODVERSIONS enabled\n";
- $modversion_warnings++;
- }
- close($module);
-}
-
-print "\tThis file reports the exported symbols usage patterns by in-tree\n",
- "\t\t\t\tmodules\n";
-printf("%s\n\n\n","x"x80);
-printf("\t\t\t\tINDEX\n\n\n");
-printf("SECTION 1: Usage counts of all exported symbols\n");
-printf("SECTION 2: List of modules and the exported symbols they use\n");
-printf("%s\n\n\n","x"x80);
-printf("SECTION 1:\tThe exported symbols and their usage count\n\n");
-printf("%-25s\t%-25s\t%-5s\t%-25s\n", "Symbol", "Module", "Usage count",
- "export type");
-
-#
-# print the list of unused exported symbols
-#
-foreach my $list (sort alphabetically values(%SYMBOL)) {
- my ($module, $value, $symbol, $gpl) = @{$list};
- printf("%-25s\t%-25s\t%-10s\t", $symbol, $module, $value);
- if (defined $gpl) {
- printf("%-25s\n",$gpl);
- } else {
- printf("\n");
- }
-}
-printf("%s\n\n\n","x"x80);
-
-printf("SECTION 2:\n\tThis section reports export-symbol-usage of in-kernel
-modules. Each module lists the modules, and the symbols from that module that
-it uses. Each listed symbol reports the number of modules using it\n");
-
-print "\nNOTE: Got $modversion_warnings CONFIG_MODVERSIONS warnings\n\n"
- if $modversion_warnings;
-
-print "~"x80 , "\n";
-for my $thismod (sort keys %MODULE) {
- my $list = $MODULE{$thismod};
- my %depends;
- $thismod =~ s/\.mod\.c/.ko/;
- print "\t\t\t$thismod\n";
- foreach my $symbol (@{$list}) {
- my ($module, $value, undef, $gpl) = @{$SYMBOL{$symbol}};
- push (@{$depends{"$module"}}, "$symbol $value");
- }
- print_depends_on(\%depends);
-}
diff --git a/scripts/module.lds.S b/scripts/module.lds.S
index 711c6e029936..c2f80f9141d4 100644
--- a/scripts/module.lds.S
+++ b/scripts/module.lds.S
@@ -18,10 +18,10 @@ SECTIONS {
*(.export_symbol)
}
- __ksymtab 0 : { *(SORT(___ksymtab+*)) }
- __ksymtab_gpl 0 : { *(SORT(___ksymtab_gpl+*)) }
- __kcrctab 0 : { *(SORT(___kcrctab+*)) }
- __kcrctab_gpl 0 : { *(SORT(___kcrctab_gpl+*)) }
+ __ksymtab 0 : ALIGN(8) { *(SORT(___ksymtab+*)) }
+ __ksymtab_gpl 0 : ALIGN(8) { *(SORT(___ksymtab_gpl+*)) }
+ __kcrctab 0 : ALIGN(4) { *(SORT(___kcrctab+*)) }
+ __kcrctab_gpl 0 : ALIGN(4) { *(SORT(___kcrctab_gpl+*)) }
.ctors 0 : ALIGN(8) { *(SORT(.ctors.*)) *(.ctors) }
.init_array 0 : ALIGN(8) { *(SORT(.init_array.*)) *(.init_array) }
@@ -29,6 +29,7 @@ SECTIONS {
.altinstructions 0 : ALIGN(8) { KEEP(*(.altinstructions)) }
__bug_table 0 : ALIGN(8) { KEEP(*(__bug_table)) }
__jump_table 0 : ALIGN(8) { KEEP(*(__jump_table)) }
+ __ex_table 0 : ALIGN(4) { KEEP(*(__ex_table)) }
__patchable_function_entries : { *(__patchable_function_entries) }
diff --git a/tools/testing/selftests/module/Makefile b/tools/testing/selftests/module/Makefile
new file mode 100644
index 000000000000..6132d7ddb08b
--- /dev/null
+++ b/tools/testing/selftests/module/Makefile
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+# Makefile for module loading selftests
+
+# No binaries, but make sure arg-less "make" doesn't trigger "run_tests"
+all:
+
+TEST_PROGS := find_symbol.sh
+
+include ../lib.mk
+
+# Nothing to clean up.
+clean:
diff --git a/tools/testing/selftests/module/config b/tools/testing/selftests/module/config
new file mode 100644
index 000000000000..b0c206b1ad47
--- /dev/null
+++ b/tools/testing/selftests/module/config
@@ -0,0 +1,3 @@
+CONFIG_TEST_RUNTIME=y
+CONFIG_TEST_RUNTIME_MODULE=y
+CONFIG_TEST_KALLSYMS=m
diff --git a/tools/testing/selftests/module/find_symbol.sh b/tools/testing/selftests/module/find_symbol.sh
new file mode 100755
index 000000000000..140364d3c49f
--- /dev/null
+++ b/tools/testing/selftests/module/find_symbol.sh
@@ -0,0 +1,81 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-or-later OR copyleft-next-0.3.1
+# Copyright (C) 2023 Luis Chamberlain <mcgrof@kernel.org>
+#
+# This is a stress test script for kallsyms through find_symbol()
+
+set -e
+
+# Kselftest framework requirement - SKIP code is 4.
+ksft_skip=4
+
+test_reqs()
+{
+ if ! which modprobe 2> /dev/null > /dev/null; then
+ echo "$0: You need modprobe installed" >&2
+ exit $ksft_skip
+ fi
+
+ if ! which kmod 2> /dev/null > /dev/null; then
+ echo "$0: You need kmod installed" >&2
+ exit $ksft_skip
+ fi
+
+ if ! which perf 2> /dev/null > /dev/null; then
+ echo "$0: You need perf installed" >&2
+ exit $ksft_skip
+ fi
+
+ uid=$(id -u)
+ if [ $uid -ne 0 ]; then
+ echo $msg must be run as root >&2
+ exit $ksft_skip
+ fi
+}
+
+load_mod()
+{
+ local STATS="-e duration_time"
+ STATS="$STATS -e user_time"
+ STATS="$STATS -e system_time"
+ STATS="$STATS -e page-faults"
+ local MOD=$1
+
+ local ARCH="$(uname -m)"
+ case "${ARCH}" in
+ x86_64)
+ perf stat $STATS $MODPROBE test_kallsyms_b
+ ;;
+ *)
+ time $MODPROBE test_kallsyms_b
+ exit 1
+ ;;
+ esac
+}
+
+remove_all()
+{
+ $MODPROBE -r test_kallsyms_b
+ for i in a b c d; do
+ $MODPROBE -r test_kallsyms_$i
+ done
+}
+test_reqs
+
+MODPROBE=$(</proc/sys/kernel/modprobe)
+
+remove_all
+load_mod test_kallsyms_b
+remove_all
+
+# Now pollute the namespace
+$MODPROBE test_kallsyms_c
+load_mod test_kallsyms_b
+
+# Now pollute the namespace with twice the number of symbols than the last time
+remove_all
+$MODPROBE test_kallsyms_c
+$MODPROBE test_kallsyms_d
+load_mod test_kallsyms_b
+
+exit 0