tst-dlopenfail-2 \
tst-dlopenrpath \
tst-dlsym-error \
+ tst-dst-needed-minstack \
tst-filterobj \
tst-filterobj-dlopen \
tst-glibc-hwcaps \
tst-dlopenfailmod3 \
tst-dlopenfailnodelmod \
tst-dlopenrpathmod \
+ tst-dst-needed-leaf-mod \
+ tst-dst-needed-wrap-mod \
tst-filterobj-aux \
tst-filterobj-filtee \
tst-filterobj-flt \
$(objpfx)tst-dl-cache-long-path: $(shared-thread-library)
$(objpfx)tst-dl-cache-long-path.out: $(objpfx)tst-dl-path-buf-mod.so
+LDFLAGS-tst-dst-needed-leaf-mod.so = \
+ -Wl,-soname,\$$ORIGIN/\$$ORIGIN/\$$ORIGIN/\$$ORIGIN/\$$ORIGIN/leaf.so
+$(objpfx)tst-dst-needed-wrap-mod.so: $(objpfx)tst-dst-needed-leaf-mod.so
+$(objpfx)tst-dst-needed-minstack: $(shared-thread-library)
+$(objpfx)tst-dst-needed-minstack.out: $(objpfx)tst-dst-needed-wrap-mod.so
+
LDFLAGS-tst-filterobj-flt.so = -Wl,--filter=$(objpfx)tst-filterobj-filtee.so
$(objpfx)tst-filterobj: $(objpfx)tst-filterobj-flt.so
$(objpfx)tst-filterobj.out: $(objpfx)tst-filterobj-filtee.so
#include <scratch_buffer.h>
#include <dl-dst.h>
+#include <dl-scratch-buffer.h>
/* Whether an shared object references one or more auxiliary objects
is signaled by the AUXTAG entry in l_info. */
};
-/* Macro to expand DST. It is an macro since we use `alloca'. */
-#define expand_dst(l, str, fatal) \
- ({ \
- const char *__str = (str); \
- const char *__result = __str; \
- size_t __dst_cnt = _dl_dst_count (__str); \
- \
- if (__dst_cnt != 0) \
- { \
- char *__newp; \
- \
- /* DST must not appear in SUID/SGID programs. */ \
- if (__libc_enable_secure) \
- _dl_signal_error (0, __str, NULL, N_("\
-DST not allowed in SUID/SGID programs")); \
- \
- __newp = (char *) alloca (DL_DST_REQUIRED (l, __str, strlen (__str), \
- __dst_cnt)); \
- \
- __result = _dl_dst_substitute (l, __str, __newp); \
- \
- if (*__result == '\0') \
- { \
- /* The replacement for the DST is not known. We can't \
- processed. */ \
- if (fatal) \
- _dl_signal_error (0, __str, NULL, N_("\
-empty dynamic string token substitution")); \
- else \
- { \
- /* This is for DT_AUXILIARY. */ \
- if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS)) \
- _dl_debug_printf (N_("\
-cannot load auxiliary `%s' because of empty dynamic string token " \
- "substitution\n"), __str); \
- continue; \
- } \
- } \
- } \
- \
- __result; })
+/* Expand the dynamic-string-tokens ($ORIGIN / $LIB / $PLATFORM) in INPUT
+ using L's context. Returns the expanded string -- a pointer either into
+ SCRATCH->data (when expansion was needed) or back at INPUT (when no DSTs
+ were present, so no allocation happened). Returns NULL when a DST was
+ present but could not be resolved.
+
+ SCRATCH must be an init'd dl_scratch_buffer the caller will release once
+ the returned string is no longer needed. This function never returns when
+ called for a SUID/SGID that contains DSTs: it raises a loader error. */
+static const char *
+expand_dst (struct link_map *l, const char *input,
+ struct dl_scratch_buffer *scratch)
+{
+ size_t dst_cnt = _dl_dst_count (input);
+ if (dst_cnt == 0)
+ return input;
+
+ /* DST must not appear in SUID/SGID programs. */
+ if (__libc_enable_secure)
+ _dl_signal_error (0, input, NULL, N_("\
+DST not allowed in SUID/SGID programs"));
+
+ size_t total = DL_DST_REQUIRED (l, input, strlen (input), dst_cnt);
+ dl_scratch_buffer_allocate (scratch, total + 1, 0);
+ const char *result = _dl_dst_substitute (l, input, scratch->data);
+ if (*result == '\0')
+ return NULL;
+ return result;
+}
static void
preload (struct list *known, unsigned int *nlist, struct link_map *map)
/* Map in the needed object. */
struct link_map *dep;
- /* Recognize DSTs. */
- name = expand_dst (l, strtab + d->d_un.d_val, 0);
+ /* Recognize DSTs. Empty substitution for DT_NEEDED is
+ non-fatal: log and skip this entry. */
+ struct dl_scratch_buffer scratch
+ = dl_scratch_buffer_init ();
+ name = expand_dst (l, strtab + d->d_un.d_val, &scratch);
+ if (__glibc_unlikely (name == NULL))
+ {
+ if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
+ _dl_debug_printf (N_("\
+cannot load auxiliary `%s' because of empty dynamic string token "
+ "substitution\n"),
+ strtab + d->d_un.d_val);
+ dl_scratch_buffer_free (&scratch);
+ continue;
+ }
/* Store the tag in the argument structure. */
args.name = name;
int err = _dl_catch_exception (&exception, openaux, &args);
+ dl_scratch_buffer_free (&scratch);
if (__glibc_unlikely (exception.errstring != NULL))
{
if (err)
{
struct list *newp;
- /* Recognize DSTs. */
- name = expand_dst (l, strtab + d->d_un.d_val,
- d->d_tag == DT_AUXILIARY);
+ /* Recognize DSTs. DT_AUXILIARY is fatal on unresolved
+ DST; DT_FILTER is non-fatal and is skipped. */
+ struct dl_scratch_buffer scratch
+ = dl_scratch_buffer_init ();
+ name = expand_dst (l, strtab + d->d_un.d_val, &scratch);
+ if (__glibc_unlikely (name == NULL))
+ {
+ dl_scratch_buffer_free (&scratch);
+ if (d->d_tag == DT_AUXILIARY)
+ _dl_signal_error (0, strtab + d->d_un.d_val, NULL,
+ N_("empty dynamic string token "
+ "substitution"));
+ if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
+ _dl_debug_printf (N_("\
+cannot load auxiliary `%s' because of empty dynamic string token "
+ "substitution\n"),
+ strtab + d->d_un.d_val);
+ continue;
+ }
/* Store the tag in the argument structure. */
args.name = name;
object is not available. For filter objects the dependency
must be available. */
int err = _dl_catch_exception (&exception, openaux, &args);
+ /* NAME is consumed by openaux above; release the DST
+ scratch buffer regardless of outcome. */
+ dl_scratch_buffer_free (&scratch);
if (__glibc_unlikely (exception.errstring != NULL))
{
if (d->d_tag == DT_AUXILIARY)
--- /dev/null
+/* Leaf DSO whose SONAME contains several DST tokens. Used by
+ tst-dst-needed-minstack via tst-dst-needed-wrap-mod.so.
+ 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_dst_needed_leaf_dummy;
--- /dev/null
+/* Test that dlopen of a library whose DT_NEEDED string carries
+ several dynamic-string tokens does not overflow a
+ PTHREAD_STACK_MIN-sized thread.
+
+ 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/>. */
+
+/* The leaf library is linked with a SONAME containing five $DST tokens; the
+ wrapper library links against the leaf so its DT_NEEDED inherits that
+ string. This test deploys the wrapper in a ~3 KB deep directory (so the
+ wrapper's l_origin matches), then dlopens it from a PTHREAD_STACK_MIN
+ thread. The leaf is not actually reachable through the (impossible)
+ expanded path, so the dlopen is expected to fail -- the regression
+ assertion is that the failure occurs without a stack overflow. */
+
+#include <dlfcn.h>
+#include <limits.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <support/check.h>
+#include <support/support.h>
+#include <support/temp_file.h>
+#include <support/xstdio.h>
+#include <support/xthread.h>
+#include <support/xunistd.h>
+
+#define WRAP_MOD "tst-dst-needed-wrap-mod.so"
+
+/* Set by do_prepare; the absolute path of the wrapper as deployed in the
+ deep directory. */
+static char *deep_wrap_path;
+
+/* Build <temp>/<long components>/ and return the final path; intermediates
+ are registered with add_temp_file so cleanup is automatic. */
+static char *
+build_deep_directory (void)
+{
+ enum { component_len = 240, components = 14 };
+ char component[component_len + 1];
+ memset (component, 'd', component_len);
+ component[component_len] = '\0';
+
+ char *base = support_create_temp_directory ("tst-dst-needed-");
+ size_t cap = strlen (base) + components * (1 + component_len) + 1;
+ char *path = xmalloc (cap);
+ strcpy (path, base);
+ free (base);
+
+ for (int i = 0; i < components; ++i)
+ {
+ strcat (path, "/");
+ strcat (path, component);
+ xmkdirp (path, 0777);
+ add_temp_file (path);
+ }
+ return path;
+}
+
+static void
+do_prepare (int argc, char **argv)
+{
+ char *deep_dir = build_deep_directory ();
+ /* Deep enough that l_origin alone is well over PTHREAD_STACK_MIN. */
+ TEST_VERIFY (strlen (deep_dir) > 256);
+
+ char *src = xasprintf ("%s/elf/" WRAP_MOD, support_objdir_root);
+ deep_wrap_path = xasprintf ("%s/" WRAP_MOD, deep_dir);
+ support_copy_file (src, deep_wrap_path);
+ add_temp_file (deep_wrap_path);
+ free (src);
+ free (deep_dir);
+}
+#define PREPARE do_prepare
+
+static void *
+minstack_thread (void *closure)
+{
+ /* Trigger DT_NEEDED expansion on the deep wrapper. The leaf's five-$DST
+ SONAME, expanded against the wrapper's deep l_origin, produces a buffer
+ of several KB inside _dl_map_object_deps. We do not care whether the
+ leaf is actually findable -- the test passes if and only if the dlopen
+ returns without a stack overflow. */
+ void *h = dlopen (deep_wrap_path, RTLD_NOW);
+ TEST_VERIFY_EXIT (h == NULL);
+ return NULL;
+}
+
+static int
+do_test (void)
+{
+ size_t stacksize = support_small_thread_stack_size (true);
+
+ pthread_attr_t attr;
+ xpthread_attr_init (&attr);
+ xpthread_attr_setstacksize (&attr, stacksize);
+ pthread_t thr = xpthread_create (&attr, minstack_thread, NULL);
+ xpthread_join (thr);
+ xpthread_attr_destroy (&attr);
+
+ free (deep_wrap_path);
+ return 0;
+}
+
+#include <support/test-driver.c>
--- /dev/null
+/* Wrapper DSO whose DT_NEEDED string inherits the DST-laden SONAME
+ of tst-dst-needed-leaf-mod.so. Loading this from a
+ PTHREAD_STACK_MIN thread is what tst-dst-needed-minstack exercises.
+ 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_dst_needed_wrap_dummy;