]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
linux: Add support for PT_GNU_MUTABLE azanella/memory-seal-v6
authorAdhemerval Zanella <adhemerval.zanella@linaro.org>
Fri, 21 Feb 2025 15:53:07 +0000 (12:53 -0300)
committerAdhemerval Zanella <adhemerval.zanella@linaro.org>
Fri, 7 Mar 2025 11:46:49 +0000 (08:46 -0300)
The section mark a memory region that should not be sealed if
GNU_PROPERTY_MEMORY_SEAL attribute is present.  PT_GNU_MUTABLE
section names start with ".gnu.mutable" and are maximum page
aligned and have a size of maximum page size.

For instance the code:

  #define GNU_MUTABLE_SECTION_NAME       ".gnu.mutable"

  unsigned char mutable_array1[64]
    __attribute__ ((section (GNU_MUTABLE_SECTION_NAME)))
       = { 0 };
  unsigned char mutable_array2[32]
    __attribute__ ((section (GNU_MUTABLE_SECTION_NAME)))
       = { 0 };

places both 'mutable_array1' and 'mutable_array2' on a page
aligned memory region in a size of a page (the alignment and size
can be change with -Wl,-z,max-page-size= linker option).

The linker sets the alignment and size to make it easier to
loader to avoid sealing the area (since mseal only work on
multiple of page size areas), and to simplify the userland
process to change protection of either seal the area after
initialization.

12 files changed:
elf/dl-load.c
elf/dl-reloc.c
elf/dl-support.c
elf/elf.h
elf/rtld.c
include/link.h
sysdeps/unix/sysv/linux/Makefile
sysdeps/unix/sysv/linux/tst-dl_mseal-mutable-dlopen.c [new file with mode: 0644]
sysdeps/unix/sysv/linux/tst-dl_mseal-mutable-mod.c [new file with mode: 0644]
sysdeps/unix/sysv/linux/tst-dl_mseal-mutable-mod.h [new file with mode: 0644]
sysdeps/unix/sysv/linux/tst-dl_mseal-mutable-static.c [new file with mode: 0644]
sysdeps/unix/sysv/linux/tst-dl_mseal-mutable.c [new file with mode: 0644]

index f104cc75441a041c5398dd629c96da261d457b8e..a0d7d30e58399f368da0441ae09123c3f509bb54 100644 (file)
@@ -1220,6 +1220,11 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
          l->l_relro_addr = ph->p_vaddr;
          l->l_relro_size = ph->p_memsz;
          break;
+
+       case PT_GNU_MUTABLE:
+         l->l_mutable_addr = ph->p_vaddr;
+         l->l_mutable_size = ph->p_memsz;
+         break;
        }
 
     if (__glibc_unlikely (nloadcmds == 0))
index 2b37676182352592f535d02429a01c87ba1b98a5..d706a571017154c273b4d0cac8c86689f1d711a7 100644 (file)
@@ -37,7 +37,6 @@
 # define bump_num_cache_relocations() ((void) 0)
 #endif
 
-
 /* We are trying to perform a static TLS relocation in MAP, but it was
    dynamically loaded.  This can only work if there is enough surplus in
    the static TLS area already allocated for each running thread.  If this
@@ -371,6 +370,29 @@ cannot apply additional memory protection after relocation");
     }
 }
 
+static void
+_dl_mseal_map_2 (const struct link_map *l, ElfW(Addr) map_start,
+                ElfW(Addr) map_end)
+{
+  ElfW(Addr) mutable_start = 0, mutable_end = 0;
+  if (l->l_mutable_size != 0)
+    {
+      mutable_start = l->l_addr + l->l_mutable_addr;
+      mutable_end = mutable_start + l->l_mutable_size;
+    }
+
+  if (mutable_start >= map_start && mutable_end < map_end)
+    {
+      size_t seg1_size = mutable_start - map_start;
+      size_t seg2_size = map_end - mutable_end;
+      _dl_mseal ((void *) map_start, seg1_size, l->l_name);
+      if (seg2_size != 0)
+       _dl_mseal ((void *) mutable_end, seg2_size, l->l_name);
+    }
+  else
+    _dl_mseal ((void *) map_start, map_end - map_start, l->l_name);
+}
+
 static void
 _dl_mseal_map_1 (struct link_map *l, bool dep)
 {
@@ -388,8 +410,7 @@ _dl_mseal_map_1 (struct link_map *l, bool dep)
     return;
 
   if (l->l_contiguous)
-     _dl_mseal ((void *) l->l_map_start, l->l_map_end - l->l_map_start,
-               l->l_name);
+    _dl_mseal_map_2 (l, l->l_map_start, l->l_map_end);
   else
     {
       /* We can use the PT_LOAD segments because even if relro splits the
@@ -404,7 +425,7 @@ _dl_mseal_map_1 (struct link_map *l, bool dep)
              ElfW(Addr) mapstart = l->l_addr
                  + (ph->p_vaddr & ~(GLRO(dl_pagesize) - 1));
              ElfW(Addr) allocend = l->l_addr + ph->p_vaddr + ph->p_memsz;
-             _dl_mseal ((void *) mapstart, allocend - mapstart, l->l_name);
+             _dl_mseal_map_2 (l, mapstart, allocend);
            }
            break;
        }
index ab74f3b51ca29e092dbe5296ce777c422cf21002..62273972375b432021dfc00eee724364131ccf08 100644 (file)
@@ -334,6 +334,11 @@ _dl_non_dynamic_init (void)
        _dl_main_map.l_relro_addr = ph->p_vaddr;
        _dl_main_map.l_relro_size = ph->p_memsz;
        break;
+
+      case PT_GNU_MUTABLE:
+       _dl_main_map.l_mutable_addr = ph->p_vaddr;
+       _dl_main_map.l_mutable_size = ph->p_memsz;
+       break;
       }
   /* Process program headers again, but scan them backwards so
      that PT_NOTE can be skipped if PT_GNU_PROPERTY exits.  */
