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 <mmau...@google.com>
---
 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 59c977acfb44..1f3a07ee59c6 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -1708,6 +1708,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.
@@ -1737,29 +1818,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.
         */
@@ -1772,11 +1834,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
@@ -1793,18 +1850,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) {
@@ -1823,12 +1868,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.46.1.824.gd892dcdcdd-goog


Reply via email to