]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
aarch64: Fix LD_AUDIT with GCS in permissive mode
authorAdhemerval Zanella <adhemerval.zanella@linaro.org>
Wed, 7 Jan 2026 16:52:39 +0000 (13:52 -0300)
committerAdhemerval Zanella <adhemerval.zanella@linaro.org>
Wed, 14 Jan 2026 18:00:24 +0000 (15:00 -0300)
In permissive mode, during audit module handling, check_gcs is unaware
that it is handling audit modules rather than the binary itself.  It
causes the loader to fail to load the audit module, rather than
loading it and disabling GCS.

Also extends GCS tests with 4 LD_AUDIT tests:

1. tst-gcs-audit-disabled: checks if the audit module without GCS
   marking is loaded with default gcs support.

2. tst-gcs-audit-enforced: checks if the audit module without GCS
   marking is not loaded when GCS is enforced.

3. tst-gcs-audit-optional: checks if the audit module without GCS
   marking is loaded when GCS is optional.

4. tst-gcs-audit-override: check if the audit modules without GCS
   marking is loaded when GCS is overrided.

Checked on aarch64-linux-gnu with Linux 6.18 on Apple M4 emulated (for
BTI support) and on qemu 10.1.50 simulated (for GCS).

Reviewed-by: Yury Khrustalev <yury.khrustalev@arm.com>
Tested-by: Yury Khrustalev <yury.khrustalev@arm.com>
13 files changed:
elf/dl-open.c
sysdeps/aarch64/dl-gcs.c
sysdeps/aarch64/dl-prop.h
sysdeps/generic/dl-prop.h
sysdeps/unix/sysv/linux/aarch64/Makefile
sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit-disabled.c [new file with mode: 0644]
sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit-enforced.c [new file with mode: 0644]
sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit-optional.c [new file with mode: 0644]
sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit-override.c [new file with mode: 0644]
sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit-skeleton.c [new file with mode: 0644]
sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit1.c [new file with mode: 0644]
sysdeps/unix/sysv/linux/aarch64/tst-gcs-mod4.c [new file with mode: 0644]
sysdeps/x86/dl-prop.h

index 7685728884e3aaab95d42be136bd827b667494d0..da0e8512cfc30a1b21092f00bd78e3196c23846a 100644 (file)
@@ -625,7 +625,7 @@ dl_open_worker_begin (void *a)
 #endif
       }
 
-  _dl_open_check (new);
+  _dl_open_check (new, mode);
 
   /* Print scope information.  */
   if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_SCOPES))
index c195cbc4653742468df62816e30d0e4eb9a6bb8f..e1d1db4852aec7a6c6fc7c30f2bd54b47a82258d 100644 (file)
@@ -71,7 +71,8 @@ unsupported (void)
    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.  */
@@ -84,8 +85,10 @@ check_gcs (struct link_map *l, const char *program, bool enforced)
   /* 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)
@@ -106,18 +109,20 @@ check_gcs (struct link_map *l, const char *program, bool 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)
@@ -126,10 +131,10 @@ _dl_gcs_check (struct link_map *l, const char *program)
     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.  */
index 60f84bb0ab67f5467d13b21c48153a598fc53a91..cf236df59bb43519e6e244053e060cb4d63c9fe5 100644 (file)
@@ -24,21 +24,21 @@ extern void _dl_bti_protect (struct link_map *, int) attribute_hidden;
 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))
index a4575a50348c7b093607bdd2e322e862705b3ba7..0e65c51b8cf6ff10148837561ef5e922673c0a76 100644 (file)
@@ -32,7 +32,7 @@ _rtld_main_check (struct link_map *m, const char *program)
 }
 
 static inline void __attribute__ ((always_inline))
-_dl_open_check (struct link_map *m)
+_dl_open_check (struct link_map *m, int dlopen_mode)
 {
 }
 
index da8bb2de3d6a4b52550a02acaf72e76ff9c1686b..b8eb9c07522bdd77316208e1419dc928e69027f4 100644 (file)
@@ -20,6 +20,10 @@ tests += \
 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 \
@@ -35,11 +39,11 @@ gcs-tests-dynamic = \
   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 \
@@ -114,9 +118,11 @@ LDFLAGS-tst-gcs-ld-debug-shared = -Wl,-z,gcs=always
 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
@@ -186,6 +192,26 @@ tst-gcs-preload-override-ENV = \
   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)
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit-disabled.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit-disabled.c
new file mode 100644 (file)
index 0000000..887a24e
--- /dev/null
@@ -0,0 +1,19 @@
+/* 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"
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit-enforced.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit-enforced.c
new file mode 100644 (file)
index 0000000..2e5d682
--- /dev/null
@@ -0,0 +1,24 @@
+/* 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"
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit-optional.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit-optional.c
new file mode 100644 (file)
index 0000000..a4efec1
--- /dev/null
@@ -0,0 +1,20 @@
+/* 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"
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit-override.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit-override.c
new file mode 100644 (file)
index 0000000..b1e6423
--- /dev/null
@@ -0,0 +1,19 @@
+/* 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"
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit-skeleton.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit-skeleton.c
new file mode 100644 (file)
index 0000000..5526a23
--- /dev/null
@@ -0,0 +1,87 @@
+/* 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>
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit1.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit1.c
new file mode 100644 (file)
index 0000000..f3949a1
--- /dev/null
@@ -0,0 +1,46 @@
+/* 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;
+}
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-mod4.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-mod4.c
new file mode 100644 (file)
index 0000000..bbd2ed3
--- /dev/null
@@ -0,0 +1,22 @@
+/* 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;
+}
index ef268a04c1a082d4c288a627927e687ce426e916..19ae873927600468956288e004c430ab3668e073 100644 (file)
@@ -73,7 +73,7 @@ _rtld_main_check (struct link_map *m, const char *program)
 }
 
 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