diff mbox series

[v4,9/9] elf: Add glibc.rtld.seal tunable

Message ID 20241206173850.3766841-10-adhemerval.zanella@linaro.org
State New
Headers show
Series Add support for memory sealing | expand

Commit Message

Adhemerval Zanella Dec. 6, 2024, 5:37 p.m. UTC
The new tunable can be used to enforce memory sealing on the program and
all its dependencies.  The tunable accepts two different values:

* '0' where loaders follow the GNU_PROPERTY_MEMORY_SEAL attribute if
  present.  This is the default and no sealing would be applied if
  the object does not have the memory sealing attribute.

* '1' where sealing is enforced even if the object does not have
  the GNU_PROPERTY_MEMORY_SEAL.  Also, any syscall failure on
  memory sealing aborts the programs.

Checked on x86_64-linux-gnu and aarch64-linux-gnu.
---
 NEWS                                          |  6 ++
 elf/dl-load.c                                 |  3 +
 elf/dl-mseal-mode.h                           | 28 +++++++
 elf/dl-reloc.c                                | 13 ++++
 elf/dl-support.c                              |  2 +
 elf/dl-tunables.list                          |  6 ++
 elf/rtld.c                                    |  5 ++
 elf/tst-rtld-list-tunables.exp                |  1 +
 manual/tunables.texi                          | 35 +++++++++
 sysdeps/generic/ldsodefs.h                    |  6 ++
 sysdeps/unix/sysv/linux/Makefile              | 15 ++++
 sysdeps/unix/sysv/linux/dl-mseal.c            |  7 ++
 .../unix/sysv/linux/tst-dl_mseal-skeleton.c   |  3 +
 .../unix/sysv/linux/tst-dl_mseal-tunable.c    | 76 +++++++++++++++++++
 14 files changed, 206 insertions(+)
 create mode 100644 elf/dl-mseal-mode.h
 create mode 100644 sysdeps/unix/sysv/linux/tst-dl_mseal-tunable.c
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index 302babf497..29a65cd7b4 100644
--- a/NEWS
+++ b/NEWS
@@ -59,6 +59,12 @@  Major new features:
   memory sealing by default if the toochain supports it.  A new configure
   option, --disable-default-memory-seal, disables it.
 
+* A new tunable, glibc.rtld.seal, can enable memory sealing on the program
+  and all its dependencies.  The tunable accepts two different values,
+  with '0' applying the GNU attribute GNU_PROPERTY_MEMORY_SEAL (if present),
+  or '1' to enforce sealing the program and its dependencies (including
+  preload, audit modules, and objects opened with RTLD_NODELETE).
+
 Deprecated and removed features, and other changes affecting compatibility:
 
 * The big-endian ARC port (arceb-linux-gnu) has been removed.
diff --git a/elf/dl-load.c b/elf/dl-load.c
index b52c29ccb7..4f77836f6e 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -1315,6 +1315,9 @@  cannot enable executable stack as shared object requires");
 	break;
       }
 
+   /* Update the sealing mode based on the tunable.  */
+   _dl_mseal_update_map (l, mode);
+
   /* We are done mapping in the file.  We no longer need the descriptor.  */
   if (__glibc_unlikely (__close_nocancel (fd) != 0))
     {
diff --git a/elf/dl-mseal-mode.h b/elf/dl-mseal-mode.h
new file mode 100644
index 0000000000..745ca60064
--- /dev/null
+++ b/elf/dl-mseal-mode.h
@@ -0,0 +1,28 @@ 
+/* Memory sealing tunable.  Generic definitions.
+   Copyright (C) 2024 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/>.  */
+
+#ifndef _DL_MSEAL_MODE_H
+#define _DL_MSEAL_MODE_H
+
+enum dl_seal_mode
+{
+  DL_SEAL_DEFAULT = 0,
+  DL_SEAL_ENFORCE = 1,
+};
+
+#endif
diff --git a/elf/dl-reloc.c b/elf/dl-reloc.c
index f8127cb166..10a97e4e19 100644
--- a/elf/dl-reloc.c
+++ b/elf/dl-reloc.c
@@ -29,6 +29,8 @@ 
 #include <libc-pointer-arith.h>
 #include "dynamic-link.h"
 #include <dl-mseal.h>
+#include <dl-mseal-mode.h>
+#include <dl-tunables.h>
 
 /* Statistics function.  */
 #ifdef SHARED
@@ -371,6 +373,17 @@  cannot apply additional memory protection after relocation");
     }
 }
 
