#include #include #include #include #if defined(MODULEVERIFIER_ELF32) # define SUFFIX(x) x ## 32 # define ELFCLASSXX ELFCLASS32 # define Elf_Ehdr Elf32_Ehdr # define Elf_Phdr Elf32_Phdr # define Elf_Nhdr Elf32_Nhdr # define Elf_Addr Elf32_Addr # define Elf_Sym Elf32_Sym # define Elf_Off Elf32_Off # define Elf_Shdr Elf32_Shdr # define Elf_Rela Elf32_Rela # define Elf_Rel Elf32_Rel # define Elf_Word Elf32_Word # define Elf_Half Elf32_Half # define Elf_Section Elf32_Section # define ELF_R_SYM(val) ELF32_R_SYM(val) # define ELF_R_TYPE(val) ELF32_R_TYPE(val) # define ELF_ST_TYPE(val) ELF32_ST_TYPE(val) #elif defined(MODULEVERIFIER_ELF64) # define SUFFIX(x) x ## 64 # define ELFCLASSXX ELFCLASS64 # define Elf_Ehdr Elf64_Ehdr # define Elf_Phdr Elf64_Phdr # define Elf_Nhdr Elf64_Nhdr # define Elf_Addr Elf64_Addr # define Elf_Sym Elf64_Sym # define Elf_Off Elf64_Off # define Elf_Shdr Elf64_Shdr # define Elf_Rela Elf64_Rela # define Elf_Rel Elf64_Rel # define Elf_Word Elf64_Word # define Elf_Half Elf64_Half # define Elf_Section Elf64_Section # define ELF_R_SYM(val) ELF64_R_SYM(val) # define ELF_R_TYPE(val) ELF64_R_TYPE(val) # define ELF_ST_TYPE(val) ELF64_ST_TYPE(val) #else #error "I'm confused" #endif #define grub_target_to_host32(x) (grub_target_to_host32_real (arch, (x))) #define grub_host_to_target32(x) (grub_host_to_target32_real (arch, (x))) #define grub_target_to_host64(x) (grub_target_to_host64_real (arch, (x))) #define grub_host_to_target64(x) (grub_host_to_target64_real (arch, (x))) #define grub_host_to_target_addr(x) (grub_host_to_target_addr_real (arch, (x))) #define grub_target_to_host16(x) (grub_target_to_host16_real (arch, (x))) #define grub_host_to_target16(x) (grub_host_to_target16_real (arch, (x))) #define grub_target_to_host(val) grub_target_to_host_real(arch, (val)) static inline grub_uint32_t grub_target_to_host32_real (const struct grub_module_verifier_arch *arch, grub_uint32_t in) { if (arch->bigendian) return grub_be_to_cpu32 (in); else return grub_le_to_cpu32 (in); } static inline grub_uint64_t grub_target_to_host64_real (const struct grub_module_verifier_arch *arch, grub_uint64_t in) { if (arch->bigendian) return grub_be_to_cpu64 (in); else return grub_le_to_cpu64 (in); } static inline grub_uint64_t grub_host_to_target64_real (const struct grub_module_verifier_arch *arch, grub_uint64_t in) { if (arch->bigendian) return grub_cpu_to_be64 (in); else return grub_cpu_to_le64 (in); } static inline grub_uint32_t grub_host_to_target32_real (const struct grub_module_verifier_arch *arch, grub_uint32_t in) { if (arch->bigendian) return grub_cpu_to_be32 (in); else return grub_cpu_to_le32 (in); } static inline grub_uint16_t grub_target_to_host16_real (const struct grub_module_verifier_arch *arch, grub_uint16_t in) { if (arch->bigendian) return grub_be_to_cpu16 (in); else return grub_le_to_cpu16 (in); } static inline grub_uint16_t grub_host_to_target16_real (const struct grub_module_verifier_arch *arch, grub_uint16_t in) { if (arch->bigendian) return grub_cpu_to_be16 (in); else return grub_cpu_to_le16 (in); } static inline grub_uint64_t grub_host_to_target_addr_real (const struct grub_module_verifier_arch *arch, grub_uint64_t in) { if (arch->voidp_sizeof == 8) return grub_host_to_target64_real (arch, in); else return grub_host_to_target32_real (arch, in); } static inline grub_uint64_t grub_target_to_host_real (const struct grub_module_verifier_arch *arch, grub_uint64_t in) { if (arch->voidp_sizeof == 8) return grub_target_to_host64_real (arch, in); else return grub_target_to_host32_real (arch, in); } static Elf_Shdr * find_section (const struct grub_module_verifier_arch *arch, Elf_Ehdr *e, const char *name) { Elf_Shdr *s; const char *str; unsigned i; s = (Elf_Shdr *) ((char *) e + grub_target_to_host (e->e_shoff) + grub_target_to_host16 (e->e_shstrndx) * grub_target_to_host16 (e->e_shentsize)); str = (char *) e + grub_target_to_host (s->sh_offset); for (i = 0, s = (Elf_Shdr *) ((char *) e + grub_target_to_host (e->e_shoff)); i < grub_target_to_host16 (e->e_shnum); i++, s = (Elf_Shdr *) ((char *) s + grub_target_to_host16 (e->e_shentsize))) if (strcmp (str + grub_target_to_host32 (s->sh_name), name) == 0) return s; return NULL; } static void check_license (const struct grub_module_verifier_arch *arch, Elf_Ehdr *e) { Elf_Shdr *s = find_section (arch, e, ".module_license"); if (s && (strcmp ((char *) e + grub_target_to_host(s->sh_offset), "LICENSE=GPLv3") == 0 || strcmp ((char *) e + grub_target_to_host(s->sh_offset), "LICENSE=GPLv3+") == 0 || strcmp ((char *) e + grub_target_to_host(s->sh_offset), "LICENSE=GPLv2+") == 0)) return; grub_util_error ("incompatible license"); } static Elf_Sym * get_symtab (const struct grub_module_verifier_arch *arch, Elf_Ehdr *e, Elf_Word *size, Elf_Word *entsize) { unsigned i; Elf_Shdr *s, *sections; Elf_Sym *sym; sections = (Elf_Shdr *) ((char *) e + grub_target_to_host (e->e_shoff)); for (i = 0, s = sections; i < grub_target_to_host16 (e->e_shnum); i++, s = (Elf_Shdr *) ((char *) s + grub_target_to_host16 (e->e_shentsize))) if (grub_target_to_host32 (s->sh_type) == SHT_SYMTAB) break; if (i == grub_target_to_host16 (e->e_shnum)) return NULL; sym = (Elf_Sym *) ((char *) e + grub_target_to_host (s->sh_offset)); *size = grub_target_to_host (s->sh_size); *entsize = grub_target_to_host (s->sh_entsize); return sym; } static int is_whitelisted (const char *modname, const char **whitelist) { const char **ptr; if (!whitelist) return 0; if (!modname) return 0; for (ptr = whitelist; *ptr; ptr++) if (strcmp (modname, *ptr) == 0) return 1; return 0; } static void check_symbols (const struct grub_module_verifier_arch *arch, Elf_Ehdr *e, const char *modname, const char **whitelist_empty) { Elf_Sym *sym; Elf_Word size, entsize; unsigned i; /* Module without symbol table and without .moddeps section is useless at boot time, so catch it early to prevent build errors */ sym = get_symtab (arch, e, &size, &entsize); if (!sym) { Elf_Shdr *s; /* However some modules are dependencies-only, e.g. insmod all_video pulls in all video drivers. Some platforms e.g. xen have no video drivers, so the module does nothing. */ if (is_whitelisted (modname, whitelist_empty)) return; s = find_section (arch, e, ".moddeps"); if (!s) grub_util_error ("no symbol table and no .moddeps section"); if (!s->sh_size) grub_util_error ("no symbol table and empty .moddeps section"); return; } for (i = 0; i < size / entsize; i++, sym = (Elf_Sym *) ((char *) sym + entsize)) { unsigned char type = ELF_ST_TYPE (sym->st_info); switch (type) { case STT_NOTYPE: case STT_OBJECT: case STT_FUNC: case STT_SECTION: case STT_FILE: break; default: return grub_util_error ("unknown symbol type `%d'", (int) type); } } } static int is_symbol_local(Elf_Sym *sym) { switch (ELF_ST_TYPE (sym->st_info)) { case STT_NOTYPE: case STT_OBJECT: if (sym->st_name != 0 && sym->st_shndx == 0) return 0; return 1; case STT_FUNC: case STT_SECTION: return 1; default: return 0; } } static void section_check_relocations (const struct grub_module_verifier_arch *arch, void *ehdr, Elf_Shdr *s, size_t target_seg_size) { Elf_Rel *rel, *max; Elf_Sym *symtab; Elf_Word symtabsize, symtabentsize; symtab = get_symtab (arch, ehdr, &symtabsize, &symtabentsize); if (!symtab) grub_util_error ("relocation without symbol table"); for (rel = (Elf_Rel *) ((char *) ehdr + grub_target_to_host (s->sh_offset)), max = (Elf_Rel *) ((char *) rel + grub_target_to_host (s->sh_size)); rel < max; rel = (Elf_Rel *) ((char *) rel + grub_target_to_host (s->sh_entsize))) { Elf_Sym *sym; unsigned i; if (target_seg_size < grub_target_to_host (rel->r_offset)) grub_util_error ("reloc offset is out of the segment"); grub_uint32_t type = ELF_R_TYPE (grub_target_to_host (rel->r_info)); if (arch->machine == EM_SPARCV9) type &= 0xff; for (i = 0; arch->supported_relocations[i] != -1; i++) if (type == arch->supported_relocations[i]) break; if (arch->supported_relocations[i] != -1) continue; if (!arch->short_relocations) grub_util_error ("unsupported relocation 0x%x", type); for (i = 0; arch->short_relocations[i] != -1; i++) if (type == arch->short_relocations[i]) break; if (arch->short_relocations[i] == -1) grub_util_error ("unsupported relocation 0x%x", type); sym = (Elf_Sym *) ((char *) symtab + symtabentsize * ELF_R_SYM (grub_target_to_host (rel->r_info))); if (is_symbol_local (sym)) continue; grub_util_error ("relocation 0x%x is not module-local", type); } #if defined(MODULEVERIFIER_ELF64) if (arch->machine == EM_AARCH64) { unsigned unmatched_adr_got_page = 0; Elf_Rela *rel2; for (rel = (Elf_Rel *) ((char *) ehdr + grub_target_to_host (s->sh_offset)), max = (Elf_Rel *) ((char *) rel + grub_target_to_host (s->sh_size)); rel < max; rel = (Elf_Rel *) ((char *) rel + grub_target_to_host (s->sh_entsize))) { switch (ELF_R_TYPE (grub_target_to_host (rel->r_info))) { case R_AARCH64_ADR_GOT_PAGE: unmatched_adr_got_page++; for (rel2 = (Elf_Rela *) ((char *) rel + grub_target_to_host (s->sh_entsize)); rel2 < (Elf_Rela *) max; rel2 = (Elf_Rela *) ((char *) rel2 + grub_target_to_host (s->sh_entsize))) if (ELF_R_SYM (rel2->r_info) == ELF_R_SYM (rel->r_info) && ((Elf_Rela *) rel)->r_addend == rel2->r_addend && ELF_R_TYPE (rel2->r_info) == R_AARCH64_LD64_GOT_LO12_NC) break; if (rel2 >= (Elf_Rela *) max) grub_util_error ("ADR_GOT_PAGE without matching LD64_GOT_LO12_NC"); break; case R_AARCH64_LD64_GOT_LO12_NC: if (unmatched_adr_got_page == 0) grub_util_error ("LD64_GOT_LO12_NC without matching ADR_GOT_PAGE"); unmatched_adr_got_page--; break; } } } #endif } static void check_relocations (const struct grub_module_verifier_arch *arch, Elf_Ehdr *e) { Elf_Shdr *s; unsigned i; for (i = 0, s = (Elf_Shdr *) ((char *) e + grub_target_to_host (e->e_shoff)); i < grub_target_to_host16 (e->e_shnum); i++, s = (Elf_Shdr *) ((char *) s + grub_target_to_host16 (e->e_shentsize))) if (grub_target_to_host32 (s->sh_type) == SHT_REL || grub_target_to_host32 (s->sh_type) == SHT_RELA) { Elf_Shdr *ts; if (grub_target_to_host32 (s->sh_type) == SHT_REL && !(arch->flags & GRUB_MODULE_VERIFY_SUPPORTS_REL)) grub_util_error ("unsupported SHT_REL"); if (grub_target_to_host32 (s->sh_type) == SHT_RELA && !(arch->flags & GRUB_MODULE_VERIFY_SUPPORTS_RELA)) grub_util_error ("unsupported SHT_RELA"); /* Find the target segment. */ if (grub_target_to_host32 (s->sh_info) >= grub_target_to_host16 (e->e_shnum)) grub_util_error ("orphaned reloc section"); ts = (Elf_Shdr *) ((char *) e + grub_target_to_host (e->e_shoff) + grub_target_to_host32 (s->sh_info) * grub_target_to_host16 (e->e_shentsize)); section_check_relocations (arch, e, s, grub_target_to_host (ts->sh_size)); } } void SUFFIX(grub_module_verify) (void *module_img, size_t size, const struct grub_module_verifier_arch *arch, const char **whitelist_empty) { Elf_Ehdr *e = module_img; /* Check the header size. */ if (size < sizeof (Elf_Ehdr)) grub_util_error ("ELF header smaller than expected"); /* Check the magic numbers. */ if (e->e_ident[EI_MAG0] != ELFMAG0 || e->e_ident[EI_MAG1] != ELFMAG1 || e->e_ident[EI_MAG2] != ELFMAG2 || e->e_ident[EI_MAG3] != ELFMAG3 || e->e_ident[EI_VERSION] != EV_CURRENT || grub_target_to_host32 (e->e_version) != EV_CURRENT) grub_util_error ("invalid arch-independent ELF magic"); if (e->e_ident[EI_CLASS] != ELFCLASSXX || e->e_ident[EI_DATA] != (arch->bigendian ? ELFDATA2MSB : ELFDATA2LSB) || grub_target_to_host16 (e->e_machine) != arch->machine) grub_util_error ("invalid arch-dependent ELF magic"); if (grub_target_to_host16 (e->e_type) != ET_REL) { grub_util_error ("this ELF file is not of the right type"); } /* Make sure that every section is within the core. */ if (size < grub_target_to_host (e->e_shoff) + (grub_uint32_t) grub_target_to_host16 (e->e_shentsize) * grub_target_to_host16(e->e_shnum)) { grub_util_error ("ELF sections outside core"); } check_license (arch, e); Elf_Shdr *s; const char *modname; s = find_section (arch, e, ".modname"); if (!s) grub_util_error ("no module name found"); modname = (const char *) e + grub_target_to_host (s->sh_offset); check_symbols(arch, e, modname, whitelist_empty); check_relocations(arch, e); }