diff mbox series

[v8,6/8] elf: Add support to memory sealing

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

Commit Message

Adhemerval Zanella Netto Jan. 29, 2025, 5:22 p.m. UTC
The new Linux mseal syscall allows mark a memory mapping to avoid
further changes (such as changng the protection flags).  The memory
sealing is done in multiple places where the memory is supposed to
be immutable during program execution:

  * All shared library dependencies from the binary, including the
    read-only segments after PT_GNU_RELRO setup.

  * 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.

  * Any preload libraries, including depedencies.

  * Any library loaded with dlopen with RTLD_NODELETE flag.

  * Audit modules.

  * The loader bump allocator.

The memory sealing is controled by a new gnu attribute,
GNU_PROPERTY_MEMORY_SEAL, added by the new static linker option
'-z memory-seal'.  It is set per binary, including statically linked
and shared objects.

The GNU_PROPERTY_MEMORY_SEAL enforcement depends on whether the kernel
supports the mseal syscall and how glibc is configured.  On the default
configuration that aims to support older kernel releases, the memory
sealing attribute is taken as a hint. If glibc is configured with a
minimum kernel of 6.10, where mseal is implied to be supported,
sealing is enforced.

Checked on x86_64-linux-gnu and aarch64-linux-gnu.
---
 NEWS                               |  6 ++++
 elf/dl-load.c                      |  4 +++
 elf/dl-map-segments.h              | 16 ++++++++--
 elf/dl-minimal-malloc.c            |  5 +++
 elf/dl-open.c                      |  4 +++
 elf/dl-reloc.c                     | 49 ++++++++++++++++++++++++++++
 elf/dl-support.c                   |  3 ++
 elf/elf.h                          |  2 ++
 elf/rtld.c                         | 12 ++++++-
 elf/setup-vdso.h                   |  2 ++
 include/link.h                     |  8 +++++
 sysdeps/aarch64/dl-prop.h          |  5 +++
 sysdeps/generic/dl-mseal.h         | 22 +++++++++++++
 sysdeps/generic/dl-prop-mseal.h    | 34 ++++++++++++++++++++
 sysdeps/generic/dl-prop.h          |  5 +++
 sysdeps/generic/ldsodefs.h         |  9 ++++++
 sysdeps/unix/sysv/linux/Makefile   |  4 +++
 sysdeps/unix/sysv/linux/dl-mseal.c | 51 ++++++++++++++++++++++++++++++
 sysdeps/unix/sysv/linux/dl-mseal.h | 31 ++++++++++++++++++
 sysdeps/x86/dl-prop.h              |  4 +++
 20 files changed, 272 insertions(+), 4 deletions(-)
 create mode 100644 sysdeps/generic/dl-mseal.h
 create mode 100644 sysdeps/generic/dl-prop-mseal.h
 create mode 100644 sysdeps/unix/sysv/linux/dl-mseal.c
 create mode 100644 sysdeps/unix/sysv/linux/dl-mseal.h

Comments

Yury Khrustalev Jan. 31, 2025, 3:43 p.m. UTC | #1
Hi Adhemerval,

