From: Adhemerval Zanella Date: Wed, 7 Jan 2026 16:52:39 +0000 (-0300) Subject: aarch64: Fix LD_AUDIT with GCS in permissive mode X-Git-Tag: glibc-2.43~26 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=088a2055da661b0d5b3aa6069b70dfae795b62fd;p=thirdparty%2Fglibc.git aarch64: Fix LD_AUDIT with GCS in permissive mode 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 Tested-by: Yury Khrustalev --- diff --git a/elf/dl-open.c b/elf/dl-open.c index 7685728884..da0e8512cf 100644 --- a/elf/dl-open.c +++ b/elf/dl-open.c @@ -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)) diff --git a/sysdeps/aarch64/dl-gcs.c b/sysdeps/aarch64/dl-gcs.c index c195cbc465..e1d1db4852 100644 --- a/sysdeps/aarch64/dl-gcs.c +++ b/sysdeps/aarch64/dl-gcs.c @@ -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. */ diff --git a/sysdeps/aarch64/dl-prop.h b/sysdeps/aarch64/dl-prop.h index 60f84bb0ab..cf236df59b 100644 --- a/sysdeps/aarch64/dl-prop.h +++ b/sysdeps/aarch64/dl-prop.h @@ -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)) diff --git a/sysdeps/generic/dl-prop.h b/sysdeps/generic/dl-prop.h index a4575a5034..0e65c51b8c 100644 --- a/sysdeps/generic/dl-prop.h +++ b/sysdeps/generic/dl-prop.h @@ -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) { } diff --git a/sysdeps/unix/sysv/linux/aarch64/Makefile b/sysdeps/unix/sysv/linux/aarch64/Makefile index da8bb2de3d..b8eb9c0752 100644 --- a/sysdeps/unix/sysv/linux/aarch64/Makefile +++ b/sysdeps/unix/sysv/linux/aarch64/Makefile @@ -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 index 0000000000..887a24ed70 --- /dev/null +++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit-disabled.c @@ -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 index 0000000000..2e5d6829c7 --- /dev/null +++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit-enforced.c @@ -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 index 0000000000..a4efec18ac --- /dev/null +++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit-optional.c @@ -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 index 0000000000..b1e6423383 --- /dev/null +++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit-override.c @@ -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 index 0000000000..5526a2313b --- /dev/null +++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit-skeleton.c @@ -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 + . */ + +#include +#include +#include +#include + +#include +#include +#include + +#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 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 index 0000000000..f3949a1d87 --- /dev/null +++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-audit1.c @@ -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 + . */ + +#include + +#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 index 0000000000..bbd2ed3bc3 --- /dev/null +++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-mod4.c @@ -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 + . */ + +int fun (void) +{ + return 0; +} diff --git a/sysdeps/x86/dl-prop.h b/sysdeps/x86/dl-prop.h index ef268a04c1..19ae873927 100644 --- a/sysdeps/x86/dl-prop.h +++ b/sysdeps/x86/dl-prop.h @@ -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