[PATCH v3 0/6] elf: Replace alloca/VLA with dl_scratch_buffer in dl-load.c

Adhemerval Zanella adhemerval.zanella@linaro.org
Tue May 19 13:23:50 GMT 2026


This series is a follow-up to the BZ #26577 work, where the fix removed
the two big VLAs/allocas in _dl_map_object_from_fd (the phdr table and
the loadcmds VLA).  The loader still contained several unbounded allocas
and strdupa()s in code paths reachable from ordinary dlopen() or from 
loader startup with user-controllable environment.

The series introduces a small loader-side scratch buffer abstraction
and uses it to replace the remaining unbounded alloca/VLA/strdupa
sites.  The scratch buffer keeps a 256-byte inline area on the caller
stack for the common case and spills larger allocations to malloc, or
to anonymous mmap while __minimal_malloc is still the active allocator
(so the very early loader paths -- _dl_init_paths,
_dl_load_cache_lookup called via recursive dlopen, etc. -- still
work).

After the series, the loader's dlopen()/startup path no longer grows
the stack proportionally to LD_LIBRARY_PATH length, DT_NEEDED DST
count, l_origin depth, RPATH/RUNPATH entry length, or cached path
length.

Per-commit stack reduction
--------------------------

  Synthetic worst case   Worst case in the
  (formula upper bound)  regression test       Commit

  ~ 128 KB               ~ 64 KB               elf: Use dl_scratch_buffer
  (MAX_ARG_STRLEN per                          for LD_LIBRARY_PATH copy
   env entry)                                  in _dl_init_paths

  ~ 2.4 MB               ~ 17 KB               elf: Use dl_scratch_buffer
  (dst_cnt * max(                              for DST expansion in
   l_origin, $LIB,                             _dl_map_object_deps
   $PLATFORM); pathological
   ~580 DSTs * PATH_MAX)

    4 KB                 ~ 3.4 KB              elf: Replace alloca with
  (best_len <=                                 dl_scratch_buffer in
   PATH_MAX)                                   _dl_load_cache_lookup

  ~ 12 KB                not measurable        elf: Replace alloca/VLA
  (PATH_MAX per          (transient            with dl_scratch_buffer
   call site, several    per-iteration         in dl-load.c
   on stack during one   alloca released
   dlopen call chain)    before next sample)


The synthetic worst case is what the formula in the loader can be
made to produce given a maximally adversarial input.  The test
column is what the new regression test included with that commit
actually triggers on a default x86_64 build.

Cumulative effect on the two workloads where massif can cleanly
capture a steady-state peak (origin/master baseline -> series HEAD,
x86_64, valgrind --tool=massif --stacks=yes):

  DST-laden DT_NEEDED workload   (tst-dst-needed-minstack):
      peak mem_stacks_B  22,208 B  ->  7,208 B    (-15 KB)
  Long LD_LIBRARY_PATH workload  (tst-dl-llp-stack):
      peak mem_stacks_B  18,280 B  ->  7,128 B    (-11 KB)

Scratch buffer safety against _dl_signal_error
----------------------------------------------

The scratch buffer's MMAP/MALLOC backing must be released on every
exit path between dl_scratch_buffer_allocate and
dl_scratch_buffer_free.  The hazard is that _dl_signal_error fired
between the two -- and caught by an outer _dl_catch_exception --
would skip the matching free and leak the mapping.  Every use site
in the series has been audited against this:

  * dl_scratch_buffer_allocate only sets b->backend to MMAP/MALLOC
    after the underlying mmap/malloc returns.  If the allocation
    itself fails, _dl_signal_error fires while b->backend is still
    INLINE; there is nothing to free, so no leak is possible.

  * dl-cache.c _dl_load_cache_lookup: only memcpy + __strdup run
    between allocate and free; __strdup returns NULL on OOM, never
    signals.

  * dl-deps.c expand_dst: an __libc_enable_secure check short-
    circuits with _dl_signal_error before allocate runs.  After
    allocate succeeds, _dl_dst_substitute can only call
    is_trusted_path_normalize through the check_for_trusted path,
    which is gated on __libc_enable_secure -- already known false
    by construction -- so the sub-call is unreachable.

  * dl-deps.c _dl_map_object_deps DT_NEEDED / DT_AUXILIARY /
    DT_FILTER paths: openaux is wrapped in _dl_catch_exception, so
    any signal_error from the actual dependency open is caught and
    the matching free runs.  DT_AUXILIARY's own signal_error on
    empty DST substitution is placed after the scratch free.

  * dl-load.c is_trusted_path_normalize and open_path: between the
    matching allocate and free, only pure string manipulation,
    open_verify (which returns -1 on error, never signals), stat,
    and malloc-returning-NULL run.  None can _dl_signal_error.

  * dl-load.c _dl_init_paths (LD_LIBRARY_PATH copy): the two heap
    allocations the loader controls -- the search-path dirs array
    and fillin_rpath's per-entry mallocs -- are now signalled with
    the scratch in a known state.  The dirs array is allocated
    before the scratch is live (its OOM signals with no scratch
    to leak), and fillin_rpath has been changed to return bool
    instead of signalling internally; the caller frees the scratch
    first and then signals the error from a clean state.  This
    also fixes a pre-existing leak in fillin_rpath where the
    per-iteration to_free heap copy was not released before the
    _dl_signal_error.

