[PATCH v3 4/6] elf: Replace alloca with dl_scratch_buffer in _dl_load_cache_lookup
H.J. Lu
hjl.tools@gmail.com
Tue May 19 20:52:56 GMT 2026
On Tue, May 19, 2026 at 9:26 PM Adhemerval Zanella
<adhemerval.zanella@linaro.org> wrote:
>
> The alloca added by commit ccdb048d ("Fix recursive dlopen") to
> snapshot the matched cache entry before __strdup runs through
> interposable malloc is sized by best_len, which can reach PATH_MAX.
> On PTHREAD_STACK_MIN threads that's enough to overflow the stack
> mid-dlopen.
>
> Use dl_scratch_buffer with DL_SCRATCH_NO_MALLOC: short entries stay
> in the 256-byte inline area, longer ones spill to anonymous mmap
> rather than to interposable malloc. The recursive-dlopen invariant
> is preserved.
>
> New container test elf/tst-dl-cache-long-path constructs a ~3.4 KB
> deep directory, populates ld.so.cache with that entry, and dlopens
> from a PTHREAD_STACK_MIN thread under deliberate stack pressure;
> reliably SIGSEGVs against the alloca-based code and passes with the
> fix.
>
> Checked on aarch64-linux-gnu, x86_64-linux-gnu, and i686-linux-gnu.
> ---
> elf/Makefile | 5 +
> elf/dl-cache.c | 14 ++-
> elf/tst-dl-cache-long-path.c | 174 +++++++++++++++++++++++++++++++++++
> 3 files changed, 188 insertions(+), 5 deletions(-)
> create mode 100644 elf/tst-dl-cache-long-path.c
>
> diff --git a/elf/Makefile b/elf/Makefile
> index f668dec368e..00d1a558d89 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -293,6 +293,7 @@ CRT-tst-tls1-static-non-pie := $(csu-objpfx)crt1.o
> tst-tls1-static-non-pie-no-pie = yes
>
> tests-container := \
> + tst-dl-cache-long-path \
> tst-ldconfig-bad-aux-cache \
> tst-ldconfig-ld_so_conf-update \
> # tests-container
> @@ -2886,6 +2887,10 @@ LDFLAGS-tst-dlopen-nodelete-reloc-mod17.so = -Wl,--no-as-needed
>
> $(objpfx)tst-ldconfig-ld_so_conf-update.out: $(objpfx)tst-ldconfig-ld-mod.so
>
> +# Reuses the trivial module already built for tst-dl-path-buf.
> +$(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-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
> diff --git a/elf/dl-cache.c b/elf/dl-cache.c
> index 9458ffae2a6..c1de93f2041 100644
> --- a/elf/dl-cache.c
> +++ b/elf/dl-cache.c
> @@ -21,6 +21,7 @@
> #include <ldsodefs.h>
> #include <sys/mman.h>
> #include <dl-cache.h>
> +#include <dl-scratch-buffer.h>
> #include <stdint.h>
> #include <_itoa.h>
> #include <dl-hwcaps.h>
> @@ -490,12 +491,15 @@ _dl_load_cache_lookup (const char *name)
> /* The double copy is *required* since malloc may be interposed
> and call dlopen itself whose completion would unmap the data
> we are accessing. Therefore we must make the copy of the
> - mapping data without using malloc. */
> - char *temp;
> + mapping data without using malloc. The DL_SCRATCH_NO_MALLOC
> + forces any spill to anonymous mmap rather than the malloc. */
> + struct dl_scratch_buffer scratch = dl_scratch_buffer_init ();
> size_t best_len = strlen (best) + 1;
> - temp = alloca (best_len);
> - memcpy (temp, best, best_len);
> - return __strdup (temp);
> + dl_scratch_buffer_allocate (&scratch, best_len, DL_SCRATCH_NO_MALLOC);
> + memcpy (scratch.data, best, best_len);
> + char *result = __strdup (scratch.data);
> + dl_scratch_buffer_free (&scratch);
> + return result;
> }
>
> #ifndef MAP_COPY
> diff --git a/elf/tst-dl-cache-long-path.c b/elf/tst-dl-cache-long-path.c
> new file mode 100644
> index 00000000000..e7304934228
> --- /dev/null
> +++ b/elf/tst-dl-cache-long-path.c
> @@ -0,0 +1,174 @@
> +/* Test dlopen through ld.so.cache with a cache entry longer than the
> + dl_scratch_buffer inline area.
> + 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/>. */
> +
> +/* This test populates the cache with a single library sitting in a directory
> + whose absolute path is far larger than the dl_scratch_buffer inline area
> + (256 bytes) and most of the way to PATH_MAX; the loader's cache lookup
> + therefore exercises the anonymous-mmap spill. The dlopen is also repeated
> + from a PTHREAD_STACK_MIN thread to demonstrate that the path no longer
> + consumes too much caller's stack. */
> +
> +#include <dlfcn.h>
> +#include <limits.h>
> +#include <pthread.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/stat.h>
> +
> +#include <support/capture_subprocess.h>
> +#include <support/check.h>
> +#include <support/support.h>
> +#include <support/temp_file.h>
> +#include <support/xdlfcn.h>
> +#include <support/xstdio.h>
> +#include <support/xthread.h>
> +#include <support/xunistd.h>
> +
> +/* ldconfig only indexes filenames starting with "lib", so the module is
> + deployed under the lib-prefixed name (MOD_DEPLOYED) in the deep directory
> + and dlopened by that name. */
> +#define MOD_BUILT "tst-dl-path-buf-mod.so"
> +#define MOD_DEPLOYED "libtst-dl-path-buf-mod.so"
> +#define MOD_SYMBOL "tst_dl_path_buf_mod_value"
> +#define MOD_EXPECTED 0xaabbccddu
> +
> +/* Final absolute path of the deep directory holding the module; filled in by
> + setup (). Kept around so dlopen_module can sanity-print it on failure. */
> +static char *deep_dir;
> +
> +static void
> +run_ldconfig (void *x)
> +{
> + char *prog = xasprintf ("%s/ldconfig", support_install_rootsbindir);
> + char *args[] = { prog, NULL };
> + execv (args[0], args);
> + FAIL_EXIT1 ("execv (%s): %m", prog);
> +}
> +
> +/* Build /tst-dl-cache-long-path/d.../d.../d... with several long components,
> + totalling well past dl_scratch_buffer's inline area
> + (DL_SCRATCH_BUFFER_INLINE_SIZE = 256 bytes) and close to PATH_MAX. */
> +static char *
> +build_deep_directory (void)
> +{
> + enum { component_len = 250, components = 15 };
> + /* 14 * (1 + 240) = 3374 bytes of nesting, plus the base. */
> + char component[component_len + 1];
> + memset (component, 'd', component_len);
> + component[component_len] = '\0';
> +
> + const char *base = "/tst-dl-cache-long-path";
> + size_t cap = strlen (base) + components * (1 + component_len) + 1;
> + char *path = xmalloc (cap);
> + strcpy (path, base);
> + xmkdirp (path, 0777);
> + add_temp_file (path);
> +
> + 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)
> +{
> + deep_dir = build_deep_directory ();
> + TEST_VERIFY (strlen (deep_dir) > 256);
> +
> + char *src = xasprintf ("%s/elf/" MOD_BUILT, support_objdir_root);
> + char *dst = xasprintf ("%s/" MOD_DEPLOYED, deep_dir);
> + support_copy_file (src, dst);
> + add_temp_file (dst);
> + free (src);
> + free (dst);
> +
> + char *conf = xasprintf ("%s/ld.so.conf", support_sysconfdir_prefix);
> + FILE *fp = xfopen (conf, "w");
> + fprintf (fp, "%s\n", deep_dir);
> + xfclose (fp);
> + free (conf);
> +
> + xmkdirp ("/var/cache/ldconfig", 0777);
> + struct support_capture_subprocess r
> + = support_capture_subprocess (run_ldconfig, NULL);
> + support_capture_subprocess_check (&r, "ldconfig", 0, sc_allow_none);
> + support_capture_subprocess_free (&r);
> +}
> +#define PREPARE do_prepare
> +
> +static void
> +__attribute_noinline__
> +dlopen_via_cache (volatile char *pressure)
> +{
> + if (pressure != NULL)
> + (void) *pressure;
> +
> + void *h = xdlopen (MOD_DEPLOYED, RTLD_NOW | RTLD_LOCAL);
> + unsigned int (*fn) (void) = xdlsym (h, MOD_SYMBOL);
> + TEST_COMPARE (fn (), MOD_EXPECTED);
> + xdlclose (h);
> +}
> +
> +/* Reduce the stack budget available to the dlopen call chain by
> + STACK_PRESSURE bytes. */
> +enum { STACK_PRESSURE = 5 * 1024 };
> +
> +static void
> +__attribute_noinline__
> +dlopen_via_cache_under_pressure (void)
> +{
> + char filler[STACK_PRESSURE];
> + dlopen_via_cache (&filler[0]);
> +}
> +
> +static void *
> +minstack_thread (void *closure)
> +{
> + dlopen_via_cache_under_pressure ();
> + return NULL;
> +}
> +
> +static int
> +do_test (void)
> +{
> + /* Sanity: from the main thread (no stack pressure needed). */
> + dlopen_via_cache (NULL);
> +
> + /* The motivating scenario: from a PTHREAD_STACK_MIN thread. Before
> + _dl_load_cache_lookup was converted to dl_scratch_buffer this would
> + have alloca'd ~3 KB mid-dlopen and risked overflowing. */
> + 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_dir);
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
> --
> 2.43.0
>
LGTM.
Reviewed-by: H.J. Lu <hjl.tools@gmail.com>
Thanks.
--
H.J.
More information about the Libc-alpha
mailing list