[v4,09/17] riscv/cfi: introduce tunables for CFI features

Message ID 20260526061703.2188042-10-jesse.huang@sifive.com (mailing list archive)
State New
Headers
Series Support RISC-V Control Flow Integrifty (CFI) |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_glibc_check--master-arm success Test passed

Commit Message

Jesse Huang May 26, 2026, 6:16 a.m. UTC
  dl_riscv_feature_control is a structure with each member represents a
feature configuration. At this time it's only used by CFI features.

Each cfi feature is a 2-bit enum, which could be [on|off|permissive],
these values decide whether a new legacy object should be blocked
while loaded dynamically, and could be controled by glibc tunables.
---
 manual/tunables.texi                |  22 +++
 sysdeps/riscv/Makefile              |   1 +
 sysdeps/riscv/cpu-features.c        |  46 ++++++
 sysdeps/riscv/cpu-tunables.c        |  50 +++++++
 sysdeps/riscv/dl-cfi.c              | 223 ++++++++++++++++++++++++++--
 sysdeps/riscv/dl-get-cpu-features.c |  27 ++++
 sysdeps/riscv/dl-machine.h          |  19 +++
 sysdeps/riscv/dl-procruntime.c      |  17 +++
 sysdeps/riscv/dl-tunables.list      |  27 ++++
 sysdeps/riscv/feature-control.h     |  42 ++++++
 sysdeps/riscv/ldsodefs.h            |   1 +
 sysdeps/riscv/libc-start.c          |  31 ++++
 sysdeps/riscv/libc-start.h          |  13 +-
 13 files changed, 500 insertions(+), 19 deletions(-)
 create mode 100644 sysdeps/riscv/cpu-features.c
 create mode 100644 sysdeps/riscv/cpu-tunables.c
 create mode 100644 sysdeps/riscv/dl-get-cpu-features.c
 create mode 100644 sysdeps/riscv/dl-tunables.list
 create mode 100644 sysdeps/riscv/feature-control.h
 create mode 100644 sysdeps/riscv/libc-start.c
  

Patch

diff --git a/manual/tunables.texi b/manual/tunables.texi
index 12b515c628..9e79762f26 100644
--- a/manual/tunables.texi
+++ b/manual/tunables.texi
@@ -486,6 +486,28 @@  assume that the CPU is @code{xxx} where xxx may have one of these values:
 This tunable is specific to aarch64.
 @end deftp
 
+@deftp Tunable glibc.cpu.riscv_cfi_lp
+The @code{glibc.cpu.riscv_cfi_lp=[on|off|permissive]} tunable allows the
+user to temporarily turn off the branch control flow protection (a.k.a.
+landing pad) or set to permissive mode. The default value is @code{on} for
+target compiled with Zicfilp extension. Permissive mode allows the protection
+to be turned off on program trying to dynamically load a legacy shared library
+without landing pad support.
+
+This tunable is specific to riscv.
+@end deftp
+
+@deftp Tunable glibc.cpu.riscv_cfi_ss
+The @code{glibc.cpu.riscv_cfi_ss=[on|off|permissive]} tunable allows the
+user to temporarily turn off the return control flow protection (a.k.a.
+shadow stack) or set to permissive mode. The default value is @code{on} for
+target compiled with Zicfiss extension. Permissive mode allows the protection
+to be turned off on program trying to dynamically load a legacy shared library
+without shadow stack support.
+
+This tunable is specific to riscv.
+@end deftp
+
 @deftp Tunable glibc.cpu.x86_data_cache_size
 The @code{glibc.cpu.x86_data_cache_size} tunable allows the user to set
 data cache size in bytes for use in memory and string routines.
diff --git a/sysdeps/riscv/Makefile b/sysdeps/riscv/Makefile
index 11e48be02e..b1f074a3eb 100644
--- a/sysdeps/riscv/Makefile
+++ b/sysdeps/riscv/Makefile
@@ -1,6 +1,7 @@ 
 ifeq ($(subdir),misc)
 sysdep_headers += sys/asm.h
 endif
+sysdep-dl-routines += dl-get-cpu-features
 
 ifeq ($(subdir),elf)
 gen-as-const-headers += dl-link.sym features-offsets.sym