index f7d38eeffbc0fb1b871456573dbeddc821712b5b..efde5bae73b47fae865edff581249896f711f7fb 100644 (file)
--- a/elf/elf.h
+++ b/elf/elf.h
@@ -729,6 +729,7 @@ typedef struct
 #define PT_GNU_RELRO   0x6474e552      /* Read-only after relocation */
 #define PT_GNU_PROPERTY        0x6474e553      /* GNU property */
 #define PT_GNU_SFRAME  0x6474e554      /* SFrame segment.  */
+#define PT_GNU_MUTABLE 0x6474f555      /* Like bss, but not immutable.  */
 #define PT_LOSUNW      0x6ffffffa
 #define PT_SUNWBSS     0x6ffffffa      /* Sun Specific segment */
 #define PT_SUNWSTACK   0x6ffffffb      /* Stack segment */
@@ -1352,6 +1353,7 @@ typedef struct
 
 /* Note section name of program property.   */
 #define NOTE_GNU_PROPERTY_SECTION_NAME ".note.gnu.property"
+#define GNU_MUTABLE_SECTION_NAME       ".gnu.mutable"
 
 /* Values used in GNU .note.gnu.property notes (NT_GNU_PROPERTY_TYPE_0).  */
 
index 305fbda7139c2f24aca4ee11dcd51dee8b4aeff0..1fba19db75cfd245eae56ed4bfa1da052a237d63 100644 (file)
@@ -1209,6 +1209,11 @@ rtld_setup_main_map (struct link_map *main_map)
        main_map->l_relro_addr = ph->p_vaddr;
        main_map->l_relro_size = ph->p_memsz;
        break;
+
+      case PT_GNU_MUTABLE:
+       main_map->l_mutable_addr = ph->p_vaddr;
+       main_map->l_mutable_size = ph->p_memsz;
+       break;
       }
   /* Process program headers again, but scan them backwards so
      that PT_NOTE can be skipped if PT_GNU_PROPERTY exits.  */
index 677d82b38bfb77ccfddc7f6fe78c7a82608ee094..c77fbf10dea60a87824ee970351d47b325af670c 100644 (file)
@@ -353,6 +353,10 @@ struct link_map
     ElfW(Addr) l_relro_addr;
     size_t l_relro_size;
 
+    /* Information used to not memory seal after relocations are done.  */
+    ElfW(Addr) l_mutable_addr;
+    size_t l_mutable_size;
+
     unsigned long long int l_serial;
   };
 
index 8fe74be95f0e0cc17ab9450bd2948eaadaebe719..d14ba1b121126ffec1bfdb420712e8e1d74b7c84 100644 (file)
@@ -690,6 +690,7 @@ endif
 
 ifeq ($(have-z-memory-seal),yes)
 tests-static += \
+  tst-dl_mseal-mutable-static \
   tst-dl_mseal-static \
   tst-dl_mseal-static-noseal \
   # tests-static
@@ -697,6 +698,7 @@ tests-static += \
 tests += \
   $(tests-static) \
   tst-dl_mseal \
+  tst-dl_mseal-mutable \
   tst-dl_mseal-noseal \
   # tests
 
