[PATCH 1/1] aarch64: Add LD_DEBUG=protect to log BTI and GCS warnings

Yury Khrustalev yury.khrustalev@arm.com
Mon Dec 8 13:51:44 GMT 2025


Introduce DL_DEBUG_PROTECT mask to enable messages related to
loading modules that lack certain target-dependent protection
features.

Use this mask for warnings related to BTI and GCS.

---

Please see cover letter for more details on the feedback that is needed.

---
 elf/rtld.c                                    |  4 +-
 manual/dynlink.texi                           |  4 ++
 misc/tst-dl-debug-protect.sh                  | 48 +++++++++++++++++++
 sysdeps/aarch64/Makefile                      | 19 ++++++++
 sysdeps/aarch64/dl-bti.c                      | 10 +++-
 sysdeps/aarch64/dl-gcs.c                      |  3 ++
 .../aarch64/tst-bti-ld-debug-unprot-exe-dep.c |  3 ++
 .../aarch64/tst-bti-ld-debug-unprot-exe-one.c | 35 ++++++++++++++
 sysdeps/generic/ldsodefs.h                    |  1 +
 sysdeps/unix/sysv/linux/aarch64/Makefile      | 19 ++++++++
 .../aarch64/tst-gcs-ld-debug-unprot-exe-dep.c | 37 ++++++++++++++
 .../aarch64/tst-gcs-ld-debug-unprot-exe-one.c |  2 +
 12 files changed, 182 insertions(+), 3 deletions(-)
 create mode 100644 misc/tst-dl-debug-protect.sh
 create mode 100644 sysdeps/aarch64/tst-bti-ld-debug-unprot-exe-dep.c
 create mode 100644 sysdeps/aarch64/tst-bti-ld-debug-unprot-exe-one.c
 create mode 100644 sysdeps/unix/sysv/linux/aarch64/tst-gcs-ld-debug-unprot-exe-dep.c
 create mode 100644 sysdeps/unix/sysv/linux/aarch64/tst-gcs-ld-debug-unprot-exe-one.c

diff --git a/elf/rtld.c b/elf/rtld.c
index 5ea5383eb6..fa3ed85ac8 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -2430,10 +2430,12 @@ process_dl_debug (struct dl_main_state *state, const char *dl_debug)
 	DL_DEBUG_SCOPES },
       { LEN_AND_STR ("tls"), "display TLS structures processing",
 	DL_DEBUG_TLS },