On Wed, Jan 29, 2025 at 02:22:40PM -0300, Adhemerval Zanella wrote:
> The new Linux mseal syscall allows mark a memory mapping to avoid
>
> ...
>
> diff --git a/sysdeps/aarch64/dl-prop.h b/sysdeps/aarch64/dl-prop.h
> index abca2be7fa..5ffff60cf5 100644
> --- a/sysdeps/aarch64/dl-prop.h
> +++ b/sysdeps/aarch64/dl-prop.h
> @@ -19,6 +19,8 @@
>  #ifndef _DL_PROP_H
>  #define _DL_PROP_H
>  
> +#include <dl-prop-mseal.h>
> +
>  extern void _dl_bti_protect (struct link_map *, int) attribute_hidden;
>  
>  extern void _dl_bti_check (struct link_map *, const char *)
> @@ -50,6 +52,9 @@ static inline int
>  _dl_process_gnu_property (struct link_map *l, int fd, uint32_t type,
>  			  uint32_t datasz, void *data)
>  {
> +  if (_dl_process_gnu_property_seal (l, fd, type, datasz, data))
> +    return 0;
> +

Returning 0 here prevents other properties from being processed.
Returning 1 seems to fix it, however the whole model of relying
on return value of this function for deciding whether to continue
or stop seem a bit strange.

>    if (type == GNU_PROPERTY_AARCH64_FEATURE_1_AND)
>      {
>
> ...
>
> diff --git a/sysdeps/generic/dl-prop-mseal.h b/sysdeps/generic/dl-prop-mseal.h
> new file mode 100644
> index 0000000000..94ce72b4d2
> --- /dev/null
> +++ b/sysdeps/generic/dl-prop-mseal.h
> @@ -0,0 +1,34 @@
> +/* Support for GNU properties.  Generic version.
> +   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/>.  */
> +
> +#ifndef _DL_PROP_MSEAL_H
> +#define _LD_PROP_MSEAL_H

Should be _DL_PROP_MSEAL_H (typo: DL <-> LD).

> ...

I'm still in the process of reviewing and testing it on AArch64, so expect
more feedback.

Kind regards,
Yury
Andrei Vagin Feb. 6, 2025, 7:46 p.m. UTC | #2
On Wed, Jan 29, 2025 at 02:22:40PM -0300, Adhemerval Zanella wrote:
> diff --git a/sysdeps/generic/dl-prop-mseal.h b/sysdeps/generic/dl-prop-mseal.h
> new file mode 100644
> index 0000000000..94ce72b4d2
> --- /dev/null
> +++ b/sysdeps/generic/dl-prop-mseal.h
> @@ -0,0 +1,34 @@
> +/* Support for GNU properties.  Generic version.
> +   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/>.  */
> +
> +#ifndef _DL_PROP_MSEAL_H
> +#define _LD_PROP_MSEAL_H

nit: s/_LD_PROP_MSEAL_H/_DL_PROP_MSEAL_H/

> +
> +static __always_inline bool
> +_dl_process_gnu_property_seal (struct link_map *l, int fd, uint32_t type,
> +			       uint32_t datasz, void *data)
> +{
> +  if (type == GNU_PROPERTY_MEMORY_SEAL && datasz == 0)
> +    {
> +      l->l_seal = lt_seal_toseal;
> +      return true;
> +    }
> +  return false;
> +}
> +
> +#endif
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index 4732ec2522..c35ac3ed28 100644
--- a/NEWS
+++ b/NEWS
@@ -13,6 +13,12 @@  Major new features:
   mappings to avoid further change during process execution such as protection
   permissions, unmapping, moving to another location, or shrinking the size.
 
+* The loader will memory seal all libraries that contains the new gnu
+  attribute GNU_PROPERTY_MEMORY_SEAL.  The memory sealing uses the new Linux
+  mseal syscall, and it will be applied to all shared libraries dependencies,
+  the binary, any preload and audit modules, and aby library loaded with
+  RTLD_NODELETE.
+
 Deprecated and removed features, and other changes affecting compatibility:
 
   [Add deprecations, removals and changes affecting compatibility here]
diff --git a/elf/dl-load.c b/elf/dl-load.c
index f905578a65..d1414f65df 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -1451,6 +1451,10 @@  cannot enable executable stack as shared object requires");
     /* Assign the next available module ID.  */
     _dl_assign_tls_modid (l);
 
+  if (l->l_seal == lt_seal_toseal
+      && (mode & __RTLD_DLOPEN) && !(mode & RTLD_NODELETE))
+    l->l_seal = lt_seal_dont_dlopen;
+
 #ifdef DL_AFTER_LOAD
   DL_AFTER_LOAD (l);
 #endif
diff --git a/elf/dl-map-segments.h b/elf/dl-map-segments.h
index 203b6c7b0b..6d14ac27e0 100644
--- a/elf/dl-map-segments.h
+++ b/elf/dl-map-segments.h
@@ -18,6 +18,7 @@ 
    <https://www.gnu.org/licenses/>.  */
 
 #include <dl-load.h>
+#include <dl-mseal.h>
 
 /* Map a segment and align it properly.  */
 
@@ -115,11 +116,15 @@  _dl_map_segments (struct link_map *l, int fd,
 	  if (__glibc_unlikely (loadcmds[nloadcmds - 1].mapstart <
 				c->mapend))
 	    return N_("ELF load command address/offset not page-aligned");
+
+	  caddr_t hole_start = (caddr_t) (l->l_addr + c->mapend);
+	  size_t hole_size = loadcmds[nloadcmds - 1].mapstart - c->mapend;
+
           if (__glibc_unlikely
-              (__mprotect ((caddr_t) (l->l_addr + c->mapend),
-                           loadcmds[nloadcmds - 1].mapstart - c->mapend,
-                           PROT_NONE) < 0))
+              (__mprotect (hole_start, hole_size, PROT_NONE) < 0))
             return DL_MAP_SEGMENTS_ERROR_MPROTECT;
+	  if (l->l_seal)
+	    _dl_mseal (hole_start, hole_size, l->l_name);
         }
 
       l->l_contiguous = 1;
@@ -188,6 +193,11 @@  _dl_map_segments (struct link_map *l, int fd,
                               -1, 0);
               if (__glibc_unlikely (mapat == MAP_FAILED))
                 return DL_MAP_SEGMENTS_ERROR_MAP_ZERO_FILL;
+	      /* We need to seal this here because it will not be part of
+		 the PT_LOAD segments, nor it is taken in RELRO
+		 calculation.  */
+	      if (l->l_seal)
+		_dl_mseal (mapat, zeroend - zeropage, l->l_name);
             }
         }
 
