#endif
}
- _dl_open_check (new);
+ _dl_open_check (new, mode);
/* Print scope information. */
if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_SCOPES))
ignored and GCS is supposed to be enabled. This occurs
for the GCS_POLICY_ENFORCED and GCS_POLICY_OPTIONAL policies. */
static bool
-check_gcs (struct link_map *l, const char *program, bool enforced)
+check_gcs (struct link_map *l, const char *program, bool enforced,
+ int dlopen_mode)
{
#ifdef SHARED
/* Ignore GCS marking on ld.so: its properties are not processed. */
/* 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)
+ /* Binary is not marked and loaded via dlopen: abort. Also, do not
+ fail is optional dlopne_mode is being used with audit modules without
+ GCS support. */
+ if (program == NULL && (dlopen_mode & __RTLD_AUDIT) == 0)
fail (l, program);
/* Binary is not marked and we enforce GCS: abort. */
if (enforced)
We interrupt checking if GCS is optional and we already know
it is going to be disabled. */
static void
-check_gcs_depends (struct link_map *l, const char *program, bool enforced)
+check_gcs_depends (struct link_map *l, const char *program, bool enforced,
+ int dlopen_mode)
{
- if (check_gcs (l, program, enforced))
+ if (check_gcs (l, program, enforced, dlopen_mode))
for (unsigned int i = 0; i < l->l_searchlist.r_nlist; i++)
- if (!check_gcs (l->l_searchlist.r_list[i], program, enforced))
+ if (!check_gcs (l->l_searchlist.r_list[i], program, enforced,
+ dlopen_mode))
break;
}
/* Apply GCS policy for L and its dependencies.
PROGRAM is NULL when this check is invoked for dl_open. */
void
-_dl_gcs_check (struct link_map *l, const char *program)
+_dl_gcs_check (struct link_map *l, const char *program, int dlopen_mode)
{
unsigned long policy = GL (dl_aarch64_gcs);
switch (policy)
case GCS_POLICY_OVERRIDE:
return;
case GCS_POLICY_ENFORCED:
- check_gcs_depends (l, program, true);
+ check_gcs_depends (l, program, true, dlopen_mode);
return;
case GCS_POLICY_OPTIONAL:
- check_gcs_depends (l, program, false);
+ check_gcs_depends (l, program, false, dlopen_mode);
return;
default:
/* All other policy values are not supported: abort. */
extern void _dl_bti_check (struct link_map *, const char *)
attribute_hidden;
-extern void _dl_gcs_check (struct link_map *, const char *)
+extern void _dl_gcs_check (struct link_map *, const char *, int)
attribute_hidden;
static inline void __attribute__ ((always_inline))
_rtld_main_check (struct link_map *m, const char *program)
{
_dl_bti_check (m, program);
- _dl_gcs_check (m, program);
+ _dl_gcs_check (m, program, 0);
}
static inline void __attribute__ ((always_inline))
-_dl_open_check (struct link_map *m)
+_dl_open_check (struct link_map *m, int dlopen_mode)
{
_dl_bti_check (m, NULL);
- _dl_gcs_check (m, NULL);
+ _dl_gcs_check (m, NULL, dlopen_mode);
}
static inline void __attribute__ ((always_inline))
}
static inline void __attribute__ ((always_inline))
-_dl_open_check (struct link_map *m)
+_dl_open_check (struct link_map *m, int dlopen_mode)
{
}
ifeq (yes,$(have-test-gcs))
gcs-tests-dynamic = \
+ tst-gcs-audit-disabled \
+ tst-gcs-audit-enforced \
+ tst-gcs-audit-optional \
+ tst-gcs-audit-override \
tst-gcs-disabled \
tst-gcs-dlopen-disabled \
tst-gcs-dlopen-enforced \
tst-gcs-noreturn \
tst-gcs-optional-off \
tst-gcs-optional-on \
+ tst-gcs-override \
tst-gcs-preload-disabled \
tst-gcs-preload-enforced-abort \
tst-gcs-preload-optional \
tst-gcs-preload-override \
- tst-gcs-override \
tst-gcs-shared-disabled \
tst-gcs-shared-enforced-abort \
tst-gcs-shared-optional \
LDFLAGS-tst-gcs-ld-debug-dlopen = -Wl,-z,gcs=always
modules-names += \
+ tst-gcs-audit1 \
tst-gcs-mod1 \
tst-gcs-mod2 \
tst-gcs-mod3 \
+ tst-gcs-mod4 \
# modules-names
$(objpfx)tst-gcs-shared-disabled: $(objpfx)tst-gcs-mod1.so $(objpfx)tst-gcs-mod3.so
GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=3 \
LD_PRELOAD=$(objpfx)tst-gcs-mod1.so
+LDFLAGS-tst-gcs-audit1.so += -Wl,-z,gcs=never
+
+LDFLAGS-tst-gcs-audit-disabled += -Wl,-z,gcs=always
+LDFLAGS-tst-gcs-audit-enforced += -Wl,-z,gcs=always
+LDFLAGS-tst-gcs-audit-optional += -Wl,-z,gcs=always
+
+$(objpfx)tst-gcs-audit-disabled.out: $(objpfx)tst-gcs-audit1.so
+$(objpfx)tst-gcs-audit-disabled: $(objpfx)tst-gcs-mod4.so
+$(objpfx)tst-gcs-audit-enforced.out: $(objpfx)tst-gcs-audit1.so
+$(objpfx)tst-gcs-audit-enforced: $(objpfx)tst-gcs-mod4.so
+$(objpfx)tst-gcs-audit-optional.out: $(objpfx)tst-gcs-audit1.so
+$(objpfx)tst-gcs-audit-optional: $(objpfx)tst-gcs-mod4.so
+$(objpfx)tst-gcs-audit-override.out: $(objpfx)tst-gcs-audit1.so
+$(objpfx)tst-gcs-audit-override: $(objpfx)tst-gcs-mod4.so
+
+tst-gcs-audit-disabled-ARGS = -- $(host-test-program-cmd)
+tst-gcs-audit-enforced-ARGS = -- $(host-test-program-cmd)
+tst-gcs-audit-optional-ARGS = -- $(host-test-program-cmd)
+tst-gcs-audit-override-ARGS = -- $(host-test-program-cmd)
+
endif # ifeq ($(have-test-gcs),yes)
endif # ifeq ($(subdir),misc)
--- /dev/null
+/* Checks if the audit module without GCS marking is loaded with default GCS
+ support. */
+
+#define AUDIT_MOD "tst-gcs-audit1.so"
+
+/* The audit modules should load, to expect the AUDIT function wrapper return
+ value. */
+#define HANDLE_RESTART TEST_COMPARE (fun (), 42)
+
+#define GCS_MODE "0"
+
+#define ALLOW_OUTPUT sc_allow_stdout | sc_allow_stderr
+
+#define CHECK_STDOUT \
+ TEST_COMPARE_STRING (result.out.buffer, "GCS not enabled\n");
+
+#define CHECK_STDERR
+
+#include "tst-gcs-audit-skeleton.c"
--- /dev/null
+/* Checks if the audit module without GCS marking is not loaded when GCS is
+ enforced. */
+
+#define AUDIT_MOD "tst-gcs-audit1.so"
+
+/* The audit moduled should not load, the function returns the expected
+ value. */
+#define HANDLE_RESTART TEST_COMPARE (fun (), 0)
+
+#define GCS_MODE "1"
+
+#define ALLOW_OUTPUT sc_allow_stdout | sc_allow_stderr
+
+#define CHECK_STDOUT \
+ TEST_COMPARE_STRING (result.out.buffer, \
+ "GCS enabled\n");
+
+#define CHECK_STDERR \
+ TEST_COMPARE_STRING (result.err.buffer, \
+ "ERROR: ld.so: object '" AUDIT_MOD "' " \
+ "cannot be loaded as audit interface: " \
+ "not GCS compatible; ignored.\n")
+
+#include "tst-gcs-audit-skeleton.c"
--- /dev/null
+/* Checks if the audit module without GCS marking is loaded when GCS is
+ optional and the GCS is disabled. */
+
+#define AUDIT_MOD "tst-gcs-audit1.so"
+
+/* The audit moduled should not load, the function returns the expected
+ value. */
+#define HANDLE_RESTART TEST_COMPARE (fun (), 42)
+
+#define GCS_MODE "2"
+
+#define ALLOW_OUTPUT sc_allow_stdout | sc_allow_stderr
+
+#define CHECK_STDOUT \
+ TEST_COMPARE_STRING (result.out.buffer, \
+ "GCS not enabled\n");
+
+#define CHECK_STDERR
+
+#include "tst-gcs-audit-skeleton.c"
--- /dev/null
+/* Check if the audit modules without GCS marking is loaded when GCS is
+ overrided. */
+
+#define AUDIT_MOD "tst-gcs-audit1.so"
+
+/* The audit modules should load, to expect the AUDIT function wrapper return
+ value. */
+#define HANDLE_RESTART TEST_COMPARE (fun (), 42)
+
+#define GCS_MODE "3"
+
+#define ALLOW_OUTPUT sc_allow_stdout | sc_allow_stderr
+
+#define CHECK_STDOUT \
+ TEST_COMPARE_STRING (result.out.buffer, "GCS enabled\n");
+
+#define CHECK_STDERR
+
+#include "tst-gcs-audit-skeleton.c"
--- /dev/null
+/* Skeleton for GCS tests with LD_AUDIT.
+ 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 <getopt.h>
+#include <stdlib.h>
+#include <sys/auxv.h>
+#include <unistd.h>
+
+#include <support/capture_subprocess.h>
+#include <support/check.h>
+#include <support/xstdio.h>
+
+#include "tst-gcs-helper.h"
+
+static int restart;
+#define CMDLINE_OPTIONS \
+ { "restart", no_argument, &restart, 1 },
+
+/* Defined in tst-bti-mod.c file. */
+extern int fun (void);
+typedef int (*fun_t) (void);
+
+static int
+handle_restart (void)
+{
+ if (__check_gcs_status ())
+ puts ("GCS enabled");
+ else
+ puts ("GCS not enabled");
+
+ HANDLE_RESTART;
+ return 0;
+}
+
+static int
+do_test (int argc, char *argv[])
+{
+ if ((getauxval (AT_HWCAP) & HWCAP_GCS) == 0)
+ FAIL_UNSUPPORTED ("kernel or CPU does not support GCS");
+
+ /* We must have either:
+ - One our fource parameters left if called initially:
+ + path to ld.so optional
+ + "--library-path" optional
+ + the library path optional
+ + the application name */
+ if (restart)
+ return handle_restart ();
+
+ char *spargv[9];
+ int i = 0;
+ for (; i < argc - 1; i++)
+ spargv[i] = argv[i + 1];
+ spargv[i++] = (char *) "--direct";
+ spargv[i++] = (char *) "--restart";
+ spargv[i] = NULL;
+
+ setenv ("LD_AUDIT", AUDIT_MOD, 0);
+ setenv ("GLIBC_TUNABLES", "glibc.cpu.aarch64_gcs=" GCS_MODE, 0);
+
+ struct support_capture_subprocess result
+ = support_capture_subprogram (spargv[0], spargv, NULL);
+ support_capture_subprocess_check (&result, "tst-gcs-audit", 0, ALLOW_OUTPUT);
+
+ CHECK_STDERR;
+ CHECK_STDOUT;
+
+ return 0;
+}
+
+#define TEST_FUNCTION_ARGV do_test
+#include <support/test-driver.c>
--- /dev/null
+/* Audit DSO for GCS testing.
+ 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 <link.h>
+
+#define TST_COOKIE 0x1
+
+unsigned int
+la_version (unsigned int current)
+{
+ return LAV_CURRENT;
+}
+
+unsigned int la_objopen (struct link_map *map, Lmid_t lmid, uintptr_t *cookie)
+{
+ return LA_FLG_BINDFROM | LA_FLG_BINDTO;
+}
+
+static int fun_wrapper (void)
+{
+ return 42;
+}
+
+uintptr_t
+la_symbind64 (Elf64_Sym *sym, unsigned int ndx,
+ uintptr_t *refcook, uintptr_t *defcook,
+ unsigned int *flags, const char *symname)
+{
+ return strcmp (symname, "fun") == 0
+ ? (uintptr_t) fun_wrapper : sym->st_value;
+}
--- /dev/null
+/* DSO for testing GCS.
+ 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/>. */
+
+int fun (void)
+{
+ return 0;
+}
}
static inline void __attribute__ ((always_inline))
-_dl_open_check (struct link_map *m)
+_dl_open_check (struct link_map *m, int dlopne_mode)
{
dl_isa_level_check (m, NULL);
#if CET_ENABLED