+      { LEN_AND_STR ("protect"), "show protection warnings for modules",
+	DL_DEBUG_PROTECT },
       { LEN_AND_STR ("all"), "all previous options combined",
 	DL_DEBUG_LIBS | DL_DEBUG_RELOC | DL_DEBUG_FILES | DL_DEBUG_SYMBOLS
 	| DL_DEBUG_BINDINGS | DL_DEBUG_VERSIONS | DL_DEBUG_IMPCALLS
-	| DL_DEBUG_SCOPES | DL_DEBUG_TLS },
+	| DL_DEBUG_SCOPES | DL_DEBUG_TLS | DL_DEBUG_PROTECT },
       { LEN_AND_STR ("statistics"), "display relocation statistics",
 	DL_DEBUG_STATISTICS },
       { LEN_AND_STR ("unused"), "determined unused DSOs",
diff --git a/manual/dynlink.texi b/manual/dynlink.texi
index a78a065af4..53b83f7dcc 100644
--- a/manual/dynlink.texi
+++ b/manual/dynlink.texi
@@ -392,6 +392,10 @@ Display information about Thread-Local Storage (TLS) handling, including TCB
 allocation, deallocation, and reuse. This is useful for debugging issues
 related to thread creation and lifecycle.
 
+@item protect
+Display protection warnings that are related to loading modules that lack
+certain target-dependent features. This may be useful for audit purposes.
+
 @item all
 All previous options combined.
 
diff --git a/misc/tst-dl-debug-protect.sh b/misc/tst-dl-debug-protect.sh
new file mode 100644
index 0000000000..865c9e2977
--- /dev/null
+++ b/misc/tst-dl-debug-protect.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+# Test for protection warnings in dynamic linker.
+# Copyright (C) 2025 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/>.
+
+# Arguments are from Makefile.
+objpfx="$1"
+wrapper="$2"
+loader="$3"
+runenv="$4"
+pattern="$5"
+program="$6"
+
+output="${objpfx}tst-dl-debug-protect.debug"
+rm -f "${output}".*
+
+eval "${wrapper}" \
+  LD_DEBUG=protect LD_DEBUG_OUTPUT="${output}" ${runenv} \
+  "${loader}" "${program}"
+rc=$?
+
+if test $rc -eq 77; then
+  echo "Test is not supported"
+  rm -f "${output}".*
+  exit 77
+fi
+
+output=$(ls "${output}".*)
+cat "${output}"
+if ! grep -q "${pattern}" "${output}"; then
+  echo "Could not find expected pattern in '${pattern}'"
+  exit 1
+fi
+rm -f "${output}"
diff --git a/sysdeps/aarch64/Makefile b/sysdeps/aarch64/Makefile
index d0e4be8829..67c6b79015 100644
--- a/sysdeps/aarch64/Makefile
+++ b/sysdeps/aarch64/Makefile
@@ -97,6 +97,10 @@ tests += \
   tst-bti-dlopen-imm \
   tst-bti-dlopen-prot \
   tst-bti-dlopen-transitive \
+  tst-bti-ld-debug-abort-transitive \
+  tst-bti-ld-debug-permissive-dlopen \
+  tst-bti-ld-debug-unprot-exe-dep \
+  tst-bti-ld-debug-unprot-exe-one \
   tst-bti-permissive-dlopen \
   tst-bti-permissive-imm \
   tst-bti-permissive-transitive \
@@ -115,8 +119,11 @@ $(objpfx)tst-bti-dep-prot: $(objpfx)tst-bti-mod-prot.so
 $(objpfx)tst-bti-mod.so: $(objpfx)tst-bti-mod-unprot.so
 $(objpfx)tst-bti-permissive-imm: $(objpfx)tst-bti-mod-unprot.so
 $(objpfx)tst-bti-permissive-transitive: $(objpfx)tst-bti-mod.so
+$(objpfx)tst-bti-ld-debug-unprot-exe-dep: $(objpfx)tst-bti-mod-unprot.so
 
 CFLAGS-tst-bti-abort-unprot.o += -mbranch-protection=none
+CFLAGS-tst-bti-ld-debug-unprot-exe-one.o += -mbranch-protection=none
+CFLAGS-tst-bti-ld-debug-unprot-exe-dep.o += -mbranch-protection=none
 CFLAGS-tst-bti-mod-unprot.os += -mbranch-protection=none
 
 tst-bti-abort-imm-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_bti=1
@@ -141,6 +148,18 @@ $(objpfx)tst-bti-abort-%.out: $(..)sysdeps/aarch64/tst-bti-abort.sh \
 	$(SHELL) $< $(common-objpfx) $(test-name) '$(run-bti-abort-test)'; \
 	$(evaluate-test)
 
+$(objpfx)tst-bti-ld-debug-%.out: tst-dl-debug-protect.sh $(objpfx)tst-bti-%
+	$(SHELL) $< $(objpfx) '$(test-wrapper-env)' '$(rtld-prefix)' \
+	  '$(run-program-env) GLIBC_TUNABLES=glibc.cpu.aarch64_bti=0' \
+	  'AArch64 BTI: unmarked module $(objpfx)tst-bti-mod-unprot.so' \
+	  $(objpfx)tst-bti-$* > $@; $(evaluate-test)
+
+$(objpfx)tst-bti-ld-debug-unprot-exe-%.out: tst-dl-debug-protect.sh $(objpfx)tst-bti-ld-debug-unprot-exe-%
+	$(SHELL) $< $(objpfx) '$(test-wrapper-env)' '$(rtld-prefix)' \
+	  '$(run-program-env) GLIBC_TUNABLES=glibc.cpu.aarch64_bti=0' \
+	  'AArch64 BTI: unmarked module $(objpfx)tst-bti-ld-debug-unprot-exe-$*' \
+	  $(objpfx)tst-bti-ld-debug-unprot-exe-$* > $@; $(evaluate-test)
+
 endif # ifeq (yes,$(have-test-bti))
 
 endif
diff --git a/sysdeps/aarch64/dl-bti.c b/sysdeps/aarch64/dl-bti.c
index e654dde484..284412f1ab 100644
--- a/sysdeps/aarch64/dl-bti.c
+++ b/sysdeps/aarch64/dl-bti.c
@@ -102,7 +102,13 @@ _dl_bti_check (struct link_map *l, const char *program)
       if (is_rtld_link_map (dep->l_real))
 	continue;
 #endif
-      if (enforce_bti && !dep->l_mach.bti)
-	bti_failed (dep, program);
+      if (!dep->l_mach.bti)
+	{
+	  if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_PROTECT))
+	    _dl_debug_printf ("AArch64 BTI: unmarked module %s\n",
+			      dep->l_name && dep->l_name[0] ? dep->l_name : program);
+	  if (enforce_bti)
+	    bti_failed (dep, program);
+	}
     }
 }