diff --git a/elf/dl-minimal-malloc.c b/elf/dl-minimal-malloc.c
index b4bd1628bc..e81152e586 100644
--- a/elf/dl-minimal-malloc.c
+++ b/elf/dl-minimal-malloc.c
@@ -27,6 +27,7 @@ 
 #include <ldsodefs.h>
 #include <malloc/malloc-internal.h>
 #include <setvmaname.h>
+#include <dl-mseal.h>
 
 static void *alloc_ptr, *alloc_end, *alloc_last_block;
 
@@ -62,6 +63,10 @@  __minimal_malloc (size_t n)
       if (page == MAP_FAILED)
 	return NULL;
       __set_vma_name (page, nup, " glibc: loader malloc");
+#if IS_IN(rtld)
+      if (_dl_rtld_map.l_seal == lt_seal_toseal)
+	_dl_mseal (page, nup, _dl_rtld_map.l_name);
+#endif
       if (page != alloc_end)
 	alloc_ptr = page;
       alloc_end = page + nup;
diff --git a/elf/dl-open.c b/elf/dl-open.c
index 1501428de2..e464b22536 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -807,6 +807,10 @@  dl_open_worker (void *a)
   if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_FILES))
     _dl_debug_printf ("opening file=%s [%lu]; direct_opencount=%u\n\n",
 		      new->l_name, new->l_ns, new->l_direct_opencount);
+
+  /* The seal flag is set only for NEW, however its dependencies could not be
+     unloaded and thus can also be sealed.  */
+  _dl_mseal_map (new, true, false);
 }
 
 void *
diff --git a/elf/dl-reloc.c b/elf/dl-reloc.c
index 05bf54bebd..3e7d03d7b2 100644
--- a/elf/dl-reloc.c
+++ b/elf/dl-reloc.c
@@ -28,6 +28,7 @@ 
 #include <_itoa.h>
 #include <libc-pointer-arith.h>
 #include "dynamic-link.h"