diff --git a/sysdeps/riscv/cpu-features.c b/sysdeps/riscv/cpu-features.c
new file mode 100644
index 0000000000..aed8e9062b
--- /dev/null
+++ b/sysdeps/riscv/cpu-features.c
@@ -0,0 +1,46 @@ 
+/* Initialize CPU feature data.
+   This file is part of the GNU C Library.
+   Copyright (C) 2026 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/>.  */
+
+#ifndef _CPU_FEATURES_RISCV_H
+#define _CPU_FEATURES_RISCV_H
+
+# define TUNABLE_NAMESPACE cpu
+# include <cpu-tunables.c>
+
+# ifdef __riscv_landing_pad
+extern void TUNABLE_CALLBACK (set_riscv_cfi_lp) (tunable_val_t *)
+  attribute_hidden;
+# endif
+# ifdef __riscv_shadow_stack
+extern void TUNABLE_CALLBACK (set_riscv_cfi_ss) (tunable_val_t *)
+  attribute_hidden;
+# endif
+
+static inline void
+init_cpu_features (void)
+{
+# ifdef __riscv_landing_pad
+  TUNABLE_GET (riscv_cfi_lp, tunable_val_t *,
+	       TUNABLE_CALLBACK (set_riscv_cfi_lp));
+# endif
+# ifdef __riscv_shadow_stack
+  TUNABLE_GET (riscv_cfi_ss, tunable_val_t *,
+	       TUNABLE_CALLBACK (set_riscv_cfi_ss));
+# endif
+}
+#endif /* _CPU_FEATURES_RISCV_H  */
diff --git a/sysdeps/riscv/cpu-tunables.c b/sysdeps/riscv/cpu-tunables.c
new file mode 100644
index 0000000000..84ef911866
--- /dev/null
+++ b/sysdeps/riscv/cpu-tunables.c
@@ -0,0 +1,50 @@ 
+/* RISC-V CPU feature tuning.
+   This file is part of the GNU C Library.
+   Copyright (C) 2026 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/>.  */
+
+#define TUNABLE_NAMESPACE cpu
+#include <elf/dl-tunables.h>
+#include <dl-tunables-parse.h>
+#include <ldsodefs.h>
+
+#ifdef __riscv_landing_pad
+attribute_hidden
+void
+TUNABLE_CALLBACK (set_riscv_cfi_lp) (tunable_val_t *valp)
+{
+  if (tunable_strcmp_cte (valp, "permissive"))
+    GL(dl_riscv_feature_control).lp = cfi_permissive;
+  else if (tunable_strcmp_cte (valp, "off"))
+    GL(dl_riscv_feature_control).lp = cfi_always_off;
+  else
+    GL(dl_riscv_feature_control).lp = cfi_always_on;
+}
+#endif
+
+#ifdef __riscv_shadow_stack
+attribute_hidden
+void
+TUNABLE_CALLBACK (set_riscv_cfi_ss) (tunable_val_t *valp)
+{
+  if (tunable_strcmp_cte (valp, "permissive"))
+    GL(dl_riscv_feature_control).ss = cfi_permissive;
+  else if (tunable_strcmp_cte (valp, "off"))
+    GL(dl_riscv_feature_control).ss = cfi_always_off;
+  else
+    GL(dl_riscv_feature_control).ss = cfi_always_on;
+}
+#endif
diff --git a/sysdeps/riscv/dl-cfi.c b/sysdeps/riscv/dl-cfi.c
index 9bb0000103..275faddcc9 100644
--- a/sysdeps/riscv/dl-cfi.c
+++ b/sysdeps/riscv/dl-cfi.c
@@ -15,6 +15,7 @@ 
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
+#include "feature-control.h"
 #include <asm-generic/errno-base.h>
 #include <unistd.h>
 #include <libintl.h>
@@ -22,8 +23,39 @@ 
 #include <dl-cfi.h>
 #include <sys/mman.h>
 