diff --git a/sysdeps/aarch64/dl-gcs.c b/sysdeps/aarch64/dl-gcs.c
index 4ac86a5d6f..853929aa82 100644
--- a/sysdeps/aarch64/dl-gcs.c
+++ b/sysdeps/aarch64/dl-gcs.c
@@ -61,6 +61,9 @@ check_gcs (struct link_map *l, const char *program, bool enforced)
   /* Binary is marked, all good.  */
   if (l->l_mach.gcs)
     return true;
+  if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_PROTECT))
+    _dl_debug_printf ("AArch64 GCS: unmarked module %s\n",
+		      l->l_name && l->l_name[0] ? l->l_name : program);
   /* Binary is not marked and loaded via dlopen: abort.  */
   if (program == NULL)
     fail (l, program);
diff --git a/sysdeps/aarch64/tst-bti-ld-debug-unprot-exe-dep.c b/sysdeps/aarch64/tst-bti-ld-debug-unprot-exe-dep.c
new file mode 100644
index 0000000000..1f1b6b9fea
--- /dev/null
+++ b/sysdeps/aarch64/tst-bti-ld-debug-unprot-exe-dep.c
@@ -0,0 +1,3 @@
+/* This test allows to test case when both the exe and one of
+   its dependencies are not marked with BTI.  */
+#include "tst-bti-skeleton.c"
diff --git a/sysdeps/aarch64/tst-bti-ld-debug-unprot-exe-one.c b/sysdeps/aarch64/tst-bti-ld-debug-unprot-exe-one.c
new file mode 100644
index 0000000000..c0e62e0693
--- /dev/null
+++ b/sysdeps/aarch64/tst-bti-ld-debug-unprot-exe-one.c
@@ -0,0 +1,35 @@
+/* Simple test for an executable without BTI marking.
+   Copyright (C) 2025 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/auxv.h>
+#include <sys/signal.h>
+
+#include <support/check.h>
+#include <support/test-driver.h>
+
+static int
+do_test (void)
+{
+  unsigned long hwcap2 = getauxval (AT_HWCAP2);
+  if ((hwcap2 & HWCAP2_BTI) == 0)
+    FAIL_UNSUPPORTED ("BTI is not supported by this system");
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 8c3541602f..d14d1d01c5 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -526,6 +526,7 @@ struct rtld_global_ro
 /* DL_DEBUG_HELP is only used internally.  */
 #define DL_DEBUG_HELP       (1 << 10)
 #define DL_DEBUG_TLS        (1 << 11)
+#define DL_DEBUG_PROTECT    (1 << 12)
 
   /* Platform name.  */
   EXTERN const char *_dl_platform;
diff --git a/sysdeps/unix/sysv/linux/aarch64/Makefile b/sysdeps/unix/sysv/linux/aarch64/Makefile
index 6741f8ec00..8ba7a97e0d 100644
--- a/sysdeps/unix/sysv/linux/aarch64/Makefile
+++ b/sysdeps/unix/sysv/linux/aarch64/Makefile
@@ -28,6 +28,10 @@ gcs-tests-dynamic = \
   tst-gcs-dlopen-override \
   tst-gcs-enforced \
   tst-gcs-enforced-abort \
+  tst-gcs-ld-debug-dlopen-override \
+  tst-gcs-ld-debug-shared-optional \
+  tst-gcs-ld-debug-unprot-exe-dep \
+  tst-gcs-ld-debug-unprot-exe-one \
   tst-gcs-noreturn \
   tst-gcs-optional-off \
   tst-gcs-optional-on \
