Modules changes for v6.13-rc1

Highlights for this merge window:
 
   * 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.
 -----BEGIN PGP SIGNATURE-----
 
 iQJGBAABCgAwFiEENnNq2KuOejlQLZofziMdCjCSiKcFAmdGbrcSHG1jZ3JvZkBr
 ZXJuZWwub3JnAAoJEM4jHQowkoinIDEQAMa1H7hsneNT0Z/YewzOfdSKZIkTzpk3
 /fLl7PfWyFvk7yHT1JiUXidS/80SEMnWb+u8Sn00/uvcJomnPcK9oTwTzBQ0vefl
 FWIUM0DmBzBOi5xdjrPLjg5o6TFt7hVae3hoRJzIlLD02vGfrPYpyHo7XmRrLM4C
 8p+3geziwZMpjcGM254eSiTGxNL8z1iZVRsz8QrrBruRfBDnHNgwtmK097v13Xdb
 qmLX6CN2irmNPZSZwDqP8QL2sJk9qQpNdPmpjMvaY3VfaMVkM46FLy0k9yeXXNqw
 E1p/GuylCZq4NG1hic9zB1I1CE910ugCztJnPcGw4C7CSm54YoLiUJrIeRyTZhk6
 et9N25AlJHxyq72GIRTMQCA9Njxaavx5KilvuWYZmaILfeI0k/3gvcxUqp/EJQ9Q
 axPu69HJFRSKMVh1o+QrSaPmEtSydpYwuuNJ6ONRpq5I3bzOVDSCroceAdXEMO9K
 yoSfm4KwN/BSnmX6KVLonrSM91nv2/v9UokuaZMV/CsDpXIZs996PvAoopCm1Twb
 K3fv0uD+2q2FTOOBInkuRJo2zBUvNnDRPAS2pE3DMXy8xhsQXdovEpjijuCGb8eC
 y0R+I4RIugIB2n6YBUFfyma1veGlT3PtrWQnO6E3YJpv8bqIJoYVT5IGo9M9YRO9
 lzjtR9NzGtmh
 =Ny84
 -----END PGP SIGNATURE-----

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
This commit is contained in:
Linus Torvalds 2024-11-27 10:20:50 -08:00
commit b5361254c9
14 changed files with 854 additions and 412 deletions

View File

@ -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

View File

@ -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;
};

View File

