From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Peter Jones Date: Mon, 21 Mar 2022 17:46:35 -0400 Subject: [PATCH] nx: set page permissions for loaded modules. For NX, we need to set write and executable permissions on the sections of grub modules when we load them. On sections with SHF_ALLOC set, which is typically everything except .modname and the symbol and string tables, this patch clears the Read Only flag on sections that have the ELF flag SHF_WRITE set, and clears the No eXecute flag on sections with SHF_EXECINSTR set. In all other cases it sets both flags. Signed-off-by: Peter Jones [rharwood: arm tgptr -> tgaddr] Signed-off-by: Robbie Harwood --- grub-core/kern/dl.c | 120 +++++++++++++++++++++++++++++++++++++++------------- include/grub/dl.h | 44 +++++++++++++++++++ 2 files changed, 134 insertions(+), 30 deletions(-) diff --git a/grub-core/kern/dl.c b/grub-core/kern/dl.c index 8c7aacef39..d5de80186f 100644 --- a/grub-core/kern/dl.c +++ b/grub-core/kern/dl.c @@ -285,6 +285,8 @@ grub_dl_load_segments (grub_dl_t mod, const Elf_Ehdr *e) #endif char *ptr; + grub_dprintf ("modules", "loading segments for \"%s\"\n", mod->name); + arch_addralign = grub_arch_dl_min_alignment (); for (i = 0, s = (const Elf_Shdr *)((const char *) e + e->e_shoff); @@ -384,6 +386,7 @@ grub_dl_load_segments (grub_dl_t mod, const Elf_Ehdr *e) ptr += got; #endif + grub_dprintf ("modules", "done loading segments for \"%s\"\n", mod->name); return GRUB_ERR_NONE; } @@ -517,23 +520,6 @@ grub_dl_find_section (Elf_Ehdr *e, const char *name) return s; return NULL; } -static long -grub_dl_find_section_index (Elf_Ehdr *e, const char *name) -{ - Elf_Shdr *s; - const char *str; - unsigned i; - - s = (Elf_Shdr *) ((char *) e + e->e_shoff + e->e_shstrndx * e->e_shentsize); - str = (char *) e + s->sh_offset; - - for (i = 0, s = (Elf_Shdr *) ((char *) e + e->e_shoff); - i < e->e_shnum; - i++, s = (Elf_Shdr *) ((char *) s + e->e_shentsize)) - if (grub_strcmp (str + s->sh_name, name) == 0) - return (long)i; - return -1; -} /* Me, Vladimir Serbinenko, hereby I add this module check as per new GNU module policy. Note that this license check is informative only. @@ -662,6 +648,7 @@ grub_dl_relocate_symbols (grub_dl_t mod, void *ehdr) Elf_Shdr *s; unsigned i; + grub_dprintf ("modules", "relocating symbols for \"%s\"\n", mod->name); for (i = 0, s = (Elf_Shdr *) ((char *) e + e->e_shoff); i < e->e_shnum; i++, s = (Elf_Shdr *) ((char *) s + e->e_shentsize)) @@ -670,24 +657,95 @@ grub_dl_relocate_symbols (grub_dl_t mod, void *ehdr) grub_dl_segment_t seg; grub_err_t err; - /* Find the target segment. */ - for (seg = mod->segment; seg; seg = seg->next) - if (seg->section == s->sh_info) - break; + seg = grub_dl_find_segment(mod, s->sh_info); + if (!seg) + continue; - if (seg) - { - if (!mod->symtab) - return grub_error (GRUB_ERR_BAD_MODULE, "relocation without symbol table"); + if (!mod->symtab) + return grub_error (GRUB_ERR_BAD_MODULE, "relocation without symbol table"); - err = grub_arch_dl_relocate_symbols (mod, ehdr, s, seg); - if (err) - return err; - } + err = grub_arch_dl_relocate_symbols (mod, ehdr, s, seg); + if (err) + return err; } + grub_dprintf ("modules", "done relocating symbols for \"%s\"\n", mod->name); return GRUB_ERR_NONE; } + +static grub_err_t +grub_dl_set_mem_attrs (grub_dl_t mod, void *ehdr) +{ + unsigned i; + const Elf_Shdr *s; + const Elf_Ehdr *e = ehdr; +#if !defined (__i386__) && !defined (__x86_64__) && !defined(__riscv) + grub_size_t arch_addralign = grub_arch_dl_min_alignment (); + grub_addr_t tgaddr; + grub_uint64_t tgsz; +#endif + + grub_dprintf ("modules", "updating memory attributes for \"%s\"\n", + mod->name); + for (i = 0, s = (const Elf_Shdr *)((const char *) e + e->e_shoff); + i < e->e_shnum; + i++, s = (const Elf_Shdr *)((const char *) s + e->e_shentsize)) + { + grub_dl_segment_t seg; + grub_uint64_t set_attrs = GRUB_MEM_ATTR_R; + grub_uint64_t clear_attrs = GRUB_MEM_ATTR_W|GRUB_MEM_ATTR_X; + + seg = grub_dl_find_segment(mod, i); + if (!seg) + continue; + + if (seg->size == 0 || !(s->sh_flags & SHF_ALLOC)) + continue; + + if (s->sh_flags & SHF_WRITE) + { + set_attrs |= GRUB_MEM_ATTR_W; + clear_attrs &= ~GRUB_MEM_ATTR_W; + } + + if (s->sh_flags & SHF_EXECINSTR) + { + set_attrs |= GRUB_MEM_ATTR_X; + clear_attrs &= ~GRUB_MEM_ATTR_X; + } + + grub_dprintf ("modules", "setting memory attrs for section \"%s\" to -%s%s%s+%s%s%s\n", + grub_dl_get_section_name(e, s), + (clear_attrs & GRUB_MEM_ATTR_R) ? "r" : "", + (clear_attrs & GRUB_MEM_ATTR_W) ? "w" : "", + (clear_attrs & GRUB_MEM_ATTR_X) ? "x" : "", + (set_attrs & GRUB_MEM_ATTR_R) ? "r" : "", + (set_attrs & GRUB_MEM_ATTR_W) ? "w" : "", + (set_attrs & GRUB_MEM_ATTR_X) ? "x" : ""); + grub_update_mem_attrs ((grub_addr_t)(seg->addr), seg->size, set_attrs, clear_attrs); + } + +#if !defined (__i386__) && !defined (__x86_64__) && !defined(__riscv) + tgaddr = grub_min((grub_addr_t)mod->tramp, (grub_addr_t)mod->got); + tgsz = grub_max((grub_addr_t)mod->trampptr, (grub_addr_t)mod->gotptr) - tgaddr; + + if (tgsz) + { + tgsz = ALIGN_UP(tgsz, arch_addralign); + + grub_dprintf ("modules", "updating attributes for GOT and trampolines\n", + mod->name); + grub_update_mem_attrs (tgaddr, tgsz, GRUB_MEM_ATTR_R|GRUB_MEM_ATTR_X, + GRUB_MEM_ATTR_W); + } +#endif + + grub_dprintf ("modules", "done updating module memory attributes for \"%s\"\n", + mod->name); + + return GRUB_ERR_NONE; +} + static void grub_dl_print_gdb_info (grub_dl_t mod, Elf_Ehdr *e) { @@ -753,6 +811,7 @@ grub_dl_load_core_noinit (void *addr, grub_size_t size) mod->ref_count = 1; grub_dprintf ("modules", "relocating to %p\n", mod); + /* Me, Vladimir Serbinenko, hereby I add this module check as per new GNU module policy. Note that this license check is informative only. Modules have to be licensed under GPLv3 or GPLv3+ (optionally @@ -766,7 +825,8 @@ grub_dl_load_core_noinit (void *addr, grub_size_t size) || grub_dl_resolve_dependencies (mod, e) || grub_dl_load_segments (mod, e) || grub_dl_resolve_symbols (mod, e) - || grub_dl_relocate_symbols (mod, e)) + || grub_dl_relocate_symbols (mod, e) + || grub_dl_set_mem_attrs (mod, e)) { mod->fini = 0; grub_dl_unload (mod); diff --git a/include/grub/dl.h b/include/grub/dl.h index f36ed5cb17..45ac8e339f 100644 --- a/include/grub/dl.h +++ b/include/grub/dl.h @@ -27,6 +27,7 @@ #include #include #include +#include #endif /* @@ -268,6 +269,49 @@ grub_dl_is_persistent (grub_dl_t mod) return mod->persistent; } +static inline const char * +grub_dl_get_section_name (const Elf_Ehdr *e, const Elf_Shdr *s) +{ + Elf_Shdr *str_s; + const char *str; + + str_s = (Elf_Shdr *) ((char *) e + e->e_shoff + e->e_shstrndx * e->e_shentsize); + str = (char *) e + str_s->sh_offset; + + return str + s->sh_name; +} + +static inline long +grub_dl_find_section_index (Elf_Ehdr *e, const char *name) +{ + Elf_Shdr *s; + const char *str; + unsigned i; + + s = (Elf_Shdr *) ((char *) e + e->e_shoff + e->e_shstrndx * e->e_shentsize); + str = (char *) e + s->sh_offset; + + for (i = 0, s = (Elf_Shdr *) ((char *) e + e->e_shoff); + i < e->e_shnum; + i++, s = (Elf_Shdr *) ((char *) s + e->e_shentsize)) + if (grub_strcmp (str + s->sh_name, name) == 0) + return (long)i; + return -1; +} + +/* Return the segment for a section of index N */ +static inline grub_dl_segment_t +grub_dl_find_segment (grub_dl_t mod, unsigned n) +{ + grub_dl_segment_t seg; + + for (seg = mod->segment; seg; seg = seg->next) + if (seg->section == n) + return seg; + + return NULL; +} + #endif void * EXPORT_FUNC(grub_resolve_symbol) (const char *name);