/* x86 CET initializers function. Copyright (C) 2018-2020 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 . */ #include #include #include #include #include #include /* GNU_PROPERTY_X86_FEATURE_1_IBT and GNU_PROPERTY_X86_FEATURE_1_SHSTK are defined in , which are only available for C sources. X86_FEATURE_1_IBT and X86_FEATURE_1_SHSTK are defined in 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) { THREAD_SETMEM (THREAD_SELF, header.feature_1, GL(dl_x86_feature_1)[0]); 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.cpu.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 */