From patchwork Fri May 15 18:21:47 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Adhemerval Zanella X-Patchwork-Id: 135085 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 58A88409FC99 for ; Fri, 15 May 2026 18:24:03 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 58A88409FC99 Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=stQ9/DLI X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-ua1-x930.google.com (mail-ua1-x930.google.com [IPv6:2607:f8b0:4864:20::930]) by sourceware.org (Postfix) with ESMTPS id A7D0451A431D for ; Fri, 15 May 2026 18:22:31 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org A7D0451A431D Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=linaro.org Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=linaro.org ARC-Filter: OpenARC Filter v1.0.0 sourceware.org A7D0451A431D Authentication-Results: sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:20::930 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778869351; cv=none; b=ewZHsfYP9qtZ6wuK6cw3ktnKjHaozZ0crVpAbltCYTTJGXzU4K/FNwRAex1GKkSQvEE7PG5gFh5DuRsbKXi2OqEmqdzAmUy+n6yYBpXQ/ppgdf2/pXKyYSlKG3g+ZI87lbZxlWIxLnLioyBnbqQ1ABayND8i4XUxeOJJ2/2kou4= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778869351; c=relaxed/simple; bh=zk/4TUVzlrI6yBLPo1KaDgi7uq7P5ofkTaDYy6J+L/Y=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=YcLvSY0CizLNukYF0WK5q7uTHQYIbrFxi50FnjYXsafk+bhjwhl2ql0xdUbMlZBRyOZ74xy/KpD5oicQZOiSYunOP09yFHRAvGtdXADQJ0ZIqRCRx6V4sqbxV0o7u5JhMjFs/MWfqfOqt90NUfHsCFYlF02pNMoUqJ85A0XAvfs= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=stQ9/DLI DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org A7D0451A431D Received: by mail-ua1-x930.google.com with SMTP id a1e0cc1a2514c-95d18b33f93so28784241.1 for ; Fri, 15 May 2026 11:22:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1778869351; x=1779474151; darn=sourceware.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=8ITFmL2vshPaC/tslBO8j5IHg8dQgG3E8w3K7OMZcg8=; b=stQ9/DLIP00KsB/ydqt9sIiM2ZAz0+n2fIq8B5njVwVr/J7p78H2veUD3E0iIWDv64 wM/8pQI3I6NSv+37H0AP9kOgz79nn7qaKsseUos0hPWwSrr91rc9/X2oxcKKPUMOYEMi 5sszHkRqv12ltELC4KFgsA6PuF1vuwbAeCbNdianRRZlnqZOcUVbNiVIqJz82YJND0E3 j1wRuv4QEuFF9tcBd/aB8k/05aB7lPYCyBOqZ+oIGBR3XaXcdmtcPrCHh6ZrOX9I+Q8F VL1SY+9kaFTImvmMDb6C7qPpURR90R0queoRetV+eb3WeDLSLrB/3r8Byk9TD8NUoaUP /gBg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778869351; x=1779474151; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=8ITFmL2vshPaC/tslBO8j5IHg8dQgG3E8w3K7OMZcg8=; b=b0QtU1rGzb7Z2K78y6xNMqvKUYobL4u6TORA5KOHl4tnaRWLcmTRKpgtnAC3n/dj7Z PUb2zUVKX4XSbtk1IEaNVvQYjtNkzdoyvbFKliFpOx7w4zJ46jYpoS7QWypoYKu8H5R7 gXDIzTQJ5Mp2sJO0ZXv3QVRN6kH4TzoiNH4679mrE6D4SaC2a4vAycRbjck9G+ws1FC3 WTJJrk5ixDRTm/yt4xyJi5nc/DCmpLOSo7u7B/9FmgsGutG8nzyQkGmr4aOfFCvIyEqF cQzdjdbtLNO8OMdFIDxjj6c59kYKjl7S5Inqo8LVRJgGA5NlHnFgKFvuAzTSdT0ACtsI sG+Q== X-Gm-Message-State: AOJu0YwGqC1MgCtaWujBn0UVy83OOePin2HYzbT0oYk3Q6lQhMZ2J7OJ vIy8+HTj/evUN5IlM6u4cVmO44K2Ccs6OrWwBVnvaI0GbQkH2ho1vhP14rbs5MNKwRTY4iGoElX hZvJU X-Gm-Gg: Acq92OFuOH503I9QFB6Sj553DZ5rkQSicq/zK19NJsGmBgz+4wspoI5gddH7kvJR7Ob ZGnZ9iXFJYx0ATq/7E/YkmTlZI/QjO9L49TIjGqNCH4VVs1vsKCOK8PvlbGSL9qtBAZqizyZLnp FPK6H+6Kr/L6Uy1Wgy/j024cOyo8fN14JTbP1dTOBJTlLCw0S0JwSkbcCoxLOq7mwBwd3xTZbZx 94knq6/D8kKYu5UVs64WMV407P06o43eJNtIujIRq9nkzMMa882RD48/ZC8ClJZKNyHYMMgNYt0 Ve1TAl8EXahzv3lzwypxAXftqCxUVSlDa8K6+YzpLIyG9IO3iLt8c898k7wymCY/hDXNxLv5LSD GnfZ0yMVYfP0X/nT8J+oAZJCyRIS7AvZ+xfBsWcgLtqyKTYk/CtoB7U5oeloPDT34EOIO8x01Ts XZ8FDUiEzF3KzzAu4FHU4Hr0ui+IjWkWNDnonpwoCnJXiu X-Received: by 2002:a05:6102:548c:b0:633:9ac8:6bfd with SMTP id ada2fe7eead31-638b4c525bdmr4442660137.4.1778869350377; Fri, 15 May 2026 11:22:30 -0700 (PDT) Received: from mandiga.. ([2804:1b3:a7c1:d905:4b30:6c4:9ff3:893c]) by smtp.gmail.com with ESMTPSA id ada2fe7eead31-63ccf18e114sm1314488137.1.2026.05.15.11.22.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 15 May 2026 11:22:29 -0700 (PDT) From: Adhemerval Zanella To: libc-alpha@sourceware.org Cc: "H . J . Lu" Subject: [PATCH v2 1/5] elf: Add dl_scratch_buffer, a loader-side scratch buffer Date: Fri, 15 May 2026 15:21:47 -0300 Message-ID: <20260515182221.855960-2-adhemerval.zanella@linaro.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260515182221.855960-1-adhemerval.zanella@linaro.org> References: <20260515182221.855960-1-adhemerval.zanella@linaro.org> MIME-Version: 1.0 X-Spam-Status: No, score=-11.9 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_NONE, TXREP shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org Several loader code paths need a short-lived scratch buffer sized by attacker-influenced inputs (RPATH entries, ld.so.cache strings, etc.). The available primitives are all unsuitable: - alloca is unbounded and can overflow PTHREAD_STACK_MIN stacks. - is unaware of __minimal_malloc: a malloc'd spill freed during early loader startup silently leaks because __minimal_free only releases the most-recent allocation. - A few paths cannot route through the interposable malloc at all -- ld.so.cache lookup in particular, because an interposed user malloc may recursively call dlopen and __munmap the cache mapping mid-copy (commit ccdb048d, "Fix recursive dlopen"). Add a loader-side analogue of : a 256-byte inline area for the common case, with spill to malloc by default or to anonymous mmap when __minimal_malloc is active or the caller passes DL_SCRATCH_NO_MALLOC. Mmap spills are tagged " glibc: loader scratch" via __set_vma_name for /proc/self/maps visibility. On OOM dl_scratch_buffer_allocate raises a loader error via _dl_signal_error and does not return. The one-shot contract (no second allocate without an intervening free) is enforced by an assertion in _dl_scratch_buffer_allocate. No functional change in this commit; consumers are added separately. Reviewed-by: H.J. Lu --- elf/Makefile | 1 + elf/dl-scratch-buffer.c | 90 +++++++++++++++++++++++++ elf/dl-scratch-buffer.h | 145 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 236 insertions(+) create mode 100644 elf/dl-scratch-buffer.c create mode 100644 elf/dl-scratch-buffer.h diff --git a/elf/Makefile b/elf/Makefile index f4d22c15991..67bcd7f072d 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -78,6 +78,7 @@ dl-routines = \ dl-reloc \ dl-runtime \ dl-scope \ + dl-scratch-buffer \ dl-setup_hash \ dl-sort-maps \ dl-thread_gscope_wait \ diff --git a/elf/dl-scratch-buffer.c b/elf/dl-scratch-buffer.c new file mode 100644 index 00000000000..eb836017159 --- /dev/null +++ b/elf/dl-scratch-buffer.c @@ -0,0 +1,90 @@ +/* Loader-internal scratch buffer. + 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 + . */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +void +_dl_scratch_buffer_allocate (struct dl_scratch_buffer *b, size_t size, + unsigned int flags) +{ + /* Enforce the one-shot contract. */ + assert (b->backend == DL_SCRATCH_INLINE); + + bool use_malloc = !(flags & DL_SCRATCH_NO_MALLOC); +#ifdef SHARED + /* While __minimal_malloc is the active allocator, __minimal_free + only releases the most-recent block; route through mmap instead so + dl_scratch_buffer_free can symmetrically release the spill. */ + if (!__rtld_malloc_is_complete ()) + use_malloc = false; +#endif + + if (use_malloc) + { + void *p = malloc (size); + if (__glibc_unlikely (p == NULL)) + _dl_signal_error (ENOMEM, NULL, NULL, + N_("cannot allocate loader scratch buffer")); + b->data = p; + b->size = size; + b->backend = DL_SCRATCH_MALLOC; + return; + } + + size_t map_size = ALIGN_UP (size, GLRO(dl_pagesize)); + void *p = __mmap (NULL, map_size, PROT_READ | PROT_WRITE, + MAP_ANON | MAP_PRIVATE, -1, 0); + if (__glibc_unlikely (p == MAP_FAILED)) + _dl_signal_error (ENOMEM, NULL, NULL, + N_("cannot allocate loader scratch buffer")); + __set_vma_name (p, map_size, " glibc: loader scratch"); + b->data = p; + b->size = map_size; + b->backend = DL_SCRATCH_MMAP; +} +rtld_hidden_def (_dl_scratch_buffer_allocate) + +void +_dl_scratch_buffer_free (struct dl_scratch_buffer *b) +{ + switch (b->backend) + { + case DL_SCRATCH_MALLOC: + free (b->data); + break; + case DL_SCRATCH_MMAP: + __munmap (b->data, b->size); + break; + case DL_SCRATCH_INLINE: + /* Unreachable in normal use; guarded by the inline wrapper. */ + break; + } + b->data = b->inline_data; + b->size = sizeof b->inline_data; + b->backend = DL_SCRATCH_INLINE; +} +rtld_hidden_def (_dl_scratch_buffer_free) diff --git a/elf/dl-scratch-buffer.h b/elf/dl-scratch-buffer.h new file mode 100644 index 00000000000..b6b0b690aee --- /dev/null +++ b/elf/dl-scratch-buffer.h @@ -0,0 +1,145 @@ +/* Loader-internal scratch buffer. + 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 + . */ + +/* This is the loader-side analogue of . It exists + because the loader has two constraints that does + not address: + + 1. While the active allocator is __minimal_malloc (early startup, + before __rtld_malloc_init_real has switched in libc's malloc), + __minimal_free only releases the most-recent allocation -- a + malloc'd spill would silently leak. + + 2. Some loader code paths cannot route a spill through the + interposable malloc at all because the user malloc may + recursively re-enter the loader and invalidate state we are + copying from (the canonical example is _dl_load_cache_lookup + copying out of the file-backed ld.so.cache mapping). + + The buffer starts in a stack-resident inline area; if the caller + needs more bytes, the spill is to anonymous mmap (always safe, + tagged for /proc/self/maps visibility) or to malloc (cheaper, only + chosen when both the active allocator is real malloc and the + caller does not pass DL_SCRATCH_NO_MALLOC). + + Typical usage: + + struct dl_scratch_buffer scratch = dl_scratch_buffer_init (); + dl_scratch_buffer_allocate (&scratch, needed, 0); + ... use scratch.data ... + dl_scratch_buffer_free (&scratch); + + The interface is one-shot: every consumer knows the required size + upfront and calls dl_scratch_buffer_allocate exactly once, so there + is no incremental-growth model. A second allocate without an + intervening free is a programming error and is checked by an + assertion in _dl_scratch_buffer_allocate. + + On allocation failure dl_scratch_buffer_allocate does not return; + it raises a loader ENOMEM via _dl_signal_error. Callers may + therefore treat scratch.data as valid after a successful return. */ + +#ifndef _DL_SCRATCH_BUFFER_H +#define _DL_SCRATCH_BUFFER_H 1 + +#include +#include +#include + +/* Size of the inline area. Tuned to cover typical ld.so.cache + entries (well under 256 bytes) so that the common case stays + entirely on-stack with no syscall and no malloc. */ +enum { DL_SCRATCH_BUFFER_INLINE_SIZE = 256 }; + +enum dl_scratch_backend +{ + DL_SCRATCH_INLINE, + DL_SCRATCH_MMAP, + DL_SCRATCH_MALLOC, +}; + +struct dl_scratch_buffer +{ + void *data; + size_t size; + enum dl_scratch_backend backend; + char inline_data[DL_SCRATCH_BUFFER_INLINE_SIZE] + __attribute__ ((aligned (__alignof__ (max_align_t)))); +}; + +enum +{ + /* Forbid the malloc backend for spill allocations -- the spill must + come from anonymous mmap so that interposed user malloc cannot + recursively re-enter the loader and invalidate state the caller + is copying from. See _dl_load_cache_lookup. */ + DL_SCRATCH_NO_MALLOC = 1 << 0, +}; + +/* Return a freshly-initialized scratch buffer suitable for use as a + stack-resident initializer. */ +static __always_inline __attribute_warn_unused_result__ +struct dl_scratch_buffer +dl_scratch_buffer_init (void) +{ + return (struct dl_scratch_buffer) { + .data = NULL, + .size = sizeof ((struct dl_scratch_buffer *) 0)->inline_data, + .backend = DL_SCRATCH_INLINE, + }; +} + +extern void _dl_scratch_buffer_allocate (struct dl_scratch_buffer *b, + size_t size, unsigned int flags) + __nonnull ((1)) attribute_hidden; +rtld_hidden_proto (_dl_scratch_buffer_allocate) + +extern void _dl_scratch_buffer_free (struct dl_scratch_buffer *b) + __nonnull ((1)) attribute_hidden; +rtld_hidden_proto (_dl_scratch_buffer_free) + +/* Ensure B->data points to a buffer of at least SIZE bytes; updates + B->size and B->backend accordingly. Intended to be called exactly + once per buffer lifetime (callers know the required size upfront -- + there is no incremental growth model). Raises a loader ENOMEM + error via _dl_signal_error on failure -- does not return NULL. */ +static __always_inline __nonnull ((1)) void +dl_scratch_buffer_allocate (struct dl_scratch_buffer *b, size_t size, + unsigned int flags) +{ + /* First call after dl_scratch_buffer_init: point .data at the + caller's inline area now that its address is in scope. */ + if (__glibc_unlikely (b->data == NULL)) + b->data = b->inline_data; + if (__glibc_likely (size <= b->size)) + return; + _dl_scratch_buffer_allocate (b, size, flags); +} + +/* Release any out-of-line allocation held by B and restore the + inline state. Safe to call multiple times (and on an already-freed + or freshly-initialized buffer). */ +static __always_inline __nonnull ((1)) void +dl_scratch_buffer_free (struct dl_scratch_buffer *b) +{ + if (__glibc_likely (b->backend == DL_SCRATCH_INLINE)) + return; + _dl_scratch_buffer_free (b); +} + +#endif /* dl-scratch-buffer.h */ From patchwork Fri May 15 18:21:48 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Adhemerval Zanella X-Patchwork-Id: 135090 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 7D4C8409FCB6 for ; Fri, 15 May 2026 18:27:29 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 7D4C8409FCB6 Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=lItE1Umb X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-vs1-xe32.google.com (mail-vs1-xe32.google.com [IPv6:2607:f8b0:4864:20::e32]) by sourceware.org (Postfix) with ESMTPS id 03A03409FC96 for ; Fri, 15 May 2026 18:22:35 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 03A03409FC96 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=linaro.org Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=linaro.org ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 03A03409FC96 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:20::e32 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778869355; cv=none; b=IomCnax3xm51opXgbO3soON3R6TMZ/FowYu6bVBrZxi1LJ4uS7gUmcKZ66CGq7FpHjwaCTQiVst12Ulp72boCF8yw9F7w29oo2E9f3b/gKmQwGgpurC++XmjPcirJKQ+O5NVNDpZOpFwl/VudfzbqPYi0xtLQqRRXrevjd/5Jvc= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778869355; c=relaxed/simple; bh=nnwUA+tEf1GYP3Do2k1azLWP17FWjf7V9z5okXmo8yI=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=H8U4Sp/Jhrbm+pGPWaUD42FvyJeArQMtVPZ74YUKGKx6w2AkrCw4Edz2ai4PLR4VndKZZlGtFndE++1cPflBxJmyTa0ZltTa/hVf+wL+H/vi14NqkbCjdhOpaa0ddii7SMzjPZfWsIb7toi3pd5M2hv99SHGOC/wYiGeWyRfY/Y= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=lItE1Umb DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 03A03409FC96 Received: by mail-vs1-xe32.google.com with SMTP id ada2fe7eead31-63329e1c77aso28844137.3 for ; Fri, 15 May 2026 11:22:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1778869354; x=1779474154; darn=sourceware.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=1L+pyXyzxM/oJoDRTIuZToG2yZHo1yDozmoezD24OD8=; b=lItE1UmbwqTsxDkDMy0JD0dr2xEmLcsXYfsC59tG+EZSvQ0VXlsre19P6x+fIpCNIz gp9p6WH9jrY6RgeAmX5YraQmUuJLzFAgW0iNGRWTJ/y4tYpF/DVP3trhdRrwqWVM0aLw 49VYb2HHrpJQv+VsEnIWpFHVv6y9EkxGFLrugmAGlPwMvEPh5M0QbW7qZ7nQpKJEljuU UqGavi/Y6nki1pImflLLSrXcifhm7foQm2OcI8EJvGC4FEKK5OkEaN9dFpPTDRXh6Flk s5/XH+oEHzz2v0oLY/ev8NcS+LZit+Qmqi/uOGWS2lciLgJLd6kAuvPp+9y/Epa+OcKG auHQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778869354; x=1779474154; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=1L+pyXyzxM/oJoDRTIuZToG2yZHo1yDozmoezD24OD8=; b=Ji3hM4d8DxgJyX/2dq148MHQGkCT0enLQWhqnrqHmB3a4rxgvEtQEEJrxqdElNmb7p x0fDWBen7epP/dqBHgYuXiGZQBy98VtRvtZH0z9WyIzv78YTDi2vkc1JPYvLUBIZVIMh Qy+Ayue0C51UrhOnMHi2Ds7pzMQrGOVlHncVq4b+QRjWaYxvnb3mU02gBQuV2hQXxfsU Q4tWquohPjqX7D2yWU+iqf+l5IZ0Xl05DpJpDfurFkbeAVoM+IBAalvYaOFtWhSg3GzB LZjB3riVBcmGjoLlcm0Cbobf7+NJlcc0PKzezYfqA+n1SxNs3dEo2kUWPyGJ22xLB0KA xd/w== X-Gm-Message-State: AOJu0YwPTbfC8q8ywk6N73Ad3XoRLbAfkIK1MmZogyeX+aPmN78u+ont f8dQiQ4i3nFRbEJLO1Ut4g+qKhpsLexkPTQ2udBpu/IXqfMB/7A3UciLLJ4hwKnLOdupnnBraQO rrhn1 X-Gm-Gg: Acq92OHLw9JfR0aLgBTj2GGQbnxxm5kMdbkd91B54S7sJ+X22HydUByCyyczHUsY7j0 xFens3YvFumNNkGRwk+5S8Meb3V/Vq9N6VQ3Tl1oeZVRvmwYpOQ7rjtI+N2rEGOrQUwCQ6i5iwb SzvEnlyWAFeZ3ON1RKeRg+k5uC7emVMJmdJbiehK6rMiMPkMVC4H3WJ21yx3MSDdz7IRmPxlYtN 25WpvXCIBS48HMd3s0nUNt3j2fcott294YuaFL8USmIhJaPBsXQOJsFnYscfGKYtRRfjaJglDmj M7DpF3tK9IUYVWnZrOr+eYd6vKZmFG0eHufM5DdEn4g/zveZwGUQ4Gly/AdZktswJtx50l4OT65 6k4YE2YRavposlin3Sa1pOUmTn7kM2Lf9NfJ7/kZtRIyq2McY2/xsfeR6IOxitWPnuOXXXG6bTZ Aykj0EzeEwBrI3n+MjTX1omHrkfpiHJaWqOA== X-Received: by 2002:a05:6102:f94:b0:631:5ef5:8325 with SMTP id ada2fe7eead31-63a3d93b73cmr3167374137.8.1778869354130; Fri, 15 May 2026 11:22:34 -0700 (PDT) Received: from mandiga.. ([2804:1b3:a7c1:d905:4b30:6c4:9ff3:893c]) by smtp.gmail.com with ESMTPSA id ada2fe7eead31-63ccf18e114sm1314488137.1.2026.05.15.11.22.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 15 May 2026 11:22:32 -0700 (PDT) From: Adhemerval Zanella To: libc-alpha@sourceware.org Cc: "H . J . Lu" Subject: [PATCH v2 2/5] elf: Replace alloca/VLA with dl_scratch_buffer in dl-load.c Date: Fri, 15 May 2026 15:21:48 -0300 Message-ID: <20260515182221.855960-3-adhemerval.zanella@linaro.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260515182221.855960-1-adhemerval.zanella@linaro.org> References: <20260515182221.855960-1-adhemerval.zanella@linaro.org> MIME-Version: 1.0 X-Spam-Status: No, score=-12.2 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_NONE, TXREP shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org is_trusted_path_normalize, print_search_path, and open_path used alloca or a VLA to hold a path scratch buffer sized by user-controlled inputs (an RPATH directory length, or max_dirnamelen + max_capstrlen + namelen). On the worst case that consumes up to PATH_MAX bytes of stack per call, which can overflow a PTHREAD_STACK_MIN-sized stack mid-dlopen when combined with the loader's other on-stack scratch (struct filebuf, etc.). Replace those allocations with dl_scratch_buffer. As a small cleanup, print_search_path now takes the scratch buffer from its caller (open_path's buffer is already large enough -- max_dirnamelen + max_capstrlen + namelen with namelen >= 1 covers the max_dirnamelen + max_capstrlen + 1 print_search_path requires), so LD_DEBUG=libs no longer pays for an extra allocation per open_path invocation. A new test elf/tst-dl-path-buf exercises the relevant paths -- dlopen via DT_RPATH, open_path failure cleanup, dlopen with an over-long name, dlopen from a PTHREAD_STACK_MIN thread. Checked on aarch64-linux-gnu, x86_64-linux-gnu, and i686-linux-gnu. Reviewed-by: H.J. Lu --- elf/Makefile | 13 +++ elf/dl-load.c | 51 +++++++-- elf/tst-dl-path-buf-mod.c | 23 ++++ elf/tst-dl-path-buf.c | 225 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 301 insertions(+), 11 deletions(-) create mode 100644 elf/tst-dl-path-buf-mod.c create mode 100644 elf/tst-dl-path-buf.c diff --git a/elf/Makefile b/elf/Makefile index 67bcd7f072d..66502a92ae8 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -410,6 +410,7 @@ tests += \ tst-debug1 \ tst-deep1 \ tst-dl-is_dso \ + tst-dl-path-buf \ tst-dlclose-lazy \ tst-dlmodcount \ tst-dlmopen-dlerror \ @@ -703,6 +704,7 @@ ifeq (yes,$(build-shared)) ifneq ($(PERL),no) tests-special += \ $(objpfx)noload-mem.out \ + $(objpfx)tst-dl-path-buf-mem.out \ $(objpfx)tst-leaks1-mem.out \ # tests-special endif @@ -912,6 +914,7 @@ modules-names += \ tst-deep1mod1 \ tst-deep1mod2 \ tst-deep1mod3 \ + tst-dl-path-buf-mod \ tst-dl_find_object-mod1 \ tst-dl_find_object-mod2 \ tst-dl_find_object-mod3 \ @@ -2271,6 +2274,16 @@ CFLAGS-tst-dlopenrpath.c += -DPFX=\"$(objpfx)\" LDFLAGS-tst-dlopenrpathmod.so += -Wl,-rpath,\$$ORIGIN/test-subdir $(objpfx)tst-dlopenrpath.out: $(objpfx)firstobj.so +$(objpfx)tst-dl-path-buf: $(objpfx)tst-dl-path-buf-mod.so $(shared-thread-library) +LDFLAGS-tst-dl-path-buf += -Wl,-rpath,\$$ORIGIN/tst-dl-path-buf-subdir +tst-dl-path-buf-TUNABLES = glibc.mem.decorate_maps=1 +tst-dl-path-buf-ENV = MALLOC_TRACE=$(objpfx)tst-dl-path-buf.mtrace \ + LD_PRELOAD=$(common-objpfx)/malloc/libc_malloc_debug.so + +$(objpfx)tst-dl-path-buf-mem.out: $(objpfx)tst-dl-path-buf.out + $(common-objpfx)malloc/mtrace $(objpfx)tst-dl-path-buf.mtrace > $@; \ + $(evaluate-test) + $(objpfx)tst-deep1mod2.so: $(objpfx)tst-deep1mod3.so $(objpfx)tst-deep1: $(objpfx)tst-deep1mod1.so $(objpfx)tst-deep1.out: $(objpfx)tst-deep1mod2.so diff --git a/elf/dl-load.c b/elf/dl-load.c index d20e49d526a..204faffaa0b 100644 --- a/elf/dl-load.c +++ b/elf/dl-load.c @@ -33,6 +33,7 @@ #include #include #include +#include #include "dynamic-link.h" #include "get-dynamic-info.h" @@ -96,7 +97,9 @@ is_trusted_path_normalize (const char *path, size_t len) if (len == 0) return false; - char *npath = (char *) alloca (len + 2); + struct dl_scratch_buffer scratch = dl_scratch_buffer_init (); + dl_scratch_buffer_allocate (&scratch, len + 2, 0); + char *npath = scratch.data; char *wnp = npath; while (*path != '\0') { @@ -131,19 +134,24 @@ is_trusted_path_normalize (const char *path, size_t len) if (wnp == npath || wnp[-1] != '/') *wnp++ = '/'; + bool result = false; const char *trun = system_dirs; for (size_t idx = 0; idx < nsystem_dirs_len; ++idx) { if (wnp - npath >= system_dirs_len[idx] && memcmp (trun, npath, system_dirs_len[idx]) == 0) - /* Found it. */ - return true; + { + /* Found it. */ + result = true; + break; + } trun += system_dirs_len[idx] + 1; } - return false; + dl_scratch_buffer_free (&scratch); + return result; } /* Given a substring starting at INPUT, just after the DST '$' start @@ -1470,12 +1478,17 @@ cannot enable executable stack as shared object requires"); return l; } -/* Print search path. */ +/* Print search path. BUF is a scratch buffer provided by the caller; + it must be large enough to hold the longest "" plus + a trailing NUL byte -- i.e. at least + max_dirnamelen + max_capstrlen + 1 bytes. open_path's path buffer + (max_dirnamelen + max_capstrlen + namelen, namelen >= 1) is reused + here so that enabling LD_DEBUG=libs does not require an extra mmap + per call. */ static void print_search_path (struct r_search_path_elem **list, - const char *what, const char *name) + const char *what, const char *name, char *buf) { - char buf[max_dirnamelen + max_capstrlen]; int first = 1; _dl_debug_printf (" search path="); @@ -1725,7 +1738,6 @@ open_path (const char *name, size_t namelen, int mode, bool *found_other_class) { struct r_search_path_elem **dirs = sps->dirs; - char *buf; int fd = -1; const char *current_what = NULL; int any = 0; @@ -1735,7 +1747,18 @@ open_path (const char *name, size_t namelen, int mode, given on the command line when rtld is run directly. */ return -1; - buf = alloca (max_dirnamelen + max_capstrlen + namelen); + /* The scratch buffer below is sized to satisfy both this function's + candidate-path construction (max_dirnamelen + max_capstrlen + namelen) + and print_search_path's buffer precondition + (max_dirnamelen + max_capstrlen + 1). An empty NAME would under-size the + buffer for the latter and would also produce a meaningless lookup (the + loader rejects empty names well before reaching here). */ + assert (namelen >= 1); + + size_t bufsize = max_dirnamelen + max_capstrlen + namelen; + struct dl_scratch_buffer scratch = dl_scratch_buffer_init (); + dl_scratch_buffer_allocate (&scratch, bufsize, 0); + char *buf = scratch.data; do { struct r_search_path_elem *this_dir = *dirs; @@ -1750,7 +1773,7 @@ open_path (const char *name, size_t namelen, int mode, && current_what != this_dir->what) { current_what = this_dir->what; - print_search_path (dirs, current_what, this_dir->where); + print_search_path (dirs, current_what, this_dir->where, buf); } edp = (char *) __mempcpy (buf, this_dir->dirname, this_dir->dirnamelen); @@ -1838,6 +1861,7 @@ open_path (const char *name, size_t namelen, int mode, if (*realname != NULL) { memcpy (*realname, buf, buflen); + dl_scratch_buffer_free (&scratch); return fd; } else @@ -1845,6 +1869,7 @@ open_path (const char *name, size_t namelen, int mode, /* No memory for the name, we certainly won't be able to load and link it. */ __close_nocancel (fd); + dl_scratch_buffer_free (&scratch); return -1; } } @@ -1854,7 +1879,10 @@ open_path (const char *name, size_t namelen, int mode, directory (for instance, if the component is a existing file meaning essentially that the pathname is invalid - ENOTDIR). */ if (here_any && errno != ENOENT && errno != EACCES && errno != ENOTDIR) - return -1; + { + dl_scratch_buffer_free (&scratch); + return -1; + } /* Remember whether we found anything. */ any |= here_any; @@ -1875,6 +1903,7 @@ open_path (const char *name, size_t namelen, int mode, sps->dirs = (void *) -1; } + dl_scratch_buffer_free (&scratch); return -1; } diff --git a/elf/tst-dl-path-buf-mod.c b/elf/tst-dl-path-buf-mod.c new file mode 100644 index 00000000000..5642ea985a8 --- /dev/null +++ b/elf/tst-dl-path-buf-mod.c @@ -0,0 +1,23 @@ +/* Trivial DSO used by tst-dl-path-buf. + 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 + . */ + +unsigned int +tst_dl_path_buf_mod_value (void) +{ + return 0xaabbccddu; +} diff --git a/elf/tst-dl-path-buf.c b/elf/tst-dl-path-buf.c new file mode 100644 index 00000000000..af7cc29d546 --- /dev/null +++ b/elf/tst-dl-path-buf.c @@ -0,0 +1,225 @@ +/* Exercise the mmap-backed path scratch buffer in elf/dl-load.c. + 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 + . */ + +/* open_path()'s scratch buffer comes from dl_scratch_buffer, which uses + anonymous mmap while __minimal_malloc is active (during loader startup) + and libc malloc afterwards. Mappings from the mmap backend are tagged + with the VMA name " glibc: loader scratch". This test exercises the + relevant code paths -- search via DT_RPATH, open_path failure cleanup, + dlopen with an over-long name, dlopen from a minimal-stack thread, + and per-backend leak checks -- to verify each path properly + alloc/frees the scratch buffer. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifndef PATH_MAX +# define PATH_MAX 1024 +#endif + +/* Must match LDFLAGS-tst-dl-path-buf in elf/Makefile. The test binary's + DT_RPATH resolves to $ORIGIN of the binary plus this subdirectory. */ +#define MOD_SUBDIR "tst-dl-path-buf-subdir" +#define MOD_NAME "tst-dl-path-buf-mod.so" + +/* Tag installed by _dl_scratch_buffer_allocate via __set_vma_name. Mappings + in /proc/self/maps annotated with this string belong to a live scratch + buffer; after a successful dlopen/dlclose cycle there must be zero of + them. */ +#define SCRATCH_VMA_TAG "[anon: glibc: loader scratch]" + +/* Open MOD_NAME via DT_RPATH. Returns the handle; the caller closes it. */ +static void +dlopen_module (void) +{ + void *h = xdlopen (MOD_NAME, RTLD_NOW | RTLD_LOCAL); + unsigned int (*fn) (void) = xdlsym (h, "tst_dl_path_buf_mod_value"); + TEST_COMPARE (fn (), 0xaabbccddu); + xdlclose (h); +} + +/* Subtest 1: basic dlopen/dlclose via DT_RPATH search. */ +static void +test_basic (void) +{ + dlopen_module (); +} + +/* Subtest 2: non-existent name: open_path is exercised on every search list + (DT_RPATH then cache then __rtld_search_dirs), failing each time. Each + failure path must release its scratch buffer. */ +static void +test_nonexistent (void) +{ + void *h = dlopen ("tst-dl-path-buf-does-not-exist.so", + RTLD_NOW | RTLD_LOCAL); + TEST_VERIFY (h == NULL); +} + +/* Subtest 3: a name whose resolved length far exceeds PATH_MAX cannot refer + to a real file: open_path will allocate a large scratch buffer, build + candidate paths, and have every open() return ENAMETOOLONG. dlopen must + therefore fail cleanly (and without leaking the scratch buffer on the + failure paths). */ +static void +test_overlong_name (void) +{ + char *huge = xmalloc (PATH_MAX + 64); + memset (huge, 'a', PATH_MAX + 32); + memcpy (huge + PATH_MAX, ".so", 4); + + void *h = dlopen (huge, RTLD_NOW | RTLD_LOCAL); + TEST_VERIFY (h == NULL); + + free (huge); +} + +/* Count anonymous mappings in /proc/self/maps annotated with SCRATCH_VMA_TAG. + Used by the mmap-backend leak subtest below. */ +static unsigned int +count_scratch_mappings (void) +{ + FILE *f = xfopen ("/proc/self/maps", "r"); + unsigned int n = 0; + char *line = NULL; + size_t line_len = 0; + while (xgetline (&line, &line_len, f)) + if (strstr (line, SCRATCH_VMA_TAG) != NULL) + ++n; + free (line); + xfclose (f); + return n; +} + +/* Subtest 4a (mmap backend). dl_scratch_buffer's mmap backend is used while + __rtld_malloc_is_complete returns false -- that window covers the entire + loader-startup phase, during which the loader resolves the test binary's + DT_NEEDED dependencies via open_path() (and so allocates and frees scratch + buffers). */ +static void +test_no_leak_mmap (void) +{ + if (!support_set_vma_name_supported ()) + { + printf ("info: skipping mmap-backend leak subtest:" + " kernel does not support PR_SET_VMA_ANON_NAME\n"); + return; + } + + unsigned int residual = count_scratch_mappings (); + if (residual != 0) + FAIL_EXIT1 ("%u leaked loader scratch mapping(s) survived loader" + " startup -- _dl_scratch_buffer_free's mmap backend" + " is broken", residual); +} + +/* Subtest 4b (malloc backend). Once libc malloc is active, + dl_scratch_buffer_allocate routes through malloc and the mapping VMA tag is + no longer used. Drive enough dlopen success+failure cycles to exercise + every path in dl_scratch_buffer_free. */ +static void +test_no_leak_malloc (void) +{ + mtrace (); + + enum { iterations = 10 }; + for (unsigned int i = 0; i < iterations; ++i) + { + dlopen_module (); + void *nh = dlopen ("tst-dl-path-buf-does-not-exist.so", + RTLD_NOW | RTLD_LOCAL); + TEST_VERIFY (nh == NULL); + } +} + +/* Subtest 5. Run the success path from a PTHREAD_STACK_MIN thread. */ +static void * +minstack_thread (void *closure) +{ + dlopen_module (); + void *nh = dlopen ("tst-dl-path-buf-does-not-exist.so", + RTLD_NOW | RTLD_LOCAL); + TEST_VERIFY (nh == NULL); + return NULL; +} + +static void +test_minstack (void) +{ + size_t stacksize = +#ifdef PTHREAD_STACK_MIN + PTHREAD_STACK_MIN; +#else + support_small_thread_stack_size (); +#endif + + 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); +} + +static int +do_test (void) +{ + support_need_proc ("/proc/self/maps is read for the leak subtest."); + + char *subdir = xasprintf ("%s/elf/" MOD_SUBDIR, support_objdir_root); + xmkdirp (subdir, 0777); + add_temp_file (subdir); + + char *src = xasprintf ("%s/elf/" MOD_NAME, support_objdir_root); + char *dst = xasprintf ("%s/" MOD_NAME, subdir); + support_copy_file (src, dst); + add_temp_file (dst); + + /* Check the mmap backend's startup behavior first, before any + subtest can perturb /proc/self/maps with its own allocations. */ + test_no_leak_mmap (); + + test_basic (); + test_nonexistent (); + test_overlong_name (); + test_no_leak_malloc (); + test_minstack (); + + free (src); + free (dst); + free (subdir); + return 0; +} + +#include From patchwork Fri May 15 18:21:49 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Adhemerval Zanella X-Patchwork-Id: 135084 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id DF5884BA2E16 for ; Fri, 15 May 2026 18:24:00 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org DF5884BA2E16 Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=vrq0FRqp X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-ua1-x931.google.com (mail-ua1-x931.google.com [IPv6:2607:f8b0:4864:20::931]) by sourceware.org (Postfix) with ESMTPS id 3369C409FC90 for ; Fri, 15 May 2026 18:22:37 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 3369C409FC90 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=linaro.org Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=linaro.org ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 3369C409FC90 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:20::931 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778869357; cv=none; b=QA9QHBV27VHvTrailFhY2uG+Yc5BEYjA0d7mn7brRUO2RbMfTsmifov+d4TSBK5asFlf+57iXQ9dPreQAa9zB3RJapyyRC9LK1sSmVHWEgABjtV2ndgMPcug82fhf1YvisP2u89H393d5WmC3ee9cD4RhlwGxh5XA77c6KMZN7M= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778869357; c=relaxed/simple; bh=7RNc+juq7uP1w2O0D5/KbPy6a4dn87yMWlKQ9DDRmUs=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=E1xZLNuup/EqTuqTmEzUKs7srNGYuCDYqLA1kh9bfKboSoNcfo7Jb1JuPYuUdLcoPeekZgX1cHtgyfOUF5YkvCaHlZfHLPh4OFAkSAXYI5r6YCwfXQreoV7iXCyZf2p9gXwGy+QQs1qIFTIFwlE/u/VMVCT8hBs0j81LBnpQYoQ= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=vrq0FRqp DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 3369C409FC90 Received: by mail-ua1-x931.google.com with SMTP id a1e0cc1a2514c-95ce7b777ccso730659241.1 for ; Fri, 15 May 2026 11:22:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1778869356; x=1779474156; darn=sourceware.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=OZ0gSon8RMZYZIhwbdSWcE+Gd2wJ6K3vAoBaaDNjpfs=; b=vrq0FRqpFV+64rNkL62jWjjQppQ8nJQgKl/YcO4oEFAwYKwT0k/YnbzNf/3ko9QAE0 3DoZZSFKvPvAYihqG7vGI7kSN7oqjFsgkAvlwtI23IMxjdaYEq9Byxxg4HpyRFL5hS26 ag5hQ35gdMOGtpEGZLvVHh2Sv8uvZ4vixpIsHk2M9l0S5P3I6khO5HQjSquKhIr68Kum wa67xv/NDM/A37Adzuw/IPcpEUD3JvqvOi6Fv1KHakk+M332tTBYMoVWJBv80+suM9jj gh1K0JzxHuwtMAwdfLH2Q2SikaVZVtky9hNgtRegJI4Fksql3QIO2n5ZSaCgBV2zU9pS s38g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778869356; x=1779474156; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=OZ0gSon8RMZYZIhwbdSWcE+Gd2wJ6K3vAoBaaDNjpfs=; b=P7khNkjdVPpLC6lQ18gL95g3dhzd+nSRIL1YKI4vi0LnF4hQ7CwOgzOp4aq5yZKnne +D66HvpNfQpgUH24SZ7nu7i2hMiKOyjuiVmk6G5d4aZ3xJK3iPa2hgwSnu/JLLtppAAK Izr+0EFyZQiiL1tYnxspV2krW80ET2xa9K9HsnkgF7ArCI5I6ogA8sejmTD0UD2wNTlT UtShs0778HdnYUiEHJKN8vSTCcc8iAPGcV1T+zCsQ8oLJ1tHL6tsYIBUWmPVpRKR/vRP 234PEuR3pOlPO/bk4a8oGJq3xN+XuRCVP10GI5HMfCtn8txP3MzEgixZlWKMJUQaTfj8 pomQ== X-Gm-Message-State: AOJu0YwdYlbq6BrSJh6jutbLVr+feuMBYXpKOjc1nZNMfIT/0EDQWobf vVP4N5FQ0P6JBYzznijKX/zFgh/BKTqOPoEFcFHRXv2ef9DqIZEauomC5UOHtUeSyXDeWNdpGSD Pq5rm X-Gm-Gg: Acq92OFG2g9IVLMT/nF7ytAVj0xg3OjczULOGJ9PhHqz4XUw8IFEuuLXAcKV7KKvL8I 2sXkFbsMeFIs186bRhIJ1Ui5i6tCK5g2ByniT1hhHqPPsDRdoz9wxy5j89Z8DcOZNFuSFSZB06P Ze8U98mN160agDUMs4APFeEZv5Ca6e43W8WxS6j8YteVKOtXWWzp4l8Jkls8En5CMK7sq2ZRBjn dmMKtc9bAiIj0B3NM7njkhy+cNMEFAK8GXl1vnfCfGbcMmmdUSMlX/T+t9YZZN3IkD6cg4xlwqc pBdQcLrir+Y3fR/rVgO4QNsbQELcaE3zoqezrBExrWyjDXRthZVDqB90NdNUIFun191QuoqnPp+ WZ/EBDNIq5LSR0NX6yJ+F4BUGNtQIhdl8fByjNh8ukjSeK28JLAZfLpCP+1JjI3+KVRt5G0Rh/1 PKl52C9D+1GRpsw3iXTwrBVbXpKxObIrERtg== X-Received: by 2002:a05:6102:598a:b0:634:420a:ab25 with SMTP id ada2fe7eead31-63a24673de2mr2545463137.9.1778869356013; Fri, 15 May 2026 11:22:36 -0700 (PDT) Received: from mandiga.. ([2804:1b3:a7c1:d905:4b30:6c4:9ff3:893c]) by smtp.gmail.com with ESMTPSA id ada2fe7eead31-63ccf18e114sm1314488137.1.2026.05.15.11.22.34 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 15 May 2026 11:22:35 -0700 (PDT) From: Adhemerval Zanella To: libc-alpha@sourceware.org Cc: "H . J . Lu" Subject: [PATCH v2 3/5] elf: Replace alloca with dl_scratch_buffer in _dl_load_cache_lookup Date: Fri, 15 May 2026 15:21:49 -0300 Message-ID: <20260515182221.855960-4-adhemerval.zanella@linaro.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260515182221.855960-1-adhemerval.zanella@linaro.org> References: <20260515182221.855960-1-adhemerval.zanella@linaro.org> MIME-Version: 1.0 X-Spam-Status: No, score=-12.3 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_NONE, TXREP shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org 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 | 179 +++++++++++++++++++++++++++++++++++ 3 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 elf/tst-dl-cache-long-path.c diff --git a/elf/Makefile b/elf/Makefile index 66502a92ae8..239d146566d 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 @@ -2866,6 +2867,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 #include #include +#include #include #include <_itoa.h> #include @@ -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..f956fd0837b --- /dev/null +++ b/elf/tst-dl-cache-long-path.c @@ -0,0 +1,179 @@ +/* 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 + . */ + +/* 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 = +#ifdef PTHREAD_STACK_MIN + PTHREAD_STACK_MIN; +#else + support_small_thread_stack_size (); +#endif + 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 From patchwork Fri May 15 18:21:50 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Adhemerval Zanella X-Patchwork-Id: 135088 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id ABA64409FC96 for ; Fri, 15 May 2026 18:26:41 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org ABA64409FC96 Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=GUiuHcDf X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-ua1-x92c.google.com (mail-ua1-x92c.google.com [IPv6:2607:f8b0:4864:20::92c]) by sourceware.org (Postfix) with ESMTPS id E069D409FCAA for ; Fri, 15 May 2026 18:22:38 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org E069D409FCAA Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=linaro.org Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=linaro.org ARC-Filter: OpenARC Filter v1.0.0 sourceware.org E069D409FCAA Authentication-Results: sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:20::92c ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778869359; cv=none; b=SENNZ0Vso9iA1Vva3/lSJetU5eH5Yvs7ev8RD5+rpxzddyUHt5ig/xVm7eDgtDMZ0yQ8otyr3SDTmTnjdiQDpkdDg89S6/yerUQN28TI7hLyRwCIWA+lhl+76SJpXzGI+0mAQcCR3ax0sqIgo6vfx5Yc6CLzT9YnKuXwvNUPumo= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778869359; c=relaxed/simple; bh=ksKZi5C4NXhWV0v0Gbgpk2Jps3D9VDAtEmoo5i5QGm4=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=IPfJxCwuON7xHFzuRvhjJupFkmm/Q0pSO0L/5xYTfSXrO4mCoYN83JYIv1xk5YB5GLARF8sG+mAt9/zIHHAUCfA9ThgiKSee53OQYgO0v2aIQJHHe5P7vSCQU/Meq/DZ5mPKJ0nq9MHXJ6ZvZePS4uK34UjoJ3HtNLhluZFQCIo= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=GUiuHcDf DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org E069D409FCAA Received: by mail-ua1-x92c.google.com with SMTP id a1e0cc1a2514c-9568bae58f7so6340241.3 for ; Fri, 15 May 2026 11:22:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1778869358; x=1779474158; darn=sourceware.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=3k1h9wDnVi+ZCBgNXuT9P3OFZqN+xbDMkYwmoB+vWAE=; b=GUiuHcDfuehJhSfljWiW21WNVU/gS9gS24oDEtccjzuzo8fQzUa91WW8r+2Ai8ib6o CvO5SiYlLhGN6ay/HcESuhG0npPLfelHyQjs6Cxzoecckee1U+5q7QRpPENuG9d1wLi7 DDBzSuxTPSvr7zYOe3tXBvjIOm/hOZ/NYXNDH5XM91GfpLuXa3u6tJHdIQaucTpCzfF2 vdEVsTNOyBP/TaFtmVSju8hUfsA6WZPRXmTFNvSFjfNroVJKnYEvJcAH8eDccSj9K4qA eNWLQemLAj01/734b36DMWjh+gKFOjOlJHurC8emfK/afgnGkLAriYX0KaRLZutrtgLh RnZA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778869358; x=1779474158; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=3k1h9wDnVi+ZCBgNXuT9P3OFZqN+xbDMkYwmoB+vWAE=; b=SVGWVetZoDkh92OgeprthEWWUXRMsfBbgYY0vnbpwQKNtNe3TyDOOpw0Fi50r0izzn b25Z/jl6CkyJa+eB99mYItR3qGBBSKXfThobPSsNgPZQOe4ERIjMloMF2CvKqNk5hiUH +ta8/BfHWVnPNoZCBs6aXvWiHKLp97Aid8B1/E2DEVwN9cZyXZI95WOi+o0F0hjLPS8C VIcws7rj4VAzIHdDFTnxyR0r/agIBylwUJf7PlHsKMN46UBppjgpKPMPOQigwwMTKMkJ sxr3z4eoxNEIyD6ClEHhpVMNEqYh+p/+3j8cCoLST2DTei7yZaLNWLIq/yqFNsHhbk1k W/Tw== X-Gm-Message-State: AOJu0Yw3N4ZoerGG8w/rXXzJTOIQ9uT17eqH8J4IJKyGBin21eJ7UNCB 4rm3EqkskRw4V7wsoz8rOIx1CvTvyaPZHd4zwfJY4/tSY5ryzyvY2MR6ncSh9erxtebc3Avi39d vXk9i X-Gm-Gg: Acq92OHEQoiqPHT544LPU6Hg1GSJiFUL1RQ2QGuRne32LoI07CzWxC+vHDEEXeSTWKI L8C7T/ynjlUm9h3UAWKzrDMlSpawG7P60Fly23N3D9D7ipAJBMjgfULTkof2U9pC70pWcjZFanI hKAA4RpkYU6LVlkzg7E+bWEJXI0JTFXN9BN5wOwJLy+zEElYagNaqE0o1OaVwaYhvcyMNoywTzK YLacxi+JeDnWAhvPDFSWi1PohKTH/C9a4Z36QHim4tMW8C6koTkCLhyx0+0F9vSqQIlzWgDOtA2 IhERqMeH9MAEgnOyX/gDo4fLOItilMeAdxiOoyNW/dtUkmjOnizWTr0MHlafFejzmGH4iKPe4Dh FRIeFGMlS5CGDpTwQh+voD0IpfPG+GOqW1GEop0pMfTFZ1p/MYTGjBkaAu/MwGU4i9nOXcMzuUp uD6/qrLNu5RvwMgdpoMwn1RZuKopv1+J6nSB4h/gJkBOCD X-Received: by 2002:a05:6102:50a9:b0:631:8665:350c with SMTP id ada2fe7eead31-63a3f997351mr3222624137.25.1778869357926; Fri, 15 May 2026 11:22:37 -0700 (PDT) Received: from mandiga.. ([2804:1b3:a7c1:d905:4b30:6c4:9ff3:893c]) by smtp.gmail.com with ESMTPSA id ada2fe7eead31-63ccf18e114sm1314488137.1.2026.05.15.11.22.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 15 May 2026 11:22:37 -0700 (PDT) From: Adhemerval Zanella To: libc-alpha@sourceware.org Cc: "H . J . Lu" Subject: [PATCH v2 4/5] elf: Use dl_scratch_buffer for DST expansion in _dl_map_object_deps Date: Fri, 15 May 2026 15:21:50 -0300 Message-ID: <20260515182221.855960-5-adhemerval.zanella@linaro.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260515182221.855960-1-adhemerval.zanella@linaro.org> References: <20260515182221.855960-1-adhemerval.zanella@linaro.org> MIME-Version: 1.0 X-Spam-Status: No, score=-12.3 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, PROLO_LEO1, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_NONE, TXREP shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org The expand_dst macro in _dl_map_object_deps performs an unbounded alloca via DL_DST_REQUIRED, which scales with the link map's l_origin length plus the count of dynamic-string tokens in the input string. When a DT_NEEDED entry carries several DSTs and the link map sits in a deep directory, the resulting allocation grows to several kilobytes -- enough to overflow a PTHREAD_STACK_MIN thread that calls dlopen. Convert the macro to a static function that draws from a caller- owned dl_scratch_buffer, so oversized expansions land on the heap (or anonymous mmap during early startup) instead of the stack. The scratch buffer is reused across DT_NEEDED, DT_AUXILIARY, and DT_FILTER entries of the same map and freed once dependency expansion completes. A new regression test, tst-dst-needed-minstack, builds a wrapper library that inherits a five-DST SONAME from a leaf module, deploys it under a deep temporary directory, and dlopens it from a PTHREAD_STACK_MIN thread. Without the fix the dlopen overflows the thread stack and crashes; with the fix the dlopen returns cleanly (with or without a successful load). Checked on aarch64-linux-gnu, x86_64-linux-gnu, and i686-linux-gnu. --- elf/Makefile | 9 +++ elf/dl-deps.c | 114 ++++++++++++++++++------------ elf/tst-dst-needed-leaf-mod.c | 20 ++++++ elf/tst-dst-needed-minstack.c | 127 ++++++++++++++++++++++++++++++++++ elf/tst-dst-needed-wrap-mod.c | 21 ++++++ 5 files changed, 245 insertions(+), 46 deletions(-) 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 diff --git a/elf/Makefile b/elf/Makefile index 239d146566d..443e29493c2 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -434,6 +434,7 @@ tests += \ tst-dlopenfail-2 \ tst-dlopenrpath \ tst-dlsym-error \ + tst-dst-needed-minstack \ tst-filterobj \ tst-filterobj-dlopen \ tst-glibc-hwcaps \ @@ -946,6 +947,8 @@ modules-names += \ 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 \ @@ -2871,6 +2874,12 @@ $(objpfx)tst-ldconfig-ld_so_conf-update.out: $(objpfx)tst-ldconfig-ld-mod.so $(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 diff --git a/elf/dl-deps.c b/elf/dl-deps.c index 4c363180ce7..446163b55c8 100644 --- a/elf/dl-deps.c +++ b/elf/dl-deps.c @@ -30,6 +30,7 @@ #include #include +#include /* Whether an shared object references one or more auxiliary objects is signaled by the AUXTAG entry in l_info. */ @@ -80,47 +81,35 @@ struct list }; -/* 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) @@ -224,12 +213,26 @@ _dl_map_object_deps (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) @@ -267,9 +270,25 @@ _dl_map_object_deps (struct link_map *map, { 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; @@ -285,6 +304,9 @@ _dl_map_object_deps (struct link_map *map, 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) diff --git a/elf/tst-dst-needed-leaf-mod.c b/elf/tst-dst-needed-leaf-mod.c new file mode 100644 index 00000000000..30d756be399 --- /dev/null +++ b/elf/tst-dst-needed-leaf-mod.c @@ -0,0 +1,20 @@ +/* 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 + . */ + +int tst_dst_needed_leaf_dummy; diff --git a/elf/tst-dst-needed-minstack.c b/elf/tst-dst-needed-minstack.c new file mode 100644 index 00000000000..2bb84df7a47 --- /dev/null +++ b/elf/tst-dst-needed-minstack.c @@ -0,0 +1,127 @@ +/* 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 + . */ + +/* 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 // 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 = +#ifdef PTHREAD_STACK_MIN + PTHREAD_STACK_MIN; +#else + support_small_thread_stack_size (); +#endif + + 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 diff --git a/elf/tst-dst-needed-wrap-mod.c b/elf/tst-dst-needed-wrap-mod.c new file mode 100644 index 00000000000..d6b95d8b119 --- /dev/null +++ b/elf/tst-dst-needed-wrap-mod.c @@ -0,0 +1,21 @@ +/* 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 + . */ + +int tst_dst_needed_wrap_dummy; From patchwork Fri May 15 18:21:51 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Adhemerval Zanella X-Patchwork-Id: 135086 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 00DCF409FCAA for ; Fri, 15 May 2026 18:24:12 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 00DCF409FCAA Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=fpHa8Gme X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-vs1-xe30.google.com (mail-vs1-xe30.google.com [IPv6:2607:f8b0:4864:20::e30]) by sourceware.org (Postfix) with ESMTPS id 7326240A1FA7 for ; Fri, 15 May 2026 18:22:42 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 7326240A1FA7 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=linaro.org Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=linaro.org ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 7326240A1FA7 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:20::e30 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778869362; cv=none; b=N5E8XQuSJX3bHF7g57QDQZM1NbKS878AyhbQxslnWU3HBlx1Nirxf5JXsrjnYcfzwvqy4Q5AWklDBLMYwkIDoL0MtdMTH+3mWKMQdSjMg+vHzB85iF0hvstys1MXoMYetx7pGGHADKvwgGovmwqgKy/5Iy50ZOEjx7alfFWRhb8= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778869362; c=relaxed/simple; bh=7X3g6vR6DjCpH1Nlhpw9x32vXfgN2Ly8VwJy6/8bQns=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=yBASzzugCYy7BBPfro9hfMZaXnvfKb4zD2wpTbKowLyU3+ZXbYeR5Mx9uNsleVTjMBsv3v26X4j0ofjZPbdQGgFgQY0JEgElJhzjh+AufB1a1iNHCFoNJfB3y5zDzexB0vBr17G3CzNVeXAFGZQ9oBnO1Wr8uOUNh/LeWTVmtRs= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=fpHa8Gme DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 7326240A1FA7 Received: by mail-vs1-xe30.google.com with SMTP id ada2fe7eead31-6312af106e3so86414137.0 for ; Fri, 15 May 2026 11:22:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1778869362; x=1779474162; darn=sourceware.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=xNuIXhakJjkRJLYCIynZYzDsFpILcx53GDfmQXBDAw4=; b=fpHa8Gmeob5tdmwYjSkGpxswTfYEVjdSqPWQP3I2YYidGuqVbDcEUlCbrAl6MF0nPA mE/Wu2r/2CEW22fuakziCuQ0SrM1oABp2gZtzYtQeQ0fcuHHSiPKhRfeNCX+fLRO5BBZ ZC231uqVvxUlUT3I3M8HN/RsKaOeVa0JzNNEBX4ooAV2rXJe+rw78tu3w/XRB43SFjcO oSw/bYn5CKEZZUKqfTKql4FJ3tRXMQRinBMKMeidPeu70UNRJTbhKfOg4dvoZ1YqU8iC OGMDa9JKavv3pLcurpw0imrNrzjHvM5cU5MtqmxfSxxlmouoxf7PJyxM+jvENFrOYck+ odFw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778869362; x=1779474162; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=xNuIXhakJjkRJLYCIynZYzDsFpILcx53GDfmQXBDAw4=; b=Ez/CFY3/Gknt/ZOUz6AlI8HjL+ZJVgACygqsuGgsEkzBirTA8qlyrOpgPJuALKNjM3 nWfLuSpO0qgiFDC1jK+pswUuo+a+QfTfLPP1yI7ZxH9qZb7bKbrC45aLlP23iJ9yZta2 CV4b3XqwEG+iODUA/zeSE7hxmG7Jf/k0k3iY/yQrLYUvsMGlVW9sfQ3+latiZGvOlZwp frU1DFJSM+p+6fjvkUk/CRdOrNOesoFG9O/9sN8EXGh/NEInjdaS77UPdMyn4v2S1cSb k/iM0q5zSkVvHOFR3m+uvW8AaGv9OTsUPqFZHNhLYA4ZCoEle9Er7MCWL03xgmdv6hw6 B3Tw== X-Gm-Message-State: AOJu0Yw4im7mZTmY3PJMEm0afmqvxxnXJ0tRaRTRG2oo0m/N0H2Tpv4U c4FJUGYrDh1VsNcF78CkCicNqmMllUHf0EJR/yufsjumnUM1sLEA25L8tpK+RkfOgxSEdOynPja GDV++ X-Gm-Gg: Acq92OFkP16hv88eIkbCl5XUZw9je/yJRMmmlHgSbVwpUb5Q9ddDT9EvDV44Yv3z/Vo 01Jy/pHBd+RAPxpmdccEQFn1Tf2RXOQCHWdomIcq67V8Qp5MRd2qgIzWA/87LaTkCxYUJIuHCZ1 YJAe0jZ3JeJitKnUEXKW237Jsow1BEc+BUzqSSJlC+VnycVwlkMkvdl1OewrXQqTaPrd2y+3DA5 jtgDFi8GL48O1SVQuaC6PmGKVwnSMPxtnHakrDEo9u0sC15Za8vDLo9dgk1+JmEl+O8JB7zJQyF PnmBNuDC0V1T+YjgllJ0yRGlQ68e8f9COYbaDpgvRgh8PvluDK/rxX6157D13YPZ+hx9czygqNT 7mMv7a2kK9iuf0wFMtu/AuWFVsgtd30quhpcEr0cjzxbgRKQT4OnZHMlWyZL6YH8dmgUrWrdFM0 J5F98ybTe2P9291oK2ljnAtilebdEqLOHz9Q== X-Received: by 2002:a05:6102:4428:b0:632:3bd5:d572 with SMTP id ada2fe7eead31-63a3fb965a0mr3119988137.27.1778869361700; Fri, 15 May 2026 11:22:41 -0700 (PDT) Received: from mandiga.. ([2804:1b3:a7c1:d905:4b30:6c4:9ff3:893c]) by smtp.gmail.com with ESMTPSA id ada2fe7eead31-63ccf18e114sm1314488137.1.2026.05.15.11.22.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 15 May 2026 11:22:41 -0700 (PDT) From: Adhemerval Zanella To: libc-alpha@sourceware.org Cc: "H . J . Lu" Subject: [PATCH v2 5/5] elf: Use dl_scratch_buffer for LD_LIBRARY_PATH copy in _dl_init_paths Date: Fri, 15 May 2026 15:21:51 -0300 Message-ID: <20260515182221.855960-6-adhemerval.zanella@linaro.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260515182221.855960-1-adhemerval.zanella@linaro.org> References: <20260515182221.855960-1-adhemerval.zanella@linaro.org> MIME-Version: 1.0 X-Spam-Status: No, score=-12.4 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_NONE, TXREP shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org _dl_init_paths used strdupa to make a mutable copy of LD_LIBRARY_PATH for fillin_rpath to tokenize. The env block is attacker-controllable and Linux allows individual variables up to MAX_ARG_STRLEN (32 * PAGE_SIZE = 128 KB), so the strdupa can push tens of KB onto the loader's startup stack on top of the env block that already sits on the initial stack. With a reduced RLIMIT_STACK the doubled copy overflows before main () is reached. Replace the strdupa with a dl_scratch_buffer: short paths stay in the 256-byte inline area, longer ones spill to anonymous mmap (malloc is not yet available during _dl_init_paths). Two follow-on changes make the new scratch lifetime safe against _dl_signal_error: * Count entries directly off the const LD_LIBRARY_PATH and allocate __rtld_env_path_list.dirs *before* the scratch is live. That way the larger of the two heap allocations the loader controls signals its OOM with no scratch to leak. * Convert fillin_rpath to return bool instead of calling _dl_signal_error internally on per-entry malloc failure. Its only caller in the LLP path now frees the scratch first and then signals the error from a clean state. decompose_rpath, the other caller, is updated symmetrically. This also fixes a pre-existing leak in fillin_rpath's OOM path, where the to_free heap copy from expand_dynamic_string_token was not released before the _dl_signal_error. Checked on x86_64-linux-gnu, aarch64-linux-gnu, and i686-linux-gnu. Reviewed-by: H.J. Lu --- elf/Makefile | 3 ++ elf/dl-load.c | 53 ++++++++++++++----- elf/tst-dl-llp-stack.c | 112 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+), 12 deletions(-) create mode 100644 elf/tst-dl-llp-stack.c diff --git a/elf/Makefile b/elf/Makefile index 443e29493c2..2a90eaaeb30 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -411,6 +411,7 @@ tests += \ tst-debug1 \ tst-deep1 \ tst-dl-is_dso \ + tst-dl-llp-stack \ tst-dl-path-buf \ tst-dlclose-lazy \ tst-dlmodcount \ @@ -2789,6 +2790,8 @@ generated += tst-bz26577-mod.so $(objpfx)tst-bz26577-minstack: $(shared-thread-library) $(objpfx)tst-bz26577-minstack.out: $(objpfx)tst-bz26577-mod.so +tst-dl-llp-stack-ARGS = -- $(host-test-program-cmd) + $(objpfx)tst-unwind-ctor: $(objpfx)tst-unwind-ctor-lib.so LDLIBS-tst-unwind-ctor += $(libunwind) LDFLAGS-tst-unwind-ctor-lib.so = -Wl,--unresolved-symbols=ignore-all diff --git a/elf/dl-load.c b/elf/dl-load.c index 204faffaa0b..95404adae94 100644 --- a/elf/dl-load.c +++ b/elf/dl-load.c @@ -422,7 +422,10 @@ struct r_search_path_struct __rtld_search_dirs attribute_relro; static size_t max_dirnamelen; -static struct r_search_path_elem ** +/* Tokenize RPATH (in place) and populate RESULT with one entry per non-empty + directory. Returns false if a per-entry allocation fails, leaving the + caller responsible for signaling any error. */ +static bool fillin_rpath (char *rpath, struct r_search_path_elem **result, const char *sep, const char *what, const char *where, struct link_map *l) { @@ -490,8 +493,10 @@ fillin_rpath (char *rpath, struct r_search_path_elem **result, const char *sep, malloc (sizeof (*dirp) + ncapstr * sizeof (enum r_dir_status) + where_len + len + 1); if (dirp == NULL) - _dl_signal_error (ENOMEM, NULL, NULL, - N_("cannot create cache for search path")); + { + free (to_free); + return false; + } dirp->dirname = ((char *) dirp + sizeof (*dirp) + ncapstr * sizeof (enum r_dir_status)); @@ -528,7 +533,7 @@ fillin_rpath (char *rpath, struct r_search_path_elem **result, const char *sep, /* Terminate the array. */ result[nelems] = NULL; - return result; + return true; } @@ -609,7 +614,13 @@ decompose_rpath (struct r_search_path_struct *sps, _dl_signal_error (ENOMEM, NULL, NULL, errstring); } - fillin_rpath (copy, result, ":", what, where, l); + if (!fillin_rpath (copy, result, ":", what, where, l)) + { + free (copy); + free (result); + errstring = N_("cannot create cache for search path"); + goto signal_error; + } /* Free the copied RPATH string. `fillin_rpath' make own copies if necessary. */ @@ -782,12 +793,13 @@ _dl_init_paths (const char *llp, const char *source, if (llp != NULL && *llp != '\0') { - char *llp_tmp = strdupa (llp); - - /* Decompose the LD_LIBRARY_PATH contents. First determine how many - elements it has. */ + /* Count entries directly off the const LD_LIBRARY_PATH so the + search-path dirs array can be allocated before the scratch buffer is + live; that way an OOM on either of the two heap allocations the + loader controls (the dirs array or the per-entry malloc inside + fillin_rpath) is signalled after the scratch has been released. */ size_t nllp = 1; - for (const char *cp = llp_tmp; *cp != '\0'; ++cp) + for (const char *cp = llp; *cp != '\0'; ++cp) if (*cp == ':' || *cp == ';') ++nllp; @@ -799,8 +811,25 @@ _dl_init_paths (const char *llp, const char *source, goto signal_error; } - (void) fillin_rpath (llp_tmp, __rtld_env_path_list.dirs, ":;", - source, NULL, l); + /* fillin_rpath needs a mutable copy because __strsep punches NULs + into it as it tokenizes. */ + size_t llp_len = strlen (llp); + struct dl_scratch_buffer scratch = dl_scratch_buffer_init (); + dl_scratch_buffer_allocate (&scratch, llp_len + 1, 0); + char *llp_tmp = memcpy (scratch.data, llp, llp_len + 1); + + bool ok = fillin_rpath (llp_tmp, __rtld_env_path_list.dirs, ":;", + source, NULL, l); + + dl_scratch_buffer_free (&scratch); + + if (!ok) + { + free (__rtld_env_path_list.dirs); + __rtld_env_path_list.dirs = NULL; + errstring = N_("cannot create cache for search path"); + goto signal_error; + } if (__rtld_env_path_list.dirs[0] == NULL) { diff --git a/elf/tst-dl-llp-stack.c b/elf/tst-dl-llp-stack.c new file mode 100644 index 00000000000..c6f7b85c1e3 --- /dev/null +++ b/elf/tst-dl-llp-stack.c @@ -0,0 +1,112 @@ +/* Test that a long LD_LIBRARY_PATH does not overflow loader startup + stack. + 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 + . */ + +/* This test reduces RLIMIT_STACK to a value that just covers regular + loader startup, builds an envp with only a long LD_LIBRARY_PATH + (16 pages of synthetic entries). + + The envp size and stack rlimit are both expressed in pages so the + test scales naturally across PAGE_SIZE values. On exec the kernel + places argv+envp at the top of the initial stack and rounds the + reservation to page granularity, which would otherwise eat the + loader's entire budget on architectures with large pages (e.g. + 64 KB-page aarch64). */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +static int restart; +#define CMDLINE_OPTIONS \ + { "restart", no_argument, &restart, 1 }, + +enum { llp_entries = 16 }; + +/* Build a "LD_LIBRARY_PATH=" string with llp_entries synthetic + colon-separated entries, each one page long, so the total payload + is 16 * PAGE_SIZE bytes. */ +static char * +build_llp_env (size_t entry_len) +{ + size_t junk_len = (size_t) llp_entries * (1 + entry_len); + char *env = xmalloc (strlen ("LD_LIBRARY_PATH=") + junk_len + 1); + char *p = stpcpy (env, "LD_LIBRARY_PATH="); + for (int i = 0; i < llp_entries; i++) + { + *p++ = ':'; + *p++ = '/'; + memset (p, 'd', entry_len - 1); + p += entry_len - 1; + } + *p = '\0'; + return env; +} + +static int +do_test (int argc, char *argv[]) +{ + if (restart) + return 0; + + TEST_VERIFY_EXIT (argc == 2 || argc == 5); + const char *test_binary = argv[argc - 1]; + + /* Scale envp and stack rlimit with PAGE_SIZE to handle kernels with + different page sizes. */ + long page_size = sysconf (_SC_PAGESIZE); + TEST_VERIFY_EXIT (page_size > 0); + size_t llp_entry_len = (size_t) page_size - 1; + size_t stack_limit = (size_t) 24 * page_size; + + char *llp_env = build_llp_env (llp_entry_len); + char *envp[] = { llp_env, NULL }; + + /* Reduce the stack rlimit; the posix_spawn'd child inherits it. */ + struct rlimit rl_save, rl_small; + TEST_VERIFY_EXIT (getrlimit (RLIMIT_STACK, &rl_save) == 0); + rl_small.rlim_cur = (rlim_t) stack_limit; + rl_small.rlim_max = rl_save.rlim_max; + TEST_VERIFY_EXIT (setrlimit (RLIMIT_STACK, &rl_small) == 0); + + char *child_argv[] = { + (char *) test_binary, + (char *) "--direct", + (char *) "--restart", + NULL + }; + struct support_capture_subprocess proc + = support_capture_subprogram (test_binary, child_argv, envp); + + TEST_VERIFY_EXIT (setrlimit (RLIMIT_STACK, &rl_save) == 0); + + support_capture_subprocess_check (&proc, "tst-dl-llp-stack", 0, + sc_allow_none); + support_capture_subprocess_free (&proc); + free (llp_env); + return 0; +} + +#define TEST_FUNCTION_ARGV do_test +#include