@@ -13,6 +13,16 @@ 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.
+
+ If the GNU_PROPERTY_MEMORY_SEAL gnu attribute it not present on the binary,
+ memory sealing will not be applied for its dependencies (and even if the
+ objects has the memory sealing attribute).
+
Deprecated and removed features, and other changes affecting compatibility:
[Add deprecations, removals and changes affecting compatibility here]
@@ -1447,6 +1447,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
@@ -19,6 +19,7 @@
#include <dl-load.h>
#include <setvmaname.h>
+#include <dl-mseal.h>
/* Map a segment and align it properly. */
@@ -116,11 +117,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 (GLRO(dl_enable_seal) && l->l_seal == lt_seal_toseal)
+ _dl_mseal (hole_start, hole_size, l->l_name);
}
l->l_contiguous = 1;
@@ -218,6 +223,12 @@ _dl_map_segments (struct link_map *l, int fd,
}
__set_vma_name ((void*)zeropage, zeroend - zeropage, bssname);
}
+
+ /* 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 (GLRO(dl_enable_seal) && l->l_seal == lt_seal_toseal)
+ _dl_mseal (mapat, zeroend - zeropage, l->l_name);
}
}
@@ -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);
}
void
@@ -369,6 +371,58 @@ cannot apply additional memory protection after relocation");
}
}
+static void
+_dl_mseal_map_1 (struct link_map *l, bool dep)
+{
+ if (!GLRO(dl_enable_seal))
+ return;
+
+ /* The DEP is used internaly to memory seal audit modules after they are
+ dynamic loaded, since they might be unloaded if some constraints are not
+ met (for instance, if la_version is not supported).
+ For dlopen without RTLD_NODELETE, do not apply memory sealing. */
+ if (l->l_seal == lt_seal_dont
+ || (dep
+ ? 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
+ protection 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)
+{
+ if (l->l_searchlist.r_list == NULL || !dep)
+ _dl_mseal_map_1 (l, dep);
+ else
+ for (unsigned int i = 0; i < l->l_searchlist.r_nlist; ++i)
+ _dl_mseal_map_1 (l->l_searchlist.r_list[i], dep);
+}
+
void
__attribute_noinline__
_dl_reloc_bad_type (struct link_map *map, unsigned int type, int plt)
@@ -46,6 +46,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. */
@@ -100,6 +101,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. */
@@ -164,6 +166,8 @@ uint64_t _dl_hwcap4;
enum dso_sort_algorithm _dl_dso_sort_algo;
+bool _dl_enable_seal;
+
/* The value of the FPU control word the kernel will preset in hardware. */
fpu_control_t _dl_fpu_control = _FPU_DEFAULT;
@@ -344,6 +348,8 @@ _dl_non_dynamic_init (void)
break;
}
+ GLRO(dl_enable_seal) = _dl_main_map.l_seal == lt_seal_toseal;
+
if ((__glibc_unlikely (GL(dl_stack_flags)) & PF_X)
&& TUNABLE_GET (glibc, rtld, execstack, int32_t, NULL) == 0)
_dl_fatal_printf ("Fatal glibc error: executable stack is not allowed\n");
@@ -352,6 +358,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);
}
#ifdef DL_SYSINFO_IMPLEMENTATION
@@ -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. */
@@ -52,6 +52,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>
@@ -478,6 +479,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
@@ -1023,6 +1025,10 @@ 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
+ after after loading (including its dependencies) */
+ _dl_mseal_map (dlmargs.map, true);
}
/* Load all audit modules. */
@@ -1101,6 +1107,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
@@ -1216,6 +1223,8 @@ rtld_setup_main_map (struct link_map *main_map)
break;
}
+ GLRO(dl_enable_seal) = main_map->l_seal == lt_seal_toseal;
+
/* 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)
@@ -2318,8 +2327,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);
/* Relocation is complete. Perform early libc initialization. This
is the initial libc, even if audit modules have been loaded with
@@ -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.
@@ -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
@@ -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 1;
+
if (type == GNU_PROPERTY_AARCH64_FEATURE_1_AND)
{
/* Stop if the property note is ill-formed. */
new file mode 100644
@@ -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)
+{
+}
new file mode 100644
@@ -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 _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
@@ -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 1;
+
/* Continue until GNU_PROPERTY_1_NEEDED is found. */
if (type == GNU_PROPERTY_1_NEEDED)
{
@@ -642,6 +642,8 @@ struct rtld_global_ro
EXTERN enum dso_sort_algorithm _dl_dso_sort_algo;
+ EXTERN bool _dl_enable_seal;
+
#ifdef SHARED
/* We add a function table to _rtld_global which is then used to
call the function instead of going through the PLT. The result
@@ -1021,6 +1023,14 @@ 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, and it is
+ used for the case where sealing is done after loading (for instance
+ for audit modules). */
+extern void _dl_mseal_map (struct link_map *map, bool dep) 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. */
@@ -652,6 +652,10 @@ sysdep-rtld-routines += \
dl-sbrk \
# sysdep-rtld-routines
+dl-routines += \
+ dl-mseal \
+ # dl-routines
+
others += \
pldd \
# others
new file mode 100644
@@ -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);
+ }
+}
new file mode 100644
@@ -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
@@ -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 1;
+
unsigned int needed_1 = 0;
unsigned int feature_1_and = 0;
unsigned int isa_1_needed = 0;