[PATCH v2 1/2] elf: Honour skip_ifunc for cross-object IFUNC relocations [BZ #34428]
Adhemerval Zanella <[email protected]> Mon, 3 Aug 2026 11:02:53 -0300
| Newsgroups | gmane.comp.lib.glibc.alpha |
|---|---|
| Message-ID | <[email protected]> |
Commit 63b31c05a8a ("elf: Defer all IRELATIVE relocations until after PLT
setup") dropped the skip_ifunc argument from elf_dynamic_do_Rel, assuming
the new deferred 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 its IFUNC
nature is only known after symbol resolution inside 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 thread it through _ELF_DYNAMIC_DO_RELOC.
This new semantic shows that ELF_DYNAMIC_RELOCATE_NOIFUNC naming is misleading
(it reads as "do not process IFUNC", yet it takes a skip_ifunc
argument). Replace it to:
DL_RELOC_BOTH -> DL_RELOC_ALL
DL_RELOC_NOIFUNC -> DL_RELOC_NORMAL
DL_RELOC_IFUNC -> DL_RELOC_IRELATIVE
ELF_DYNAMIC_RELOCATE_NOIFUNC and ELF_DYNAMIC_RELOCATE_IFUNC become a single
ELF_DYNAMIC_RELOCATE_PASS taking the pass as its first argument, and
ELF_DYNAMIC_DO_REL/ELF_DYNAMIC_DO_RELA take the pass instead of having three
near-identical variants each.
Checked on x86_64-linux-gnu, and built for all supported architectures.
---
elf/Makefile | 28 +++++++
elf/dl-reloc-static-pie.c | 4 +-
elf/dl-reloc.c | 6 +-
elf/do-rel.h | 24 ++++--
elf/dynamic-link.h | 117 +++++++++++++-----------------
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, 186 insertions(+), 77 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..5dc5a545a8d 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_PASS (DL_RELOC_NORMAL, main_map, NULL, 0, 0, 0);
/* Initialize _r_debug_extended. */
struct r_debug *r = _dl_debug_initialize (0, LM_ID_BASE);
@@ -98,7 +98,7 @@ void
_dl_relocate_static_pie_ifunc (void)
{
struct link_map *main_map = _dl_get_dl_main_map ();
- ELF_DYNAMIC_RELOCATE_IFUNC (main_map, NULL, 0, 0);
+ ELF_DYNAMIC_RELOCATE_PASS (DL_RELOC_IRELATIVE, main_map, NULL, 0, 0, 0);
main_map->l_relocated = 1;
}
#endif
diff --git a/elf/dl-reloc.c b/elf/dl-reloc.c
index 15a6a4cffef..fa2f41ac445 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_PASS (DL_RELOC_NORMAL, l, scope, lazy,
+ consider_profiling, skip_ifunc);
#ifdef SHARED
/* Re-initialise the static TLS slot with the .tdata so the IRELATIVE
@@ -291,7 +292,8 @@ _dl_relocate_object_no_relro (struct link_map *l, struct r_scope_elem *scope[],
_dl_init_static_tls (l);
#endif
- ELF_DYNAMIC_RELOCATE_IFUNC (l, scope, lazy, skip_ifunc);
+ ELF_DYNAMIC_RELOCATE_PASS (DL_RELOC_IRELATIVE, l, scope, lazy,
+ 0, skip_ifunc);
if ((consider_profiling || consider_symbind)
&& l->l_info[DT_PLTRELSZ] != NULL)
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..0130c63feb1 100644
--- a/elf/dynamic-link.h
+++ b/elf/dynamic-link.h
@@ -78,18 +78,23 @@ elf_machine_lazy_rel (struct link_map *map, struct r_scope_elem *scope[],
consumes precisely the very end of the DT_REL*, or DT_JMPREL and DT_REL*
are completely separate and there is a gap between them. */
-/* 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. */
-enum elf_dynamic_reloc_phase
+/* Selects which relocations a pass processes. Splitting them allows the
+ caller to interleave TLS / stack-protector setup between the two passes,
+ so IFUNC resolvers see a fully-initialised TCB.
+
+ This is orthogonal to the skip_ifunc argument, which says whether an IFUNC
+ resolver may be run at all and is honoured by every pass. In particular
+ DL_RELOC_NORMAL also runs IFUNC resolvers, for relocations against an
+ IFUNC symbol defined in another object. */
+enum elf_dynamic_reloc_pass
{
- DL_RELOC_BOTH = 0, /* Non-IRELATIVE pass then IRELATIVE pass. */
- DL_RELOC_NOIFUNC = 1, /* Non-IRELATIVE pass only. */
- DL_RELOC_IFUNC = 2, /* IRELATIVE pass only. */
+ DL_RELOC_ALL = 0, /* Non-IRELATIVE relocations, then IRELATIVE. */
+ DL_RELOC_NORMAL = 1, /* Non-IRELATIVE relocations only. */
+ DL_RELOC_IRELATIVE = 2, /* IRELATIVE relocations only. */
};
# define _ELF_DYNAMIC_DO_RELOC(RELOC, reloc, map, scope, do_lazy, skip_ifunc, \
- test_rel, phase) \
+ test_rel, pass) \
do { \
struct { ElfW(Addr) start, size; \
__typeof (((ElfW(Dyn) *) 0)->d_un.d_val) nrelative; int lazy; } \
@@ -136,14 +141,15 @@ enum elf_dynamic_reloc_phase
by the linker. */ \
if (!DO_RTLD_BOOTSTRAP) \
{ \
- if ((phase) != DL_RELOC_IFUNC) \
+ if ((pass) != DL_RELOC_IRELATIVE) \
for (int ranges_index = 0; ranges_index < 2; ++ranges_index) \
elf_dynamic_do_##reloc ((map), scope, \
ranges[ranges_index].start, \
ranges[ranges_index].size, \
ranges[ranges_index].nrelative, \
- ranges[ranges_index].lazy); \
- if ((phase) != DL_RELOC_NOIFUNC) \
+ ranges[ranges_index].lazy, \
+ skip_ifunc); \
+ if ((pass) != DL_RELOC_NORMAL) \
for (int ranges_index = 0; ranges_index < 2; ++ranges_index) \
elf_dynamic_do_##reloc##_irelative ((map), scope, \
ranges[ranges_index].start, \
@@ -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
@@ -169,37 +176,21 @@ enum elf_dynamic_reloc_phase
# if ! ELF_MACHINE_NO_REL
# include "do-rel.h"
-# define ELF_DYNAMIC_DO_REL(map, scope, lazy, skip_ifunc) \
+# define ELF_DYNAMIC_DO_REL(map, scope, lazy, skip_ifunc, pass) \
_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, \
- _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)
+ _ELF_CHECK_REL, pass)
# 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_IFUNCONLY(map, scope, lazy, skip_ifunc) /* Nothing. */
+# define ELF_DYNAMIC_DO_REL(map, scope, lazy, skip_ifunc, pass) /* Nothing. */
# endif
# if ! ELF_MACHINE_NO_RELA
# define DO_RELA
# include "do-rel.h"
-# define ELF_DYNAMIC_DO_RELA(map, scope, lazy, skip_ifunc) \
+# define ELF_DYNAMIC_DO_RELA(map, scope, lazy, skip_ifunc, pass) \
_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, \
- _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)
+ _ELF_CHECK_REL, pass)
# 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_IFUNCONLY(map, scope, lazy, skip_ifunc) /* Nothing. */
+# define ELF_DYNAMIC_DO_RELA(map, scope, lazy, skip_ifunc, pass) /* Nothing. */
# endif
# define ELF_DYNAMIC_DO_RELR(map) \
@@ -240,37 +231,33 @@ enum elf_dynamic_reloc_phase
# else
# define DO_RTLD_BOOTSTRAP 0
# endif
+/* Perform one relocation pass over MAP. PASS selects which relocations are
+ processed. It is orthogonal to SKIP_IFUNC, which suppresses running IFUNC
+ resolvers in whichever pass is selected.
+
+ Unless PASS is DL_RELOC_IRELATIVE, this also performs the machine-specific
+ PLT/GOT setup, the DT_RELR relocations, and the ELF_DYNAMIC_AFTER_RELOC
+ hook. */
+# define ELF_DYNAMIC_RELOCATE_PASS(pass, map, scope, lazy, consider_profile, \
+ skip_ifunc) \
+ do { \
+ int edr_lazy = (lazy); \
+ if ((pass) != DL_RELOC_IRELATIVE) \
+ { \
+ 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 ((map), (scope), edr_lazy, skip_ifunc, (pass)); \
+ ELF_DYNAMIC_DO_RELA ((map), (scope), edr_lazy, skip_ifunc, (pass)); \
+ if ((pass) != DL_RELOC_IRELATIVE) \
+ ELF_DYNAMIC_AFTER_RELOC ((map), edr_lazy); \
+ } while (0)
+
+/* Run both passes back to back, for callers with nothing to interleave. */
# define ELF_DYNAMIC_RELOCATE(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 ((map), (scope), edr_lazy, skip_ifunc); \
- ELF_DYNAMIC_DO_RELA ((map), (scope), edr_lazy, skip_ifunc); \
- ELF_DYNAMIC_AFTER_RELOC ((map), (edr_lazy)); \
- } while (0)
-
-/* Like ELF_DYNAMIC_RELOCATE but only processes the non-IRELATIVE pass.
- 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) \
- 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_AFTER_RELOC ((map), (edr_lazy)); \
- } while (0)
-
-/* IRELATIVE-only companion to ELF_DYNAMIC_RELOCATE_NOIFUNC. */
-# define ELF_DYNAMIC_RELOCATE_IFUNC(map, scope, lazy, skip_ifunc) \
- do { \
- ELF_DYNAMIC_DO_REL_IFUNCONLY ((map), (scope), (lazy), skip_ifunc); \
- ELF_DYNAMIC_DO_RELA_IFUNCONLY ((map), (scope), (lazy), skip_ifunc); \
- } while (0)
+ ELF_DYNAMIC_RELOCATE_PASS (DL_RELOC_ALL, (map), (scope), (lazy), \
+ (consider_profile), skip_ifunc)
#endif
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