Commit 0be41a93 authored by Matthew Maurer's avatar Matthew Maurer Committed by Luis Chamberlain
Browse files

module: Factor out elf_validity_cache_index_mod



Centralize .gnu.linkonce.this_module detection and property validation.

Signed-off-by: default avatarMatthew Maurer <mmaurer@google.com>
Reviewed-by: default avatarSami Tolvanen <samitolvanen@google.com>
Signed-off-by: default avatarLuis Chamberlain <mcgrof@kernel.org>
parent fbc0e4e4
Loading
Loading
Loading
Loading
+67 −62
Original line number Diff line number Diff line
@@ -1919,6 +1919,68 @@ static int elf_validity_cache_index_info(struct load_info *info)
	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;
}


/*
 * Check userspace passed ELF module against our expectations, and cache
 * useful variables for further processing as we go.
@@ -1944,7 +2006,6 @@ static int elf_validity_cache_copy(struct load_info *info, int flags)
	unsigned int i;
	Elf_Shdr *shdr;
	int err;
	unsigned int num_mod_secs = 0, mod_idx;
	unsigned int num_sym_secs = 0, sym_idx;

	err = elf_validity_cache_sechdrs(info);
@@ -1954,16 +2015,15 @@ static int elf_validity_cache_copy(struct load_info *info, int flags)
	if (err < 0)
		return 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;

	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_type == 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",
@@ -1973,14 +2033,6 @@ static int elf_validity_cache_copy(struct load_info *info, int flags)
			}
			num_sym_secs++;
			sym_idx = i;
			fallthrough;
		default:
			if (strcmp(info->secstrings + shdr->sh_name,
				   ".gnu.linkonce.this_module") == 0) {
				num_mod_secs++;
				mod_idx = i;
			}
			break;
		}
	}

@@ -1996,55 +2048,8 @@ static int elf_validity_cache_copy(struct load_info *info, int flags)
	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