+void
+_dl_mseal_update_map (struct link_map *map, int mode)
+{
+  /* Also enable forced sealing on audit modules, loader will apply it
+     after the modules is being loaded and validated.  */
+  if (TUNABLE_GET (glibc, rtld, seal, int32_t, NULL) == DL_SEAL_ENFORCE
+      && (!(mode & __RTLD_DLOPEN)
+	  || (mode & RTLD_NODELETE) || (mode & __RTLD_AUDIT)))
+    map->l_seal = lt_seal_toseal;
+}
+
 static void
 _dl_mseal_map_1 (struct link_map *l, bool force)
 {
diff --git a/elf/dl-support.c b/elf/dl-support.c
index e43b455de4..bd0bfa3285 100644
--- a/elf/dl-support.c
+++ b/elf/dl-support.c
@@ -349,6 +349,8 @@  _dl_non_dynamic_init (void)
 	_dl_process_pt_gnu_property (&_dl_main_map, -1, &ph[-1]);
 	break;
       }
+   /* Update the sealing mode based on the tunable.  */
+   _dl_mseal_update_map (&_dl_main_map, 0);
 
   call_function_static_weak (_dl_find_object_init);
 
diff --git a/elf/dl-tunables.list b/elf/dl-tunables.list
index 40ac5b3776..4bc694bee5 100644
--- a/elf/dl-tunables.list
+++ b/elf/dl-tunables.list
@@ -135,6 +135,12 @@  glibc {
       maxval: 1
       default: 0
     }
+   seal {
+     type: INT_32
+      minval: 0
+      maxval: 1
+     default: 0
+   }
   }
 
   mem {
diff --git a/elf/rtld.c b/elf/rtld.c
index 71902de400..b78ec55cd3 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -1247,6 +1247,9 @@  rtld_setup_main_map (struct link_map *main_map)
 	break;
       }
 
+  /* Update the sealing mode based on the tunable.  */
+  _dl_mseal_update_map (main_map, 0);
+
   /* Adjust the address of the TLS initialization image in case
      the executable is actually an ET_DYN object.  */
   if (main_map->l_tls_initimage != NULL)
@@ -1766,6 +1769,8 @@  dl_main (const ElfW(Phdr) *phdr,
 	break;
       }
 
+  _dl_mseal_update_map (&GL(dl_rtld_map), 0);
+
   /* Add the dynamic linker to the TLS list if it also uses TLS.  */
   if (GL(dl_rtld_map).l_tls_blocksize != 0)
     /* Assign a module ID.  Do this before loading any audit modules.  */
diff --git a/elf/tst-rtld-list-tunables.exp b/elf/tst-rtld-list-tunables.exp
index db0e1c86e9..01e614646c 100644
--- a/elf/tst-rtld-list-tunables.exp
+++ b/elf/tst-rtld-list-tunables.exp
@@ -15,3 +15,4 @@  glibc.rtld.dynamic_sort: 2 (min: 1, max: 2)
 glibc.rtld.enable_secure: 0 (min: 0, max: 1)
 glibc.rtld.nns: 0x4 (min: 0x1, max: 0x10)
 glibc.rtld.optional_static_tls: 0x200 (min: 0x0, max: 0x[f]+)
+glibc.rtld.seal: 0 (min: 0, max: 1)
diff --git a/manual/tunables.texi b/manual/tunables.texi
index 0b1b2898c0..4dbbdf4ac2 100644
--- a/manual/tunables.texi
+++ b/manual/tunables.texi
@@ -355,6 +355,41 @@  tests for @code{AT_SECURE} programs and not meant to be a security feature.
 The default value of this tunable is @samp{0}.
 @end deftp
 
