Adds a new format for modversions which stores each field in a separate
elf section. This initially adds support for variable length names, but
could later be used to add additional fields to modversions in a
backwards compatible way if needed.

Adding support for variable length names makes it possible to enable
MODVERSIONS and RUST at the same time.

Signed-off-by: Matthew Maurer <mmau...@google.com>
---
 arch/powerpc/kernel/module_64.c | 24 +++++++++-
 init/Kconfig                    |  1 -
 kernel/module/internal.h        | 16 ++++++-
 kernel/module/main.c            |  9 +++-
 kernel/module/version.c         | 77 +++++++++++++++++++++++++++++++++
 scripts/mod/modpost.c           | 33 ++++++++++++--
 6 files changed, 151 insertions(+), 9 deletions(-)

diff --git a/arch/powerpc/kernel/module_64.c b/arch/powerpc/kernel/module_64.c
index 7112adc597a8..2582353a2048 100644
--- a/arch/powerpc/kernel/module_64.c
+++ b/arch/powerpc/kernel/module_64.c
@@ -355,6 +355,24 @@ static void dedotify_versions(struct modversion_info *vers,
                }
 }
 
+static void dedotify_ext_version_names(char *str_seq, unsigned long size)
+{
+       unsigned long out = 0;
+       unsigned long in;
+       char last = '\0';
+
+       for (in = 0; in < size; in++) {
+               if (last == '\0')
+                       /* Skip all leading dots */
+                       if (str_seq[in] == '.')
+                               continue;
+               last = str_seq[in];
+               str_seq[out++] = last;
+       }
+       /* Zero the trailing portion of the names table for robustness */
+       bzero(&str_seq[out], size - out);
+}
+
 /*
  * Undefined symbols which refer to .funcname, hack to funcname. Make .TOC.
  * seem to be defined (value set later).
@@ -424,10 +442,12 @@ int module_frob_arch_sections(Elf64_Ehdr *hdr,
                        me->arch.toc_section = i;
                        if (sechdrs[i].sh_addralign < 8)
                                sechdrs[i].sh_addralign = 8;
-               }
-               else if (strcmp(secstrings+sechdrs[i].sh_name,"__versions")==0)
+               } else if (strcmp(secstrings + sechdrs[i].sh_name, 
"__versions") == 0)
                        dedotify_versions((void *)hdr + sechdrs[i].sh_offset,
                                          sechdrs[i].sh_size);
+               else if (strcmp(secstrings + sechdrs[i].sh_name, 
"__version_ext_names") == 0)
+                       dedotify_ext_version_names((void *)hdr + 
sechdrs[i].sh_offset,
+                                                  sechdrs[i].sh_size);
 
                if (sechdrs[i].sh_type == SHT_SYMTAB)
                        dedotify((void *)hdr + sechdrs[i].sh_offset,
diff --git a/init/Kconfig b/init/Kconfig
index 9ffb103fc927..6cac5b4db8f6 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1885,7 +1885,6 @@ config RUST
        bool "Rust support"
        depends on HAVE_RUST
        depends on RUST_IS_AVAILABLE
-       depends on !MODVERSIONS
        depends on !GCC_PLUGINS
        depends on !RANDSTRUCT
        depends on !DEBUG_INFO_BTF || PAHOLE_HAS_LANG_EXCLUDE
diff --git a/kernel/module/internal.h b/kernel/module/internal.h
index c8b7b4dcf782..0c188c96a045 100644
--- a/kernel/module/internal.h
+++ b/kernel/module/internal.h
@@ -80,7 +80,7 @@ struct load_info {
        unsigned int used_pages;
 #endif
        struct {
-               unsigned int sym, str, mod, vers, info, pcpu;
+               unsigned int sym, str, mod, vers, info, pcpu, vers_ext_crc, 
vers_ext_name;
        } index;
 };
 
@@ -384,6 +384,20 @@ void module_layout(struct module *mod, struct 
modversion_info *ver, struct kerne
                   struct kernel_symbol *ks, struct tracepoint * const *tp);
 int check_modstruct_version(const struct load_info *info, struct module *mod);
 int same_magic(const char *amagic, const char *bmagic, bool has_crcs);
+struct modversion_info_ext_s32 {
+       const s32 *value;
+       const s32 *end;
+};
+struct modversion_info_ext_string {
+       const char *value;
+       const char *end;
+};
+struct modversion_info_ext {
+       struct modversion_info_ext_s32 crc;
+       struct modversion_info_ext_string name;
+};
+ssize_t modversion_ext_start(const struct load_info *info, struct 
modversion_info_ext *ver);
+int modversion_ext_advance(struct modversion_info_ext *ver);
 #else /* !CONFIG_MODVERSIONS */
 static inline int check_version(const struct load_info *info,
                                const char *symname,
diff --git a/kernel/module/main.c b/kernel/module/main.c
index 98fedfdb8db5..e69b2ae46161 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -1886,10 +1886,15 @@ 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)
+       if (flags & MODULE_INIT_IGNORE_MODVERSIONS) {
                info->index.vers = 0; /* Pretend no __versions section! */
-       else
+               info->index.vers_ext_crc = 0;
+               info->index.vers_ext_name = 0;
+       } else {
                info->index.vers = find_sec(info, "__versions");
+               info->index.vers_ext_crc = find_sec(info, "__version_ext_crcs");
+               info->index.vers_ext_name = find_sec(info, 
"__version_ext_names");
+       }
 
        info->index.pcpu = find_pcpusec(info);
 
