diff options
31 files changed, 865 insertions, 0 deletions
diff --git a/NEWS b/NEWS index a964ce00a0..fbfc86b2b2 100644 --- a/NEWS +++ b/NEWS @@ -29,6 +29,17 @@ Major new features: mappings to avoid further change during process execution such as protection permissions, unmapping, moving to another location, or shrinking the size. +* On Linux, the loader will memory seal multiple places where the memory is + supposed to immutable over program execution: all shared library + dependencies from the binary, the binary itself, any preload libraries, + any library loaded with dlopen and the RTLD_NODELETE flag, any audit modules, + and the loader metadata. + +* A new tunable, glibc.rtld.seal, can be used to control the memory sealing + with thread different states: disable, where sealing will not be applied, + enabled, where any memory sealing failure is ignored; and enforced, where + any memory failure terminates the process. The default is enabled. + 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 8a89b71016..4c2371ec46 100644 --- a/elf/dl-load.c +++ b/elf/dl-load.c @@ -1431,6 +1431,8 @@ cannot enable executable stack as shared object requires"); /* Assign the next available module ID. */ _dl_assign_tls_modid (l); + l->l_seal = mode & RTLD_NODELETE ? lt_seal_toseal : lt_seal_dont; + #ifdef DL_AFTER_LOAD DL_AFTER_LOAD (l); #endif diff --git a/elf/dl-map-segments.h b/elf/dl-map-segments.h index 30977cf800..f224a9a367 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. */ @@ -188,6 +189,10 @@ _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. */ + _dl_mseal (mapat, zeroend - zeropage); } } diff --git a/elf/dl-minimal-malloc.c b/elf/dl-minimal-malloc.c index 25d870728d..2e1cbbdae5 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,7 @@ __minimal_malloc (size_t n) if (page == MAP_FAILED) return NULL; __set_vma_name (page, nup, " glibc: loader malloc"); + _dl_mseal (page, nup); if (page != alloc_end) alloc_ptr = page; alloc_end = page + nup; diff --git a/elf/dl-mseal-mode.h b/elf/dl-mseal-mode.h new file mode 100644 index 0000000000..7f9ede4db7 --- /dev/null +++ b/elf/dl-mseal-mode.h @@ -0,0 +1,29 @@ +/* Memory sealing. 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_SEAL_MODE_H +#define _DL_SEAL_MODE_H + +enum dl_seal_mode +{ + DL_SEAL_DISABLE = 0, + DL_SEAL_ENABLE = 1, + DL_SEAL_ENFORCE = 2, +}; + +#endif diff --git a/elf/dl-open.c b/elf/dl-open.c index 076b32b977..f53b1b0572 100644 --- a/elf/dl-open.c +++ b/elf/dl-open.c @@ -838,6 +838,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); } void * diff --git a/elf/dl-reloc.c b/elf/dl-reloc.c index 4bf7aec88b..6bb424f789 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 @@ -347,6 +348,11 @@ _dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[], done, do it. */ if (l->l_relro_size != 0) _dl_protect_relro (l); + + /* Seal the memory mapping after RELRO setup, we can use the PT_LOAD + segments because even if relro splits the the original RW VMA, + mseal works with multiple VMAs with different flags. */ + _dl_mseal_map (l, false); } @@ -369,6 +375,51 @@ cannot apply additional memory protection after relocation"); } } +static void +_dl_mseal_map_1 (struct link_map *l) +{ + /* We only checked if the map is already sealed here so we can seal audit + module dependencies after the initial audit setup. */ + if (l->l_seal == lt_seal_sealed) + return; + + int r = -1; + if (l->l_contiguous) + r = _dl_mseal ((void *) l->l_map_start, l->l_map_end - l->l_map_start); + else + { + 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; + r = _dl_mseal ((void *) mapstart, allocend - mapstart); + } + break; + } + } + + if (r == 0) + l->l_seal = lt_seal_sealed; +} + +void +_dl_mseal_map (struct link_map *l, bool dep) +{ + if (l->l_seal == lt_seal_dont || l->l_seal == lt_seal_sealed) + return; + + if (l->l_searchlist.r_list == NULL || !dep) + _dl_mseal_map_1 (l); + else + for (unsigned int i = 0; i < l->l_searchlist.r_nlist; ++i) + _dl_mseal_map_1 (l->l_searchlist.r_list[i]); +} + 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 451932dd03..8290a380f3 100644 --- a/elf/dl-support.c +++ b/elf/dl-support.c @@ -45,6 +45,7 @@ #include <dl-find_object.h> #include <array_length.h> #include <dl-symbol-redir-ifunc.h> +#include <dl-mseal.h> extern char *__progname; char **_dl_argv = &__progname; /* This is checked for some error messages. */ @@ -99,6 +100,7 @@ static struct link_map _dl_main_map = .l_used = 1, .l_tls_offset = NO_TLS_OFFSET, .l_serial = 1, + .l_seal = SUPPORT_MSEAL, }; /* Namespace information. */ @@ -340,6 +342,11 @@ _dl_non_dynamic_init (void) /* Setup relro on the binary itself. */ if (_dl_main_map.l_relro_size != 0) _dl_protect_relro (&_dl_main_map); + + /* Seal the memory mapping after RELRO setup, we can use the PT_LOAD + segments because even if relro splits the the original RW VMA, + mseal works with multiple VMAs with different flags. */ + _dl_mseal_map (&_dl_main_map, false); } #ifdef DL_SYSINFO_IMPLEMENTATION diff --git a/elf/dl-tunables.list b/elf/dl-tunables.list index 40ac5b3776..5eb3a2380b 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: 2 + default: 1 + } } mem { diff --git a/elf/rtld.c b/elf/rtld.c index 1090d89bfe..efe7e97b04 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> @@ -477,6 +478,7 @@ _dl_start_final (void *arg, struct dl_start_final_info *info) GL(dl_rtld_map).l_real = &GL(dl_rtld_map); GL(dl_rtld_map).l_map_start = (ElfW(Addr)) &__ehdr_start; GL(dl_rtld_map).l_map_end = (ElfW(Addr)) _end; + GL(dl_rtld_map).l_seal = 1; /* Copy the TLS related data if necessary. */ #ifndef DONT_USE_BOOTSTRAP_MAP # if NO_TLS_OFFSET != 0 @@ -1043,6 +1045,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; + + /* Seal the audit modules and their dependencies. */ + dlmargs.map->l_seal = lt_seal_toseal; + _dl_mseal_map (dlmargs.map, true); } /* Load all audit modules. */ @@ -1125,6 +1131,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 = 1; /* A PT_LOAD segment at an unexpected address will clear the l_contiguous flag. The ELF specification says that PT_LOAD diff --git a/elf/setup-vdso.h b/elf/setup-vdso.h index 888e1e4897..f115e6eb78 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/elf/tst-rtld-list-tunables.exp b/elf/tst-rtld-list-tunables.exp index db0e1c86e9..d40a478dd7 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: 1 (min: 0, max: 2) diff --git a/include/link.h b/include/link.h index cb0d7d8e2f..fd8e7f25bf 100644 --- a/include/link.h +++ b/include/link.h @@ -212,6 +212,12 @@ struct link_map unsigned int l_find_object_processed:1; /* Zero if _dl_find_object_update needs to process this lt_library map. */ + enum /* Memory sealing status. */ + { + lt_seal_dont, /* Do not seal the object. */ + 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, diff --git a/manual/tunables.texi b/manual/tunables.texi index 0b1b2898c0..d15eabc9e8 100644 --- a/manual/tunables.texi +++ b/manual/tunables.texi @@ -355,6 +355,48 @@ 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 maped memory region, such as shrinking +or expanding, mapping another segment over a pre-existing region, or change +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 +immuatable 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. In both cases it is up +either to binary or the loader to setup the sealing. + +@item +The vDSO vma provided by the kernel (if existent). + +@item +Any preload libraries. + +@item +Any library loaded with @code{dlopen} with @code{RTLD_NODELETE} flag. + +@item +Any runtime library used for process unwind (such as required by @code{backtrace} +or @code{pthread_exit}). + +@item +All audit modules and their dependencies. +@end itemize + +The tunable accepts three diferent values: @samp{0} where sealing is disabled, +@samp{1} where sealing is enabled, and @samp{2} where sealing is enforced. For +the enforced mode, if the memory can not be sealed the process terminates the +execution. + +The default value of this tunable is @samp{1}. +@end deftp + @node Elision Tunables @section Elision Tunables @cindex elision tunables diff --git a/string/strerrorname_np.c b/string/strerrorname_np.c index 042cea381c..e0e22fa79e 100644 --- a/string/strerrorname_np.c +++ b/string/strerrorname_np.c @@ -17,6 +17,7 @@ <https://www.gnu.org/licenses/>. */ #include <stdio.h> +#include <string.h> const char * strerrorname_np (int errnum) diff --git a/sysdeps/generic/dl-mseal.h b/sysdeps/generic/dl-mseal.h new file mode 100644 index 0000000000..d542fcac75 --- /dev/null +++ b/sysdeps/generic/dl-mseal.h @@ -0,0 +1,25 @@ +/* Memory sealing. Generic version. + 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/>. */ + +static inline int +_dl_mseal (void *addr, size_t len) +{ + return 0; +} + +#define SUPPORT_MSEAL lt_seal_dont diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h index 656e8a3fa0..aad5a219df 100644 --- a/sysdeps/generic/ldsodefs.h +++ b/sysdeps/generic/ldsodefs.h @@ -1017,6 +1017,12 @@ extern void _dl_relocate_object (struct link_map *map, /* Protect PT_GNU_RELRO area. */ extern void _dl_protect_relro (struct link_map *map) attribute_hidden; +/* Protect MAP with mseal. If MAP is contiguous the while region is + sealed, otherwise iterate over the phdr to seal each PT_LOAD. The DEP + specify whether to seal the dependencies as well. */ +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. */ diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile index 9e57621720..3161363db1 100644 --- a/sysdeps/unix/sysv/linux/Makefile +++ b/sysdeps/unix/sysv/linux/Makefile @@ -629,6 +629,10 @@ sysdep-rtld-routines += \ dl-sbrk \ # sysdep-rtld-routines +dl-routines += \ + dl-mseal \ + # dl-routines + others += \ pldd \ # others @@ -638,6 +642,49 @@ install-bin += \ # install-bin $(objpfx)pldd: $(objpfx)xmalloc.o + +tests-static += \ + tst-dl_mseal-static \ + # tests-static + +tests += \ + $(tests-static) \ + tst-dl_mseal \ + # tests + +modules-names += \ + tst-dl_mseal-auditmod \ + tst-dl_mseal-dlopen-1 \ + tst-dl_mseal-dlopen-1-1 \ + tst-dl_mseal-dlopen-2 \ + tst-dl_mseal-dlopen-2-1 \ + tst-dl_mseal-mod-1 \ + tst-dl_mseal-mod-2 \ + tst-dl_mseal-preload \ + # modules-names + +$(objpfx)tst-dl_mseal.out: \ + $(objpfx)tst-dl_mseal-auditmod.so \ + $(objpfx)tst-dl_mseal-preload.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 + +tst-dl_mseal-ARGS = -- $(host-test-program-cmd) +$(objpfx)tst-dl_mseal: $(objpfx)tst-dl_mseal-mod-1.so +LDFLAGS-tst-dl_mseal = -Wl,--no-as-needed +$(objpfx)tst-dl_mseal-mod-1.so: $(objpfx)tst-dl_mseal-mod-2.so +LDFLAGS-tst-dl_mseal-mod-1.so = -Wl,--no-as-needed + +$(objpfx)tst-dl_mseal-dlopen-1.so: $(objpfx)tst-dl_mseal-dlopen-1-1.so +LDFLAGS-tst-dl_mseal-dlopen-1.so = -Wl,--no-as-needed +$(objpfx)tst-dl_mseal-dlopen-2.so: $(objpfx)tst-dl_mseal-dlopen-2-1.so +LDFLAGS-tst-dl_mseal-dlopen-2.so = -Wl,--no-as-needed + +tst-dl_mseal-static-ARGS = -- $(host-test-program-cmd) endif ifeq ($(subdir),rt) diff --git a/sysdeps/unix/sysv/linux/dl-mseal.c b/sysdeps/unix/sysv/linux/dl-mseal.c new file mode 100644 index 0000000000..69124b34af --- /dev/null +++ b/sysdeps/unix/sysv/linux/dl-mseal.c @@ -0,0 +1,51 @@ +/* Memory sealing. Linux version. + 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 <atomic.h> +#include <dl-mseal.h> +#include <dl-mseal-mode.h> +#include <dl-tunables.h> +#include <ldsodefs.h> + +int +_dl_mseal (void *addr, size_t len) +{ + int32_t mode = TUNABLE_GET (glibc, rtld, seal, int32_t, NULL); + if (mode == DL_SEAL_DISABLE) + return 0; + + int r; +#if __ASSUME_MSEAL + r = INTERNAL_SYSCALL_CALL (mseal, addr, len, 0); +#else + r = -ENOSYS; + static int mseal_supported = true; + if (atomic_load_relaxed (&mseal_supported)) + { + r = INTERNAL_SYSCALL_CALL (mseal, addr, len, 0); + if (r == -ENOSYS) + atomic_store_relaxed (&mseal_supported, false); + } +#endif + if (mode == 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/dl-mseal.h b/sysdeps/unix/sysv/linux/dl-mseal.h new file mode 100644 index 0000000000..89b19e33c4 --- /dev/null +++ b/sysdeps/unix/sysv/linux/dl-mseal.h @@ -0,0 +1,29 @@ +/* Memory sealing. Linux version. + 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/>. */ + +/* Seal the ADDR or size LEN to protect against modifications, 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. + + Return 0 in case of success or a negative value otherwise (a negative + errno). */ +int _dl_mseal (void *addr, size_t len) attribute_hidden; + +#define SUPPORT_MSEAL lt_seal_toseal diff --git a/sysdeps/unix/sysv/linux/tst-dl_mseal-auditmod.c b/sysdeps/unix/sysv/linux/tst-dl_mseal-auditmod.c new file mode 100644 index 0000000000..d909a1561c --- /dev/null +++ b/sysdeps/unix/sysv/linux/tst-dl_mseal-auditmod.c @@ -0,0 +1,23 @@ +/* Audit module for tst-dl_mseal test. + 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/>. */ + +unsigned int +la_version (unsigned int v) +{ + return v; +} diff --git a/sysdeps/unix/sysv/linux/tst-dl_mseal-dlopen-1-1.c b/sysdeps/unix/sysv/linux/tst-dl_mseal-dlopen-1-1.c new file mode 100644 index 0000000000..ef1372f47e --- /dev/null +++ b/sysdeps/unix/sysv/linux/tst-dl_mseal-dlopen-1-1.c @@ -0,0 +1,19 @@ +/* Additional module for tst-dl_mseal test. + 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/>. */ + +int foo2_1 (void) { return 42; } diff --git a/sysdeps/unix/sysv/linux/tst-dl_mseal-dlopen-1.c b/sysdeps/unix/sysv/linux/tst-dl_mseal-dlopen-1.c new file mode 100644 index 0000000000..3c2cbe6035 --- /dev/null +++ b/sysdeps/unix/sysv/linux/tst-dl_mseal-dlopen-1.c @@ -0,0 +1,19 @@ +/* Additional module for tst-dl_mseal test. + 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/>. */ + +int foo2 (void) { return 42; } diff --git a/sysdeps/unix/sysv/linux/tst-dl_mseal-dlopen-2-1.c b/sysdeps/unix/sysv/linux/tst-dl_mseal-dlopen-2-1.c new file mode 100644 index 0000000000..0cd647de46 --- /dev/null +++ b/sysdeps/unix/sysv/linux/tst-dl_mseal-dlopen-2-1.c @@ -0,0 +1,19 @@ +/* Additional module for tst-dl_mseal test. + 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/>. */ + +int bar2_1 (void) { return 42; } diff --git a/sysdeps/unix/sysv/linux/tst-dl_mseal-dlopen-2.c b/sysdeps/unix/sysv/linux/tst-dl_mseal-dlopen-2.c new file mode 100644 index 0000000000..f719dd3cba --- /dev/null +++ b/sysdeps/unix/sysv/linux/tst-dl_mseal-dlopen-2.c @@ -0,0 +1,19 @@ +/* Additional module for tst-dl_mseal test. + 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/>. */ + +int bar2 (void) { return 42; } diff --git a/sysdeps/unix/sysv/linux/tst-dl_mseal-mod-1.c b/sysdeps/unix/sysv/linux/tst-dl_mseal-mod-1.c new file mode 100644 index 0000000000..3bd188efe8 --- /dev/null +++ b/sysdeps/unix/sysv/linux/tst-dl_mseal-mod-1.c @@ -0,0 +1,19 @@ +/* Additional module for tst-dl_mseal test. + 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/>. */ + +int foo1 (void) { return 42; } diff --git a/sysdeps/unix/sysv/linux/tst-dl_mseal-mod-2.c b/sysdeps/unix/sysv/linux/tst-dl_mseal-mod-2.c new file mode 100644 index 0000000000..636e9777af --- /dev/null +++ b/sysdeps/unix/sysv/linux/tst-dl_mseal-mod-2.c @@ -0,0 +1,19 @@ +/* Additional module for tst-dl_mseal test. + 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/>. */ + +int bar1 (void) { return 42; } diff --git a/sysdeps/unix/sysv/linux/tst-dl_mseal-preload.c b/sysdeps/unix/sysv/linux/tst-dl_mseal-preload.c new file mode 100644 index 0000000000..7831608dd4 --- /dev/null +++ b/sysdeps/unix/sysv/linux/tst-dl_mseal-preload.c @@ -0,0 +1,19 @@ +/* Additional module for tst-dl_mseal test. + 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/>. */ + +int foo (void) { return 42; } diff --git a/sysdeps/unix/sysv/linux/tst-dl_mseal-skeleton.c b/sysdeps/unix/sysv/linux/tst-dl_mseal-skeleton.c new file mode 100644 index 0000000000..fbf18d9b7c --- /dev/null +++ b/sysdeps/unix/sysv/linux/tst-dl_mseal-skeleton.c @@ -0,0 +1,272 @@ +/* Basic tests for sealing. Static version. + 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 <array_length.h> +#include <errno.h> +#include <getopt.h> +#include <inttypes.h> +#include <libgen.h> +#include <stdio.h> +#include <string.h> +#include <sys/mman.h> +#include <unistd.h> + +#include <support/capture_subprocess.h> +#include <support/check.h> +#include <support/support.h> +#include <support/xdlfcn.h> +#include <support/xstdio.h> +#include <support/xthread.h> + +#if UINTPTR_MAX == UINT64_MAX +# define PTR_FMT "#018" PRIxPTR +#else +# define PTR_FMT "#010" PRIxPTR +#endif + +static int +new_flags (const char flags[4]) +{ + bool read_flag = flags[0] == 'r'; + bool write_flag = flags[1] == 'w'; + bool exec_flag = flags[2] == 'x'; + + write_flag = !write_flag; + + return (read_flag ? PROT_READ : 0) + | (write_flag ? PROT_WRITE : 0) + | (exec_flag ? PROT_EXEC : 0); +} + +/* Libraries/VMA that could not be sealed, and that checking for sealing + does not work (kernel does not allow changing protection). */ +static const char *non_sealed_vmas[] = +{ + ".", /* basename value for empty string anonymous + mappings. */ + "[heap]", + "[vsyscall]", + "[vvar]", + "[stack]", + "zero", /* /dev/zero */ +}; + +static int +is_in_string_list (const char *s, const char *const list[], size_t len) +{ + for (size_t i = 0; i != len; i++) + if (strcmp (s, list[i]) == 0) + return i; + return -1; +} +#define IS_IN_STRING_LIST(__s, __list) \ + is_in_string_list (__s, __list, array_length (__list)) + +static void * +tf (void *closure) +{ + pthread_exit (NULL); + return NULL; +} + +static int +handle_restart (void) +{ +#ifndef TEST_STATIC + xdlopen (LIB_DLOPEN_NODELETE, RTLD_NOW | RTLD_NODELETE); + xdlopen (LIB_DLOPEN_DEFAULT, RTLD_NOW); +#endif + + /* pthread_exit will load LIBGCC_S_SO. */ + xpthread_join (xpthread_create (NULL, tf, NULL)); + + FILE *fp = xfopen ("/proc/self/maps", "r"); + char *line = NULL; + size_t linesiz = 0; + + unsigned long pagesize = getpagesize (); + + bool found_expected[array_length(expected_sealed_libs)] = { false }; + while (xgetline (&line, &linesiz, fp) > 0) + { + uintptr_t start; + uintptr_t end; + char flags[5] = { 0 }; + char name[256] = { 0 }; + int idx; + + /* The line is in the form: + start-end flags offset dev inode pathname */ + int r = sscanf (line, + "%" SCNxPTR "-%" SCNxPTR " %4s %*s %*s %*s %256s", + &start, + &end, + flags, + name); + TEST_VERIFY_EXIT (r == 3 || r == 4); + + int found = false; + + const char *libname = basename (name); + if ((idx = IS_IN_STRING_LIST (libname, expected_sealed_libs)) + != -1) + { + /* Check if we can change the protection flags of the segment. */ + int new_prot = new_flags (flags); + TEST_VERIFY_EXIT (mprotect ((void *) start, end - start, + new_prot) == -1); + TEST_VERIFY_EXIT (errno == EPERM); + + /* Also checks trying to map over the sealed libraries. */ + { + char *p = mmap ((void *) start, pagesize, new_prot, + MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + TEST_VERIFY_EXIT (p == MAP_FAILED); + TEST_VERIFY_EXIT (errno == EPERM); + } + + /* And if remap is also blocked. */ + { + char *p = mremap ((void *) start, end - start, end - start, 0); + TEST_VERIFY_EXIT (p == MAP_FAILED); + TEST_VERIFY_EXIT (errno == EPERM); + } + + printf ("sealed: vma: %" PTR_FMT "-%" PTR_FMT " %s %s\n", + start, + end, + flags, + name); + + found_expected[idx] = true; + found = true; + } + else if ((idx = IS_IN_STRING_LIST (libname, expected_non_sealed_libs)) + != -1) + { + /* Check if expected non-sealed segments protection can indeed be + changed. The idea is to use something that would not break + process execution, so just try to mprotect with all protection + bits. */ + int new_prot = PROT_READ | PROT_WRITE | PROT_EXEC; + TEST_VERIFY_EXIT (mprotect ((void *) start, end - start, new_prot) + == 0); + + printf ("not-sealed: vma: %" PTR_FMT "-%" PTR_FMT " %s %s\n", + start, + end, + flags, + name); + + found = true; + } + + if (!found) + { + if (IS_IN_STRING_LIST (libname, non_sealed_vmas) != -1) + printf ("not-sealed: vma: %" PTR_FMT "-%" PTR_FMT " %s %s\n", + start, + end, + flags, + name); + else + FAIL_EXIT1 ("unexpected vma: %" PTR_FMT "-%" PTR_FMT " %s %s\n", + start, + end, + flags, + name); + } + } + xfclose (fp); + + printf ("\n"); + + /* Also check if all the expected sealed maps were found. */ + for (int i = 0; i < array_length (expected_sealed_libs); i++) + if (!found_expected[i]) + FAIL_EXIT1 ("expected VMA %s not sealed\n", expected_sealed_libs[i]); + + return 0; +} + +static int restart; +#define CMDLINE_OPTIONS \ + { "restart", no_argument, &restart, 1 }, + +static int +do_test (int argc, char *argv[]) +{ + /* We must have either: + - One or four parameters left if called initially: + + path to ld.so optional + + "--library-path" optional + + the library path optional + + the application name */ + if (restart) + return handle_restart (); + + /* Check the test requirements. */ + { + int r = mseal (NULL, 0, 0); + if (r == -1 && errno == ENOSYS) + FAIL_UNSUPPORTED ("mseal is not supported by the kernel"); + else + TEST_VERIFY_EXIT (r == 0); + } + support_need_proc ("Reads /proc/self/maps to get stack names."); + + char *spargv[9]; + int i = 0; + for (; i < argc - 1; i++) + spargv[i] = argv[i + 1]; + spargv[i++] = (char *) "--direct"; + spargv[i++] = (char *) "--restart"; + spargv[i] = NULL; + + char *envvarss[4]; + envvarss[0] = (char *) "GLIBC_TUNABLES=glibc.rtld.seal=2"; +#ifndef TEST_STATIC + envvarss[1] = (char *) "LD_PRELOAD=" LIB_PRELOAD; + envvarss[2] = (char *) "LD_AUDIT=" LIB_AUDIT, + envvarss[3] = NULL; +#else + envvarss[1] = NULL; +#endif + + struct support_capture_subprocess result = + support_capture_subprogram (spargv[0], spargv, envvarss); + support_capture_subprocess_check (&result, "tst-dl_mseal", 0, + sc_allow_stdout); + + { + FILE *out = fmemopen (result.out.buffer, result.out.length, "r"); + TEST_VERIFY (out != NULL); + char *line = NULL; + size_t linesz = 0; + while (xgetline (&line, &linesz, out)) + printf ("%s", line); + fclose (out); + } + + support_capture_subprocess_free (&result); + + return 0; +} + +#define TEST_FUNCTION_ARGV do_test +#include <support/test-driver.c> diff --git a/sysdeps/unix/sysv/linux/tst-dl_mseal-static.c b/sysdeps/unix/sysv/linux/tst-dl_mseal-static.c new file mode 100644 index 0000000000..e2a5fc2669 --- /dev/null +++ b/sysdeps/unix/sysv/linux/tst-dl_mseal-static.c @@ -0,0 +1,36 @@ +/* Basic tests for sealing. Static version. + 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/>. */ + +/* This test checks the memory sealing work on a statically built binary. */ + +#define GLIBC_RTLD_SEAL "2" +#define TEST_STATIC 1 + +/* Expected libraries that loader will seal. */ +static const char *expected_sealed_libs[] = +{ + "tst-dl_mseal-static", +}; + +/* Expected non sealed libraries. */ +static const char *expected_non_sealed_libs[] = +{ + "[vdso]", +}; + +#include "tst-dl_mseal-skeleton.c" diff --git a/sysdeps/unix/sysv/linux/tst-dl_mseal.c b/sysdeps/unix/sysv/linux/tst-dl_mseal.c new file mode 100644 index 0000000000..2673e51825 --- /dev/null +++ b/sysdeps/unix/sysv/linux/tst-dl_mseal.c @@ -0,0 +1,67 @@ +/* Basic tests for sealing. + 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> + +/* Check if memory sealing works as expected on multiples places: + - On the binary itself. + - On a LD_PRELOAD library. + - On a depedency modules (tst-dl_mseal-mod-{1,2}.so). + - On a audit modules (tst-dl_mseal-auditmod.so). + - On a dlopen dependency opened with RTLD_NODELET + (tst-dl_mseal-dlopen-{2,2-1}.so). + - On the libgcc_s opened by thread unwind. +*/ + +#define GLIBC_RTLD_SEAL "2" +#define LIB_AUDIT "tst-dl_mseal-auditmod.so" + +#define LIB_PRELOAD "tst-dl_mseal-preload.so" +#define LIB_AUDIT "tst-dl_mseal-auditmod.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.so" +#define LIB_DLOPEN_NODELETE_DEP "tst-dl_mseal-dlopen-2-1.so" + +/* Expected libraries that loader will seal. */ +static const char *expected_sealed_libs[] = +{ + "libc.so", + "ld.so", + "tst-dl_mseal", + "tst-dl_mseal-mod-1.so", + "tst-dl_mseal-mod-2.so", + LIB_PRELOAD, + LIB_AUDIT, + LIB_DLOPEN_NODELETE, + LIB_DLOPEN_NODELETE_DEP, + LIBGCC_S_SO, +}; + +/* Expected non sealed libraries. */ +static const char *expected_non_sealed_libs[] = +{ + "tst-dl_mseal-no-memory-seal", + LIB_PRELOAD, + LIB_DLOPEN_DEFAULT, + LIB_DLOPEN_DEFAULT_DEP, + "[vdso]", +}; + +#include "tst-dl_mseal-skeleton.c" |