DL_DEBUG_SCOPES },
{ LEN_AND_STR ("tls"), "display TLS structures processing",
DL_DEBUG_TLS },
+ { LEN_AND_STR ("security"), "show security warnings for input files",
+ DL_DEBUG_SECURITY },
{ 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_SECURITY },
{ LEN_AND_STR ("statistics"), "display relocation statistics",
DL_DEBUG_STATISTICS },
{ LEN_AND_STR ("unused"), "determined unused DSOs",
--- /dev/null
+#!/bin/sh
+# A script to run tests with LD_DEBUG=security and check
+# that output contains expected pattern.
+# 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/>.
+
+# Arguments are from Makefile:
+# Path to the current build folder
+objpfx="$1"
+# Test wrapper command line
+wrapper="$2"
+# Dynamic loader command line
+loader="$3"
+# Test environment variables
+runenv="$4"
+# Grep pattern to look for in the test output
+pattern="$5"
+# Path to the test executable to run
+program="$6"
+
+output="${objpfx}`basename ${program}`.debug"
+rm -f "${output}".*
+
+eval "${wrapper}" \
+ LD_DEBUG=security 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 LD_DEBUG_OUTPUT file"
+ exit 1
+fi
+rm -f "${output}"
allocation, deallocation, and reuse. This is useful for debugging issues
related to thread creation and lifecycle.
+@item security
+Display security warnings that are related to loading binaries that lack
+certain target-dependent hardening features. This may be useful for audit
+purposes.
+
@item all
All previous options combined.
tst-bti-dlopen-imm \
tst-bti-dlopen-prot \
tst-bti-dlopen-transitive \
+ tst-bti-ld-debug-both \
+ tst-bti-ld-debug-dlopen \
+ tst-bti-ld-debug-exe \
+ tst-bti-ld-debug-shared \
tst-bti-permissive-dlopen \
tst-bti-permissive-imm \
tst-bti-permissive-transitive \
$(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-shared: $(objpfx)tst-bti-mod.so
+$(objpfx)tst-bti-ld-debug-both: $(objpfx)tst-bti-mod-unprot.so
CFLAGS-tst-bti-abort-unprot.o += -mbranch-protection=none
+CFLAGS-tst-bti-ld-debug-exe.o += -mbranch-protection=none
+CFLAGS-tst-bti-ld-debug-both.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
tst-bti-abort-static-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_bti=1
CFLAGS-tst-bti-abort-static.o += -mbranch-protection=none
+$(objpfx)tst-bti-ld-debug-%.out: $(..)elf/tst-dl-debug-protect.sh $(objpfx)tst-bti-ld-debug-%
+ $(SHELL) $< $(objpfx) '$(test-wrapper-env)' '$(rtld-prefix)' \
+ '$(run-program-env) GLIBC_TUNABLES=glibc.cpu.aarch64_bti=0' \
+ 'security: not compatible with AArch64 BTI: $(objpfx)' \
+ $(objpfx)tst-bti-ld-debug-$* > $@; $(evaluate-test)
+
endif # ifeq (yes,$(have-test-bti))
endif
"failed to turn on BTI protection");
}
+static void
+bti_warning (struct link_map *l, const char *program)
+{
+ if (l->l_name[0] != '\0')
+ _dl_debug_printf ("security: not compatible with AArch64 BTI: %s\n",
+ l->l_name);
+ else if (__glibc_likely (program != NULL))
+ _dl_debug_printf ("security: not compatible with AArch64 BTI: %s\n",
+ program);
+}
/* Enable BTI for L and its dependencies. */
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 (enforce_bti)
+ bti_failed (dep, program);
+ else if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_SECURITY))
+ bti_warning (dep, program);
+ }
}
}
_dl_signal_error (0, l->l_name, "dlopen", "not GCS compatible");
}
+static void
+warn (struct link_map *l, const char *program)
+{
+ if (l->l_name[0] != '\0')
+ _dl_debug_printf ("security: not compatible with AArch64 GCS: %s\n",
+ l->l_name);
+ else if (__glibc_likely (program != NULL))
+ _dl_debug_printf ("security: not compatible with AArch64 GCS: %s\n",
+ program);
+}
+
static void
unsupported (void)
{
/* This function is called only when binary markings are not
ignored and GCS is supposed to be enabled. This occurs
- for the GCS_POLICY_ENFORCED and GCS_POLICY_ENFORCED policies. */
+ for the GCS_POLICY_ENFORCED and GCS_POLICY_OPTIONAL policies. */
static bool
check_gcs (struct link_map *l, const char *program, bool enforced)
{
/* Binary is marked, all good. */
if (l->l_mach.gcs)
return true;
+ /* Extra logging requested, print path to failed binary. */
+ if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_SECURITY))
+ warn (l, program);
/* Binary is not marked and loaded via dlopen: abort. */
if (program == NULL)
fail (l, program);
--- /dev/null
+/* This LD_DEBUG warning test allows to test a case when both the exe and
+ one of its dependencies are not marked with BTI. */
+#include "tst-bti-skeleton.c"
--- /dev/null
+/* Test that when BTI is not enforced an LD_DEBUG warning is printed
+ when a library that does not have BTI marking is loaded via dlopen. */
+#define TEST_BTI_DLOPEN_MODULE "tst-bti-mod-unprot.so"
+#define TEST_BTI_EXPECT_DLOPEN 1
+#include "tst-bti-skeleton-dlopen.c"
--- /dev/null
+/* Simple test for an executable without BTI marking.
+ 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/>. */
+
+#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>
--- /dev/null
+/* Test that when BTI is not enforced an LD_DEBUG warning is printed
+ when one of the shared library dependencies does not have BTI marking. */
+#include "tst-bti-skeleton.c"
/* DL_DEBUG_HELP is only used internally. */
#define DL_DEBUG_HELP (1 << 10)
#define DL_DEBUG_TLS (1 << 11)
+#define DL_DEBUG_SECURITY (1 << 12)
/* Platform name. */
EXTERN const char *_dl_platform;
tst-gcs-dlopen-override \
tst-gcs-enforced \
tst-gcs-enforced-abort \
+ tst-gcs-ld-debug-both \
+ tst-gcs-ld-debug-dlopen \
+ tst-gcs-ld-debug-exe \
+ tst-gcs-ld-debug-shared \
tst-gcs-noreturn \
tst-gcs-optional-off \
tst-gcs-optional-on \
LDFLAGS-tst-gcs-optional-off += -Wl,-z,gcs=never
LDFLAGS-tst-gcs-override += -Wl,-z,gcs=never
-CFLAGS-tst-gcs-enforced-static-abort.o += -mbranch-protection=none
-
LDFLAGS-tst-gcs-disabled-static += -Wl,-z,gcs=always
LDFLAGS-tst-gcs-enforced-static += -Wl,-z,gcs=always
+LDFLAGS-tst-gcs-enforced-static-abort += -Wl,-z,gcs=never
LDFLAGS-tst-gcs-optional-static-on += -Wl,-z,gcs=always
LDFLAGS-tst-gcs-optional-static-off += -Wl,-z,gcs=never
LDFLAGS-tst-gcs-override-static += -Wl,-z,gcs=never
tst-gcs-disabled-static-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=0
tst-gcs-enforced-static-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=1
-tst-gcs-enforced-static-abort-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=1:glibc.cpu.aarch64_bti=0
+tst-gcs-enforced-static-abort-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=1
tst-gcs-optional-static-on-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=2
tst-gcs-optional-static-off-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=2
tst-gcs-override-static-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=3
LDFLAGS-tst-gcs-shared-optional = -Wl,-z,gcs=always
LDFLAGS-tst-gcs-shared-override = -Wl,-z,gcs=always
+LDFLAGS-tst-gcs-ld-debug-shared = -Wl,-z,gcs=always
+LDFLAGS-tst-gcs-ld-debug-dlopen = -Wl,-z,gcs=always
+
modules-names += \
tst-gcs-mod1 \
tst-gcs-mod2 \
$(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-both: $(objpfx)tst-gcs-mod2.so
+$(objpfx)tst-gcs-ld-debug-shared: $(objpfx)tst-gcs-mod1.so $(objpfx)tst-gcs-mod3.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
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-exe = -Wl,-z,gcs=never
+LDFLAGS-tst-gcs-ld-debug-both = -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
$(objpfx)tst-gcs-dlopen-optional-on.out: $(objpfx)tst-gcs-mod2.so
$(objpfx)tst-gcs-dlopen-optional-off.out: $(objpfx)tst-gcs-mod2.so
$(objpfx)tst-gcs-dlopen-override.out: $(objpfx)tst-gcs-mod2.so
+$(objpfx)tst-gcs-ld-debug-dlopen.out: $(objpfx)tst-gcs-mod2.so
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: $(..)elf/tst-dl-debug-protect.sh $(objpfx)tst-gcs-ld-debug-%
+ $(SHELL) $< $(objpfx) '$(test-wrapper-env)' '$(rtld-prefix)' \
+ '$(run-program-env) GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=2' \
+ 'security: not compatible with AArch64 GCS: $(objpfx)' \
+ $(objpfx)tst-gcs-ld-debug-$* > $@; $(evaluate-test)
+
endif # ifeq ($(have-test-gcs),yes)
endif # ifeq ($(subdir),misc)
--- /dev/null
+/* Test that when GCS is optional an LD_DEBUG warning is printed when
+ both the executable and its shared library dependency do not have
+ GCS marking.
+ 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/>. */
+
+#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>
--- /dev/null
+/* Test that when GCS is optional an LD_DEBUG warning is printed when
+ a library that does not have GCS marking is loaded via dlopen. */
+#define TEST_GCS_EXPECT_ENABLED 0
+#define TEST_GCS_EXPECT_DLOPEN 0
+#include "tst-gcs-dlopen.c"
--- /dev/null
+/* Test that when GCS is optional an LD_DEBUG warning is printed when
+ the executable does not have GCS marking. */
+#define TEST_GCS_EXPECT_ENABLED 0
+#include "tst-gcs-skeleton.c"
--- /dev/null
+/* Test that when GCS is optional an LD_DEBUG warning is printed when
+ one of the shared library dependencies does not have GCS marking. */
+#define TEST_GCS_EXPECT_ENABLED 0
+#include "tst-gcs-shared.c"