summaryrefslogtreecommitdiff
path: root/sysdeps/x86/dl-cet.c
diff options
context:
space:
mode:
Diffstat (limited to 'sysdeps/x86/dl-cet.c')
-rw-r--r--sysdeps/x86/dl-cet.c346
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 */