diff options
Diffstat (limited to 'sysdeps/x86/dl-cet.c')
-rw-r--r-- | sysdeps/x86/dl-cet.c | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/sysdeps/x86/dl-cet.c b/sysdeps/x86/dl-cet.c new file mode 100644 index 0000000000..b82ba14e75 --- /dev/null +++ b/sysdeps/x86/dl-cet.c @@ -0,0 +1,346 @@ +/* x86 CET initializers function. + Copyright (C) 2018 Free Software Foundation, Inc. + + 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 + <http://www.gnu.org/licenses/>. */ + +#include <unistd.h> +#include <errno.h> +#include <libintl.h> +#include <ldsodefs.h> +#include <dl-cet.h> +#include <cet-tunables.h> + +/* GNU_PROPERTY_X86_FEATURE_1_IBT and GNU_PROPERTY_X86_FEATURE_1_SHSTK + are defined in <elf.h>, which are only available for C sources. + X86_FEATURE_1_IBT and X86_FEATURE_1_SHSTK are defined in <sysdep.h> + which are available for both C and asm sources. They must match. */ +#if GNU_PROPERTY_X86_FEATURE_1_IBT != X86_FEATURE_1_IBT +# error GNU_PROPERTY_X86_FEATURE_1_IBT != X86_FEATURE_1_IBT +#endif +#if GNU_PROPERTY_X86_FEATURE_1_SHSTK != X86_FEATURE_1_SHSTK +# error GNU_PROPERTY_X86_FEATURE_1_SHSTK != X86_FEATURE_1_SHSTK +#endif + +static int +dl_cet_mark_legacy_region (struct link_map *l) +{ + /* Mark PT_LOAD segments with PF_X in legacy code page bitmap. */ + size_t i, phnum = l->l_phnum; + const ElfW(Phdr) *phdr = l->l_phdr; +#ifdef __x86_64__ + typedef unsigned long long word_t; +#else + typedef unsigned long word_t; +#endif + unsigned int bits_to_set; + word_t mask_to_set; +#define BITS_PER_WORD (sizeof (word_t) * 8) +#define BITMAP_FIRST_WORD_MASK(start) \ + (~((word_t) 0) << ((start) & (BITS_PER_WORD - 1))) +#define BITMAP_LAST_WORD_MASK(nbits) \ + (~((word_t) 0) >> (-(nbits) & (BITS_PER_WORD - 1))) + + word_t *bitmap = (word_t *) GL(dl_x86_legacy_bitmap)[0]; + word_t bitmap_size = GL(dl_x86_legacy_bitmap)[1]; + word_t *p; + size_t page_size = GLRO(dl_pagesize); + + for (i = 0; i < phnum; i++) + if (phdr[i].p_type == PT_LOAD && (phdr[i].p_flags & PF_X)) + { + /* One bit in legacy bitmap represents a page. */ + ElfW(Addr) start = (phdr[i].p_vaddr + l->l_addr) / page_size; + ElfW(Addr) len = (phdr[i].p_memsz + page_size - 1) / page_size; + ElfW(Addr) end = start + len; + + if ((end / 8) > bitmap_size) + return -EINVAL; + + p = bitmap + (start / BITS_PER_WORD); + bits_to_set = BITS_PER_WORD - (start % BITS_PER_WORD); + mask_to_set = BITMAP_FIRST_WORD_MASK (start); + + while (len >= bits_to_set) + { + *p |= mask_to_set; + len -= bits_to_set; + bits_to_set = BITS_PER_WORD; + mask_to_set = ~((word_t) 0); + p++; + } + if (len) + { + mask_to_set &= BITMAP_LAST_WORD_MASK (end); + *p |= mask_to_set; + } + } + + return 0; +} + +/* Check if object M is compatible with CET. */ + +static void +dl_cet_check (struct link_map *m, const char *program) +{ + /* Check how IBT should be enabled. */ + unsigned int enable_ibt_type + = GL(dl_x86_feature_1)[1] & ((1 << CET_MAX) - 1); + /* Check how SHSTK should be enabled. */ + unsigned int enable_shstk_type + = ((GL(dl_x86_feature_1)[1] >> CET_MAX) & ((1 << CET_MAX) - 1)); + + /* No legacy object check if both IBT and SHSTK are always on. */ + if (enable_ibt_type == CET_ALWAYS_ON + && enable_shstk_type == CET_ALWAYS_ON) + return; + + /* Check if IBT is enabled by kernel. */ + bool ibt_enabled + = (GL(dl_x86_feature_1)[0] & GNU_PROPERTY_X86_FEATURE_1_IBT) != 0; + /* Check if SHSTK is enabled by kernel. */ + bool shstk_enabled + = (GL(dl_x86_feature_1)[0] & GNU_PROPERTY_X86_FEATURE_1_SHSTK) != 0; + + if (ibt_enabled || shstk_enabled) + { + struct link_map *l = NULL; + + /* Check if IBT and SHSTK are enabled in object. */ + bool enable_ibt = (ibt_enabled + && enable_ibt_type != CET_ALWAYS_OFF); + bool enable_shstk = (shstk_enabled + && enable_shstk_type != CET_ALWAYS_OFF); + if (program) + { + /* Enable IBT and SHSTK only if they are enabled in executable. + NB: IBT and SHSTK may be disabled by environment variable: + + GLIBC_TUNABLES=glibc.tune.hwcaps=-IBT,-SHSTK + */ + enable_ibt &= (HAS_CPU_FEATURE (IBT) + && (enable_ibt_type == CET_ALWAYS_ON + || (m->l_cet & lc_ibt) != 0)); + enable_shstk &= (HAS_CPU_FEATURE (SHSTK) + && (enable_shstk_type == CET_ALWAYS_ON + || (m->l_cet & lc_shstk) != 0)); + } + + /* ld.so is CET-enabled by kernel. But shared objects may not + support IBT nor SHSTK. */ + if (enable_ibt || enable_shstk) + { + int res; + unsigned int i; + unsigned int first_legacy, last_legacy; + bool need_legacy_bitmap = false; + + i = m->l_searchlist.r_nlist; + while (i-- > 0) + { + /* Check each shared object to see if IBT and SHSTK are + enabled. */ + l = m->l_initfini[i]; + + if (l->l_init_called) + continue; + +#ifdef SHARED + /* Skip CET check for ld.so since ld.so is CET-enabled. + CET will be disabled later if CET isn't enabled in + executable. */ + if (l == &GL(dl_rtld_map) + || l->l_real == &GL(dl_rtld_map) + || (program && l == m)) + continue; +#endif + + if (enable_ibt + && enable_ibt_type != CET_ALWAYS_ON + && !(l->l_cet & lc_ibt)) + { + /* Remember the first and last legacy objects. */ + if (!need_legacy_bitmap) + last_legacy = i; + first_legacy = i; + need_legacy_bitmap = true; + } + + /* SHSTK is enabled only if it is enabled in executable as + well as all shared objects. */ + enable_shstk &= (enable_shstk_type == CET_ALWAYS_ON + || (l->l_cet & lc_shstk) != 0); + } + + if (need_legacy_bitmap) + { + if (GL(dl_x86_legacy_bitmap)[0]) + { + /* Change legacy bitmap to writable. */ + if (__mprotect ((void *) GL(dl_x86_legacy_bitmap)[0], + GL(dl_x86_legacy_bitmap)[1], + PROT_READ | PROT_WRITE) < 0) + { +mprotect_failure: + if (program) + _dl_fatal_printf ("%s: mprotect legacy bitmap failed\n", + l->l_name); + else + _dl_signal_error (EINVAL, l->l_name, "dlopen", + N_("mprotect legacy bitmap failed")); + } + } + else + { + /* Allocate legacy bitmap. */ + int res = dl_cet_allocate_legacy_bitmap + (GL(dl_x86_legacy_bitmap)); + if (res != 0) + { + if (program) + _dl_fatal_printf ("%s: legacy bitmap isn't available\n", + l->l_name); + else + _dl_signal_error (EINVAL, l->l_name, "dlopen", + N_("legacy bitmap isn't available")); + } + } + + /* Put legacy shared objects in legacy bitmap. */ + for (i = first_legacy; i <= last_legacy; i++) + { + l = m->l_initfini[i]; + + if (l->l_init_called || (l->l_cet & lc_ibt)) + continue; + +#ifdef SHARED + if (l == &GL(dl_rtld_map) + || l->l_real == &GL(dl_rtld_map) + || (program && l == m)) + continue; +#endif + + /* If IBT is enabled in executable and IBT isn't enabled + in this shard object, mark PT_LOAD segments with PF_X + in legacy code page bitmap. */ + res = dl_cet_mark_legacy_region (l); + if (res != 0) + { + if (program) + _dl_fatal_printf ("%s: failed to mark legacy code region\n", + l->l_name); + else + _dl_signal_error (-res, l->l_name, "dlopen", + N_("failed to mark legacy code region")); + } + } + + /* Change legacy bitmap to read-only. */ + if (__mprotect ((void *) GL(dl_x86_legacy_bitmap)[0], + GL(dl_x86_legacy_bitmap)[1], PROT_READ) < 0) + goto mprotect_failure; + } + } + + bool cet_feature_changed = false; + + if (enable_ibt != ibt_enabled || enable_shstk != shstk_enabled) + { + if (!program + && enable_shstk_type != CET_PERMISSIVE) + { + /* When SHSTK is enabled, we can't dlopening a shared + object without SHSTK. */ + if (enable_shstk != shstk_enabled) + _dl_signal_error (EINVAL, l->l_name, "dlopen", + N_("shadow stack isn't enabled")); + return; + } + + /* Disable IBT and/or SHSTK if they are enabled by kernel, but + disabled in executable or shared objects. */ + unsigned int cet_feature = 0; + + /* Disable IBT only during program startup. */ + if (program && !enable_ibt) + cet_feature |= GNU_PROPERTY_X86_FEATURE_1_IBT; + if (!enable_shstk) + cet_feature |= GNU_PROPERTY_X86_FEATURE_1_SHSTK; + + int res = dl_cet_disable_cet (cet_feature); + if (res != 0) + { + if (program) + _dl_fatal_printf ("%s: can't disable CET\n", program); + else + _dl_signal_error (-res, l->l_name, "dlopen", + N_("can't disable CET")); + } + + /* Clear the disabled bits in dl_x86_feature_1. */ + GL(dl_x86_feature_1)[0] &= ~cet_feature; + + cet_feature_changed = true; + } + +#ifdef SHARED + if (program + && (!shstk_enabled + || enable_shstk_type != CET_PERMISSIVE) + && (ibt_enabled || shstk_enabled)) + { + /* Lock CET if IBT or SHSTK is enabled in executable. Don't + lock CET if SHSTK is enabled permissively. */ + int res = dl_cet_lock_cet (); + if (res != 0) + _dl_fatal_printf ("%s: can't lock CET\n", program); + + cet_feature_changed = true; + } +#endif + + if (cet_feature_changed) + { + unsigned int feature_1 = 0; + if (enable_ibt) + feature_1 |= GNU_PROPERTY_X86_FEATURE_1_IBT; + if (enable_shstk) + feature_1 |= GNU_PROPERTY_X86_FEATURE_1_SHSTK; + struct pthread *self = THREAD_SELF; + THREAD_SETMEM (self, header.feature_1, feature_1); + } + } +} + +void +_dl_cet_open_check (struct link_map *l) +{ + dl_cet_check (l, NULL); +} + +#ifdef SHARED + +# ifndef LINKAGE +# define LINKAGE +# endif + +LINKAGE +void +_dl_cet_check (struct link_map *main_map, const char *program) +{ + dl_cet_check (main_map, program); +} +#endif /* SHARED */ |