@@ -113,6 +117,7 @@ $(objpfx)tst-gcs-shared-enforced-abort: $(objpfx)tst-gcs-mod1.so $(objpfx)tst-gc
 $(objpfx)tst-gcs-shared-optional: $(objpfx)tst-gcs-mod1.so $(objpfx)tst-gcs-mod3.so
 $(objpfx)tst-gcs-shared-override: $(objpfx)tst-gcs-mod1.so $(objpfx)tst-gcs-mod3.so
 $(objpfx)tst-gcs-mod1.so: $(objpfx)tst-gcs-mod2.so
+$(objpfx)tst-gcs-ld-debug-unprot-exe-dep: $(objpfx)tst-gcs-mod2.so
 
 tst-gcs-shared-disabled-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=0
 tst-gcs-shared-enforced-abort-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=1
@@ -124,6 +129,8 @@ LDFLAGS-tst-gcs-dlopen-enforced = -Wl,-z,gcs=always
 LDFLAGS-tst-gcs-dlopen-optional-on = -Wl,-z,gcs=always
 LDFLAGS-tst-gcs-dlopen-optional-off = -Wl,-z,gcs=never
 LDFLAGS-tst-gcs-dlopen-override = -Wl,-z,gcs=always
+LDFLAGS-tst-gcs-ld-debug-unprot-exe-one = -Wl,-z,gcs=never
+LDFLAGS-tst-gcs-ld-debug-unprot-exe-dep = -Wl,-z,gcs=never
 
 tst-gcs-dlopen-disabled-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=0
 tst-gcs-dlopen-enforced-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=1
@@ -141,6 +148,18 @@ LDFLAGS-tst-gcs-noreturn = -Wl,-z,gcs=always
 
 tst-gcs-noreturn-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=0
 
+$(objpfx)tst-gcs-ld-debug-%.out: tst-dl-debug-protect.sh $(objpfx)tst-gcs-%
+	$(SHELL) $< $(objpfx) '$(test-wrapper-env)' '$(rtld-prefix)' \
+	  '$(run-program-env) GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=2' \
+	  'AArch64 GCS: unmarked module $(objpfx)tst-gcs-mod2.so' \
+	  $(objpfx)tst-gcs-$* > $@; $(evaluate-test)
+
+$(objpfx)tst-gcs-ld-debug-unprot-exe-%.out: tst-dl-debug-protect.sh $(objpfx)tst-gcs-ld-debug-unprot-exe-%
+	$(SHELL) $< $(objpfx) '$(test-wrapper-env)' '$(rtld-prefix)' \
+	  '$(run-program-env) GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=2' \
+	  'AArch64 GCS: unmarked module $(objpfx)tst-gcs-ld-debug-unprot-exe-$*' \
+	  $(objpfx)tst-gcs-ld-debug-unprot-exe-$* > $@; $(evaluate-test)
+
 endif # ifeq ($(have-test-gcs),yes)
 
 endif # ifeq ($(subdir),misc)
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-ld-debug-unprot-exe-dep.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-ld-debug-unprot-exe-dep.c
new file mode 100644
index 0000000000..5284fb3265
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-ld-debug-unprot-exe-dep.c
@@ -0,0 +1,37 @@
+/* This test allows to test case when both the exe and one of
+   its dependencies are not marked with GCS.
+   Copyright (C) 2025 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 "tst-gcs-helper.h"
+
+/* Defined in tst-gcs-mod2.c.  */
+extern int fun2 (void);
+
+static int
+do_test (void)
+{
+  /* Check if GCS could possible by enabled.  */
+  if (!(getauxval (AT_HWCAP) & HWCAP_GCS))
+    FAIL_UNSUPPORTED ("kernel or CPU does not support GCS");
+  bool gcs_enabled = __check_gcs_status ();
+  puts (gcs_enabled ? "GCS enabled" : "GCS not enabled");
+  TEST_VERIFY (!gcs_enabled);
+  return fun2();
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-ld-debug-unprot-exe-one.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-ld-debug-unprot-exe-one.c
new file mode 100644
index 0000000000..bd688785bb
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-ld-debug-unprot-exe-one.c
@@ -0,0 +1,2 @@
+#define TEST_GCS_EXPECT_ENABLED 0
+#include "tst-gcs-skeleton.c"
-- 
2.47.3



More information about the Libc-alpha mailing list