+struct dl_cfi_info
+{
+  const char *program;
+
+  /* Check how lp and ss should be enabled.  */
+#ifdef __riscv_landing_pad
+  enum dl_riscv_cfi_control enable_lp_type;
+#endif
+#ifdef __riscv_shadow_stack
+  enum dl_riscv_cfi_control enable_ss_type;
+#endif
+
+  /* Previously enabled features.  */
+  unsigned int feature_1_enabled;
+
+  /* Features that 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.  */
+#ifdef __riscv_landing_pad
+  unsigned int feature_1_legacy_lp;
+#endif
+#ifdef __riscv_shadow_stack
+  unsigned int feature_1_legacy_ss;
+#endif
+};
+
+
 static void
-dl_check_legacy_object (struct link_map *m, unsigned int *feature_1)
+dl_check_legacy_object (struct link_map *m, struct dl_cfi_info *info)
 {
   /* Iterate through the dependencies and disable if needed here  */
   struct link_map *l = NULL;
@@ -42,32 +74,150 @@  dl_check_legacy_object (struct link_map *m, unsigned int *feature_1)
       /* 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))
+      if (is_rtld_link_map (l)
+          || is_rtld_link_map (l->l_real)
+          || (info->program != NULL && l == m))
         continue;
 #endif /* SHARED  */
 
-      *feature_1 &= l->l_riscv_feature_1_and;
+      info->enable_feature_1 &= ((l->l_riscv_feature_1_and
+                                  & (GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED
+                                     | GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS))
+                                  | ~(GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED
+                                     | GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS));
+
+      /* Bookkeeping legacy objects  */
+#ifdef __riscv_landing_pad
+      if ((info->feature_1_legacy & GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED) == 0
+          && ((info->enable_feature_1 & GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED)
+              != (info->feature_1_enabled & GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED))
+         )
+        {
+          info->feature_1_legacy_lp = i;
+          info->feature_1_legacy |= GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED;
+        }
+#endif
+#ifdef __riscv_shadow_stack
+      if ((info->feature_1_legacy & GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS) == 0
+          && ((info->enable_feature_1 & GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS)
+              != (info->feature_1_enabled & GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS))
+         )
+        {
+          info->feature_1_legacy_ss = i;
+          info->feature_1_legacy |= GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS;
+        }
+#endif
     }
+
+  /* Keep bits set if cfi_always_on  */
+#ifdef __riscv_landing_pad
+  if ((info->feature_1_enabled & GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED) != 0
+      && info->enable_lp_type == cfi_always_on)
+    {
+      info->enable_feature_1 |= GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED;
+    }
+#endif
+#ifdef __riscv_shadow_stack
+  if ((info->feature_1_enabled & GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS) != 0
+      && info->enable_ss_type == cfi_always_on)
+    {
+      info->enable_feature_1 |= GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS;
+    }
+#endif
 }
 
 #ifdef SHARED
 static void
-dl_cfi_check_startup (struct link_map *m, unsigned int *feature_1)
+dl_cfi_check_startup (struct link_map *m, struct dl_cfi_info *info)
 {
-  /* FIXME: Add tunables here  */
-  if (!*feature_1)
-    return;
-  dl_check_legacy_object (m, feature_1);
+# ifdef __riscv_landing_pad
+  if (info->enable_lp_type == cfi_always_on)
+    info->enable_feature_1 |= GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED;
+  else if (info->enable_lp_type == cfi_always_off)
+    info->enable_feature_1 &= ~GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED;
+  else
+    info->enable_feature_1 &= ((m->l_riscv_feature_1_and
+                                & GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED)
+                               | ~GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED);
+# endif
+# ifdef __riscv_shadow_stack
+  if (info->enable_ss_type == cfi_always_on)
+    info->enable_feature_1 |= GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS;
+  else if (info->enable_ss_type == cfi_always_off)
+    info->enable_feature_1 &= ~GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS;
+  else
+    info->enable_feature_1 &= ((m->l_riscv_feature_1_and
+                                & GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS)
+                               | ~GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS);
+# endif
+
+  if (info->enable_feature_1 != 0)
+    dl_check_legacy_object (m, info);
 
   /* Update GL(dl_riscv_feature_1)  */
-  GL(dl_riscv_feature_1) = *feature_1;
+  if (info->enable_feature_1 ^ info->feature_1_enabled) {
+    info->feature_1_enabled = info->enable_feature_1;
+    GL(dl_riscv_feature_1) = info->enable_feature_1;
+  }
 }
 #endif /* SHARED  */
 
 static void
