glibc/sysdeps/x86/dl-cet.c

367 lines
11 KiB
C

/* x86 CET initializers function.
Copyright (C) 2018-2023 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
<https://www.gnu.org/licenses/>. */
#include <unistd.h>
#include <errno.h>
#include <libintl.h>
#include <ldsodefs.h>
#include <dl-cet.h>
#include <sys/single_threaded.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
struct dl_cet_info
{
const char *program;
/* Check how IBT and SHSTK should be enabled. */
enum dl_x86_cet_control enable_ibt_type;
enum dl_x86_cet_control enable_shstk_type;
/* If IBT and SHSTK were previously enabled. */
unsigned int feature_1_enabled;
/* If IBT and SHSTK should be enabled. */
unsigned int enable_feature_1;
/* If there are any legacy shared object. */
unsigned int feature_1_legacy;
/* Which shared object is the first legacy shared object. */
unsigned int feature_1_legacy_ibt;
unsigned int feature_1_legacy_shstk;
};
/* Check if the object M and its dependencies are legacy object. */
static void
dl_check_legacy_object (struct link_map *m,
struct dl_cet_info *info)
{
unsigned int i;
struct link_map *l = NULL;
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 check for ld.so since it has the features enabled. The
features will be disabled later if they are not enabled in
executable. */
if (l == &GL(dl_rtld_map)
|| l->l_real == &GL(dl_rtld_map)
|| (info->program != NULL && l == m))
continue;
#endif
/* IBT and SHSTK set only if enabled in executable and all DSOs.
NB: cet_always_on is handled outside of the loop. */
info->enable_feature_1 &= ((l->l_x86_feature_1_and
& (GNU_PROPERTY_X86_FEATURE_1_IBT
| GNU_PROPERTY_X86_FEATURE_1_SHSTK))
| ~(GNU_PROPERTY_X86_FEATURE_1_IBT
| GNU_PROPERTY_X86_FEATURE_1_SHSTK));
if ((info->feature_1_legacy
& GNU_PROPERTY_X86_FEATURE_1_IBT) == 0
&& ((info->enable_feature_1
& GNU_PROPERTY_X86_FEATURE_1_IBT)
!= (info->feature_1_enabled
& GNU_PROPERTY_X86_FEATURE_1_IBT)))
{
info->feature_1_legacy_ibt = i;
info->feature_1_legacy |= GNU_PROPERTY_X86_FEATURE_1_IBT;
}
if ((info->feature_1_legacy
& GNU_PROPERTY_X86_FEATURE_1_SHSTK) == 0
&& ((info->enable_feature_1
& GNU_PROPERTY_X86_FEATURE_1_SHSTK)
!= (info->feature_1_enabled
& GNU_PROPERTY_X86_FEATURE_1_SHSTK)))
{
info->feature_1_legacy_shstk = i;
info->feature_1_legacy |= GNU_PROPERTY_X86_FEATURE_1_SHSTK;
}
}
/* Handle cet_always_on. */
if ((info->feature_1_enabled
& GNU_PROPERTY_X86_FEATURE_1_IBT) != 0
&& info->enable_ibt_type == cet_always_on)
{
info->feature_1_legacy &= ~GNU_PROPERTY_X86_FEATURE_1_IBT;
info->enable_feature_1 |= GNU_PROPERTY_X86_FEATURE_1_IBT;
}
if ((info->feature_1_enabled
& GNU_PROPERTY_X86_FEATURE_1_SHSTK) != 0
&& info->enable_shstk_type == cet_always_on)
{
info->feature_1_legacy &= ~GNU_PROPERTY_X86_FEATURE_1_SHSTK;
info->enable_feature_1 |= GNU_PROPERTY_X86_FEATURE_1_SHSTK;
}
}
#ifdef SHARED
/* Enable IBT and SHSTK only if they are enabled in executable. Set
feature bits properly at the start of the program. */
static void
dl_cet_check_startup (struct link_map *m, struct dl_cet_info *info)
{
/* NB: IBT and SHSTK may be disabled by environment variable:
GLIBC_TUNABLES=glibc.cpu.hwcaps=-IBT,-SHSTK.
*/
if (CPU_FEATURE_USABLE (IBT))
{
if (info->enable_ibt_type == cet_always_on)
info->enable_feature_1 |= GNU_PROPERTY_X86_FEATURE_1_IBT;
else
info->enable_feature_1 &= ((m->l_x86_feature_1_and
& GNU_PROPERTY_X86_FEATURE_1_IBT)
| ~GNU_PROPERTY_X86_FEATURE_1_IBT);
}
else
info->enable_feature_1 &= ~GNU_PROPERTY_X86_FEATURE_1_IBT;
if (CPU_FEATURE_USABLE (SHSTK))
{
if (info->enable_shstk_type == cet_always_on)
info->enable_feature_1 |= GNU_PROPERTY_X86_FEATURE_1_SHSTK;
else
info->enable_feature_1 &= ((m->l_x86_feature_1_and
& GNU_PROPERTY_X86_FEATURE_1_SHSTK)
| ~GNU_PROPERTY_X86_FEATURE_1_SHSTK);
}
else
info->enable_feature_1 &= ~GNU_PROPERTY_X86_FEATURE_1_SHSTK;
if (info->enable_feature_1 != 0)
dl_check_legacy_object (m, info);
unsigned int disable_feature_1
= info->enable_feature_1 ^ info->feature_1_enabled;
if (disable_feature_1 != 0)
{
/* Disable features in the kernel because of legacy objects or
cet_always_off. */
if (dl_cet_disable_cet (disable_feature_1) != 0)
_dl_fatal_printf ("%s: can't disable x86 Features\n",
info->program);
/* Clear the disabled bits. Sync dl_x86_feature_1 and
info->feature_1_enabled with info->enable_feature_1. */
info->feature_1_enabled = info->enable_feature_1;
GL(dl_x86_feature_1) = info->enable_feature_1;
}
if (HAS_CPU_FEATURE (IBT) || HAS_CPU_FEATURE (SHSTK))
{
/* Lock CET features only if IBT or SHSTK are enabled and are not
enabled permissively. */
unsigned int feature_1_lock = 0;
if (((info->feature_1_enabled & GNU_PROPERTY_X86_FEATURE_1_IBT)
!= 0)
&& info->enable_ibt_type != cet_permissive)
feature_1_lock |= GNU_PROPERTY_X86_FEATURE_1_IBT;
if (((info->feature_1_enabled & GNU_PROPERTY_X86_FEATURE_1_SHSTK)
!= 0)
&& info->enable_shstk_type != cet_permissive)
feature_1_lock |= GNU_PROPERTY_X86_FEATURE_1_SHSTK;
if (feature_1_lock != 0
&& dl_cet_lock_cet (feature_1_lock) != 0)
_dl_fatal_printf ("%s: can't lock CET\n", info->program);
}
THREAD_SETMEM (THREAD_SELF, header.feature_1, GL(dl_x86_feature_1));
}
#endif
/* Check feature bits when dlopening the shared object M. */
static void
dl_cet_check_dlopen (struct link_map *m, struct dl_cet_info *info)
{
/* Check if there are any legacy objects loaded. */
if (info->enable_feature_1 != 0)
{
dl_check_legacy_object (m, info);
/* Skip if there are no legacy shared objects loaded. */
if (info->feature_1_legacy == 0)
return;
}
unsigned int disable_feature_1 = 0;
unsigned int legacy_obj = 0;
const char *msg = NULL;
if ((info->feature_1_enabled
& GNU_PROPERTY_X86_FEATURE_1_IBT) != 0
&& (info->feature_1_legacy
& GNU_PROPERTY_X86_FEATURE_1_IBT) != 0)
{
/* Don't disable IBT if not single threaded since IBT may be still
enabled in other threads. */
if (info->enable_ibt_type != cet_permissive
|| !SINGLE_THREAD_P)
{
legacy_obj = info->feature_1_legacy_ibt;
msg = N_("rebuild shared object with IBT support enabled");
}
else
disable_feature_1 |= GNU_PROPERTY_X86_FEATURE_1_IBT;
}
/* Check the next feature only if there is no error. */
if (msg == NULL
&& (info->feature_1_enabled
& GNU_PROPERTY_X86_FEATURE_1_SHSTK) != 0
&& (info->feature_1_legacy
& GNU_PROPERTY_X86_FEATURE_1_SHSTK) != 0)
{
/* Don't disable SHSTK if not single threaded since SHSTK may be
still enabled in other threads. */
if (info->enable_shstk_type != cet_permissive
|| !SINGLE_THREAD_P)
{
legacy_obj = info->feature_1_legacy_shstk;
msg = N_("rebuild shared object with SHSTK support enabled");
}
else
disable_feature_1 |= GNU_PROPERTY_X86_FEATURE_1_SHSTK;
}
/* If there is an error, long jump back to the caller. */
if (msg != NULL)
_dl_signal_error (0, m->l_initfini[legacy_obj]->l_name, "dlopen",
msg);
if (disable_feature_1 != 0)
{
int res = dl_cet_disable_cet (disable_feature_1);
if (res)
{
if ((disable_feature_1
& GNU_PROPERTY_X86_FEATURE_1_IBT) != 0)
msg = N_("can't disable IBT");
else
msg = N_("can't disable SHSTK");
/* Long jump back to the caller on error. */
_dl_signal_error (-res, m->l_initfini[legacy_obj]->l_name,
"dlopen", msg);
}
/* Clear the disabled bits in dl_x86_feature_1. */
GL(dl_x86_feature_1) &= ~disable_feature_1;
THREAD_SETMEM (THREAD_SELF, header.feature_1,
GL(dl_x86_feature_1));
}
}
static void
dl_cet_check (struct link_map *m, const char *program)
{
struct dl_cet_info info;
/* Check how IBT and SHSTK should be enabled. */
info.enable_ibt_type = GL(dl_x86_feature_control).ibt;
info.enable_shstk_type = GL(dl_x86_feature_control).shstk;
info.feature_1_enabled = GL(dl_x86_feature_1);
/* No legacy object check if IBT and SHSTK are always on. */
if (info.enable_ibt_type == cet_always_on
&& info.enable_shstk_type == cet_always_on)
{
#ifdef SHARED
/* Set it only during startup. */
if (program != NULL)
THREAD_SETMEM (THREAD_SELF, header.feature_1,
info.feature_1_enabled);
#endif
return;
}
/* Check if IBT and SHSTK were enabled by kernel. */
if (info.feature_1_enabled == 0)
return;
info.program = program;
/* Check which features should be enabled. */
info.enable_feature_1 = 0;
if (info.enable_ibt_type != cet_always_off)
info.enable_feature_1 |= (info.feature_1_enabled
& GNU_PROPERTY_X86_FEATURE_1_IBT);
if (info.enable_shstk_type != cet_always_off)
info.enable_feature_1 |= (info.feature_1_enabled
& GNU_PROPERTY_X86_FEATURE_1_SHSTK);
/* Start with no legacy objects. */
info.feature_1_legacy = 0;
info.feature_1_legacy_ibt = 0;
info.feature_1_legacy_shstk = 0;
#ifdef SHARED
if (program)
dl_cet_check_startup (m, &info);
else
#endif
dl_cet_check_dlopen (m, &info);
}
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 */