+#include <dl-mseal.h>
 
 /* Statistics function.  */
 #ifdef SHARED
@@ -345,6 +346,7 @@  _dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[],
     return;
   _dl_relocate_object_no_relro (l, scope, reloc_mode, consider_profiling);
   _dl_protect_relro (l);
+  _dl_mseal_map (l, false, false);
 }
 
 void
@@ -369,6 +371,53 @@  cannot apply additional memory protection after relocation");
     }
 }
 
+static void
+_dl_mseal_map_1 (struct link_map *l, bool force)
+{
+  /* The 'force' check allow to seal audit with sealing enabled after
+     they are loader during process startup.  */
+  if (l->l_seal == lt_seal_dont
+      || (force
+	  ? l->l_seal != lt_seal_dont_dlopen
+	  : l->l_seal == lt_seal_dont_dlopen))
+    return;
+
+  if (l->l_contiguous)
+     _dl_mseal ((void *) l->l_map_start, l->l_map_end - l->l_map_start,
+		l->l_name);
+  else
+    {
+      /* We can use the PT_LOAD segments because even if relro splits the
+	 original RW VMA, mseal works with multiple VMAs with different
+	 flags.  */
+      const ElfW(Phdr) *ph;
+      for (ph = l->l_phdr; ph < &l->l_phdr[l->l_phnum]; ++ph)
+	switch (ph->p_type)
+	  {
+	  case PT_LOAD:
+	    {
+	      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);
+	    }
+	    break;
+	}
+    }
+
+  l->l_seal = lt_seal_sealed;
+}
+
+void
+_dl_mseal_map (struct link_map *l, bool dep, bool force)
+{
+  if (l->l_searchlist.r_list == NULL || !dep)
+    _dl_mseal_map_1 (l, force);
+  else
+    for (unsigned int i = 0; i < l->l_searchlist.r_nlist; ++i)
+      _dl_mseal_map_1 (l->l_searchlist.r_list[i], force);
+}
+
 void
 __attribute_noinline__
 _dl_reloc_bad_type (struct link_map *map, unsigned int type, int plt)
diff --git a/elf/dl-support.c b/elf/dl-support.c
index 6daa196f12..7129a82975 100644
--- a/elf/dl-support.c
+++ b/elf/dl-support.c
@@ -47,6 +47,7 @@ 
 #include <dl-symbol-redir-ifunc.h>
 #include <dl-tunables.h>
 #include <dl-prop.h>
+#include <dl-mseal.h>
 
 extern char *__progname;
 char **_dl_argv = &__progname;	/* This is checked for some error messages.  */
@@ -101,6 +102,7 @@  static struct link_map _dl_main_map =
     .l_used = 1,
     .l_tls_offset = NO_TLS_OFFSET,
     .l_serial = 1,
+    .l_seal = lt_seal_dont,
   };
 
 /* Namespace information.  */
@@ -353,6 +355,7 @@  _dl_non_dynamic_init (void)
 
   /* Setup relro on the binary itself.  */
   _dl_protect_relro (&_dl_main_map);
+  _dl_mseal_map (&_dl_main_map, false, false);
 }
 
 #ifdef DL_SYSINFO_IMPLEMENTATION
diff --git a/elf/elf.h b/elf/elf.h
index 96df2eec01..781f8d776f 100644
--- a/elf/elf.h
+++ b/elf/elf.h
@@ -1359,6 +1359,8 @@  typedef struct
 #define GNU_PROPERTY_STACK_SIZE			1
 /* No copy relocation on protected data symbol.  */
 #define GNU_PROPERTY_NO_COPY_ON_PROTECTED	2
+/* No memory sealing.  */
+#define GNU_PROPERTY_MEMORY_SEAL		3
 
 /* A 4-byte unsigned integer property: A bit is set if it is set in all
    relocatable inputs.  */
diff --git a/elf/rtld.c b/elf/rtld.c
index 4ab285abce..6656da329c 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -53,6 +53,7 @@ 
 #include <dl-find_object.h>
 #include <dl-audit-check.h>
 #include <dl-call_tls_init_tp.h>
