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