+@deftp Tunable glibc.rtld.seal
+Sets whether to enable memory sealing during program execution.  The sealed
+memory prevents further changes to the mapped memory region, such as shrinking
+or expanding, mapping another segment over a pre-existing region, or changing
+the memory protection flags (check the @code{mseal} for more information).
+The sealing is done in multiple places where the memory is supposed to be
+immutable over program execution:
+
+@itemize @bullet
+@item
+All shared library dependencies from the binary, including the read-only segments
+after @code{PT_GNU_RELRO} setup.
+
+@item
+The binary itself, including dynamic and static linked ones.  In both cases, it is
+up either to binary or the loader to set up the sealing.
+
+@item
+Any preload libraries.
+
+@item
+Any library loaded with @code{dlopen} with @code{RTLD_NODELETE} flag.
+
+@item
+All audit modules and their dependencies.
+@end itemize
+
+The tunable accepts two values: @samp{0} where sealing applies the GNU attribute
+@code{GNU_PROPERTY_MEMORY_SEAL} if present, and @samp{1} where sealing is
+enforced on the binary and its dependencies.  For the enforced mode,
+if the memory can not be sealed the process terminates the execution.
+
+The default value of this tunable is @samp{0}.
+@end deftp
+
 @node Elision Tunables
 @section Elision Tunables
 @cindex elision tunables
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index dbfa5d7a6a..4bde2df3bc 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -1024,6 +1024,12 @@  void _dl_relocate_object_no_relro (struct link_map *map,
 /* Protect PT_GNU_RELRO area.  */
 extern void _dl_protect_relro (struct link_map *map) attribute_hidden;
 
+/* The the sealing mode of MAP based on open MODE and on the rtld.seal
+   tunable.  */
+extern void _dl_mseal_update_map (struct link_map *map,
+				  int mode)
+     attribute_hidden;
+
 /* Issue memory sealing for the link map MAP.  If MAP is contiguous the
    whole region is sealed, otherwise iterate over the program headerrs and
    seal each PT_LOAD segment.i
diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
index 19a9e401ca..231f044fe4 100644
--- a/sysdeps/unix/sysv/linux/Makefile
+++ b/sysdeps/unix/sysv/linux/Makefile
@@ -668,6 +668,7 @@  tests += \
   $(tests-static) \
   tst-dl_mseal \
   tst-dl_mseal-noseal \
+  tst-dl_mseal-tunable \
   # tests
 
 modules-names += \
@@ -707,6 +708,16 @@  $(objpfx)tst-dl_mseal-noseal.out: \
   $(objpfx)tst-dl_mseal-dlopen-2-noseal.so \
   $(objpfx)tst-dl_mseal-dlopen-2-1-noseal.so
 
+$(objpfx)tst-dl_mseal-tunable.out: \
+  $(objpfx)tst-dl_mseal-auditmod-noseal.so \
+  $(objpfx)tst-dl_mseal-preload-noseal.so \
+  $(objpfx)tst-dl_mseal-mod-1-noseal.so \
+  $(objpfx)tst-dl_mseal-mod-2-noseal.so \
+  $(objpfx)tst-dl_mseal-dlopen-1.so \
+  $(objpfx)tst-dl_mseal-dlopen-1-1.so \
+  $(objpfx)tst-dl_mseal-dlopen-2-noseal.so \
+  $(objpfx)tst-dl_mseal-dlopen-2-1-noseal.so
+
 LDFLAGS-tst-dl_mseal = -Wl,--no-as-needed
 LDFLAGS-tst-dl_mseal-mod-1.so = -Wl,--no-as-needed
 LDFLAGS-tst-dl_mseal-dlopen-1.so = -Wl,--no-as-needed
@@ -739,10 +750,14 @@  $(objpfx)tst-dl_mseal-dlopen-2-noseal.so: $(objpfx)tst-dl_mseal-dlopen-2-1-nosea
 
 tst-dl_mseal-static-noseal-no-memory-seal = yes
 
+tst-dl_mseal-tunable-no-memory-seal = yes
+$(objpfx)tst-dl_mseal-tunable: $(objpfx)tst-dl_mseal-mod-1-noseal.so
+
 tst-dl_mseal-ARGS = -- $(host-test-program-cmd)
 tst-dl_mseal-static-ARGS = -- $(host-test-program-cmd)
 tst-dl_mseal-noseal-ARGS = -- $(host-test-program-cmd)
 tst-dl_mseal-static-noseal-ARGS = -- $(host-test-program-cmd)
+tst-dl_mseal-tunable-ARGS = -- $(host-test-program-cmd)
 endif
 endif
 
diff --git a/sysdeps/unix/sysv/linux/dl-mseal.c b/sysdeps/unix/sysv/linux/dl-mseal.c
index c99fd991cb..e4da0c32d2 100644
--- a/sysdeps/unix/sysv/linux/dl-mseal.c
+++ b/sysdeps/unix/sysv/linux/dl-mseal.c
@@ -17,6 +17,7 @@ 
    <https://www.gnu.org/licenses/>.  */
 
 #include <atomic.h>
+#include <dl-mseal-mode.h>
 #include <dl-mseal.h>
 #include <dl-tunables.h>
 #include <ldsodefs.h>
@@ -37,5 +38,11 @@  _dl_mseal (void *addr, size_t len)
 	atomic_store_relaxed (&mseal_supported, false);
     }
 #endif