@ -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,6 +1730,452 @@ static int validate_section_offset(struct load_info *info, Elf_Shdr *shdr)
return 0;
}
/**
* elf_validity_ehdr() - Checks an ELF header for module validity
* @info: Load info containing the ELF header to check
*
* Checks whether an ELF header could belong to a valid module. Checks:
*
* * 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.
*
* Return: %0 if valid, %-ENOEXEC on failure.
*/
static int elf_validity_ehdr(const struct load_info *info)
{
if (info->len < sizeof(*(info->hdr))) {
pr_err("Invalid ELF header len %lu\n", info->len);
return -ENOEXEC;
}
if (memcmp(info->hdr->e_ident, ELFMAG, SELFMAG) != 0) {
pr_err("Invalid ELF header magic: != %s\n", ELFMAG);
return -ENOEXEC;
}
if (info->hdr->e_type != ET_REL) {
pr_err("Invalid ELF header type: %u != %u\n",
info->hdr->e_type, ET_REL);
return -ENOEXEC;
}
if (!elf_check_arch(info->hdr)) {
pr_err("Invalid architecture in ELF header: %u\n",
info->hdr->e_machine);
return -ENOEXEC;
}
if (!module_elf_check_arch(info->hdr)) {
pr_err("Invalid module architecture in ELF header: %u\n",
info->hdr->e_machine);
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");
return -ENOEXEC;
}
/*
* e_shnum is 16 bits, and sizeof(Elf_Shdr) is
* known and small. So e_shnum * sizeof(Elf_Shdr)
* will not overflow unsigned long on any platform.
*/
if (info->hdr->e_shoff >= info->len
|| (info->hdr->e_shnum * sizeof(Elf_Shdr) >
info->len - info->hdr->e_shoff)) {
pr_err("Invalid ELF section header overflow\n");
return -ENOEXEC;
}
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.
*/
if (info->hdr->e_shstrndx == SHN_UNDEF
|| info->hdr->e_shstrndx >= info->hdr->e_shnum) {
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);
return -ENOEXEC;
}
strhdr = &info->sechdrs[info->hdr->e_shstrndx];
/*
* 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.
*/
secstrings = (void *)info->hdr + strhdr->sh_offset;
if (strhdr->sh_size == 0) {
pr_err("empty section name table\n");
return -ENOEXEC;
}
if (secstrings[strhdr->sh_size - 1] != '\0') {
pr_err("ELF Spec violation: section name table isn't null terminated\n");
return -ENOEXEC;
}
for (i = 0; i < info->hdr->e_shnum; i++) {
shdr = &info->sechdrs[i];
/* SHT_NULL means sh_name has an undefined value */
if (shdr->sh_type == SHT_NULL)
continue;
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;
}
}
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");
return -ENOEXEC;
}
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");
return 0;
}
/**
* 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)");
return -ENOEXEC;
}
shdr = &info->sechdrs[mod_idx];
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)");
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)");
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)");
return -ENOEXEC;
}
info->index.mod = mod_idx;
return 0;
}
/**
* 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! */
else
info->index.vers = find_sec(info, "__versions");
info->index.pcpu = find_pcpusec(info);
return 0;
}
/**
* 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.
@ -1720,216 +2198,23 @@ static int validate_section_offset(struct load_info *info, Elf_Shdr *shdr)
*/
static int elf_validity_cache_copy(struct load_info *info, int flags)
{
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;
}
if (memcmp(info->hdr->e_ident, ELFMAG, SELFMAG) != 0) {
pr_err("Invalid ELF header magic: != %s\n", ELFMAG);
goto no_exec;
}
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;
}
if (!elf_check_arch(info->hdr)) {
pr_err("Invalid architecture in ELF header: %u\n",
info->hdr->e_machine);
goto no_exec;
}
if (!module_elf_check_arch(info->hdr)) {
pr_err("Invalid module architecture in ELF header: %u\n",
info->hdr->e_machine);
goto no_exec;
}
if (info->hdr->e_shentsize != sizeof(Elf_Shdr)) {
pr_err("Invalid ELF section header size\n");
goto no_exec;
}
/*
* e_shnum is 16 bits, and sizeof(Elf_Shdr) is
* known and small. So e_shnum * sizeof(Elf_Shdr)
* will not overflow unsigned long on any platform.
*/
if (info->hdr->e_shoff >= info->len
|| (info->hdr->e_shnum * sizeof(Elf_Shdr) >
info->len - info->hdr->e_shoff)) {
pr_err("Invalid ELF section header overflow\n");
goto no_exec;
}
info->sechdrs = (void *)info->hdr + info->hdr->e_shoff;
/*
* Verify if the section name table index is valid.
*/
if (info->hdr->e_shstrndx == SHN_UNDEF
|| info->hdr->e_shstrndx >= info->hdr->e_shnum) {
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;
}
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);
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;
}
/*
* 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;
if (strhdr->sh_size == 0) {
pr_err("empty section name table\n");
goto no_exec;
}
if (info->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;
}
for (i = 1; i < info->hdr->e_shnum; i++) {
shdr = &info->sechdrs[i];
switch (shdr->sh_type) {
case SHT_NULL:
case SHT_NOBITS:
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 (num_info_secs > 1) {
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");
}
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;
}
/* 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;
/*
* 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",
info->name ?: "(missing .modinfo section or name field)");
goto no_exec;
}
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;
}
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;
}
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;
}
info->index.mod = mod_idx;
/* This is temporary: point mod into copy of data. */
info->mod = (void *)info->hdr + shdr->sh_offset;
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
@ -1938,17 +2223,7 @@ static int elf_validity_cache_copy(struct load_info *info, int flags)
if (!info->name)
info->name = info->mod->name;
if (flags & MODULE_INIT_IGNORE_MODVERSIONS)
info->index.vers = 0; /* Pretend no __versions section! */
else
info->index.vers = find_sec(info, "__versions");
info->index.pcpu = find_pcpusec(info);
return 0;
no_exec:
return -ENOEXEC;
}
#define COPY_CHUNK_SIZE (16*PAGE_SIZE)

View File

@ -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

View File

@ -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

1
lib/tests/Makefile Normal file
View File

@ -0,0 +1 @@
obj-$(CONFIG_TEST_RUNTIME_MODULE) += module/

4
lib/tests/module/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
test_kallsyms_a.c
test_kallsyms_b.c
test_kallsyms_c.c
test_kallsyms_d.c

15
lib/tests/module/Makefile Normal file
View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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) }

View File

@ -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:

View File

@ -0,0 +1,3 @@
CONFIG_TEST_RUNTIME=y
CONFIG_TEST_RUNTIME_MODULE=y
CONFIG_TEST_KALLSYMS=m

View File

@ -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