aarch64: Add LD_DEBUG=security to log BTI and GCS warnings

Introduce DL_DEBUG_SECURITY mask to enable messages related to
loading modules that lack certain target-dependent hardening
or security features.

Use this mask for warnings related to AArch64 BTI and GCS.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
This commit is contained in:
Yury Khrustalev 2026-01-06 13:43:34 +00:00
parent 274441f62a
commit 99b8ec8fd4
16 changed files with 227 additions and 7 deletions

View File

@ -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 ("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",

View File

@ -0,0 +1,55 @@
#!/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}"

View File

@ -392,6 +392,11 @@ 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 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.

View File

@ -97,6 +97,10 @@ tests += \
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 \
@ -115,8 +119,12 @@ $(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-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
@ -148,6 +156,12 @@ tests-static += \
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

View File

@ -85,6 +85,16 @@ bti_failed (struct link_map *l, const char *program)
"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. */
@ -112,7 +122,12 @@ _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)
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);
}
}
}

View File

@ -50,6 +50,17 @@ fail (struct link_map *l, const char *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)
{
@ -58,7 +69,7 @@ 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)
{
@ -70,6 +81,9 @@ 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);

View File

@ -0,0 +1,3 @@
/* 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"

View File

@ -0,0 +1,5 @@
/* 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"

View File

@ -0,0 +1,35 @@
/* 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>

View File

@ -0,0 +1,3 @@
/* 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"

View File

@ -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_SECURITY (1 << 12)
/* Platform name. */
EXTERN const char *_dl_platform;

View File

@ -28,6 +28,10 @@ gcs-tests-dynamic = \
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 \
@ -73,10 +77,9 @@ LDFLAGS-tst-gcs-optional-on += -Wl,-z,gcs=always
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
@ -90,7 +93,7 @@ tst-gcs-override-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=3
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
@ -103,6 +106,9 @@ LDFLAGS-tst-gcs-shared-enforced-abort = -Wl,-z,gcs=always
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 \
@ -114,6 +120,8 @@ $(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-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
@ -125,6 +133,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-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
@ -137,11 +147,18 @@ $(objpfx)tst-gcs-dlopen-enforced.out: $(objpfx)tst-gcs-mod2.so
$(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)

View File

@ -0,0 +1,38 @@
/* 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>

View File

@ -0,0 +1,5 @@
/* 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"

View File

@ -0,0 +1,4 @@
/* 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"

View File

@ -0,0 +1,4 @@
/* 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"