x86: Modularize sysdeps/x86/dl-cet.c
authorH.J. Lu <hjl.tools@gmail.com>
Fri, 24 Mar 2023 20:20:06 +0000 (13:20 -0700)
committerH.J. Lu <hjl.tools@gmail.com>
Wed, 20 Dec 2023 12:57:55 +0000 (04:57 -0800)
Improve readability and make maintenance easier for dl-feature.c by
modularizing sysdeps/x86/dl-cet.c:
1. Support processors with:
   a. Only IBT.  Or
   b. Only SHSTK.  Or
   c. Both IBT and SHSTK.
2. Lock CET features only if IBT or SHSTK are enabled and are not
enabled permissively.

sysdeps/x86/dl-cet.c

index 60ea1cb..67c51ee 100644 (file)
 # error GNU_PROPERTY_X86_FEATURE_1_SHSTK != X86_FEATURE_1_SHSTK
 #endif
 
-/* Check if object M is compatible with CET.  */
+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_cet_check (struct link_map *m, const char *program)
+dl_check_legacy_object (struct link_map *m,
+                       struct dl_cet_info *info)
 {
-  /* Check how IBT should be enabled.  */
-  enum dl_x86_cet_control enable_ibt_type
-    = GL(dl_x86_feature_control).ibt;
-  /* Check how SHSTK should be enabled.  */
-  enum dl_x86_cet_control enable_shstk_type
-    = GL(dl_x86_feature_control).shstk;
-
-  /* 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)
+  unsigned int i;
+  struct link_map *l = NULL;
+
+  i = m->l_searchlist.r_nlist;
+  while (i-- > 0)
     {
-      THREAD_SETMEM (THREAD_SELF, header.feature_1, GL(dl_x86_feature_1));
-      return;
-    }
+      /* Check each shared object to see if IBT and SHSTK are enabled.  */
+      l = m->l_initfini[i];
 
-  /* Check if IBT is enabled by kernel.  */
-  bool ibt_enabled
-    = (GL(dl_x86_feature_1) & GNU_PROPERTY_X86_FEATURE_1_IBT) != 0;
-  /* Check if SHSTK is enabled by kernel.  */
-  bool shstk_enabled
-    = (GL(dl_x86_feature_1) & GNU_PROPERTY_X86_FEATURE_1_SHSTK) != 0;
+      if (l->l_init_called)
+        continue;
 
-  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
-                        && enable_ibt_type != cet_always_off);
-      bool enable_shstk = (shstk_enabled
-                          && enable_shstk_type != cet_always_off);
-      if (program)
+#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)))
        {
-         /* 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 &= (CPU_FEATURE_USABLE (IBT)
-                        && (enable_ibt_type == cet_always_on
-                            || (m->l_x86_feature_1_and
-                                & GNU_PROPERTY_X86_FEATURE_1_IBT) != 0));
-         enable_shstk &= (CPU_FEATURE_USABLE (SHSTK)
-                          && (enable_shstk_type == cet_always_on
-                              || (m->l_x86_feature_1_and
-                                  & GNU_PROPERTY_X86_FEATURE_1_SHSTK) != 0));
+         info->feature_1_legacy_ibt = i;
+         info->feature_1_legacy |= GNU_PROPERTY_X86_FEATURE_1_IBT;
        }
 
-      /* ld.so is CET-enabled by kernel.  But shared objects may not
-        support IBT nor SHSTK.  */
-      if (enable_ibt || enable_shstk)
-       {
-         unsigned int i;
+      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;
+        }
+    }
 
-         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];
+  /* 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 (l->l_init_called)
-               continue;
+  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
-             /* 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;
+/* 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 () != 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
 
-             /* 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_x86_feature_1_and
-                                & GNU_PROPERTY_X86_FEATURE_1_IBT) != 0);
-             if (!found_ibt_legacy && enable_ibt != ibt_enabled)
-               {
-                 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_x86_feature_1_and
-                                  & GNU_PROPERTY_X86_FEATURE_1_SHSTK) != 0);
-             if (enable_shstk != shstk_enabled)
-               {
-                 found_shstk_legacy = true;
-                 shstk_legacy = i;
-               }
-           }
-       }
+/* 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);
 
-      bool cet_feature_changed = false;
+      /* Skip if there are no legacy shared objects loaded.  */
+      if (info->feature_1_legacy == 0)
+       return;
+    }
 
-      if (enable_ibt != ibt_enabled || enable_shstk != shstk_enabled)
-       {
-         if (!program)
-           {
-             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 (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;
-           }
-
-         /* Disable IBT and/or SHSTK if they are enabled by kernel, but
-            disabled in executable or shared objects.  */
-         unsigned int cet_feature = 0;
-
-         if (!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
-               {
-                 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) &= ~cet_feature;
-
-         cet_feature_changed = true;
-       }
+  unsigned int disable_feature_1 = 0;
+  unsigned int legacy_obj = 0;
+  const char *msg = NULL;
 
-#ifdef SHARED
-      if (program && (ibt_enabled || shstk_enabled))
+  if ((info->feature_1_enabled
+       & GNU_PROPERTY_X86_FEATURE_1_IBT) != 0
+      && (info->feature_1_legacy
+         & GNU_PROPERTY_X86_FEATURE_1_IBT) != 0)
+    {
+      if (info->enable_ibt_type != cet_permissive)
        {
-         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;
+         legacy_obj = info->feature_1_legacy_ibt;
+         msg = N_("rebuild shared object with IBT support enabled");
        }
-#endif
+      else
+        disable_feature_1 |= GNU_PROPERTY_X86_FEATURE_1_IBT;
+    }
 
-      if (cet_feature_changed)
+  /* 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)
+    {
+      if (info->enable_shstk_type != cet_permissive)
        {
-         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);
+         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