@@ -708,6 +710,8 @@ modules-names += \
   tst-dl_mseal-dlopen-2-1 \
   tst-dl_mseal-mod-1 \
   tst-dl_mseal-mod-2 \
+  tst-dl_mseal-mutable-dlopen \
+  tst-dl_mseal-mutable-mod \
   tst-dl_mseal-preload \
   # modules-names
 
@@ -731,6 +735,10 @@ $(objpfx)tst-dl_mseal-noseal.out: \
   $(objpfx)tst-dl_mseal-dlopen-2.so \
   $(objpfx)tst-dl_mseal-dlopen-2-1.so
 
+$(objpfx)tst-dl_mseal-mutable.out: \
+  $(objpfx)tst-dl_mseal-mutable-mod.so \
+  $(objpfx)tst-dl_mseal-mutable-dlopen.so
+
 ifeq ($(enable-memory-seal),yes)
 CFLAGS-tst-dl_mseal.c += -DDEFAULT_MEMORY_SEAL
 CFLAGS-tst-dl_mseal-noseal.c += -DDEFAULT_MEMORY_SEAL
@@ -738,6 +746,8 @@ endif
 
 LDFLAGS-tst-dl_mseal = -Wl,--no-as-needed -Wl,-z,memory-seal
 LDFLAGS-tst-dl_mseal-static = -Wl,--no-as-needed -Wl,-z,memory-seal
+LDFLAGS-tst-dl_mseal-mutable = -Wl,--no-as-needed -Wl,-z,memory-seal
+LDFLAGS-tst-dl_mseal-mutable-static = -Wl,-z,memory-seal
 LDFLAGS-tst-dl_mseal-mod-1.so = -Wl,--no-as-needed -Wl,-z,memory-seal
 LDFLAGS-tst-dl_mseal-mod-2.so = -Wl,-z,memory-seal
 LDFLAGS-tst-dl_mseal-dlopen-1.so = -Wl,--no-as-needed
@@ -745,12 +755,16 @@ LDFLAGS-tst-dl_mseal-dlopen-2.so = -Wl,--no-as-needed -Wl,-z,memory-seal
 LDFLAGS-tst-dl_mseal-preload.so = -Wl,-z,memory-seal
 LDFLAGS-tst-dl_mseal-auditmod.so = -Wl,-z,memory-seal
 
+LDFLAGS-tst-dl_mseal-mutable-mod.so = -Wl,-z,memory-seal
+LDFLAGS-tst-dl_mseal-mutable-dlopen.so = -Wl,-z,memory-seal
+
 tst-dl_mseal-noseal-no-memory-seal = yes
 tst-dl_mseal-dlopen-1-1.so-no-memory-seal = yes
 tst-dl_mseal-dlopen-2-1.so-no-memory-seal = yes
 
 $(objpfx)tst-dl_mseal: $(objpfx)tst-dl_mseal-mod-1.so
 $(objpfx)tst-dl_mseal-noseal: $(objpfx)tst-dl_mseal-mod-1.so
+$(objpfx)tst-dl_mseal-mutable: $(objpfx)tst-dl_mseal-mutable-mod.so
 $(objpfx)tst-dl_mseal-mod-1.so: $(objpfx)tst-dl_mseal-mod-2.so
 $(objpfx)tst-dl_mseal-dlopen-1.so: $(objpfx)tst-dl_mseal-dlopen-1-1.so
 $(objpfx)tst-dl_mseal-dlopen-2.so: $(objpfx)tst-dl_mseal-dlopen-2-1.so
