[PATCH 1/2] elf: Honour skip_ifunc for cross-object IFUNC relocations [BZ #34428]
Adhemerval Zanella <[email protected]> Sun, 2 Aug 2026 20:44:03 -0300
| Newsgroups | gmane.comp.lib.glibc.alpha |
|---|---|
| Message-ID | <[email protected]> |
Commit 63b31c05a8a dropped the skip_ifunc argument from elf_dynamic_do_Rel,
assuming the elf_dynamic_do_Rel_irelative pass handles every relocation
that may run an IFUNC resolver. That only holds for IFUNC symbols defined
in the object being relocated, a reference to an IFUNC in another object is
an ordinary JMP_SLOT or GLOB_DAT against an undefined symbol, and being
IFUNC is only known after symbol resolution (elf_machine_rel). Those
relocations stay in the regular pass, which no longer propagated
skip_ifunc, so __RTLD_NOIFUNC was ignored for them.
ldd -u forces non-lazy binding (GLRO(dl_lazy) = 0 for DL_DEBUG_UNUSED), so
the resolver was called and the diagnostic emitted:
$ ldd -u /bin/ls
/bin/ls: Relink `' with `/usr/lib64/libc.so.6' for IFUNC symbol `__mempcpy_chk'
ldd -r with LD_BIND_NOW is affected in the same way.
Restore the skip_ifunc parameter and pass it through _ELF_DYNAMIC_DO_RELOC
and ELF_DYNAMIC_RELOCATE_NOIFUNC.
Checked on x86_64-linux-gnu, aarch64-linux-gnu, and i686-linux-gnu. I
also built for all supported architectures and run the elf tests on
qemu-system for armhf, alpha, hppa, loongarch64, mipsel, mips64le,
powerpc*, riscv64, s390x, and sparc64.
---
elf/Makefile | 28 +++++++++++++++++++++++
elf/dl-reloc-static-pie.c | 2 +-
elf/dl-reloc.c | 3 ++-
elf/do-rel.h | 24 ++++++++++++-------
elf/dynamic-link.h | 32 ++++++++++++++++----------
elf/tst-ifunc-fault-dep-bindnow.c | 19 ++++++++++++++++
elf/tst-ifunc-fault-dep-lazy.c | 27 ++++++++++++++++++++++
elf/tst-ifunc-fault-mod.c | 38 +++++++++++++++++++++++++++++++
8 files changed, 151 insertions(+), 22 deletions(-)
create mode 100644 elf/tst-ifunc-fault-dep-bindnow.c
create mode 100644 elf/tst-ifunc-fault-dep-lazy.c
create mode 100644 elf/tst-ifunc-fault-mod.c
diff --git a/elf/Makefile b/elf/Makefile
index 94c5b7e6ed8..6cfb1b5cf30 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -1396,6 +1396,13 @@ modules-names += \
tst-ifunc-tls-write-lib \
tst-tls-tdata-reloc-lib \
# modules-names
+ifeq (yes,$(have-gcc-ifunc))
+tests += \
+ tst-ifunc-fault-dep-bindnow \
+ tst-ifunc-fault-dep-lazy \
+ # tests
+modules-names += tst-ifunc-fault-mod
+endif
ifneq (no,$(have-test-mtls-descriptor))
tests += tst-ifunc-tls-init-tlsdesc
modules-names += tst-ifunc-tls-init-tlsdesc-lib
@@ -2545,6 +2552,27 @@ $(objpfx)tst-ifunc-fault-bindnow.out: $(objpfx)tst-ifunc-fault-bindnow \
$(objpfx)ld.so
$(tst-ifunc-fault-script)
+LDFLAGS-tst-ifunc-fault-dep-lazy = -Wl,-z,lazy
+LDFLAGS-tst-ifunc-fault-dep-bindnow = -Wl,-z,now
+define tst-ifunc-fault-dep-script
+( $(test-wrapper) $(rtld-prefix) --verify $< \
+ && $(test-wrapper-env) LD_TRACE_LOADED_OBJECTS=1 $(rtld-prefix) $< \
+ && $(test-wrapper-env) LD_TRACE_LOADED_OBJECTS=1 LD_DEBUG=unused \
+ $(rtld-prefix) $< \
+ && $(test-wrapper-env) LD_TRACE_LOADED_OBJECTS=1 LD_WARN=yes LD_BIND_NOW=1 \
+ $(rtld-prefix) $< \
+) > $@; $(evaluate-test)
+endef
+$(objpfx)tst-ifunc-fault-dep-lazy: $(objpfx)tst-ifunc-fault-mod.so
+$(objpfx)tst-ifunc-fault-dep-bindnow: $(objpfx)tst-ifunc-fault-mod.so
+$(objpfx)tst-ifunc-fault-dep-lazy.out: $(objpfx)tst-ifunc-fault-dep-lazy \
+ $(objpfx)tst-ifunc-fault-mod.so $(objpfx)ld.so
+ $(tst-ifunc-fault-dep-script)
+$(objpfx)tst-ifunc-fault-dep-bindnow.out: \
+ $(objpfx)tst-ifunc-fault-dep-bindnow \
+ $(objpfx)tst-ifunc-fault-mod.so $(objpfx)ld.so
+ $(tst-ifunc-fault-dep-script)
+
LDFLAGS-tst-ifunc-plt-lib.so = -Wl,-z,lazy
tst-ifunc-plt-bindnow-ENV = LD_BIND_NOW=1
diff --git a/elf/dl-reloc-static-pie.c b/elf/dl-reloc-static-pie.c
index 8463e46147d..53260b57372 100644
--- a/elf/dl-reloc-static-pie.c
+++ b/elf/dl-reloc-static-pie.c
@@ -80,7 +80,7 @@ _dl_relocate_static_pie (void)
/* Relocate ourselves so we can do normal function calls and data access
using the global offset table. IRELATIVE entries are deferred. */
- ELF_DYNAMIC_RELOCATE_NOIFUNC (main_map, NULL, 0, 0);
+ ELF_DYNAMIC_RELOCATE_NOIFUNC (main_map, NULL, 0, 0, 0);
/* Initialize _r_debug_extended. */
struct r_debug *r = _dl_debug_initialize (0, LM_ID_BASE);
diff --git a/elf/dl-reloc.c b/elf/dl-reloc.c
index 15a6a4cffef..80cd7e25d16 100644
--- a/elf/dl-reloc.c
+++ b/elf/dl-reloc.c
@@ -278,7 +278,8 @@ _dl_relocate_object_no_relro (struct link_map *l, struct r_scope_elem *scope[],
IFUNC resolvers. Without this, a resolver would see the unrelocated
initialiser bytes that were placed into the slot by the early
_dl_allocate_tls_init. */
- ELF_DYNAMIC_RELOCATE_NOIFUNC (l, scope, lazy, consider_profiling);
+ ELF_DYNAMIC_RELOCATE_NOIFUNC (l, scope, lazy, consider_profiling,
+ skip_ifunc);
#ifdef SHARED
/* Re-initialise the static TLS slot with the .tdata so the IRELATIVE
diff --git a/elf/do-rel.h b/elf/do-rel.h
index 77022447348..c610d12dbeb 100644
--- a/elf/do-rel.h
+++ b/elf/do-rel.h
@@ -79,17 +79,23 @@ elf_dynamic_Rel_audit_symbind (struct link_map *map,
/* Perform the relocations in MAP on the running program image as specified
by RELTAG, SZTAG. If LAZY is nonzero, this is the first pass on PLT
relocations; they should be set up to call _dl_runtime_resolve, rather
- than fully resolved now.
+ than fully resolved now. If SKIP_IFUNC is nonzero no IFUNC resolver is
+ called; this is required for the trace modes (ldd -u / ldd -r), which
+ relocate objects.
- IRELATIVE entries are always skipped (non-bootstrap); they are handled
+ IRELATIVE entries and relocations against an STT_GNU_IFUNC symbol defined
+ in MAP itself are always skipped (non-bootstrap); they are handled
separately by elf_dynamic_do_Rel_irelative after all other relocations
- for both .rel.dyn and .rel.plt have been processed. */
+ for both .rel.dyn and .rel.plt have been processed. Relocations against
+ an IFUNC symbol defined in *another* object are not deferred, since the
+ IFUNC symbol is only known after symbol resolution, and the defining object
+ has already been relocated at this point. */
static inline void __attribute__ ((always_inline))
elf_dynamic_do_Rel (struct link_map *map, struct r_scope_elem *scope[],
ElfW(Addr) reladdr, ElfW(Addr) relsize,
__typeof (((ElfW(Dyn) *) 0)->d_un.d_val) nrelative,
- int lazy)
+ int lazy, int skip_ifunc)
{
const ElfW(Rel) *relative = (const void *) reladdr;
const ElfW(Rel) *r = relative + nrelative;
@@ -111,7 +117,7 @@ elf_dynamic_do_Rel (struct link_map *map, struct r_scope_elem *scope[],
void *const r_addr_arg = (void *) (l_addr + r->r_offset);
const struct r_found_version *rversion = &map->l_versions[ndx];
- elf_machine_rel (map, scope, r, sym, rversion, r_addr_arg, 0);
+ elf_machine_rel (map, scope, r, sym, rversion, r_addr_arg, skip_ifunc);
}
#else /* !RTLD_BOOTSTRAP */
#if !defined DO_RELA || !defined ELF_MACHINE_PLT_REL
@@ -126,7 +132,7 @@ elf_dynamic_do_Rel (struct link_map *map, struct r_scope_elem *scope[],
const ElfW (Sym) *sym = &symtab[ELFW (R_SYM) (r->r_info)];
if (elf_dynamic_is_Rel_irelative (r, sym))
continue;
- elf_machine_lazy_rel (map, scope, l_addr, r, 0);
+ elf_machine_lazy_rel (map, scope, l_addr, r, skip_ifunc);
}
}
else
@@ -158,7 +164,8 @@ elf_dynamic_do_Rel (struct link_map *map, struct r_scope_elem *scope[],
if (elf_dynamic_is_Rel_irelative (r, sym))
continue;
- elf_machine_rel (map, scope, r, sym, rversion, r_addr_arg, 0);
+ elf_machine_rel (map, scope, r, sym, rversion, r_addr_arg,
+ skip_ifunc);
elf_dynamic_Rel_audit_symbind (map, scope, r, sym, rversion,
r_addr_arg);
}
@@ -172,7 +179,8 @@ elf_dynamic_do_Rel (struct link_map *map, struct r_scope_elem *scope[],
if (elf_dynamic_is_Rel_irelative (r, sym))
continue;
- elf_machine_rel (map, scope, r, sym, NULL, r_addr_arg, 0);
+ elf_machine_rel (map, scope, r, sym, NULL, r_addr_arg,
+ skip_ifunc);
elf_dynamic_Rel_audit_symbind (map, scope, r, sym, NULL,
r_addr_arg);
}
diff --git a/elf/dynamic-link.h b/elf/dynamic-link.h
index 35141acec4b..9baef6d7b84 100644
--- a/elf/dynamic-link.h
+++ b/elf/dynamic-link.h
@@ -80,7 +80,12 @@ elf_machine_lazy_rel (struct link_map *map, struct r_scope_elem *scope[],
/* This controls which sub-passes _ELF_DYNAMIC_DO_RELOC runs. Used to
interleave TLS / stack-protector setup between the two passes so IFUNC
- resolvers see a fully-initialised TCB. */
+ resolvers see a fully-initialised TCB.
+
+ This selects *which relocations* a pass looks at, and it is independent of
+ the skip_ifunc argument (which says whether a resolver may be run at all).
+ The non-IRELATIVE pass also runs IFUNC resolvers, for relocations against
+ an IFUNC symbol defined in another object. */
enum elf_dynamic_reloc_phase
{
DL_RELOC_BOTH = 0, /* Non-IRELATIVE pass then IRELATIVE pass. */
@@ -142,7 +147,8 @@ enum elf_dynamic_reloc_phase
ranges[ranges_index].start, \
ranges[ranges_index].size, \
ranges[ranges_index].nrelative, \
- ranges[ranges_index].lazy); \
+ ranges[ranges_index].lazy, \
+ skip_ifunc); \
if ((phase) != DL_RELOC_NOIFUNC) \
for (int ranges_index = 0; ranges_index < 2; ++ranges_index) \
elf_dynamic_do_##reloc##_irelative ((map), scope, \
@@ -158,7 +164,8 @@ enum elf_dynamic_reloc_phase
ranges[ranges_index].start, \
ranges[ranges_index].size, \
ranges[ranges_index].nrelative, \
- ranges[ranges_index].lazy); \
+ ranges[ranges_index].lazy, \
+ skip_ifunc); \
} while (0)
# if ELF_MACHINE_NO_REL || ELF_MACHINE_NO_RELA
@@ -172,15 +179,15 @@ enum elf_dynamic_reloc_phase
# define ELF_DYNAMIC_DO_REL(map, scope, lazy, skip_ifunc) \
_ELF_DYNAMIC_DO_RELOC (REL, Rel, map, scope, lazy, skip_ifunc, \
_ELF_CHECK_REL, DL_RELOC_BOTH)
-# define ELF_DYNAMIC_DO_REL_NOIFUNC(map, scope, lazy) \
- _ELF_DYNAMIC_DO_RELOC (REL, Rel, map, scope, lazy, 0, \
+# define ELF_DYNAMIC_DO_REL_NOIFUNC(map, scope, lazy, skip_ifunc) \
+ _ELF_DYNAMIC_DO_RELOC (REL, Rel, map, scope, lazy, skip_ifunc, \
_ELF_CHECK_REL, DL_RELOC_NOIFUNC)
# define ELF_DYNAMIC_DO_REL_IFUNCONLY(map, scope, lazy, skip_ifunc) \
_ELF_DYNAMIC_DO_RELOC (REL, Rel, map, scope, lazy, skip_ifunc, \
_ELF_CHECK_REL, DL_RELOC_IFUNC)
# else
# define ELF_DYNAMIC_DO_REL(map, scope, lazy, skip_ifunc) /* Nothing to do. */
-# define ELF_DYNAMIC_DO_REL_NOIFUNC(map, scope, lazy) /* Nothing to do. */
+# define ELF_DYNAMIC_DO_REL_NOIFUNC(map, scope, lazy, skip_ifunc) /* Nothing. */
# define ELF_DYNAMIC_DO_REL_IFUNCONLY(map, scope, lazy, skip_ifunc) /* Nothing. */
# endif
@@ -190,15 +197,15 @@ enum elf_dynamic_reloc_phase
# define ELF_DYNAMIC_DO_RELA(map, scope, lazy, skip_ifunc) \
_ELF_DYNAMIC_DO_RELOC (RELA, Rela, map, scope, lazy, skip_ifunc, \
_ELF_CHECK_REL, DL_RELOC_BOTH)
-# define ELF_DYNAMIC_DO_RELA_NOIFUNC(map, scope, lazy) \
- _ELF_DYNAMIC_DO_RELOC (RELA, Rela, map, scope, lazy, 0, \
+# define ELF_DYNAMIC_DO_RELA_NOIFUNC(map, scope, lazy, skip_ifunc) \
+ _ELF_DYNAMIC_DO_RELOC (RELA, Rela, map, scope, lazy, skip_ifunc, \
_ELF_CHECK_REL, DL_RELOC_NOIFUNC)
# define ELF_DYNAMIC_DO_RELA_IFUNCONLY(map, scope, lazy, skip_ifunc) \
_ELF_DYNAMIC_DO_RELOC (RELA, Rela, map, scope, lazy, skip_ifunc, \
_ELF_CHECK_REL, DL_RELOC_IFUNC)
# else
# define ELF_DYNAMIC_DO_RELA(map, scope, lazy, skip_ifunc) /* Nothing to do. */
-# define ELF_DYNAMIC_DO_RELA_NOIFUNC(map, scope, lazy) /* Nothing to do. */
+# define ELF_DYNAMIC_DO_RELA_NOIFUNC(map, scope, lazy, skip_ifunc) /* Nothing. */
# define ELF_DYNAMIC_DO_RELA_IFUNCONLY(map, scope, lazy, skip_ifunc) /* Nothing. */
# endif
@@ -255,14 +262,15 @@ enum elf_dynamic_reloc_phase
The IRELATIVE pass must be completed later via ELF_DYNAMIC_RELOCATE_IFUNC.
Used by the static-pie startup so the TCB and stack-protector canary can
be initialised between the two passes. */
-# define ELF_DYNAMIC_RELOCATE_NOIFUNC(map, scope, lazy, consider_profile) \
+# define ELF_DYNAMIC_RELOCATE_NOIFUNC(map, scope, lazy, consider_profile, \
+ skip_ifunc) \
do { \
int edr_lazy = elf_machine_runtime_setup ((map), (scope), (lazy), \
(consider_profile)); \
if (!is_rtld_link_map (map) || DO_RTLD_BOOTSTRAP) \
ELF_DYNAMIC_DO_RELR (map); \
- ELF_DYNAMIC_DO_REL_NOIFUNC ((map), (scope), edr_lazy); \
- ELF_DYNAMIC_DO_RELA_NOIFUNC ((map), (scope), edr_lazy); \
+ ELF_DYNAMIC_DO_REL_NOIFUNC ((map), (scope), edr_lazy, skip_ifunc); \
+ ELF_DYNAMIC_DO_RELA_NOIFUNC ((map), (scope), edr_lazy, skip_ifunc); \
ELF_DYNAMIC_AFTER_RELOC ((map), (edr_lazy)); \
} while (0)
diff --git a/elf/tst-ifunc-fault-dep-bindnow.c b/elf/tst-ifunc-fault-dep-bindnow.c
new file mode 100644
index 00000000000..60d97dcaa44
--- /dev/null
+++ b/elf/tst-ifunc-fault-dep-bindnow.c
@@ -0,0 +1,19 @@
+/* Program calling an IFUNC defined in a dependency. BIND_NOW variant.
+ 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 "tst-ifunc-fault-dep-lazy.c"
diff --git a/elf/tst-ifunc-fault-dep-lazy.c b/elf/tst-ifunc-fault-dep-lazy.c
new file mode 100644
index 00000000000..122d33f391e
--- /dev/null
+++ b/elf/tst-ifunc-fault-dep-lazy.c
@@ -0,0 +1,27 @@
+/* Program calling an IFUNC defined in a dependency (BZ 34428).
+ 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/>. */
+
+extern void magic (void);
+
+int
+main (void)
+{
+ /* JMP_SLOT relocation against an undefined symbol. */
+ magic ();
+ return 1;
+}
diff --git a/elf/tst-ifunc-fault-mod.c b/elf/tst-ifunc-fault-mod.c
new file mode 100644
index 00000000000..11c21b48ac4
--- /dev/null
+++ b/elf/tst-ifunc-fault-mod.c
@@ -0,0 +1,38 @@
+/* Shared object exporting an IFUNC symbol with a resolver which crashes.
+ 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 <stddef.h>
+
+static void
+implementation (void)
+{
+ /* Produce a crash, without depending on any relocations. */
+ volatile char *volatile p = NULL;
+ *p = 0;
+}
+
+static __typeof__ (implementation) *
+resolver (void)
+{
+ /* Produce a crash, without depending on any relocations. */
+ volatile char *volatile p = NULL;
+ *p = 0;
+ return implementation;
+}
+
+void magic (void) __attribute__ ((ifunc ("resolver")));
--
2.53.0