Tested on x86_64-linux-gnu, aarch64-linux-gnu, and i686-linux-gnu.
I also cross-built and run the elf tests on powerpc64le, s390x,
risc64, and loongarch64.

--
Changes from v2:
* Change support_small_thread_stack_size to add an option to return
  PTHREAD_STACK_MIN.
* Fix tst-dl-llp-stack when --enable-hardcoded-path-in-tests is not
  used.

Changes from v1:
* Scale envp/rlim tst-dl-llp-stack.c by PAGE_SIZE (to possible handle
  Linaro CI regression).
* Fixed build issues on Hurd.
* Improved some comments.

Adhemerval Zanella (6):
  elf: Add dl_scratch_buffer, a loader-side scratch buffer
  support: Add use_stack_min option to support_small_thread_stack_size
  elf: Replace alloca/VLA with dl_scratch_buffer in dl-load.c
  elf: Replace alloca with dl_scratch_buffer in _dl_load_cache_lookup
  elf: Use dl_scratch_buffer for DST expansion in _dl_map_object_deps
  elf: Use dl_scratch_buffer for LD_LIBRARY_PATH copy in _dl_init_paths

 elf/Makefile                                  |  29 +++
 elf/dl-cache.c                                |  14 +-
 elf/dl-deps.c                                 | 114 +++++----
 elf/dl-load.c                                 | 104 +++++++--
 elf/dl-scratch-buffer.c                       |  90 +++++++
 elf/dl-scratch-buffer.h                       | 145 ++++++++++++
 elf/tst-bz26577-minstack.c                    |   7 +-
 elf/tst-decorate-maps.c                       |   2 +-
 elf/tst-dl-cache-long-path.c                  | 174 ++++++++++++++
 elf/tst-dl-llp-stack.c                        | 129 ++++++++++
 elf/tst-dl-path-buf-mod.c                     |  23 ++
 elf/tst-dl-path-buf.c                         | 220 ++++++++++++++++++
 elf/tst-dst-needed-leaf-mod.c                 |  20 ++
 elf/tst-dst-needed-minstack.c                 | 122 ++++++++++
 elf/tst-dst-needed-wrap-mod.c                 |  21 ++
 nptl/tst-guard1.c                             |   4 +-
 stdlib/tst-canon-bz26341.c                    |   2 +-
 support/support_set_small_thread_stack_size.c |  13 +-
 .../support_small_stack_thread_attribute.c    |   2 +-
 support/xthread.h                             |  12 +-
 .../sysv/linux/tst-sem_getvalue-affinity.c    |   2 +-
 .../sysv/linux/tst-skeleton-thread-affinity.c |   2 +-
 22 files changed, 1155 insertions(+), 96 deletions(-)
 create mode 100644 elf/dl-scratch-buffer.c
 create mode 100644 elf/dl-scratch-buffer.h
 create mode 100644 elf/tst-dl-cache-long-path.c
 create mode 100644 elf/tst-dl-llp-stack.c
 create mode 100644 elf/tst-dl-path-buf-mod.c
 create mode 100644 elf/tst-dl-path-buf.c
 create mode 100644 elf/tst-dst-needed-leaf-mod.c
 create mode 100644 elf/tst-dst-needed-minstack.c
 create mode 100644 elf/tst-dst-needed-wrap-mod.c

-- 
2.43.0



More information about the Libc-alpha mailing list