[PATCH 03/12] module: Factor out elf_validity_cache_sechdrs

From: Matthew Maurer
Date: Tue Oct 15 2024 - 19:17:45 EST


Factor out and document the validation of section headers.

Because we now validate all section offsets and lengths before accessing
them, we can remove the ad-hoc checks.

Signed-off-by: Matthew Maurer <mmaurer@xxxxxxxxxx>
---
kernel/module/main.c | 125 ++++++++++++++++++++++++++++---------------
1 file changed, 82 insertions(+), 43 deletions(-)

diff --git a/kernel/module/main.c b/kernel/module/main.c
index c836354928f0..467e35f0232a 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -1741,6 +1741,87 @@ static int elf_validity_ehdr(const struct load_info *info)
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;
+}
+
/*
* Check userspace passed ELF module against our expectations, and cache
* useful variables for further processing as we go.
@@ -1770,29 +1851,10 @@ static int elf_validity_cache_copy(struct load_info *info, int flags)
unsigned int num_info_secs = 0, info_idx;
unsigned int num_sym_secs = 0, sym_idx;

- err = elf_validity_ehdr(info);
+ err = elf_validity_cache_sechdrs(info);
if (err < 0)
return err;

- 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.
*/
@@ -1805,11 +1867,6 @@ static int elf_validity_cache_copy(struct load_info *info, int flags)
}

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);
- return err;
- }

/*
* The section name table must be NUL-terminated, as required
@@ -1826,18 +1883,6 @@ static int elf_validity_cache_copy(struct load_info *info, int flags)
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) {
@@ -1856,12 +1901,6 @@ static int elf_validity_cache_copy(struct load_info *info, int flags)
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++;
--
2.47.0.rc1.288.g06298d1525-goog