-dl_cfi_check_dlopen (struct link_map *m)
+dl_cfi_check_dlopen (struct link_map *m, struct dl_cfi_info *info)
 {
+  if (info->enable_feature_1 != 0) {
+    dl_check_legacy_object(m, info);
+
+    if (info->feature_1_legacy == 0)
+      return;
+  }
+
+  unsigned int disable_feature_1 = 0;
+  unsigned int legacy_obj = 0;
+  const char *msg = NULL;
+
+#ifdef __riscv_landing_pad
+  if ((info->feature_1_enabled & GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED) != 0
+      && (info->feature_1_legacy & GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED) != 0)
+    {
+      if (info->enable_lp_type != cfi_permissive || !SINGLE_THREAD_P)
+        {
+          legacy_obj = info->feature_1_legacy_lp;
+          msg = N_("rebuild shared object with landing pad support");
+        }
+      else
+        disable_feature_1 |= GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED;
+    }
+#endif
+
+#ifdef __riscv_shadow_stack
+  if ((info->feature_1_enabled & GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS) != 0
+      && (info->feature_1_legacy & GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS) != 0)
+    {
+      if (info->enable_ss_type != cfi_permissive || !SINGLE_THREAD_P)
+        {
+          legacy_obj = info->feature_1_legacy_ss;
+          msg = N_("rebuild shared object with shadow stack support");
+        }
+      else
+        disable_feature_1 |= GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS;
+    }
+#endif
+
+  if (msg != NULL)
+    _dl_signal_error (0, m->l_initfini[legacy_obj]->l_name, "dlopen", msg);
+
+  if (disable_feature_1 != 0)
+      // FIXME: Disable CFI here
+      int res = -1;
+      if (res)
+        {
+          msg = N_("can't disable CFI feature");
+          _dl_signal_error (-res, m->l_initfini[legacy_obj]->l_name,
+                            "dlopen", msg);
+        }
+      GL(dl_riscv_feature_1) &= ~disable_feature_1;
+    }
 }
 
 attribute_hidden void
@@ -89,20 +239,61 @@  _dl_cfi_check (struct link_map *l, const char *program)
 {
     /* As this point we have parsed the gnu properties,
        for dynamic binary we should verify the dependencies here.  */
-    /* FIXME: Implement different policy for supporting legacy binaries  */
-  unsigned int feature_1;
+  struct dl_cfi_info info;
 #if defined SHARED && defined RTLD_START_ENABLE_RISCV_CFI
   if (program)
     {
       GL(dl_riscv_feature_1) = l->l_riscv_feature_1_and;
-      feature_1 = l->l_riscv_feature_1_and;
     }
 #endif /* SHARED  */
 
+  unsigned int supported_exts = 0;
+  unsigned int always_on_exts = 0;
+
+#ifdef __riscv_landing_pad
+  info.enable_lp_type = GL(dl_riscv_feature_control).lp;
+  supported_exts += 1;
+  always_on_exts += (info.enable_lp_type == cfi_always_on);
+#endif
+#ifdef __riscv_shadow_stack
+  info.enable_ss_type = GL(dl_riscv_feature_control).ss;
+  supported_exts += 1;
+  always_on_exts += (info.enable_ss_type == cfi_always_on);
+#endif
+
+  info.feature_1_enabled = GL(dl_riscv_feature_1);
+
+  /* No legacy check needed if all cfi exts are always on in main  */
+  if (program && (supported_exts == always_on_exts))
+    return;
+
+  /* No legacy check needed if all cfi exts are off  */
+  if (info.feature_1_enabled == 0)
+    return;
+
+  info.program = program;
+
+  info.enable_feature_1 = 0;
+#ifdef __riscv_landing_pad
+  if (info.enable_lp_type != cfi_always_off)
+    info.enable_feature_1 |= (info.feature_1_enabled
+            & GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED);
+  info.feature_1_legacy_lp = 0;
+#endif
+#ifdef __riscv_shadow_stack
+  if (info.enable_ss_type != cfi_always_off)
+    info.enable_feature_1 |= (info.feature_1_enabled
+            & GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS);
+  info.feature_1_legacy_ss = 0;
+#endif
+
+  info.feature_1_enabled = GL(dl_riscv_feature_1);
+  info.feature_1_legacy = 0;
+
 #ifdef SHARED
   if (program)
-    dl_cfi_check_startup (l, &feature_1);
+    dl_cfi_check_startup (l, &info);
   else
 #endif /* SHARED  */
-    dl_cfi_check_dlopen (l);
+    dl_cfi_check_dlopen (l, &info);
 }