+#include <dl-mseal.h>
 
 #include <assert.h>
 
@@ -479,6 +480,7 @@  _dl_start_final (void *arg, struct dl_start_final_info *info)
   _dl_rtld_map.l_real = &_dl_rtld_map;
   _dl_rtld_map.l_map_start = (ElfW(Addr)) &__ehdr_start;
   _dl_rtld_map.l_map_end = (ElfW(Addr)) _end;
+  _dl_rtld_map.l_seal = lt_seal_dont;
   /* Copy the TLS related data if necessary.  */
 #ifndef DONT_USE_BOOTSTRAP_MAP
 # if NO_TLS_OFFSET != 0
@@ -1024,6 +1026,11 @@  ERROR: audit interface '%s' requires version %d (maximum supported version %d);
 
   /* Mark the DSO as being used for auditing.  */
   dlmargs.map->l_auditing = 1;
+
+  /* Audit modules can not be loaded with RTLD_NODELETE, so apply the sealing
+     again on all dependencies an and ignore any possible missing seal due
+     dlopen without RTLD_NODELETE.  */
+  _dl_mseal_map (dlmargs.map, true, true);
 }
 
 /* Load all audit modules.  */
@@ -1106,6 +1113,7 @@  rtld_setup_main_map (struct link_map *main_map)
   /* And it was opened directly.  */
   ++main_map->l_direct_opencount;
   main_map->l_contiguous = 1;
+  main_map->l_seal = lt_seal_dont;
 
   /* A PT_LOAD segment at an unexpected address will clear the
      l_contiguous flag.  The ELF specification says that PT_LOAD
@@ -2325,8 +2333,10 @@  dl_main (const ElfW(Phdr) *phdr,
       __rtld_malloc_init_real (main_map);
     }
 
-  /* All ld.so initialization is complete.  Apply RELRO.  */
+  /* All ld.so initialization is complete.  Apply RELRO and memory
+     sealing.  */
   _dl_protect_relro (&_dl_rtld_map);
+  _dl_mseal_map (&_dl_rtld_map, false, false);
 
   /* Relocation is complete.  Perform early libc initialization.  This
      is the initial libc, even if audit modules have been loaded with
diff --git a/elf/setup-vdso.h b/elf/setup-vdso.h
index 6564557b56..551eee658c 100644
--- a/elf/setup-vdso.h
+++ b/elf/setup-vdso.h
@@ -66,6 +66,8 @@  setup_vdso (struct link_map *main_map __attribute__ ((unused)),
 
       /* The vDSO is always used.  */
       l->l_used = 1;
+      /* The PT_LOAD may not cover all the vdso mapping.  */
+      l->l_seal = lt_seal_dont;
 
       /* Initialize l_local_scope to contain just this map.  This allows
 	 the use of dl_lookup_symbol_x to resolve symbols within the vdso.
diff --git a/include/link.h b/include/link.h
index 518bfd1670..677d82b38b 100644
--- a/include/link.h
+++ b/include/link.h
@@ -214,6 +214,14 @@  struct link_map
 					       lt_library map.  */
     unsigned int l_tls_in_slotinfo:1; /* TLS slotinfo updated in dlopen.  */
 
