diff options
Diffstat (limited to 'sysdeps/x86_64/dl-cet.c')
-rw-r--r-- | sysdeps/x86_64/dl-cet.c | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/sysdeps/x86_64/dl-cet.c b/sysdeps/x86_64/dl-cet.c new file mode 100644 index 0000000..1297c09 --- /dev/null +++ b/sysdeps/x86_64/dl-cet.c @@ -0,0 +1,364 @@ +/* x86-64 CET initializers function. + Copyright (C) 2018-2024 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) + { + /* 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; + } +} +#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; + + /* CET is enabled only if RTLD_START_ENABLE_X86_FEATURES is defined. */ +#if defined SHARED && defined RTLD_START_ENABLE_X86_FEATURES + /* Set dl_x86_feature_1 to features enabled in the executable. */ + if (program != NULL) + GL(dl_x86_feature_1) = (m->l_x86_feature_1_and + & (X86_FEATURE_1_IBT + | X86_FEATURE_1_SHSTK)); +#endif + + /* 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) + return; + + /* Check if IBT and SHSTK were enabled. */ + 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); +} + +/* Set GL(dl_x86_feature_1) to the enabled features and clear the + active bits of the disabled features. */ + +attribute_hidden void +_dl_cet_setup_features (unsigned int cet_feature) +{ + /* NB: cet_feature == GL(dl_x86_feature_1) which is set to features + enabled from executable, not necessarily supported by kernel. */ + if (cet_feature != 0) + { + cet_feature = dl_cet_get_cet_status (); + if (cet_feature != 0) + { + THREAD_SETMEM (THREAD_SELF, header.feature_1, cet_feature); + + /* Lock CET if IBT or SHSTK is enabled in executable. Don't + lock CET if IBT or SHSTK is enabled permissively. */ + if (GL(dl_x86_feature_control).ibt != cet_permissive + && (GL(dl_x86_feature_control).shstk != cet_permissive)) + dl_cet_lock_cet (cet_feature); + } + /* Sync GL(dl_x86_feature_1) with kernel. */ + GL(dl_x86_feature_1) = cet_feature; + } +} + +#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 */ |