diff --git a/kernel/module/version.c b/kernel/module/version.c
index 53f43ac5a73e..93d97dad8c77 100644
--- a/kernel/module/version.c
+++ b/kernel/module/version.c
@@ -19,11 +19,28 @@ int check_version(const struct load_info *info,
        unsigned int versindex = info->index.vers;
        unsigned int i, num_versions;
        struct modversion_info *versions;
+       struct modversion_info_ext version_ext;
 
        /* Exporting module didn't supply crcs?  OK, we're already tainted. */
        if (!crc)
                return 1;
 
+       /* If we have extended version info, rely on it */
+       if (modversion_ext_start(info, &version_ext) >= 0) {
+               do {
+                       if (strncmp(version_ext.name.value, symname,
+                                   version_ext.name.end - 
version_ext.name.value) != 0)
+                               continue;
+
+                       if (*version_ext.crc.value == *crc)
+                               return 1;
+                       pr_debug("Found checksum %X vs module %X\n",
+                                *crc, *version_ext.crc.value);
+                       goto bad_version;
+               } while (modversion_ext_advance(&version_ext) == 0);
+               goto broken_toolchain;
+       }
+
        /* No versions at all?  modprobe --force does this. */
        if (versindex == 0)
                return try_to_force_load(mod, symname) == 0;
@@ -46,6 +63,7 @@ int check_version(const struct load_info *info,
                goto bad_version;
        }
 
+broken_toolchain:
        /* Broken toolchain. Warn once, then let it go.. */
        pr_warn_once("%s: no symbol version for %s\n", info->name, symname);
        return 1;
@@ -87,6 +105,65 @@ int same_magic(const char *amagic, const char *bmagic,
        return strcmp(amagic, bmagic) == 0;
 }
 
+#define MODVERSION_FIELD_START(sec, field) \
+       field.value = (typeof(field.value))sec.sh_addr; \
+       field.end = field.value + sec.sh_size
+
+ssize_t modversion_ext_start(const struct load_info *info,
+                            struct modversion_info_ext *start)
+{
+       unsigned int crc_idx = info->index.vers_ext_crc;
+       unsigned int name_idx = info->index.vers_ext_name;
+       Elf_Shdr *sechdrs = info->sechdrs;
+
+       // Both of these fields are needed for this to be useful
+       // Any future fields should be initialized to NULL if absent.
+       if ((crc_idx == 0) || (name_idx == 0))
+               return -EINVAL;
+
+       MODVERSION_FIELD_START(sechdrs[crc_idx], start->crc);
+       MODVERSION_FIELD_START(sechdrs[name_idx], start->name);
+
+       return (start->crc.end - start->crc.value) / sizeof(*start->crc.value);
+}
+
+static int modversion_ext_s32_advance(struct modversion_info_ext_s32 *field)
+{
+       if (!field->value)
+               return 0;
+       if (field->value >= field->end)
+               return -EINVAL;
+       field->value++;
+       return 0;
+}
+
+static int modversion_ext_string_advance(struct modversion_info_ext_string *s)
+{
+       if (!s->value)
+               return 0;
+       if (s->value >= s->end)
+               return -EINVAL;
+       s->value += strnlen(s->value, s->end - s->value - 1) + 1;
+       if (s->value >= s->end)
+               return -EINVAL;
+       return 0;
+}
+
+int modversion_ext_advance(struct modversion_info_ext *start)
+{
+       int ret;
+
+       ret = modversion_ext_s32_advance(&start->crc);
+       if (ret < 0)
+               return ret;
+
+       ret = modversion_ext_string_advance(&start->name);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
 /*
  * Generate the signature for all relevant module structures here.
  * If these change, we don't want to try to parse the module.
diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c
index 973b5e5ae2dd..884860c2e833 100644
--- a/scripts/mod/modpost.c
+++ b/scripts/mod/modpost.c
@@ -1910,15 +1910,42 @@ static void add_versions(struct buffer *b, struct 
module *mod)
                        continue;
                }
                if (strlen(s->name) >= MODULE_NAME_LEN) {
-                       error("too long symbol \"%s\" [%s.ko]\n",
-                             s->name, mod->name);
-                       break;
+                       /* this symbol will only be in the extended info */
+                       continue;
                }
                buf_printf(b, "\t{ %#8x, \"%s\" },\n",
                           s->crc, s->name);
        }
 
        buf_printf(b, "};\n");
+
+       buf_printf(b, "static const s32 ____version_ext_crcs[]\n");
+       buf_printf(b, "__used __section(\"__version_ext_crcs\") = {\n");
+       list_for_each_entry(s, &mod->unresolved_symbols, list) {
+               if (!s->module)
+                       continue;
+               if (!s->crc_valid) {
+                       // We already warned on this when producing the legacy
+                       // modversions table.
+                       continue;
+               }
+               buf_printf(b, "\t%#8x,\n", s->crc);
+       }
+       buf_printf(b, "};\n");
+
+       buf_printf(b, "static const char ____version_ext_names[]\n");
+       buf_printf(b, "__used __section(\"__version_ext_names\") =\n");
+       list_for_each_entry(s, &mod->unresolved_symbols, list) {
+               if (!s->module)
+                       continue;
+               if (!s->crc_valid) {
+                       // We already warned on this when producing the legacy
+                       // modversions table.
+                       continue;
+               }
+               buf_printf(b, "\t\"%s\\0\"\n", s->name);
+       }
+       buf_printf(b, ";\n");
 }
 
 static void add_depends(struct buffer *b, struct module *mod)
-- 
2.43.0.rc0.421.g78406f8d94-goog

Reply via email to