+    enum			/* Memory sealing status.  */
+      {
+	lt_seal_dont = 0,	/* Do not seal the object.  */
+	lt_seal_dont_dlopen,    /* Do not seal from a dlopen.  */
+	lt_seal_toseal,		/* The library is marked to be sealed.  */
+	lt_seal_sealed		/* The library is sealed.  */
+      } l_seal:2;
+
     /* NODELETE status of the map.  Only valid for maps of type
        lt_loaded.  Lazy binding sets l_nodelete_active directly,
        potentially from signal handlers.  Initial loading of an
diff --git a/sysdeps/aarch64/dl-prop.h b/sysdeps/aarch64/dl-prop.h
index abca2be7fa..5ffff60cf5 100644
--- a/sysdeps/aarch64/dl-prop.h
+++ b/sysdeps/aarch64/dl-prop.h
@@ -19,6 +19,8 @@ 
 #ifndef _DL_PROP_H
 #define _DL_PROP_H
 
+#include <dl-prop-mseal.h>
+
 extern void _dl_bti_protect (struct link_map *, int) attribute_hidden;
 
 extern void _dl_bti_check (struct link_map *, const char *)
@@ -50,6 +52,9 @@  static inline int
 _dl_process_gnu_property (struct link_map *l, int fd, uint32_t type,
 			  uint32_t datasz, void *data)
 {
+  if (_dl_process_gnu_property_seal (l, fd, type, datasz, data))
+    return 0;
+
   if (type == GNU_PROPERTY_AARCH64_FEATURE_1_AND)
     {
       /* Stop if the property note is ill-formed.  */
diff --git a/sysdeps/generic/dl-mseal.h b/sysdeps/generic/dl-mseal.h
new file mode 100644
index 0000000000..b100a7cb2c
--- /dev/null
+++ b/sysdeps/generic/dl-mseal.h
@@ -0,0 +1,22 @@ 
+/* Memory sealing.  Generic version.
+   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/>.  */
+
+static inline void
+_dl_mseal (void *addr, size_t len, const char *object)
+{
+}
diff --git a/sysdeps/generic/dl-prop-mseal.h b/sysdeps/generic/dl-prop-mseal.h
new file mode 100644
index 0000000000..94ce72b4d2
--- /dev/null
+++ b/sysdeps/generic/dl-prop-mseal.h
@@ -0,0 +1,34 @@ 
+/* Support for GNU properties.  Generic version.
+   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/>.  */
+
+#ifndef _DL_PROP_MSEAL_H
+#define _LD_PROP_MSEAL_H
+
+static __always_inline bool
+_dl_process_gnu_property_seal (struct link_map *l, int fd, uint32_t type,
+			       uint32_t datasz, void *data)
+{
+  if (type == GNU_PROPERTY_MEMORY_SEAL && datasz == 0)
+    {
+      l->l_seal = lt_seal_toseal;
+      return true;
+    }
+  return false;
+}
+
+#endif
diff --git a/sysdeps/generic/dl-prop.h b/sysdeps/generic/dl-prop.h
index 6d4e62ea84..035bc249cf 100644
--- a/sysdeps/generic/dl-prop.h
+++ b/sysdeps/generic/dl-prop.h
@@ -19,6 +19,8 @@ 
 #ifndef _DL_PROP_H
 #define _DL_PROP_H
 