+  if (TUNABLE_GET (glibc, rtld, seal, int32_t, NULL) == DL_SEAL_ENFORCE
+      && r != 0)
+    _dl_fatal_printf ("Fatal error: sealing is enforced and an error "
+		      "ocurred for the 0x%lx-0x%lx range\n",
+		      (long unsigned int) addr,
+		      (long unsigned int) addr + len);
   return r;
 }
diff --git a/sysdeps/unix/sysv/linux/tst-dl_mseal-skeleton.c b/sysdeps/unix/sysv/linux/tst-dl_mseal-skeleton.c
index 07cc18dde2..9c9d8ed6a4 100644
--- a/sysdeps/unix/sysv/linux/tst-dl_mseal-skeleton.c
+++ b/sysdeps/unix/sysv/linux/tst-dl_mseal-skeleton.c
@@ -248,6 +248,9 @@  do_test (int argc, char *argv[])
 #ifndef TEST_STATIC
     (char *) "LD_PRELOAD=" LIB_PRELOAD,
     (char *) "LD_AUDIT=" LIB_AUDIT,
+#endif
+#ifdef TUNABLE_ENV_VAR
+    (char *) "GLIBC_TUNABLES=" TUNABLE_ENV_VAR,
 #endif
     NULL
   };
diff --git a/sysdeps/unix/sysv/linux/tst-dl_mseal-tunable.c b/sysdeps/unix/sysv/linux/tst-dl_mseal-tunable.c
new file mode 100644
index 0000000000..a1069164bb
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-dl_mseal-tunable.c
@@ -0,0 +1,76 @@ 
+/* Basic tests for sealing.  Check the tunable in enforce mode.
+   Copyright (C) 2024 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 <gnu/lib-names.h>
+
+/* This test checks the glibc.rtld.seal enforces sealing on multiple
+   places:
+
+   - On the binary itself.
+   - On a LD_PRELOAD library.
+   - On a depedency module (tst-dl_mseal-mod-2-noseal.so).
+   - On a audit modules (tst-dl_mseal-auditmod-noeal.so).
+   - On a dlopen dependency opened with RTLD_NODELET
+     (tst-dl_mseal-dlopen-2-noseal.so).
+*/
+
+#define TUNABLE_ENV_VAR          "glibc.rtld.seal=1"
+
+#define LIB_PRELOAD              "tst-dl_mseal-preload-noseal.so"
+
+#define LIB_DLOPEN_DEFAULT       "tst-dl_mseal-dlopen-1.so"
+#define LIB_DLOPEN_DEFAULT_DEP   "tst-dl_mseal-dlopen-1-1.so"
+#define LIB_DLOPEN_NODELETE      "tst-dl_mseal-dlopen-2-noseal.so"
+#define LIB_DLOPEN_NODELETE_DEP  "tst-dl_mseal-dlopen-2-1-noseal.so"
+
+#define LIB_AUDIT                "tst-dl_mseal-auditmod-noseal.so"
+
+/* Expected libraries that loader will seal.  */
+static const char *expected_sealed_vmas[] =
+{
+  "tst-dl_mseal-tunable",
+  "libc.so",
+  "ld.so",
+  "tst-dl_mseal-mod-1-noseal.so",
+  "tst-dl_mseal-mod-2-noseal.so",
+  LIB_DLOPEN_NODELETE,
+  LIB_DLOPEN_NODELETE_DEP,
+  LIB_AUDIT,
+  LIB_PRELOAD,
+};
+
+/* Expected non sealed libraries.  */
+static const char *expected_non_sealed_vmas[] =
+{
+  LIB_DLOPEN_DEFAULT,
+  LIB_DLOPEN_DEFAULT_DEP,
+  /* Auxiary pages mapped by the kernel.  */
+  "[vdso]",
+  "[sigpage]",
+};
+
+/* Special pages, either Auxiliary kernel pages where permission can not be
+   changed or auxiliary libs that we can know prior hand that sealing is
+   enabled.  */
+static const char *expected_non_sealed_special[] =
+{
+  LIBGCC_S_SO,
+  "[vectors]",
+};
+
+#include "tst-dl_mseal-skeleton.c"