[PATCH 2/3] modpost: Extended modversion support

From: Matthew Maurer
Date: Wed Nov 15 2023 - 14:00:02 EST


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