+#include <dl-prop-mseal.h>
+
 /* The following functions are used by the dynamic loader and the
    dlopen machinery to process PT_NOTE and PT_GNU_PROPERTY entries in
    the binary or shared object.  The notes can be used to change the
@@ -47,6 +49,9 @@  static inline int __attribute__ ((always_inline))
 _dl_process_gnu_property (struct link_map *l, int fd, uint32_t type,
 			  uint32_t datasz, void *data)
 {
+  if (_dl_process_gnu_property_seal (l, fd, type, datasz, data))
+    return 0;
+
   /* Continue until GNU_PROPERTY_1_NEEDED is found.  */
   if (type == GNU_PROPERTY_1_NEEDED)
     {
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index e871f27ff2..546fd85764 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -996,6 +996,15 @@  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;
 
+/* 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
+   The DEP specify whether to seal the dependencies as well, while FORCE
+   ignores any possible missing seal due dlopen without RTLD_NODELETE.
+   The memory sealing should be done *after* RELRO setup.  */
+extern void _dl_mseal_map (struct link_map *map, bool dep, bool force)
+     attribute_hidden;
+
 /* Call _dl_signal_error with a message about an unhandled reloc type.
    TYPE is the result of ELFW(R_TYPE) (r_info), i.e. an R_<CPU>_* value.
    PLT is nonzero if this was a PLT reloc; it just affects the message.  */
diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
index aa6c387462..ac67b98406 100644
--- a/sysdeps/unix/sysv/linux/Makefile
+++ b/sysdeps/unix/sysv/linux/Makefile
@@ -651,6 +651,10 @@  sysdep-rtld-routines += \
   dl-sbrk \
   # sysdep-rtld-routines
 
+dl-routines += \
+  dl-mseal \
+  # dl-routines
+
 others += \
   pldd \
   # others
diff --git a/sysdeps/unix/sysv/linux/dl-mseal.c b/sysdeps/unix/sysv/linux/dl-mseal.c
new file mode 100644
index 0000000000..74ab688ef3
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/dl-mseal.c
@@ -0,0 +1,51 @@ 
+/* Memory sealing.  Linux version.
+   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 <atomic.h>
+#include <dl-mseal.h>
+#include <dl-tunables.h>
+#include <ldsodefs.h>
+#include <libintl.h>
+
+void
+_dl_mseal (void *addr, size_t len, const char *object)
+{
+  int r = 0;
+  bool fail = false;
+#if __ASSUME_MSEAL
+  r = INTERNAL_SYSCALL_CALL (mseal, addr, len, 0);
+  fail = r != 0;
+#else
+  static int mseal_supported = true;
+  /* Avoid issuing mseal again if it is not supported by the kernel.  */
+  if (atomic_load_relaxed (&mseal_supported))
+    {
+      int r = INTERNAL_SYSCALL_CALL (mseal, addr, len, 0);
+      if (r == -ENOSYS)
+	atomic_store_relaxed (&mseal_supported, false);
+      else
+	fail = r != 0;
+    }
+#endif
+  if (fail)
+    {
+      static const char errstring[] = N_("\
+cannot apply memory sealing");
+      _dl_signal_error (-r, DSO_FILENAME (object), NULL, errstring);
+    }
+}
diff --git a/sysdeps/unix/sysv/linux/dl-mseal.h b/sysdeps/unix/sysv/linux/dl-mseal.h
new file mode 100644
index 0000000000..cfbd0e4240
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/dl-mseal.h
@@ -0,0 +1,31 @@ 
+/* Memory sealing.  Linux version.
+   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/>.  */
+
+#ifndef _DL_MSEAL_H
+#define _DL_MSEAL_H
+
+/* Seal the ADDR or size LEN to protect against fruthermodifications, such as
+   changes on the permission flags (through mprotect), remap (through
+   mmap and/or remap), shrink, destruction changes (madvise with
+   MADV_DONTNEED), or change its size.  The input has the same constraints
+   as the mseal syscall.
+
+   Any error than than unsupported by the kerneltriggers a _dl_signal_error.  */
+void _dl_mseal (void *addr, size_t len, const char *object) attribute_hidden;
+
+#endif 
diff --git a/sysdeps/x86/dl-prop.h b/sysdeps/x86/dl-prop.h
index 9a5e10821c..5f5cc8f3e4 100644
--- a/sysdeps/x86/dl-prop.h
+++ b/sysdeps/x86/dl-prop.h
@@ -19,6 +19,7 @@ 
 #ifndef _DL_PROP_H
 #define _DL_PROP_H
 
+#include <dl-prop-mseal.h>
 #include <libintl.h>
 
 extern void _dl_cet_check (struct link_map *, const char *)
@@ -243,6 +244,9 @@  _dl_process_gnu_property (struct link_map *l, int fd, uint32_t type,
 			  uint32_t datasz, void *data)
 {
   /* This is called on each GNU property.  */
+  if (_dl_process_gnu_property_seal (l, fd, type, datasz, data))
+    return 0;
+
   unsigned int needed_1 = 0;
   unsigned int feature_1_and = 0;
   unsigned int isa_1_needed = 0;