diff --git a/elf/rtld.c b/elf/rtld.c index 00df9acfca..9842f31ee4 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 ("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", diff --git a/elf/tst-dl-debug-protect.sh b/elf/tst-dl-debug-protect.sh new file mode 100644 index 0000000000..7a9ed1175d --- /dev/null +++ b/elf/tst-dl-debug-protect.sh @@ -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 +# . + +# 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}" diff --git a/manual/dynlink.texi b/manual/dynlink.texi index a78a065af4..a388d7eeeb 100644 --- a/manual/dynlink.texi +++ b/manual/dynlink.texi @@ -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. diff --git a/sysdeps/aarch64/Makefile b/sysdeps/aarch64/Makefile index ee3981e3f6..a87d02e648 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-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 diff --git a/sysdeps/aarch64/dl-bti.c b/sysdeps/aarch64/dl-bti.c index 1301ce2e73..5ef5e37c60 100644 --- a/sysdeps/aarch64/dl-bti.c +++ b/sysdeps/aarch64/dl-bti.c @@ -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) - 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); + } } } diff --git a/sysdeps/aarch64/dl-gcs.c b/sysdeps/aarch64/dl-gcs.c index 1c6944562d..c195cbc465 100644 --- a/sysdeps/aarch64/dl-gcs.c +++ b/sysdeps/aarch64/dl-gcs.c @@ -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); diff --git a/sysdeps/aarch64/tst-bti-ld-debug-both.c b/sysdeps/aarch64/tst-bti-ld-debug-both.c new file mode 100644 index 0000000000..4189ca931d --- /dev/null +++ b/sysdeps/aarch64/tst-bti-ld-debug-both.c @@ -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" diff --git a/sysdeps/aarch64/tst-bti-ld-debug-dlopen.c b/sysdeps/aarch64/tst-bti-ld-debug-dlopen.c new file mode 100644 index 0000000000..eece559cde --- /dev/null +++ b/sysdeps/aarch64/tst-bti-ld-debug-dlopen.c @@ -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" diff --git a/sysdeps/aarch64/tst-bti-ld-debug-exe.c b/sysdeps/aarch64/tst-bti-ld-debug-exe.c new file mode 100644 index 0000000000..99511e2775 --- /dev/null +++ b/sysdeps/aarch64/tst-bti-ld-debug-exe.c @@ -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 + . */ + +#include +#include +#include + +#include +#include + +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 diff --git a/sysdeps/aarch64/tst-bti-ld-debug-shared.c b/sysdeps/aarch64/tst-bti-ld-debug-shared.c new file mode 100644 index 0000000000..c28a9a5515 --- /dev/null +++ b/sysdeps/aarch64/tst-bti-ld-debug-shared.c @@ -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" diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h index 46f334184c..0fcc14999c 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_SECURITY (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 6ea6048822..a8477087d9 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-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) diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-ld-debug-both.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-ld-debug-both.c new file mode 100644 index 0000000000..acb71156ec --- /dev/null +++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-ld-debug-both.c @@ -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 + . */ + +#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 diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-ld-debug-dlopen.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-ld-debug-dlopen.c new file mode 100644 index 0000000000..b90fa2ef80 --- /dev/null +++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-ld-debug-dlopen.c @@ -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" diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-ld-debug-exe.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-ld-debug-exe.c new file mode 100644 index 0000000000..d900c45aef --- /dev/null +++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-ld-debug-exe.c @@ -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" diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-ld-debug-shared.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-ld-debug-shared.c new file mode 100644 index 0000000000..b6cf4b19af --- /dev/null +++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-ld-debug-shared.c @@ -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"