diff --git a/sysdeps/unix/sysv/linux/tst-dl_mseal-mutable-dlopen.c b/sysdeps/unix/sysv/linux/tst-dl_mseal-mutable-dlopen.c
new file mode 100644 (file)
index 0000000..325b200
--- /dev/null
@@ -0,0 +1 @@
+#include "tst-dl_mseal-mutable-mod.c"
diff --git a/sysdeps/unix/sysv/linux/tst-dl_mseal-mutable-mod.c b/sysdeps/unix/sysv/linux/tst-dl_mseal-mutable-mod.c
new file mode 100644 (file)
index 0000000..fb7cf03
--- /dev/null
@@ -0,0 +1,47 @@
+/* Check if PT_OPENBSD_MUTABLE is correctly applied.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <elf.h>
+#include "tst-dl_mseal-mutable-mod.h"
+
+static unsigned char mutable_array1[128]
+  __attribute__ ((section (GNU_MUTABLE_SECTION_NAME)))
+     = { 0 };
+static unsigned char mutable_array2[256]
+  __attribute__ ((section (GNU_MUTABLE_SECTION_NAME)))
+     = { 0 };
+
+static unsigned char immutable_array[256];
+
+struct array_t
+get_mutable_array1 (void)
+{
+  return (struct array_t) { mutable_array1, sizeof (mutable_array1) };
+}
+
+struct array_t
+get_mutable_array2 (void)
+{
+  return (struct array_t) { mutable_array2, sizeof (mutable_array2) };
+}
+
+struct array_t
+get_immutable_array (void)
+{
+  return (struct array_t) { immutable_array, sizeof (immutable_array) };
+}
diff --git a/sysdeps/unix/sysv/linux/tst-dl_mseal-mutable-mod.h b/sysdeps/unix/sysv/linux/tst-dl_mseal-mutable-mod.h
new file mode 100644 (file)
index 0000000..cb01537
--- /dev/null
@@ -0,0 +1,33 @@
+/* Check if PT_OPENBSD_MUTABLE is correctly applied.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <stddef.h>
+
+#define LIB_DLOPEN "tst-dl_mseal-mutable-dlopen.so"
+
+struct array_t
+{
+  unsigned char *arr;
+  size_t size;
+};
+
+typedef struct array_t (*get_array_t)(void);
+
+struct array_t get_mutable_array1 (void);
+struct array_t get_mutable_array2 (void);
+struct array_t get_immutable_array (void);
diff --git a/sysdeps/unix/sysv/linux/tst-dl_mseal-mutable-static.c b/sysdeps/unix/sysv/linux/tst-dl_mseal-mutable-static.c
new file mode 100644 (file)
index 0000000..550ef3f
--- /dev/null
@@ -0,0 +1,2 @@
+#define TEST_STATIC 1
+#include "tst-dl_mseal-mutable.c"
diff --git a/sysdeps/unix/sysv/linux/tst-dl_mseal-mutable.c b/sysdeps/unix/sysv/linux/tst-dl_mseal-mutable.c
new file mode 100644 (file)
index 0000000..d547e05
--- /dev/null
@@ -0,0 +1,242 @@
+/* Check if PT_OPENBSD_MUTABLE is correctly applied.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <dlfcn.h>
+#include <errno.h>
+#include <link.h>
+#include <setjmp.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/mman.h>
+
+#include <libc-pointer-arith.h>
+#include <support/check.h>
+#include <support/test-driver.h>
+#include <support/xdlfcn.h>
+#include <support/xsignal.h>
+#include <support/xunistd.h>
+
+#include "tst-dl_mseal-mutable-mod.h"
+
+static long int pagesize;
+
+/* To check if the protection flags are correctly set, the thread tries
+   read/writes on it and checks if a SIGSEGV is generated.  */
+
+static volatile sig_atomic_t signal_jump_set;
+static sigjmp_buf signal_jmp_buf;
+
+static void
+sigsegv_handler (int sig)
+{
+  if (signal_jump_set == 0)
+    return;
+
+  siglongjmp (signal_jmp_buf, sig);
+}
+
+static bool
+try_access_buf (unsigned char *ptr, bool write)
+{
+  signal_jump_set = true;
+
+  bool failed = sigsetjmp (signal_jmp_buf, 0) != 0;
+  if (!failed)
+    {
+      if (write)
+       *(volatile unsigned char *)(ptr) = 'x';
+      else
+       *(volatile unsigned char *)(ptr);
+    }
+
+  signal_jump_set = false;
+  return !failed;
+}
+
+struct range_t
+{
+  const char *name;
+  unsigned char *start;
+  size_t size;
+  bool found;
+};
+
+static int
+callback (struct dl_phdr_info *info, size_t size, void *data)
+{
+  struct range_t *range = data;
+  if (strcmp (info->dlpi_name, range->name) != 0)
+    return 0;
+
+  for (size_t i = 0; i < info->dlpi_phnum; i++)
+    if (info->dlpi_phdr[i].p_type == PT_GNU_MUTABLE)
+      {
+       range->start = (unsigned char *) info->dlpi_phdr[i].p_vaddr;
+       range->size = info->dlpi_phdr[i].p_memsz;
+       range->found = true;
+       break;
+      }
+
+  return 0;
+}
+
+static bool
+find_mutable_range (void *addr, struct range_t *range)
+{
+  struct dl_find_object dlfo;
+  if (_dl_find_object (addr, &dlfo) != 0)
+    return false;
+
+  range->name = dlfo.dlfo_link_map->l_name;
+  range->found = false;
+  dl_iterate_phdr (callback, range);
+  if (range->found)
+    range->start = dlfo.dlfo_link_map->l_addr + range->start;
+
+  return range->found;
+}
+
+static bool
+__attribute_used__
+try_read_buf (unsigned char *ptr)
+{
+  return try_access_buf (ptr, false);
+}
+
+static bool
+__attribute_used__
+try_write_buf (unsigned char *ptr)
+{
+  return try_access_buf (ptr, true);
+}
+
+/* The GNU_MUTABLE_SECTION_NAME section is page-aligned and with a size
+   multiple of page size.  */
+
+unsigned char mutable_array1[64]
+  __attribute__ ((section (GNU_MUTABLE_SECTION_NAME)))
+     = { 0 };
+unsigned char mutable_array2[32]
+  __attribute__ ((section (GNU_MUTABLE_SECTION_NAME)))
+     = { 0 };
+
+unsigned char immutable_array[128];
+
+static void
+check_array (struct array_t *arr)
+{
+  TEST_COMPARE (try_write_buf (arr->arr), false);
+  TEST_COMPARE (try_write_buf (&arr->arr[arr->size/2]), false);
+  TEST_COMPARE (try_write_buf (&arr->arr[arr->size-1]), false);
+}
+
+static void
+check_mutable (struct array_t *mut1,
+              struct array_t *mut2,
+              struct array_t *imut)
+{
+  struct range_t range1;
+  struct range_t range2;
+
+  TEST_VERIFY_EXIT (find_mutable_range (mut1->arr, &range1));
+  TEST_VERIFY (mut1->arr >= range1.start);
+  TEST_VERIFY (mut1->arr + mut1->size <= range1.start + range1.size);
+
+  TEST_VERIFY_EXIT (find_mutable_range (mut2->arr, &range2));
+  TEST_VERIFY (mut2->arr >= range2.start);
+  TEST_VERIFY (mut2->arr + mut2->size <= range2.start + range2.size);
+
+  /* Assume that both array will be placed in the same page since their
+     combined size is less than pagesize.  */
+  TEST_VERIFY (range1.start == range2.start);
+  TEST_VERIFY (range2.size == range2.size);
+
+  if (test_verbose > 0)
+    printf ("mutable region: %-30s - %p-%p\n",
+           range1.name[0] == '\0' ? "main program" : basename (range1.name),
+           range1.start,
+           range1.start + range1.size);
+
+  memset (mut1->arr, 0xaa, mut1->size);
+  memset (mut2->arr, 0xbb, mut1->size);
+  memset (imut->arr, 0xcc, imut->size);
+
+  /* Sanity check, imut should be immutable.  */
+  {
+    void *start = PTR_ALIGN_DOWN (imut->arr, pagesize);
+    TEST_COMPARE (mprotect (start, pagesize, PROT_READ), -1);
+    TEST_COMPARE (errno, EPERM);
+  }
+
+  /* Change permission of mutable region to just allow read.  */
+  xmprotect ((void *)range1.start, range1.size, PROT_READ);
+
+  check_array (mut1);
+  check_array (mut2);
+}
+
+static int
+do_test (void)
+{
+  pagesize = xsysconf (_SC_PAGESIZE);
+
+  {
+    struct sigaction sa = {
+      .sa_handler = sigsegv_handler,
+      .sa_flags = SA_NODEFER,
+    };
+    sigemptyset (&sa.sa_mask);
+    xsigaction (SIGSEGV, &sa, NULL);
+  }
+
+#define ARR_TO_RANGE(__arr) \
+  &((struct array_t) { __arr, sizeof (__arr) })
+
+  check_mutable (ARR_TO_RANGE (mutable_array1),
+                ARR_TO_RANGE (mutable_array2),
+                ARR_TO_RANGE (immutable_array));
+
+#ifndef TEST_STATIC
+  {
+    struct array_t mut1 = get_mutable_array1 ();
+    struct array_t mut2 = get_mutable_array2 ();
+    struct array_t imut = get_immutable_array ();
+    check_mutable (&mut1, &mut2, &imut);
+  }
+
+  {
+    void *h = xdlopen (LIB_DLOPEN, RTLD_NOW | RTLD_NODELETE);
+
+#define GET_ARRAY_DLOPEN(__name) \
+    ({ \
+       get_array_t f = xdlsym (h, __name); \
+       f(); \
+    })
+
+    struct array_t mut1 = GET_ARRAY_DLOPEN ("get_mutable_array1");
+    struct array_t mut2 = GET_ARRAY_DLOPEN ("get_mutable_array2");
+    struct array_t imut = GET_ARRAY_DLOPEN ("get_immutable_array");
+    check_mutable (&mut1, &mut2, &imut);
+  }
+#endif
+
+  return 0;
+}
+
+#include <support/test-driver.c>