x86: Remove ARCH_CET_LEGACY_BITMAP [BZ #25397]

Since legacy bitmap doesn't cover jitted code generated by legacy JIT
engine, it isn't very useful.  This patch removes ARCH_CET_LEGACY_BITMAP
and treats indirect branch tracking similar to shadow stack by removing
legacy bitmap support.

Tested on CET Linux/x86-64 and non-CET Linux/x86-64.

Reviewed-by: Carlos O'Donell <carlos@redhat.com>
This commit is contained in:
H.J. Lu 2020-03-18 04:35:54 -07:00
parent 49348beafe
commit 1fabdb9908
10 changed files with 165 additions and 208 deletions

View File

@ -18,26 +18,6 @@
#include <sys/prctl.h>
#include <asm/prctl.h>
static inline int __attribute__ ((always_inline))
dl_cet_allocate_legacy_bitmap (unsigned long *legacy_bitmap)
{
/* Allocate legacy bitmap. */
#ifdef __LP64__
return (int) INTERNAL_SYSCALL_CALL (arch_prctl,
ARCH_CET_LEGACY_BITMAP, legacy_bitmap);
#else
unsigned long long legacy_bitmap_u64[2];
int res = INTERNAL_SYSCALL_CALL (arch_prctl,
ARCH_CET_LEGACY_BITMAP, legacy_bitmap_u64);
if (res == 0)
{
legacy_bitmap[0] = legacy_bitmap_u64[0];
legacy_bitmap[1] = legacy_bitmap_u64[1];
}
return res;
#endif
}
static inline int __attribute__ ((always_inline))
dl_cet_disable_cet (unsigned int cet_feature)
{

View File

@ -24,9 +24,4 @@
OUT: allocated shadow stack address: *addr.
*/
# define ARCH_CET_ALLOC_SHSTK 0x3004
/* Return legacy region bitmap info in unsigned long long *addr:
address: addr[0].
size: addr[1].
*/
# define ARCH_CET_LEGACY_BITMAP 0x3005
#endif /* ARCH_CET_STATUS */

View File

@ -20,7 +20,8 @@ sysdep-dl-routines += dl-cet
tests += tst-cet-legacy-1 tst-cet-legacy-1a tst-cet-legacy-2 \
tst-cet-legacy-2a tst-cet-legacy-3 tst-cet-legacy-4 \
tst-cet-legacy-5a tst-cet-legacy-6a
tst-cet-legacy-5a tst-cet-legacy-6a tst-cet-legacy-7 \
tst-cet-legacy-8
tst-cet-legacy-1a-ARGS = -- $(host-test-program-cmd)
ifneq (no,$(have-tunables))
tests += tst-cet-legacy-4a tst-cet-legacy-4b tst-cet-legacy-4c \
@ -43,14 +44,16 @@ CFLAGS-tst-cet-legacy-4b.c += -fcf-protection
CFLAGS-tst-cet-legacy-mod-4.c += -fcf-protection=none
CFLAGS-tst-cet-legacy-5a.c += -fcf-protection
CFLAGS-tst-cet-legacy-5b.c += -fcf-protection
CFLAGS-tst-cet-legacy-mod-5a.c += -fcf-protection=none
CFLAGS-tst-cet-legacy-mod-5a.c += -fcf-protection=branch
CFLAGS-tst-cet-legacy-mod-5b.c += -fcf-protection
CFLAGS-tst-cet-legacy-mod-5c.c += -fcf-protection
CFLAGS-tst-cet-legacy-6a.c += -fcf-protection
CFLAGS-tst-cet-legacy-6b.c += -fcf-protection
CFLAGS-tst-cet-legacy-mod-6a.c += -fcf-protection=none
CFLAGS-tst-cet-legacy-mod-6a.c += -fcf-protection=branch
CFLAGS-tst-cet-legacy-mod-6b.c += -fcf-protection
CFLAGS-tst-cet-legacy-mod-6c.c += -fcf-protection
CFLAGS-tst-cet-legacy-7.c += -fcf-protection=none
CFLAGS-tst-cet-legacy-8.c += -mshstk
$(objpfx)tst-cet-legacy-1: $(objpfx)tst-cet-legacy-mod-1.so \
$(objpfx)tst-cet-legacy-mod-2.so

View File

@ -33,63 +33,6 @@
# 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
@ -117,6 +60,8 @@ dl_cet_check (struct link_map *m, const char *program)
if (ibt_enabled || shstk_enabled)
{
struct link_map *l = NULL;
unsigned int ibt_legacy = 0, shstk_legacy = 0;
bool found_ibt_legacy = false, found_shstk_legacy = false;
/* Check if IBT and SHSTK are enabled in object. */
bool enable_ibt = (ibt_enabled
@ -142,10 +87,7 @@ dl_cet_check (struct link_map *m, const char *program)
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)
@ -167,106 +109,58 @@ dl_cet_check (struct link_map *m, const char *program)
continue;
#endif
if (enable_ibt
&& enable_ibt_type != CET_ALWAYS_ON
&& !(l->l_cet & lc_ibt))
/* IBT is enabled only if it is enabled in executable as
well as all shared objects. */
enable_ibt &= (enable_ibt_type == CET_ALWAYS_ON
|| (l->l_cet & lc_ibt) != 0);
if (!found_ibt_legacy && enable_ibt != ibt_enabled)
{
/* Remember the first and last legacy objects. */
if (!need_legacy_bitmap)
last_legacy = i;
first_legacy = i;
need_legacy_bitmap = true;
found_ibt_legacy = true;
ibt_legacy = i;
}
/* 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 (enable_shstk != shstk_enabled)
{
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"));
found_shstk_legacy = true;
shstk_legacy = i;
}
}
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)
if (!program)
{
/* When SHSTK is enabled, we can't dlopening a shared
if (enable_ibt_type != CET_PERMISSIVE)
{
/* When IBT is enabled, we cannot dlopen a shared
object without IBT. */
if (found_ibt_legacy)
_dl_signal_error (0,
m->l_initfini[ibt_legacy]->l_name,
"dlopen",
N_("rebuild shared object with IBT support enabled"));
}
if (enable_shstk_type != CET_PERMISSIVE)
{
/* When SHSTK is enabled, we cannot dlopen a shared
object without SHSTK. */
if (enable_shstk != shstk_enabled)
_dl_signal_error (EINVAL, l->l_name, "dlopen",
N_("shadow stack isn't enabled"));
if (found_shstk_legacy)
_dl_signal_error (0,
m->l_initfini[shstk_legacy]->l_name,
"dlopen",
N_("rebuild shared object with SHSTK support enabled"));
}
if (enable_ibt_type != CET_PERMISSIVE
&& enable_shstk_type != CET_PERMISSIVE)
return;
}
@ -274,8 +168,7 @@ mprotect_failure:
disabled in executable or shared objects. */
unsigned int cet_feature = 0;
/* Disable IBT only during program startup. */
if (program && !enable_ibt)
if (!enable_ibt)
cet_feature |= GNU_PROPERTY_X86_FEATURE_1_IBT;
if (!enable_shstk)
cet_feature |= GNU_PROPERTY_X86_FEATURE_1_SHSTK;
@ -286,9 +179,15 @@ mprotect_failure:
if (program)
_dl_fatal_printf ("%s: can't disable CET\n", program);
else
{
if (found_ibt_legacy)
l = m->l_initfini[ibt_legacy];
else
l = m->l_initfini[shstk_legacy];
_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;
@ -297,17 +196,21 @@ mprotect_failure:
}
#ifdef SHARED
if (program
&& (!shstk_enabled
|| enable_shstk_type != CET_PERMISSIVE)
&& (ibt_enabled || shstk_enabled))
if (program && (ibt_enabled || shstk_enabled))
{
/* Lock CET if IBT or SHSTK is enabled in executable. Don't
lock CET if SHSTK is enabled permissively. */
if ((!ibt_enabled
|| enable_ibt_type != CET_PERMISSIVE)
&& (!shstk_enabled
|| enable_shstk_type != CET_PERMISSIVE))
{
/* Lock CET if IBT or SHSTK is enabled in executable unless
IBT or SHSTK is enabled permissively. */
int res = dl_cet_lock_cet ();
if (res != 0)
_dl_fatal_printf ("%s: can't lock CET\n", program);
}
/* Set feature_1 if IBT or SHSTK is enabled in executable. */
cet_feature_changed = true;
}
#endif

View File

@ -54,15 +54,4 @@ PROCINFO_CLASS unsigned int _dl_x86_feature_1[2]
# else
,
# endif
# if !defined PROCINFO_DECL && defined SHARED
._dl_x86_legacy_bitmap
# else
PROCINFO_CLASS unsigned long _dl_x86_legacy_bitmap[2]
# endif
# if !defined SHARED || defined PROCINFO_DECL
;
# else
,
# endif
#endif

View File

@ -20,6 +20,9 @@
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <support/check.h>
static int
do_test (void)
@ -31,22 +34,18 @@ do_test (void)
h = dlopen (modname, RTLD_LAZY);
if (h == NULL)
{
printf ("cannot open '%s': %s\n", modname, dlerror ());
exit (1);
const char *err = dlerror ();
if (!strstr (err, "rebuild shared object with IBT support enabled"))
FAIL_EXIT1 ("incorrect dlopen '%s' error: %s\n", modname, err);
return 0;
}
fp = dlsym (h, "test");
if (fp == NULL)
{
printf ("cannot get symbol 'test': %s\n", dlerror ());
exit (1);
}
FAIL_EXIT1 ("cannot get symbol 'test': %s\n", dlerror ());
if (fp () != 0)
{
puts ("test () != 0");
exit (1);
}
FAIL_EXIT1 ("test () != 0");
dlclose (h);

View File

@ -35,7 +35,8 @@ do_test_1 (const char *modname, bool fail)
if (fail)
{
const char *err = dlerror ();
if (strstr (err, "shadow stack isn't enabled") == NULL)
if (strstr (err, "rebuild shared object with SHSTK support enabled")
== NULL)
{
printf ("incorrect dlopen '%s' error: %s\n", modname,
err);

View File

@ -35,7 +35,8 @@ do_test_1 (const char *modname, bool fail)
if (fail)
{
const char *err = dlerror ();
if (strstr (err, "shadow stack isn't enabled") == NULL)
if (strstr (err, "rebuild shared object with SHSTK support enabled")
== NULL)
{
printf ("incorrect dlopen '%s' error: %s\n", modname,
err);

View File

@ -0,0 +1,38 @@
/* Check compatibility of legacy executable with a JIT engine.
Copyright (C) 2020 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 <stdio.h>
#include <sys/mman.h>
#include <support/xunistd.h>
/* Check that mmapped legacy code works with -fcf-protection=none. */
static int
do_test (void)
{
void (*funcp) (void);
funcp = xmmap (NULL, 0x1000, PROT_EXEC | PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1);
printf ("mmap = %p\n", funcp);
/* Write RET instruction. */
*(char *) funcp = 0xc3;
funcp ();
return 0;
}
#include <support/test-driver.c>

View File

@ -0,0 +1,48 @@
/* Check incompatibility with legacy JIT engine.
Copyright (C) 2020 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 <stdio.h>
#include <stdlib.h>
#include <x86intrin.h>
#include <sys/mman.h>
#include <support/test-driver.h>
#include <support/xsignal.h>
#include <support/xunistd.h>
/* Check that mmapped legacy code trigges segfault with -fcf-protection. */
static int
do_test (void)
{
/* NB: This test should trigger SIGSEGV on CET platforms. If SHSTK
is disabled, assuming IBT is also disabled. */
if (_get_ssp () == 0)
return EXIT_UNSUPPORTED;
void (*funcp) (void);
funcp = xmmap (NULL, 0x1000, PROT_EXEC | PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1);
printf ("mmap = %p\n", funcp);
/* Write RET instruction. */
*(char *) funcp = 0xc3;
funcp ();
return EXIT_FAILURE;
}
#define EXPECTED_SIGNAL (_get_ssp () == 0 ? 0 : SIGSEGV)
#include <support/test-driver.c>