diff --git a/sysdeps/riscv/dl-get-cpu-features.c b/sysdeps/riscv/dl-get-cpu-features.c
new file mode 100644
index 0000000000..bdb805d417
--- /dev/null
+++ b/sysdeps/riscv/dl-get-cpu-features.c
@@ -0,0 +1,27 @@ 
+/* Initialize CPU feature data.
+   Copyright (C) 2026 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 <ldsodefs.h>
+
+#ifdef SHARED
+# include <cpu-features.c>
+void
+_dl_riscv_init_cpu_features (void)
+{
+  init_cpu_features ();
+}
+#endif
diff --git a/sysdeps/riscv/dl-machine.h b/sysdeps/riscv/dl-machine.h
index b64e7e67d8..01422e01d9 100644
--- a/sysdeps/riscv/dl-machine.h
+++ b/sysdeps/riscv/dl-machine.h
@@ -41,6 +41,7 @@  extern void _dl_cfi_setup_features (unsigned int features);
 #else
 # define SET_LPAD
 #endif
+extern void _dl_riscv_init_cpu_features (void);
 
 #ifndef _RTLD_PROLOGUE
 # define _RTLD_PROLOGUE(entry)						\
@@ -153,6 +154,24 @@  elf_machine_dynamic (void)
 #define ARCH_LA_PLTENTER riscv_gnu_pltenter
 #define ARCH_LA_PLTEXIT riscv_gnu_pltexit
 
+/* We define an initialization function.  This is called very early in
+   _dl_sysdep_start.  */
+#define DL_PLATFORM_INIT dl_platform_init ()
+
+static inline void __attribute__ ((unused))
+dl_platform_init (void)
+{
+  if (GLRO(dl_platform) != NULL && *GLRO(dl_platform) == '\0')
+    /* Avoid an empty string which would disturb us.  */
+    GLRO(dl_platform) = NULL;
+
+#ifdef SHARED
+  /* init_cpu_features which has been called early from __libc_start_main in
+     static executable.  */
+  _dl_riscv_init_cpu_features ();
+#endif
+}
+
 /* Bias .got.plt entry by the offset requested by the PLT header.  */
 #define elf_machine_plt_value(map, reloc, value) (value)
 
diff --git a/sysdeps/riscv/dl-procruntime.c b/sysdeps/riscv/dl-procruntime.c
index 06a582920e..c35473a961 100644
--- a/sysdeps/riscv/dl-procruntime.c
+++ b/sysdeps/riscv/dl-procruntime.c
@@ -57,4 +57,21 @@  PROCINFO_CLASS unsigned int _dl_riscv_feature_1
 # else
 ,
 # endif
+
+# if !defined PROCINFO_DECL && defined SHARED
+  ._dl_riscv_feature_control
+# else
+PROCINFO_CLASS struct dl_riscv_feature_control _dl_riscv_feature_control
+# endif
+# ifndef PROCINFO_DECL
+= {
+    .lp = cfi_always_on,
+    .ss = cfi_always_on,
+  }
+# endif
+# if !defined SHARED || defined PROCINFO_DECL
+;
+# else
+,
+# endif
 #endif
