From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: by sourceware.org (Postfix, from userid 1791) id 906C1395B003; Tue, 4 Jun 2024 14:08:21 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 906C1395B003 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1717510101; bh=sccqSGkWNPurWAhjuKUwBLotn6yv6W5JIFo6FUArKM4=; h=From:To:Subject:Date:From; b=Q214/HTSl1+89P47GwG1jEk6swm6X6h1TGEPyGlPfa/3IATTc8dHazm10X5qflJzG QeNh+09llz4sq8dlurNSObuZgN0OEmesxjwRMBwzrRppBlfi6iQQp1QUrx45Gytu+G zRSMLBBx7LWT3Zz2hmaqKGSHgzWgef1UwEOekFuA= Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: Adhemerval Zanella To: glibc-cvs@sourceware.org Subject: [glibc/azanella/mseal] elf: Add support to memory sealing X-Act-Checkin: glibc X-Git-Author: Adhemerval Zanella X-Git-Refname: refs/heads/azanella/mseal X-Git-Oldrev: 22dc9a470b38723c32a737c7786f0fd385cc773f X-Git-Newrev: d32da241411f932e8dccf723f7fc547cfd6b6850 Message-Id: <20240604140821.906C1395B003@sourceware.org> Date: Tue, 4 Jun 2024 14:08:21 +0000 (GMT) List-Id: https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=d32da241411f932e8dccf723f7fc547cfd6b6850 commit d32da241411f932e8dccf723f7fc547cfd6b6850 Author: Adhemerval Zanella Date: Sun Jun 2 20:31:27 2024 -0300 elf: Add support to memory sealing It uses the new Linux mseal syscall to seal memory mappings to avoid further changes (such changing the memory protection or remap). The sealing is done in multiple places where the memory is supported to be immuatable over program execution: * All DT_NEEDED dependencies from the binary, including the RELRO seguments after PT_GNU_RELRO setup. * The binary itself, including dynamic and static linked. In both cases it is up either to binary or the loader to setup the sealing. * The ld.so.cache cache. * The vDSO segment (if existent). * Any preload libraries. For binary dependencies, the RTLD_NODELETE is used to signal the link_map should be sealed. It also make dlopen objects with the flag sealed as well. The sealing is controlled by a new tunable, glibc.rtld.seal, with three different states: 0. Disabled where no sealing is done. This is the default. 1. Enabled, where loader will issue the mseal syscall on the memory mappings but any failure will be ignored. 2. Enforce, similar to Enabled but any failure from the mseal will terminate the process. Checked on x86_64-linux-gnu. Diff: --- elf/dl-cache.c | 3 +++ elf/dl-load.c | 2 ++ elf/dl-mseal-mode.h | 29 ++++++++++++++++++++++ elf/dl-reloc.c | 33 ++++++++++++++++++++++++ elf/dl-support.c | 7 ++++++ elf/dl-tunables.list | 5 ++++ elf/rtld.c | 10 +++++--- elf/setup-vdso.h | 3 +++ elf/tst-rtld-list-tunables.exp | 1 + include/link.h | 1 + string/strerrorname_np.c | 1 + sysdeps/generic/dl-mseal.h | 25 +++++++++++++++++++ sysdeps/generic/ldsodefs.h | 4 +++ sysdeps/unix/sysv/linux/Makefile | 4 +++ sysdeps/unix/sysv/linux/dl-mseal.c | 51 ++++++++++++++++++++++++++++++++++++++ sysdeps/unix/sysv/linux/dl-mseal.h | 29 ++++++++++++++++++++++ 16 files changed, 205 insertions(+), 3 deletions(-) diff --git a/elf/dl-cache.c b/elf/dl-cache.c index 85f3f179ed..1770c90194 100644 --- a/elf/dl-cache.c +++ b/elf/dl-cache.c @@ -26,6 +26,7 @@ #include <_itoa.h> #include #include +#include #ifndef _DL_PLATFORMS_COUNT # define _DL_PLATFORMS_COUNT 0 @@ -410,6 +411,8 @@ _dl_load_cache_lookup (const char *name) /* Read the contents of the file. */ void *file = _dl_sysdep_read_whole_file (LD_SO_CACHE, &cachesize, PROT_READ); + if (cache != MAP_FAILED) + _dl_mseal (file, cachesize); /* We can handle three different cache file formats here: - only the new format diff --git a/elf/dl-load.c b/elf/dl-load.c index 8a89b71016..7d8824ea89 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 ? 1 : 0; + #ifdef DL_AFTER_LOAD DL_AFTER_LOAD (l); #endif 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 + . */ + +#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-reloc.c b/elf/dl-reloc.c index 4bf7aec88b..f0bde68782 100644 --- a/elf/dl-reloc.c +++ b/elf/dl-reloc.c @@ -28,6 +28,7 @@ #include <_itoa.h> #include #include "dynamic-link.h" +#include /* 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); } @@ -369,6 +375,33 @@ cannot apply additional memory protection after relocation"); } } +void +_dl_mseal_map (const struct link_map *l) +{ + if (l->l_seal) + { + if (l->l_contiguous) + _dl_mseal ((void *) l->l_map_start, l->l_map_end - l->l_map_start); + else + { + for (const ElfW(Phdr) *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); + } + break; + } + } + } +} + 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..a206049ce3 100644 --- a/elf/dl-support.c +++ b/elf/dl-support.c @@ -45,6 +45,7 @@ #include #include #include +#include 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); } #ifdef DL_SYSINFO_IMPLEMENTATION diff --git a/elf/dl-tunables.list b/elf/dl-tunables.list index 1186272c81..ba72b3152d 100644 --- a/elf/dl-tunables.list +++ b/elf/dl-tunables.list @@ -142,6 +142,11 @@ glibc { maxval: 1 default: 0 } + seal { + type: INT_32 + minval: 0 + maxval: 2 + } } mem { diff --git a/elf/rtld.c b/elf/rtld.c index e9525ea987..174389e205 100644 --- a/elf/rtld.c +++ b/elf/rtld.c @@ -53,6 +53,7 @@ #include #include #include +#include #include @@ -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 @@ -809,7 +811,8 @@ do_preload (const char *fname, struct link_map *main_map, const char *where) args.str = fname; args.loader = main_map; - args.mode = __RTLD_SECURE; + /* RTLD_NODELETE enables sealing. */ + args.mode = __RTLD_SECURE | RTLD_NODELETE; unsigned int old_nloaded = GL(dl_ns)[LM_ID_BASE]._ns_nloaded; @@ -1123,6 +1126,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 @@ -1636,7 +1640,7 @@ dl_main (const ElfW(Phdr) *phdr, /* Create a link_map for the executable itself. This will be what dlopen on "" returns. */ main_map = _dl_new_object ((char *) "", "", lt_executable, NULL, - __RTLD_OPENEXEC, LM_ID_BASE); + __RTLD_OPENEXEC | RTLD_NODELETE, LM_ID_BASE); assert (main_map != NULL); main_map->l_phdr = phdr; main_map->l_phnum = phnum; @@ -1964,7 +1968,7 @@ dl_main (const ElfW(Phdr) *phdr, RTLD_TIMING_VAR (start); rtld_timer_start (&start); _dl_map_object_deps (main_map, preloads, npreloads, - state.mode == rtld_mode_trace, 0); + state.mode == rtld_mode_trace, RTLD_NODELETE); rtld_timer_accum (&load_time, start); } diff --git a/elf/setup-vdso.h b/elf/setup-vdso.h index 888e1e4897..fe5b1928c3 100644 --- a/elf/setup-vdso.h +++ b/elf/setup-vdso.h @@ -66,6 +66,7 @@ setup_vdso (struct link_map *main_map __attribute__ ((unused)), /* The vDSO is always used. */ l->l_used = 1; + l->l_seal = 1; /* Initialize l_local_scope to contain just this map. This allows the use of dl_lookup_symbol_x to resolve symbols within the vdso. @@ -104,6 +105,8 @@ setup_vdso (struct link_map *main_map __attribute__ ((unused)), if (GLRO(dl_sysinfo) == DL_SYSINFO_DEFAULT) GLRO(dl_sysinfo) = GLRO(dl_sysinfo_dso)->e_entry + l->l_addr; # endif + + _dl_mseal ((void *) l->l_map_start, l->l_map_end - l->l_map_start); } #endif } diff --git a/elf/tst-rtld-list-tunables.exp b/elf/tst-rtld-list-tunables.exp index db0e1c86e9..6f1044c480 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: 2) diff --git a/include/link.h b/include/link.h index cb0d7d8e2f..b7c19f61da 100644 --- a/include/link.h +++ b/include/link.h @@ -212,6 +212,7 @@ struct link_map unsigned int l_find_object_processed:1; /* Zero if _dl_find_object_update needs to process this lt_library map. */ + unsigned int l_seal:1; /* NODELETE status of the map. Only valid for maps of type lt_loaded. Lazy binding sets l_nodelete_active directly, 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 @@ . */ #include +#include 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..a5399e4d5c --- /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 + . */ + +static inline int +_dl_mseal (void *addr, size_t len) +{ + return 0; +} + +#define SUPPORT_MSEAL 0 diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h index 50f58a60e3..7ad747acf4 100644 --- a/sysdeps/generic/ldsodefs.h +++ b/sysdeps/generic/ldsodefs.h @@ -1017,6 +1017,10 @@ 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; +/* Scan all PT_LOAD entries and seal the VMA range to avoid further + changes. */ +extern void _dl_mseal_map (const struct link_map *map) 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__* 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 82d523e588..8c181d46dc 100644 --- a/sysdeps/unix/sysv/linux/Makefile +++ b/sysdeps/unix/sysv/linux/Makefile @@ -625,6 +625,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..1313fa3868 --- /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 + . */ + +#include +#include +#include +#include +#include + +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 kernel has " + "failed for 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..f56d33d97c --- /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 + . */ + +/* 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 1