module: Factor out elf_validity_cache_secstrings

Factor out the validation of section names.

There are two behavioral changes:

1. Previously, we did not validate non-SHF_ALLOC sections.
   This may have once been safe, as find_sec skips non-SHF_ALLOC
   sections, but find_any_sec, which will be used to load BTF if that is
   enabled, ignores the SHF_ALLOC flag. Since there's no need to support
   invalid section names, validate all of them, not just SHF_ALLOC
   sections.
2. Section names were validated *after* accessing them for the purposes
   of detecting ".modinfo" and ".gnu.linkonce.this_module". They are now
   checked prior to the access, which could avoid bad accesses with
   malformed modules.

Signed-off-by: Matthew Maurer <mmaurer@google.com>
Reviewed-by: Sami Tolvanen <samitolvanen@google.com>
Signed-off-by: Luis Chamberlain <mcgrof@kernel.org>
This commit is contained in:
Matthew Maurer 2024-10-15 23:16:38 +00:00 committed by Luis Chamberlain
parent c92aab819d
commit 3c5700aeab

View File

@ -1789,6 +1789,71 @@ static int elf_validity_cache_sechdrs(struct load_info *info)
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;
}
/*
* Check userspace passed ELF module against our expectations, and cache
* useful variables for further processing as we go.
@ -1812,7 +1877,7 @@ static int elf_validity_cache_sechdrs(struct load_info *info)
static int elf_validity_cache_copy(struct load_info *info, int flags)
{
unsigned int i;
Elf_Shdr *shdr, *strhdr;
Elf_Shdr *shdr;
int err;
unsigned int num_mod_secs = 0, mod_idx;
unsigned int num_info_secs = 0, info_idx;
@ -1821,34 +1886,9 @@ static int elf_validity_cache_copy(struct load_info *info, int flags)
err = elf_validity_cache_sechdrs(info);
if (err < 0)
return err;
/*
* 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];
/*
* 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;
}
err = elf_validity_cache_secstrings(info);
if (err < 0)
return err;
for (i = 1; i < info->hdr->e_shnum; i++) {
shdr = &info->sechdrs[i];
@ -1877,14 +1917,6 @@ static int elf_validity_cache_copy(struct load_info *info, int flags)
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;
}
}