diff --git a/sysdeps/riscv/dl-tunables.list b/sysdeps/riscv/dl-tunables.list
new file mode 100644
index 0000000000..f301219c19
--- /dev/null
+++ b/sysdeps/riscv/dl-tunables.list
@@ -0,0 +1,27 @@ 
+# RISC-V specific tunables.
+# Copyright (C) 2026 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/>.
+
+glibc {
+  cpu {
+    riscv_cfi_lp {
+      type: STRING
+    }
+    riscv_cfi_ss {
+      type: STRING
+    }
+}
diff --git a/sysdeps/riscv/feature-control.h b/sysdeps/riscv/feature-control.h
new file mode 100644
index 0000000000..9d24f4798b
--- /dev/null
+++ b/sysdeps/riscv/feature-control.h
@@ -0,0 +1,42 @@ 
+/* RISC-V feature tuning.
+   This file is part of the GNU C Library.
+   Copyright (C) 2026 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/>.  */
+
+#ifndef _RISCV_FEATURE_CONTROL_H
+#define _RISCV_FEATURE_CONTROL_H
+
+/* For each CFI feature, LP and SS, valid control values.  */
+enum dl_riscv_cfi_control
+{
+  /* Enable CFI features based on ELF property note.  */
+  cfi_elf_property = 0,
+  /* Always enable CFI features.  */
+  cfi_always_on,
+  /* Always disable CFI features.  */
+  cfi_always_off,
+  /* Enable CFI features permissively.  */
+  cfi_permissive
+};
+
+struct dl_riscv_feature_control
+{
+  enum dl_riscv_cfi_control lp : 2;
+  enum dl_riscv_cfi_control ss : 2;
+};
+
+#endif /* feature-control.h */
+
diff --git a/sysdeps/riscv/ldsodefs.h b/sysdeps/riscv/ldsodefs.h
index 6a9422f13d..d1d3ebd131 100644
--- a/sysdeps/riscv/ldsodefs.h
+++ b/sysdeps/riscv/ldsodefs.h
@@ -20,6 +20,7 @@ 
 #define _RISCV_LDSODEFS_H 1
 
 #include <elf.h>
+#include <feature-control.h>
 
 struct La_riscv_regs;
 struct La_riscv_retval;
diff --git a/sysdeps/riscv/libc-start.c b/sysdeps/riscv/libc-start.c
new file mode 100644
index 0000000000..2e00ce1b55
--- /dev/null
+++ b/sysdeps/riscv/libc-start.c
@@ -0,0 +1,31 @@ 
+/* Copyright (C) 2026 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/>.  */
+
+#ifndef SHARED
+
+/* Mark symbols hidden in static PIE for early self relocation to work.  */
+# if BUILD_PIE_DEFAULT
+#  pragma GCC visibility push(hidden)
+# endif
+# include <ldsodefs.h>
+# include <cpu-features.c>
+
+# define ARCH_INIT_CPU_FEATURES() init_cpu_features ()
+
+#endif /* !SHARED */
+#include <csu/libc-start.c>
+
diff --git a/sysdeps/riscv/libc-start.h b/sysdeps/riscv/libc-start.h
index 91d094cfb5..f873e1ee2e 100644
--- a/sysdeps/riscv/libc-start.h
+++ b/sysdeps/riscv/libc-start.h
@@ -31,6 +31,15 @@  get_cfi_feature (void)
 {
   unsigned int cfi_feature = 0;
   /* FIXME: check if cfi feature is supported by CPU  */
+
+#ifdef __riscv_landing_pad
+  if (GL(dl_riscv_feature_control).lp != cfi_always_off)
+    cfi_feature |= GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED;
+#endif
+#ifdef __riscv_shadow_stack
+  if (GL(dl_riscv_feature_control).ss != cfi_always_off)
+    cfi_feature |= GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS;
+#endif
   struct link_map *main_map = _dl_get_dl_main_map ();
 
   /* Scan program headers backward to check PT_GNU_PROPERTY early for
@@ -43,9 +52,7 @@  get_cfi_feature (void)
         _dl_process_pt_gnu_property (main_map, -1, &ph[-1]);
         /* Enable landing pad and shstk only if they are enabled on a static
            executable.  */
-        /* FIXME: change to &= to mask off other features after cpu_feature
-           is implemented  */
-        cfi_feature = (main_map->l_riscv_feature_1_and
+        cfi_feature &= (main_map->l_riscv_feature_1_and
                        & (GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED
                           | GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS));