[2/8] elf: Keep la_symbind and la_pltenter flags per auditor (BZ 34687)

Message ID 20260928185535.1353957-3-adhemerval.zanella@linaro.org (mailing list archive)
State Under Review
Delegated to: Florian Weimer
Headers
Series elf: Fix auditing with multiple auditors and la_pltexit |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_glibc_check--master-arm success Test passed

Commit Message

Adhemerval Zanella Sept. 28, 2026, 6:55 p.m. UTC
  Both _dl_audit_symbind and _dl_audit_pltenter used a single flags
variable for all auditors, so LA_SYMB_NOPLTENTER and LA_SYMB_NOPLTEXIT
set by one auditor were passed to the next ones and recorded as their
own choice, suppressing their la_pltenter and la_pltexit calls.

Each auditor now starts with its own flags, and only LA_SYMB_ALTVALUE
is carried over (which is also the only bit now kept in
reloc_result->flags).

The new test is XFAIL on mips, where la_symbind is not called for PLT
bindings (BZ 29404).

Checked on x86_64-linux-gnu, i686-linux-gnu, aarch64-linux-gnu,
arm-linux-gnueabihf, alpha-linux-gnu, hppa-linux-gnu,
loongarch64-linux-gnu, mips64el-linux-gnu, powerpc64-linux-gnu,
powerpc64le-linux-gnu, riscv64-linux-gnu, s390x-linux-gnu, and
sparc64-linux-gnu.
---
 elf/Makefile                       |  10 +++
 elf/dl-audit.c                     |  23 +++--
 elf/tst-audit-symbind-flags-mod.c  |  29 ++++++
 elf/tst-audit-symbind-flags.c      | 137 +++++++++++++++++++++++++++++
 elf/tst-auditmod-symbind-flags-a.c |  64 ++++++++++++++
 elf/tst-auditmod-symbind-flags-b.c |  64 ++++++++++++++
 sysdeps/mips/Makefile              |   1 +
 7 files changed, 320 insertions(+), 8 deletions(-)
 create mode 100644 elf/tst-audit-symbind-flags-mod.c
 create mode 100644 elf/tst-audit-symbind-flags.c
 create mode 100644 elf/tst-auditmod-symbind-flags-a.c
 create mode 100644 elf/tst-auditmod-symbind-flags-b.c
  

Patch

diff --git a/elf/Makefile b/elf/Makefile
index 9a0cdf42e69..e3419cd1185 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -407,6 +407,7 @@  tests += \
   tst-align2 \
   tst-align3 \
   tst-audit-symbind-dlsym \
+  tst-audit-symbind-flags \
   tst-audit-tlsdesc \
   tst-audit-tlsdesc-dlopen \
   tst-audit-tlsdesc-dlopen2 \
@@ -913,6 +914,7 @@  modules-names += \
   tst-array2dep \
   tst-array5dep \
   tst-audit-symbind-dlsym-mod \
+  tst-audit-symbind-flags-mod \
   tst-audit-tlsdesc-mod1 \
   tst-audit-tlsdesc-mod2 \
   tst-audit11mod1 \
@@ -950,6 +952,8 @@  modules-names += \
   tst-auditmanymod9 \
   tst-auditmod-symbind-dlsym-a \
   tst-auditmod-symbind-dlsym-b \
+  tst-auditmod-symbind-flags-a \
+  tst-auditmod-symbind-flags-b \
   tst-auditmod-tlsdesc  \
   tst-auditmod-tlsdesc2 \
   tst-auditmod1 \
@@ -2874,6 +2878,12 @@  $(objpfx)tst-audit-symbind-dlsym.out: \
 tst-audit-symbind-dlsym-ENV = \
   LD_AUDIT=$(objpfx)tst-auditmod-symbind-dlsym-a.so:$(objpfx)tst-auditmod-symbind-dlsym-b.so
 
+$(objpfx)tst-audit-symbind-flags: $(objpfx)tst-audit-symbind-flags-mod.so
+$(objpfx)tst-audit-symbind-flags.out: \
+  $(objpfx)tst-auditmod-symbind-flags-a.so \
+  $(objpfx)tst-auditmod-symbind-flags-b.so
+LDFLAGS-tst-audit-symbind-flags = -Wl,-z,lazy
+
 # tst-sonamemove links against an older implementation of the library.
 LDFLAGS-tst-sonamemove-linkmod1.so = \
   -Wl,--version-script=tst-sonamemove-linkmod1.map \
diff --git a/elf/dl-audit.c b/elf/dl-audit.c
index 9c79b175b84..cd2fa44cf14 100644
--- a/elf/dl-audit.c
+++ b/elf/dl-audit.c
@@ -211,7 +211,7 @@  _dl_audit_symbind (struct link_map *l, struct reloc_result *reloc_result,
 
   const char *strtab2 = (const void *) D_PTR (result, l_info[DT_STRTAB]);
 
-  unsigned int flags = 0;
+  unsigned int altvalue = 0;
   struct audit_ifaces *afct = GLRO(dl_audit);
   uintptr_t new_value = (uintptr_t) sym.st_value;
   for (unsigned int cnt = 0; cnt < GLRO(dl_naudit); ++cnt)
@@ -222,17 +222,19 @@  _dl_audit_symbind (struct link_map *l, struct reloc_result *reloc_result,
       if ((l_state->bindflags & LA_FLG_BINDFROM) != 0
 	  && (result_state->bindflags & LA_FLG_BINDTO) != 0)
 	{
+	  /* Only LA_SYMB_ALTVALUE is carried over from previous auditors.  */
+	  unsigned int flags = altvalue;
+	  if (for_jmp_slot)
+	    flags |= LA_SYMB_NOPLTENTER | LA_SYMB_NOPLTEXIT;
 	  if (afct->symbind != NULL)
 	    {
-	      flags |= for_jmp_slot ? LA_SYMB_NOPLTENTER | LA_SYMB_NOPLTEXIT
-				    : 0;
 	      new_value = afct->symbind (&sym, boundndx,
 					 &l_state->cookie,
 					 &result_state->cookie, &flags,
 					 strtab2 + defsym->st_name);
 	      if (new_value != (uintptr_t) sym.st_value)
 		{
-		  flags |= LA_SYMB_ALTVALUE;
+		  altvalue = LA_SYMB_ALTVALUE;
 		  sym.st_value = for_jmp_slot
 		    ? DL_FIXUP_BINDNOW_ADDR_VALUE (new_value) : new_value;
 		}
@@ -255,10 +257,10 @@  _dl_audit_symbind (struct link_map *l, struct reloc_result *reloc_result,
   if (!for_jmp_slot)
     {
       reloc_result->enterexit = enterexit;
-      reloc_result->flags = flags;
+      reloc_result->flags = altvalue;
     }
 
-  if (flags & LA_SYMB_ALTVALUE)
+  if (altvalue != 0)
     DL_FIXUP_BINDNOW_RELOC (l, reloc, value, new_value, sym.st_value, lazy);
 }
 
@@ -288,7 +290,7 @@  _dl_audit_pltenter (struct link_map *l, struct reloc_result *reloc_result,
   const char *symname = strtab + sym.st_name;
 
   /* Keep track of overwritten addresses.  */
-  unsigned int flags = reloc_result->flags;
+  unsigned int altvalue = reloc_result->flags;
 
   struct audit_ifaces *afct = GLRO(dl_audit);
   for (unsigned int cnt = 0; cnt < GLRO(dl_naudit); ++cnt)
@@ -301,13 +303,18 @@  _dl_audit_pltenter (struct link_map *l, struct reloc_result *reloc_result,
 	  struct auditstate *l_state = link_map_audit_state (l, cnt);
 	  struct auditstate *bound_state
 	    = link_map_audit_state (reloc_result->bound, cnt);
+	  /* Each auditor only sees its own LA_SYMB_NOPLTENTER and
+	     LA_SYMB_NOPLTEXIT.  */
+	  unsigned int flags = altvalue
+	    | ((reloc_result->enterexit >> (2 * (cnt + 1)))
+	       & (LA_SYMB_NOPLTENTER | LA_SYMB_NOPLTEXIT));
 	  uintptr_t new_value
 	    = afct->ARCH_LA_PLTENTER (&sym, reloc_result->boundndx,
 				      &l_state->cookie, &bound_state->cookie,
 				      regs, &flags, symname, &new_framesize);
 	  if (new_value != (uintptr_t) sym.st_value)
 	    {
-	      flags |= LA_SYMB_ALTVALUE;
+	      altvalue = LA_SYMB_ALTVALUE;
 	      sym.st_value = new_value;
 	    }
 
diff --git a/elf/tst-audit-symbind-flags-mod.c b/elf/tst-audit-symbind-flags-mod.c
new file mode 100644
index 00000000000..34b4396e3dd
--- /dev/null
+++ b/elf/tst-audit-symbind-flags-mod.c
@@ -0,0 +1,29 @@ 
+/* Module for tst-audit-symbind-flags.
+   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
+tst_audit_symbind_flags_func1 (void)
+{
+  return 1;
+}
+
+int
+tst_audit_symbind_flags_func2 (void)
+{
+  return 2;
+}
diff --git a/elf/tst-audit-symbind-flags.c b/elf/tst-audit-symbind-flags.c
new file mode 100644
index 00000000000..daaf81acc8e
--- /dev/null
+++ b/elf/tst-audit-symbind-flags.c
@@ -0,0 +1,137 @@ 
+/* Check that la_symbind and la_pltenter flags are per auditor.
+   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 <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/capture_subprocess.h>
+#include <support/check.h>
+#include <support/subprocess.h>
+#include <support/support.h>
+
+static int restart;
+#define CMDLINE_OPTIONS \
+  { "restart", no_argument, &restart, 1 },
+
+int tst_audit_symbind_flags_func1 (void);
+int tst_audit_symbind_flags_func2 (void);
+
+static int
+handle_restart (void)
+{
+  /* Call each function twice so la_pltenter is also exercised after
+     the lazy binding.  */
+  TEST_COMPARE (tst_audit_symbind_flags_func1 (), 1);
+  TEST_COMPARE (tst_audit_symbind_flags_func1 (), 1);
+  TEST_COMPARE (tst_audit_symbind_flags_func2 (), 2);
+  TEST_COMPARE (tst_audit_symbind_flags_func2 (), 2);
+  return 0;
+}
+
+static int
+count_lines (const char *buf, const char *line)
+{
+  size_t len = strlen (line);
+  int n = 0;
+  for (const char *p = buf; p != NULL && *p != '\0'; )
+    {
+      if (strncmp (p, line, len) == 0 && p[len] == '\n')
+	n++;
+      p = strchr (p, '\n');
+      if (p != NULL)
+	p++;
+    }
+  return n;
+}
+
+static void
+run (char *program, bool bindnow)
+{
+  if (bindnow)
+    setenv ("LD_BIND_NOW", "1", 1);
+  else
+    unsetenv ("LD_BIND_NOW");
+
+  char *args[] = { program, (char *) "--direct", (char *) "--restart", NULL };
+  struct support_spawn_wrapped *w
+    = support_spawn_wrap (program, args, NULL, 0);
+  struct support_capture_subprocess result
+    = support_capture_subprogram (w->path, w->argv, w->envp);
+  support_spawn_wrapped_free (w);
+  support_capture_subprocess_check (&result, "tst-audit-symbind-flags", 0,
+				    sc_allow_stderr);
+  const char *err = result.err.buffer;
+
+  /* tst-auditmod-symbind-flags-a.so sets LA_SYMB_NOPLTENTER and
+     LA_SYMB_NOPLTEXIT for func1 in la_symbind, which must not be seen by
+     tst-auditmod-symbind-flags-b.so.  ld.so sets both for bind-now.  */
+  const char *expected1 = bindnow
+    ? "b: la_symbind: tst_audit_symbind_flags_func1 3"
+    : "b: la_symbind: tst_audit_symbind_flags_func1 0";
+  const char *expected2 = bindnow
+    ? "b: la_symbind: tst_audit_symbind_flags_func2 3"
+    : "b: la_symbind: tst_audit_symbind_flags_func2 0";
+  TEST_COMPARE (count_lines (err, expected1), 1);
+  TEST_COMPARE (count_lines (err, expected2), 1);
+
+  /* Not all architectures support la_pltenter.  If they do, the first auditor
+     sets LA_SYMB_NOPLTENTER for func2 in its first la_pltenter, which must
+     not stop the calls for the second auditor.  */
+  if (count_lines (err, "a: la_pltenter: tst_audit_symbind_flags_func2") > 0)
+    {
+      TEST_VERIFY (!bindnow);
+      TEST_COMPARE (count_lines (err,
+		    "a: la_pltenter: tst_audit_symbind_flags_func2"), 1);
+      TEST_COMPARE (count_lines (err,
+		    "b: la_pltenter: tst_audit_symbind_flags_func1"), 2);
+      TEST_COMPARE (count_lines (err,
+		    "b: la_pltenter: tst_audit_symbind_flags_func2"), 2);
+    }
+  else if (!bindnow)
+    puts ("info: la_pltenter not called, skipping its checks");
+
+  if (support_record_failure_is_failed ())
+    printf ("info: auditor output (LD_BIND_NOW=%d):\n%s", bindnow, err);
+
+  support_capture_subprocess_free (&result);
+}
+
+static int
+do_test (void)
+{
+  if (restart)
+    return handle_restart ();
+
+  char *audit = xasprintf ("%s/elf/tst-auditmod-symbind-flags-a.so"
+			   ":%s/elf/tst-auditmod-symbind-flags-b.so",
+			   support_objdir_root, support_objdir_root);
+  setenv ("LD_AUDIT", audit, 1);
+  free (audit);
+
+  char *program = xasprintf ("%s/elf/tst-audit-symbind-flags",
+			     support_objdir_root);
+  run (program, false);
+  run (program, true);
+  free (program);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/elf/tst-auditmod-symbind-flags-a.c b/elf/tst-auditmod-symbind-flags-a.c
new file mode 100644
index 00000000000..c4f9a975707
--- /dev/null
+++ b/elf/tst-auditmod-symbind-flags-a.c
@@ -0,0 +1,64 @@ 
+/* Auditor for tst-audit-symbind-flags that opts out of PLT tracing.
+   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>
+#include <stdio.h>
+#include <string.h>
+#include <tst-audit.h>
+
+unsigned int
+la_version (unsigned int version)
+{
+  return LAV_CURRENT;
+}
+
+unsigned int
+la_objopen (struct link_map *map, Lmid_t lmid, uintptr_t *cookie)
+{
+  return LA_FLG_BINDFROM | LA_FLG_BINDTO;
+}
+
+#if __ELF_NATIVE_CLASS == 64
+uintptr_t
+la_symbind64 (Elf64_Sym *sym, unsigned int ndx,
+	      uintptr_t *refcook, uintptr_t *defcook,
+	      unsigned int *flags, const char *symname)
+#else
+uintptr_t
+la_symbind32 (Elf32_Sym *sym, unsigned int ndx,
+	      uintptr_t *refcook, uintptr_t *defcook,
+	      unsigned int *flags, const char *symname)
+#endif
+{
+  if (strcmp (symname, "tst_audit_symbind_flags_func1") == 0)
+    *flags |= LA_SYMB_NOPLTENTER | LA_SYMB_NOPLTEXIT;
+  return sym->st_value;
+}
+
+ElfW(Addr)
+pltenter (ElfW(Sym) *sym, unsigned int ndx, uintptr_t *refcook,
+	  uintptr_t *defcook, La_regs *regs, unsigned int *flags,
+	  const char *symname, long int *framesizep)
+{
+  if (strcmp (symname, "tst_audit_symbind_flags_func2") == 0)
+    {
+      fprintf (stderr, "a: la_pltenter: %s\n", symname);
+      *flags |= LA_SYMB_NOPLTENTER;
+    }
+  return sym->st_value;
+}
diff --git a/elf/tst-auditmod-symbind-flags-b.c b/elf/tst-auditmod-symbind-flags-b.c
new file mode 100644
index 00000000000..718449b82b5
--- /dev/null
+++ b/elf/tst-auditmod-symbind-flags-b.c
@@ -0,0 +1,64 @@ 
+/* Auditor for tst-audit-symbind-flags that logs the flags it gets.
+   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>
+#include <stdio.h>
+#include <string.h>
+#include <tst-audit.h>
+
+#define PREFIX "tst_audit_symbind_flags_func"
+
+unsigned int
+la_version (unsigned int version)
+{
+  return LAV_CURRENT;
+}
+
+unsigned int
+la_objopen (struct link_map *map, Lmid_t lmid, uintptr_t *cookie)
+{
+  return LA_FLG_BINDFROM | LA_FLG_BINDTO;
+}
+
+#if __ELF_NATIVE_CLASS == 64
+uintptr_t
+la_symbind64 (Elf64_Sym *sym, unsigned int ndx,
+	      uintptr_t *refcook, uintptr_t *defcook,
+	      unsigned int *flags, const char *symname)
+#else
+uintptr_t
+la_symbind32 (Elf32_Sym *sym, unsigned int ndx,
+	      uintptr_t *refcook, uintptr_t *defcook,
+	      unsigned int *flags, const char *symname)
+#endif
+{
+  if (strncmp (symname, PREFIX, strlen (PREFIX)) == 0)
+    fprintf (stderr, "b: la_symbind: %s %u\n", symname,
+	     *flags & (LA_SYMB_NOPLTENTER | LA_SYMB_NOPLTEXIT));
+  return sym->st_value;
+}
+
+ElfW(Addr)
+pltenter (ElfW(Sym) *sym, unsigned int ndx, uintptr_t *refcook,
+	  uintptr_t *defcook, La_regs *regs, unsigned int *flags,
+	  const char *symname, long int *framesizep)
+{
+  if (strncmp (symname, PREFIX, strlen (PREFIX)) == 0)
+    fprintf (stderr, "b: la_pltenter: %s\n", symname);
+  return sym->st_value;
+}
diff --git a/sysdeps/mips/Makefile b/sysdeps/mips/Makefile
index c86bd3af33c..a497c7087b5 100644
--- a/sysdeps/mips/Makefile
+++ b/sysdeps/mips/Makefile
@@ -35,6 +35,7 @@  test-xfail-tst-audit24c = yes
 test-xfail-tst-audit24d = yes
 test-xfail-tst-audit25a = yes
 test-xfail-tst-audit25b = yes
+test-xfail-tst-audit-symbind-flags = yes
 
 ifneq ($(o32-fpabi),)
 tests += tst-abi-interlink