From patchwork Tue May 19 13:23:51 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Adhemerval Zanella Netto X-Patchwork-Id: 135268 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 C3BD24BB5892 for ; Tue, 19 May 2026 13:26:44 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org C3BD24BB5892 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=wMTk7Lut X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-vk1-xa36.google.com (mail-vk1-xa36.google.com [IPv6:2607:f8b0:4864:20::a36]) by sourceware.org (Postfix) with ESMTPS id 65E754BB3B89 for ; Tue, 19 May 2026 13:26:04 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 65E754BB3B89 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 65E754BB3B89 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:20::a36 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1779197164; cv=none; b=rdRTswYGQ9CrATopVdHne2XXtdyd6x7C7hMVJz15YKZiOt/6AoQifDgSnT66zmHSbKzg4pZoeYOXDCQF3D+L+hO65yDr4ktLNY+RbQO+9YwzWRfjIKWe/4JsuP9weOZVZ2IUADyeMO1Y0vbfqbtPd7gM58MMkaZqoJdAWFnM210= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1779197164; c=relaxed/simple; bh=qykXhlGq3y3sOikviLSYYqTAv936vjSfjlk+JEXNnU8=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=QKZ0YZ8ru/qag/rMCoRcJ9z6vFWH0Jju86srwsPnOHlIC7nVkaoMFbe+Vg4UtfIVJMcLHNbwOoxvajGbJDuIn0jrWgPO339kRPTuAb9mbk9gZKJ6OP1JW1210oNSVcMvn/dWGaMff7seZNQArYfaNW//wYS4CIg1GOdrA3NOFr0= 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=wMTk7Lut DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 65E754BB3B89 Received: by mail-vk1-xa36.google.com with SMTP id 71dfb90a1353d-5774680983dso1085257e0c.0 for ; Tue, 19 May 2026 06:26:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1779197164; x=1779801964; 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=H1scpyj4hldvcuH+ICV0U2RHqJs6EeFVbhieayMKNC0=; b=wMTk7Lut6IlCBdeucdmRf3+BJirbGgmjVtG9HxATBcdi1YiyqOZ6Dh7xX1yXc5c8rm iquLp4NAXWvuATTGycmQWNqqBBGxOgUn/0fv2zlNXZrSRGLS4cbYaWGdYIpZ2Xs3K0CP k+LU9qFR/hfZkBfXdVwWkn3kuuQnYA/eYu4DmKnUjAyUz1wbRCr7VjoiqPAiwqckFRja 69Zo9DckhihVzCF3MchWcGdvaUmydUsh557pOZbWYkguWUy43siMM2etNtnfSKcMv8oU sNUl4jK2Hn0mz3k+XhrwsltusyoS9k9Ln6IqxmtfAtd3oltGZHdiKwa0kDic53BLaAcG Jl3A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779197164; x=1779801964; 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=H1scpyj4hldvcuH+ICV0U2RHqJs6EeFVbhieayMKNC0=; b=dZgKJVcyTLVQZrazXfyJ1uaLVYbUDBHJxKYCO7uFICh4hoVISknhAGZ2Wx+AOHIvjs n5eQT+nZcCylEmwtdKcXQUflxhIrZBtGRU3XQCLDWb29mWLVGjSdng1/RJs2090TPgMg exiuQwgmSlb36uZxcL78tMD1pQpClS80aTztKqdVxz0hVxb8nS34lCAnIq+lbPbCmflW 6B7swQDZXXIFSnHbomN5j68aDxWffobihziutEmIYi2vwp8XsCQi6XoRvPTqcDtDbW8W OzsuU3jdWmdZZyAfZOd48IS9E2fI3cEfD0LjlRgpVKONrQSY4G9+AE4tjOB0wvWRV/Wp csEQ== X-Gm-Message-State: AOJu0YwUsoUpsRKNCxhKmvjkxfs9FA1kIv6pVRaqIZWw+Cu9UQNvdfqx LrxQr78wedtLqKiYOfWhJDi7S7pQAKySNiagIKU9N+7mGHIUUbctb4JSd4QAfOQS2fg5otitcUD yNT5V X-Gm-Gg: Acq92OH0VoOHyTH96rcbHFC2EodjSxs1kU9yxqttJwQR/IjqktQMSjYydZZY1YjygcE DUYSYKSpU6BfC+IfQJOkNoGdSnfL9vW99av5BH4szckqfDm0Cd/0F54T68P6m2+86YNrJCiP6B+ mOAuZdBsbZX8VQQlQn0cD7/z7j3MSPtffrlU8HKWEgI6LGQGjTnIs0iJCsHnJnVFFdqVSO0e46I lqXzG9oBu0eXMVhQUYx+HzvNjgnk1PFy4igO0YlTMQIDWowCZwiJHxCfhjZ8G0r78qBbpV4ZaaN HSlYuIN0klFx+PRfuX0mfhfemwI4gkFcOpXjsaUWZ1+5AAorS1gCCwVJngaAvTncU3RcI9agOmm 94ThOj2DU55Cu+3qHTGa5XWPbkveVe4L7wzhwWfbRoQ68BZ0Gcd0r4FaEj1FoeZrkTMM2JZjEm9 +KEVbWQC/31aNFXwW0mEalzQccENVcQ6O3yNI= X-Received: by 2002:a05:6122:9006:b0:56b:8023:b89e with SMTP id 71dfb90a1353d-5760be8e77cmr10284386e0c.6.1779197163558; Tue, 19 May 2026 06:26:03 -0700 (PDT) Received: from mandiga.. ([2804:1b3:a7c1:d905:f9d7:1c9a:81ca:c01c]) by smtp.gmail.com with ESMTPSA id 71dfb90a1353d-5760faa5645sm8182177e0c.14.2026.05.19.06.26.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 19 May 2026 06:26:03 -0700 (PDT) From: Adhemerval Zanella To: libc-alpha@sourceware.org Cc: "H . J . Lu" Subject: [PATCH v3 1/6] elf: Add dl_scratch_buffer, a loader-side scratch buffer Date: Tue, 19 May 2026 10:23:51 -0300 Message-ID: <20260519132555.3024936-2-adhemerval.zanella@linaro.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260519132555.3024936-1-adhemerval.zanella@linaro.org> References: <20260519132555.3024936-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 0f888887be7..a946d5806f3 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 Tue May 19 13:23:52 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Adhemerval Zanella Netto X-Patchwork-Id: 135271 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 28F014BB58A0 for ; Tue, 19 May 2026 13:27:22 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 28F014BB58A0 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=vZsALI/G X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-vk1-xa31.google.com (mail-vk1-xa31.google.com [IPv6:2607:f8b0:4864:20::a31]) by sourceware.org (Postfix) with ESMTPS id 5217E4BB24F8 for ; Tue, 19 May 2026 13:26:06 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 5217E4BB24F8 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 5217E4BB24F8 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:20::a31 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1779197166; cv=none; b=DcL2nFOfS3P/xjEe4GtgzY0KE2PA363CrqZirENQyWdROjNRJ24OxlUx8TtytxqDcYi9bGTGiyaKOdjxHUe6NUdG4AAQn5Frw5mk9pq759WMrRHKWZinS2ohXLUSjDAm6DBymeG3YhNAmQoFkfSG/ar9Y8V2Dk6oEq0dx9f+z7c= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1779197166; c=relaxed/simple; bh=gnAthDh0tfUMOkKluakhbD2GZ6RNCYKHm9O//XUMPro=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=qdDklT1OBYJu9mf1otXmf/WPEly42a9tq+yqKaHSpWp5zhjI4LEELe2KR0TFALjzTbGkrBnhOB51Sm5UvgCLUPHbS245V2z+BRsCjQBSbPJlt5GKmkd559JKtyTBBDmWUiX6I9T9g40jMrD97LyBpyMMtaxtiQN6B4m1Bu4zKUs= 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=vZsALI/G DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 5217E4BB24F8 Received: by mail-vk1-xa31.google.com with SMTP id 71dfb90a1353d-57524e52a3dso3005284e0c.2 for ; Tue, 19 May 2026 06:26:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1779197166; x=1779801966; 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=dUdh/m84hN9N16ggPpuIzPBgYeabrb5YZbdMad2tWwE=; b=vZsALI/GKz7TKPhOSkWmYRklXLSeOZ5e8is1yswUrSjy2d0Q6dS9WGA0UXdYLV8rf8 y2W8LlQs7U3JqxBwFTFIg9wzjKYtqZAbvIsKHaGD/BCXOL6zbtfj6OM2h8ElOb88AtWL nbDVZHHgzbVZJ/bTeGsuTFMRRJYfEFruTsJUeGvVH4Zk6OgvSXqflNQopERL7RQsArGK rR8d7ZLD8bvkz1/lmWnb9AwjSJJCFWqLK2Kz9eAIV1u0gGpaxxEdxZuOaIplORBTmDhp HoUovgOaOSVBCJto9FW45YSwAi53OuqNwx45tqujYHwTR/UgbqgYfHTlfiZ8OGtPPDq7 q2CQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779197166; x=1779801966; 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=dUdh/m84hN9N16ggPpuIzPBgYeabrb5YZbdMad2tWwE=; b=fTSsf5MgJZHrKI78L1szC8kwvx5omAHpKf13Ieba3wZkQFtEklj8xUsjytCL5LPilv iJalTfUD26pkd65sYl0MN+ndq0VRKCpFqeKjs10KrAaK7cIQCn1yn+tNPMmUI4BGtoe1 Ob7N/iYdnCQbBa9lYojVuJrAGsKWap5cxqXxQF7yFtcfnArtBuGvd4bkJAq4/UfeJ0l3 qr9zi2lajQ2Y9dCGdT75DryFCitsF1zyNozxS40gtr1IAZ2W7rvuR4RSCaOS98Yb7zOQ 3em5ep8Lju+TegmP137Bj4dAY52PSsuG2M89GCl33t0jhsuM+AwUk9DBAgjv5hEq7tHY 2xUQ== X-Gm-Message-State: AOJu0YxJPU35yvVzw9UWsYbxBVdqAvSsH6vrzO22WqLhC1UbVQGMfFSQ n0eSyKlwdc8Ut2MnpSdmJZs7zmADBo1lBG4arOXCm6JzUeF0UwzPJUXLfVe+GdTwgERW80Nvxmi UOj/X X-Gm-Gg: Acq92OFvyXNBuBJH+3OoNO36toSNSvR/EyNmfk7U6PrK8cTjt6X35dgk9QYiawk14eN cDaeWHIaLWgi0JRi36B/w9QZOZTNZHC9lTsT8VhoJ8sOUANiPKQRnGZC38iuN22LglhpGByktEK 7/M57Aey1JIvqX1fzEBWqDUAFCNy6IqxNNPcY3TcJeaDqSqnqvDacvY3vVQw6X8D9ItQNx3owm3 /w5+DI3OJKYcM5PP3/lR5PE7t+oYLpXHZr5QFvwqaMubpqChAznNkwohC7LqsHlJGxu4cZ6d6a8 5VQbPhHjNcVDscmpzNPGsnT8D1Bf73y7uskZk/I4U3pIX1sd5UHthXNM0vYfN6iqHuWMPXUMLU4 /gB733qHTX7L4yZq792S6g+i7QTfHPYgFrqzA3kD1b6dzteGNuHHaZp3d+1NKSnK3p3f+DS/4xh IHPSz45WOnqb65rI2UN80oXgv/DEJewry7RTA= X-Received: by 2002:a05:6122:4d0d:b0:56e:e80c:bb25 with SMTP id 71dfb90a1353d-5760c0a673cmr11036238e0c.13.1779197165609; Tue, 19 May 2026 06:26:05 -0700 (PDT) Received: from mandiga.. ([2804:1b3:a7c1:d905:f9d7:1c9a:81ca:c01c]) by smtp.gmail.com with ESMTPSA id 71dfb90a1353d-5760faa5645sm8182177e0c.14.2026.05.19.06.26.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 19 May 2026 06:26:04 -0700 (PDT) From: Adhemerval Zanella To: libc-alpha@sourceware.org Cc: "H . J . Lu" Subject: [PATCH v3 2/6] support: Add use_stack_min option to support_small_thread_stack_size Date: Tue, 19 May 2026 10:23:52 -0300 Message-ID: <20260519132555.3024936-3-adhemerval.zanella@linaro.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260519132555.3024936-1-adhemerval.zanella@linaro.org> References: <20260519132555.3024936-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, 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 It allows it to return PTHREAD_STACK_MIN if defined. Checked on x86_64-linux-gnu and with a build for i686-gnu. Suggested-by: H.J. Lu --- elf/tst-bz26577-minstack.c | 7 +------ elf/tst-decorate-maps.c | 2 +- nptl/tst-guard1.c | 4 ++-- stdlib/tst-canon-bz26341.c | 2 +- support/support_set_small_thread_stack_size.c | 13 +++++++++---- support/support_small_stack_thread_attribute.c | 2 +- support/xthread.h | 12 +++++++----- sysdeps/unix/sysv/linux/tst-sem_getvalue-affinity.c | 2 +- .../unix/sysv/linux/tst-skeleton-thread-affinity.c | 2 +- 9 files changed, 24 insertions(+), 22 deletions(-) diff --git a/elf/tst-bz26577-minstack.c b/elf/tst-bz26577-minstack.c index 741b307392a..9d5ecd85939 100644 --- a/elf/tst-bz26577-minstack.c +++ b/elf/tst-bz26577-minstack.c @@ -51,12 +51,7 @@ static int do_test (void) { char *path = xasprintf ("%s/elf/tst-bz26577-mod.so", support_objdir_root); - size_t stacksize = -#ifdef PTHREAD_STACK_MIN - PTHREAD_STACK_MIN; -#else - support_small_thread_stack_size (); -#endif + size_t stacksize = support_small_thread_stack_size (true); pthread_attr_t attr; xpthread_attr_init (&attr); diff --git a/elf/tst-decorate-maps.c b/elf/tst-decorate-maps.c index fa3637af899..b6af31c7274 100644 --- a/elf/tst-decorate-maps.c +++ b/elf/tst-decorate-maps.c @@ -128,7 +128,7 @@ do_test_threads (bool set_guard) { pthread_attr_t attr; xpthread_attr_init (&attr); - size_t stacksize = support_small_thread_stack_size (); + size_t stacksize = support_small_thread_stack_size (false); void *stack = xmmap (0, stacksize, PROT_READ | PROT_WRITE, diff --git a/nptl/tst-guard1.c b/nptl/tst-guard1.c index b97ad23de41..3486cba9e4d 100644 --- a/nptl/tst-guard1.c +++ b/nptl/tst-guard1.c @@ -176,7 +176,7 @@ do_test1 (void *closure) pthread_attr_t attr; xpthread_attr_init (&attr); - size_t stacksize = support_small_thread_stack_size (); + size_t stacksize = support_small_thread_stack_size (false); void *stack = xmmap (0, stacksize, PROT_READ | PROT_WRITE, @@ -201,7 +201,7 @@ do_test2 (void *closure) pthread_attr_t attr; xpthread_attr_init (&attr); - size_t stacksize = support_small_thread_stack_size (); + size_t stacksize = support_small_thread_stack_size (false); void *stack = xmmap (0, stacksize, PROT_READ | PROT_WRITE, diff --git a/stdlib/tst-canon-bz26341.c b/stdlib/tst-canon-bz26341.c index 4860818a4e7..e94c924ac1c 100644 --- a/stdlib/tst-canon-bz26341.c +++ b/stdlib/tst-canon-bz26341.c @@ -81,7 +81,7 @@ do_realpath (void *arg) const size_t syscall_usage = 1 * PATH_MAX + 1024; const size_t realpath_usage = 2 * PATH_MAX + 1024; const size_t thread_usage = 1 * PATH_MAX + 1024; - size_t stack_size = support_small_thread_stack_size () + size_t stack_size = support_small_thread_stack_size (false) - syscall_usage - realpath_usage - thread_usage; char stack[stack_size]; char *resolved = stack + stack_size - thread_usage + 1024; diff --git a/support/support_set_small_thread_stack_size.c b/support/support_set_small_thread_stack_size.c index 6c2cd4f92ba..51ec05b3229 100644 --- a/support/support_set_small_thread_stack_size.c +++ b/support/support_set_small_thread_stack_size.c @@ -21,13 +21,16 @@ #include size_t -support_small_thread_stack_size (void) +support_small_thread_stack_size (bool use_stack_min) { /* Some architectures have too small values for PTHREAD_STACK_MIN which cannot be used for creating threads. Ensure that the stack - size is at least 256 KiB. */ + size is at least 256 KiB if USE_STACK_MIN is false. */ size_t stack_size = 256 * 1024; #ifdef PTHREAD_STACK_MIN + if (use_stack_min) + return PTHREAD_STACK_MIN; + if (stack_size < PTHREAD_STACK_MIN) stack_size = PTHREAD_STACK_MIN; #endif @@ -35,7 +38,9 @@ support_small_thread_stack_size (void) } void -support_set_small_thread_stack_size (pthread_attr_t *attr) +support_set_small_thread_stack_size (pthread_attr_t *attr, + bool use_stack_min) { - xpthread_attr_setstacksize (attr, support_small_thread_stack_size ()); + xpthread_attr_setstacksize + (attr, support_small_thread_stack_size (use_stack_min)); } diff --git a/support/support_small_stack_thread_attribute.c b/support/support_small_stack_thread_attribute.c index dd97e421464..229deabe9e4 100644 --- a/support/support_small_stack_thread_attribute.c +++ b/support/support_small_stack_thread_attribute.c @@ -24,7 +24,7 @@ allocate (void *closure) { pthread_attr_t *result = malloc (sizeof (*result)); xpthread_attr_init (result); - support_set_small_thread_stack_size (result); + support_set_small_thread_stack_size (result, false); return result; } diff --git a/support/xthread.h b/support/xthread.h index d585bd0c3c6..3bdc6907aab 100644 --- a/support/xthread.h +++ b/support/xthread.h @@ -90,11 +90,13 @@ void xpthread_attr_setguardsize (pthread_attr_t *attr, void xpthread_kill (pthread_t thr, int signo); -/* Return the stack size used on support_set_small_thread_stack_size. */ -size_t support_small_thread_stack_size (void); -/* Set the stack size in ATTR to a small value, but still large enough - to cover most internal glibc stack usage. */ -void support_set_small_thread_stack_size (pthread_attr_t *attr); +/* Return the stack size used on support_set_small_thread_stack_size, + or PTHREAD_STACK_MIN (if defined) is USE_STACK_MIN is set. */ +size_t support_small_thread_stack_size (bool use_stack_min); +/* Set the stack size in ATTR to a small value. Use PTHREAD_STACK_MIN (if + defined) or a large enough to cover most internal glibc stack usage. */ +void support_set_small_thread_stack_size (pthread_attr_t *attr, + bool use_stack_min); /* Return a pointer to a thread attribute which requests a small stack. The caller must not free this pointer. */ diff --git a/sysdeps/unix/sysv/linux/tst-sem_getvalue-affinity.c b/sysdeps/unix/sysv/linux/tst-sem_getvalue-affinity.c index c4c430edebd..f30e0bbed51 100644 --- a/sysdeps/unix/sysv/linux/tst-sem_getvalue-affinity.c +++ b/sysdeps/unix/sysv/linux/tst-sem_getvalue-affinity.c @@ -136,7 +136,7 @@ early_test (struct conf *conf) printf ("error: pthread_attr_init failed: %s\n", strerror (ret)); return false; } - support_set_small_thread_stack_size (&attr); + support_set_small_thread_stack_size (&attr, false); /* Spawn a thread pinned to each available CPU. */ for (int cpu = 0; cpu <= conf->last_cpu; ++cpu) diff --git a/sysdeps/unix/sysv/linux/tst-skeleton-thread-affinity.c b/sysdeps/unix/sysv/linux/tst-skeleton-thread-affinity.c index 323b2e0ffc4..4477fde150d 100644 --- a/sysdeps/unix/sysv/linux/tst-skeleton-thread-affinity.c +++ b/sysdeps/unix/sysv/linux/tst-skeleton-thread-affinity.c @@ -208,7 +208,7 @@ early_test (struct conf *conf) printf ("error: pthread_attr_init failed: %s\n", strerror (ret)); return false; } - support_set_small_thread_stack_size (&attr); + support_set_small_thread_stack_size (&attr, false); /* This count assumes that all the threads below are created successfully, and call pthread_barrier_wait(). If any threads From patchwork Tue May 19 13:23:53 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Adhemerval Zanella Netto X-Patchwork-Id: 135269 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 875BB4BB5882 for ; Tue, 19 May 2026 13:26:57 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 875BB4BB5882 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=r6hCo7Z3 X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-vk1-xa2d.google.com (mail-vk1-xa2d.google.com [IPv6:2607:f8b0:4864:20::a2d]) by sourceware.org (Postfix) with ESMTPS id 168DC4B99F42 for ; Tue, 19 May 2026 13:26:09 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 168DC4B99F42 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 168DC4B99F42 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:20::a2d ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1779197169; cv=none; b=H/SOG4/9O+armthh1YehlKM1yFlosMyddikZd81voMiwRiZ6WyyKm9qTA9PK1qRw/RjlmGX9TrRLDhlVcsMW4ofAh5ncNC143p8vqyhrC92cYIKuETCafcE12yAUB34gSLqmDd+4jp5udDLjgZFIkyXpYUalDPDbC1umHNirvtg= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1779197169; c=relaxed/simple; bh=Zz1strx/2BWPft2fUF0SA3T818nRZUhOentZvHPdIaA=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=JQUmFNL71QvmEjEsfVJsCmmjuIpMfpue4NZv9d6fZ/v9zKBdtI90FogZV+qy7Nh63otDiRg5leOojS5NqLh6qY+tDwNvS/LfV+x+pwZRBRQUTqMuTyAlWYypvmiWs44HastfpsUNL3BIadA4lbVaO/T+GxX50HZcuKQQyaQcd9M= 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=r6hCo7Z3 DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 168DC4B99F42 Received: by mail-vk1-xa2d.google.com with SMTP id 71dfb90a1353d-57754ec3462so2141828e0c.0 for ; Tue, 19 May 2026 06:26:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1779197168; x=1779801968; 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=t8aY2Ym/4OWD1K/LB4FA4wmQUzLR9UGxn9I1l3LNjgg=; b=r6hCo7Z3TNg5ajL5qTFlGO/yBTVg7qqdRLU1wifMajEhgN1Mgm4uvxhyvQPcFSkfFt IwfYyscN93BfgAkYDp9OkTNTo4Qa7v42ELW5r4mjErW07XuVZIQKJGgu2TljJGhGGRa3 yMeJxUOteBJQNVT8Vn2cmQRP1tGtsfBsw/xvERtYaDbem/JtkoGCPYe58FhEnQYN9pPA fpW/ntaSQBoHBFxXSvWrsyfxRx3TvAf8kKuDGXmEWx+lmvDMQi//0XFFTbFFmW+tWh1d 2LVMjM291I793C9p7AnqSSW7yTxMnQPYSgg6iTWXSCCsOigqNtfyhJQnh/4KbG6m45+G HHMg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779197168; x=1779801968; 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=t8aY2Ym/4OWD1K/LB4FA4wmQUzLR9UGxn9I1l3LNjgg=; b=mdAHVmZv0ItmVwWlqswSubiLWnus4l+JcA4jp2YfU3USLqIWYAJZ2Qobs0Dk4k5V/g DjaWCYqqi7o+MokZWeJI9AJApA9raVh92i2JKZgBoPjHzu3K1NlOIvS4bLBQtmikIlDB z++zbK+UfMau0dEkGVw0PapHa0gxSWqL5M9/gA7MXyt8941UIaq5ZI78GEGJSMbazU4w J1OgrKKuCNzJXTLA0uiYh1+HCyDoHKoTHYhdsBMiSxiVcA+435Eb+RI6aDM2hR0Crj6j 1JZl5ah76pjYZb1jbFLLGrkUuq7d+4nWpuPCgyf5qxUmg3lq0u4N1S3itCWrg5++LbyB Am8w== X-Gm-Message-State: AOJu0YyFwFMUwqZT5yl4Zg2IY8nEeNKVEGV+rQQWsseZCmNMdgFPqW3/ lgy85EfsIRbwvveE8Uk4/EEJgKKRcdoro6oihkEEA7HRR4l1vmMMYBNKelgMujcX+tW2cujXfGz /608D X-Gm-Gg: Acq92OHXQEq3qwNV0Uw1/RveadZ48QKqmfrfvkcpmmE57ZRYx3S3h3uRbEzC6Zp+wbp gwn5r+sHaIH1q0fRR8Mc/jpW6qWWQSzHc6Py+NXvdSAIaP/HH4YTvlOoSpRdwzfxiTET1UQgSDC /qPHvVmHstrZ6580EyaOxoxjDaCaIGLyJupPisH5Lvl8i4EQUpSlFrc57N+V9IoLXj30hzfuup7 g5Qe0CkmMOx8TFmQw+kZUx8RJTOglJOry5MzJgZDLIA6QcEzwteAzFjPqvXMVT5lSF1yBzHn6QR ohI4M7F+eKmgzhm7wa2QkJzEag9Qnd9RuWPUgNxhB/Od6or8S/sEwa0C+vEySm/+ok3MxKuFoLX kDczYycm/WYTIzWFZ7REy15Q+TUKCfipdPjaj+gFxr51fLOGbcofQc1OS9OoH1aHqIITZwBO60V WolO8g/RmD7gCMB7SEzzbzWiaeJU+5/gD6tCk= X-Received: by 2002:a05:6122:e18c:b0:575:352f:ead0 with SMTP id 71dfb90a1353d-5760be8f79cmr11635058e0c.6.1779197167739; Tue, 19 May 2026 06:26:07 -0700 (PDT) Received: from mandiga.. ([2804:1b3:a7c1:d905:f9d7:1c9a:81ca:c01c]) by smtp.gmail.com with ESMTPSA id 71dfb90a1353d-5760faa5645sm8182177e0c.14.2026.05.19.06.26.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 19 May 2026 06:26:07 -0700 (PDT) From: Adhemerval Zanella To: libc-alpha@sourceware.org Cc: "H . J . Lu" Subject: [PATCH v3 3/6] elf: Replace alloca/VLA with dl_scratch_buffer in dl-load.c Date: Tue, 19 May 2026 10:23:53 -0300 Message-ID: <20260519132555.3024936-4-adhemerval.zanella@linaro.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260519132555.3024936-1-adhemerval.zanella@linaro.org> References: <20260519132555.3024936-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 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 | 220 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 296 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 a946d5806f3..f668dec368e 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 \ @@ -2278,6 +2281,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..487de1fa0b2 --- /dev/null +++ b/elf/tst-dl-path-buf.c @@ -0,0 +1,220 @@ +/* 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 = support_small_thread_stack_size (true); + + pthread_attr_t attr; + xpthread_attr_init (&attr); + xpthread_attr_setstacksize (&attr, stacksize); + pthread_t thr = xpthread_create (&attr, minstack_thread, NULL); + xpthread_join (thr); + xpthread_attr_destroy (&attr); +} + +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 Tue May 19 13:23:54 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Adhemerval Zanella Netto X-Patchwork-Id: 135273 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 ACC584BAD17D for ; Tue, 19 May 2026 13:28:36 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org ACC584BAD17D 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=fSIRSZbb X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-vk1-xa2d.google.com (mail-vk1-xa2d.google.com [IPv6:2607:f8b0:4864:20::a2d]) by sourceware.org (Postfix) with ESMTPS id BF8914BB5883 for ; Tue, 19 May 2026 13:26:10 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org BF8914BB5883 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 BF8914BB5883 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:20::a2d ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1779197170; cv=none; b=aVrYMW+XfiG+1Gen9ty0VYs+HIFQQrJvh6ijOrsnbVc1MgJvhPy0s3jTmjt44xvLL+ojRjYqxQ4UMYX6hOCGl6cmFD/Wk4i4GhX8VhxrJZR4Qa6yWLijMKHRWIpp6krVmfnvUxSREbwPyFu/f7OT3xIUrsZC5dc8zo2wLMN8SmU= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1779197170; c=relaxed/simple; bh=xsdKVvBWIS6wFzy91ZosYcFcrbRMapFII/870j9VAu4=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=my6K0Hfaq7DmGOTSSyQ9elf1tejMhSEOM5GFGTBtV61+D63jekiyWYvcWAd1b6G81b8Qg6gMs3FumYYrdFHW6y+De1K2pQbT6jHzlFzyNNVFxjA9Jt12BJ0sfW5qqhGwOSfMvYPzVz3sW0996lYBIyPtfufn78M5g/FReKuADAE= 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=fSIRSZbb DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org BF8914BB5883 Received: by mail-vk1-xa2d.google.com with SMTP id 71dfb90a1353d-5751136c561so3534968e0c.1 for ; Tue, 19 May 2026 06:26:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1779197170; x=1779801970; 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=R7CmXknnwR3yMhkraOxzefdUONGDrYyW3DDItI7vARM=; b=fSIRSZbbHecNCUo7C3aL36Mmal4N7v23DGyunL5kKWdlh0bSqMYDy4H/z/hisgr9Xx p83OK+pFFIJ4VG2pvvNFB/wAsN7juxLDZWWxQvnhMm1niy7evNBeSx0ycJIhgZeTWr+w k4S/xl23oz3iQ2KgFifrxcgjQ5vwxl9gu+7rvoaFsuLQd/fK89DYDldhYPXBXkDjbS7j eAGBTn9o53EneVlw2SDNLwc5cmleupDDwqGJuZBzG1uAvdxkk7qz5WUbHeEiBlCa+e6w kZR5ZQSTi1E+bf4QBGWQwZVHwEgGezby/ajfLM1eWodfPOtv77HA7To0FQWowVtlxnAj zpSQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779197170; x=1779801970; 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=R7CmXknnwR3yMhkraOxzefdUONGDrYyW3DDItI7vARM=; b=tR5ZpGrjbU5l32h7ULHevhqXI16GR803bpED3tije7SIrRKlKJC3gD1OTmAQ6qwgQV iY6z9s/Y9aNWl3QEKeU+M6tilahBCgMTArtMxGcoaaAKBT8HEQ7Q6WmL01FjKX4gvKVM KFojfNBIQcgYk/oZE1oxwBH78NnzxSFiNiJAwX1rMfq4zyD1KH+QQhHWxnjp8NtmEzJ+ NqxvJFrrljGbTpVQoM5rBVfq7k/5SODPTTOLMzNRnrv4ylAGc9Pd7Ru7S3TGxWAvf3TH MPx7+dgFBVfp3TbLY1knlqPE+dFdLlAodXxm38ZatJHhQKJLRD67MYS1wuqZC6yt6Lcb s45A== X-Gm-Message-State: AOJu0YxlbFtUg8wjs5R5K8cC23alF523fUNgG6gMV6lCE16va1yMFS8v P/GMHmVxZsRM43boavjdEJJjq5sBi92aNCuOWwmlqG2hY8s/xjFAP/07wDBHYtlSngGHUXlJjfy nazkG X-Gm-Gg: Acq92OFZ7ENksVkRRzAI7IGDzdX/n2z0b3UElqALfjcu9l5CvJ9DPbJwp0wYqmDL9/x 5t8JDP4XgGxWc4SBkAk0nFExJzIUfJQivgR5ET1Ad08wsl125JxNk08Kxaa03Nx56+YRXOjRHB8 Fd+jnf4X2HBSBDkPE35miG2Q/q0bXERxLYBH9g0UimAHNISI3P+s3sDig9mXLfOMq6br8yhYtCc 3IeyZsEzofBJcQjkuCfTBieBSXDXqF0+A9780uFheR6ETMJmFv58QpYpPfahQxbg3cAZE9d9vkS 1ajkHA8lFZS1zW7Cw2oUoMiKzAF4af5OQW/MLGWSsMD65LJG28w+qJtT/CQkLMjBs7m3gsCgHmt Lw4PeEa0cAV+M12BPwqBfh+maQdp9oY5uyiZa+6Il38U5na/L6hyi7Z2GOWxK/r0MJsxJMSJ8t3 tYFRDhMg9qWcJsbshU9EZONZfT5n0F6iFg5PM= X-Received: by 2002:a05:6122:340a:b0:575:9f5:ac57 with SMTP id 71dfb90a1353d-5760b80c496mr7710443e0c.4.1779197169588; Tue, 19 May 2026 06:26:09 -0700 (PDT) Received: from mandiga.. ([2804:1b3:a7c1:d905:f9d7:1c9a:81ca:c01c]) by smtp.gmail.com with ESMTPSA id 71dfb90a1353d-5760faa5645sm8182177e0c.14.2026.05.19.06.26.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 19 May 2026 06:26:09 -0700 (PDT) From: Adhemerval Zanella To: libc-alpha@sourceware.org Cc: "H . J . Lu" Subject: [PATCH v3 4/6] elf: Replace alloca with dl_scratch_buffer in _dl_load_cache_lookup Date: Tue, 19 May 2026 10:23:54 -0300 Message-ID: <20260519132555.3024936-5-adhemerval.zanella@linaro.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260519132555.3024936-1-adhemerval.zanella@linaro.org> References: <20260519132555.3024936-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 | 174 +++++++++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+), 5 deletions(-) create mode 100644 elf/tst-dl-cache-long-path.c diff --git a/elf/Makefile b/elf/Makefile index f668dec368e..00d1a558d89 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -293,6 +293,7 @@ CRT-tst-tls1-static-non-pie := $(csu-objpfx)crt1.o tst-tls1-static-non-pie-no-pie = yes tests-container := \ + tst-dl-cache-long-path \ tst-ldconfig-bad-aux-cache \ tst-ldconfig-ld_so_conf-update \ # tests-container @@ -2886,6 +2887,10 @@ LDFLAGS-tst-dlopen-nodelete-reloc-mod17.so = -Wl,--no-as-needed $(objpfx)tst-ldconfig-ld_so_conf-update.out: $(objpfx)tst-ldconfig-ld-mod.so +# Reuses the trivial module already built for tst-dl-path-buf. +$(objpfx)tst-dl-cache-long-path: $(shared-thread-library) +$(objpfx)tst-dl-cache-long-path.out: $(objpfx)tst-dl-path-buf-mod.so + LDFLAGS-tst-filterobj-flt.so = -Wl,--filter=$(objpfx)tst-filterobj-filtee.so $(objpfx)tst-filterobj: $(objpfx)tst-filterobj-flt.so $(objpfx)tst-filterobj.out: $(objpfx)tst-filterobj-filtee.so diff --git a/elf/dl-cache.c b/elf/dl-cache.c index 9458ffae2a6..c1de93f2041 100644 --- a/elf/dl-cache.c +++ b/elf/dl-cache.c @@ -21,6 +21,7 @@ #include #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..e7304934228 --- /dev/null +++ b/elf/tst-dl-cache-long-path.c @@ -0,0 +1,174 @@ +/* Test dlopen through ld.so.cache with a cache entry longer than the + dl_scratch_buffer inline area. + Copyright (C) 2026 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +/* 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 = support_small_thread_stack_size (true); + pthread_attr_t attr; + xpthread_attr_init (&attr); + xpthread_attr_setstacksize (&attr, stacksize); + pthread_t thr = xpthread_create (&attr, minstack_thread, NULL); + xpthread_join (thr); + xpthread_attr_destroy (&attr); + + free (deep_dir); + return 0; +} + +#include From patchwork Tue May 19 13:23:55 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Adhemerval Zanella Netto X-Patchwork-Id: 135270 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 637094BB3BFC for ; Tue, 19 May 2026 13:27:14 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 637094BB3BFC 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=FfcpbHjf X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-vk1-xa2f.google.com (mail-vk1-xa2f.google.com [IPv6:2607:f8b0:4864:20::a2f]) by sourceware.org (Postfix) with ESMTPS id 868FD4BB5889 for ; Tue, 19 May 2026 13:26:12 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 868FD4BB5889 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 868FD4BB5889 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:20::a2f ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1779197172; cv=none; b=vZ8fgE7IrczqRCw/9djtbT6l08qPRxUvAEXn4dBGy5knEjwgJK63i1zsZZ4KPI7y3nnR1T9pNh+rJgQltOnLHwNEO9tcC/SxOySUFRnfUr/6yDF4lBW3z9cIG0c55y5zsSRl4Z0HJ1PijLvS0CfK8BLdTv5JerTbsCF0CnS/iCo= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1779197172; c=relaxed/simple; bh=Q4ax2zUMF73Dpi1ocpxN5qVBNN0Fooi7zsfVDp1WyX0=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=vi972sVoUbEKhaef3LxGRE2pkVhApfEh4qPDe2GQ3qSzz0VnCL2hgAy+usLSnGETZZ+TkcvnH3Lfq5P+lY7IYZx+BHVri59TLz9DEieVbbW8HukDyVAZKGBXxl2yGfbHVIbJ+s5QlXJ5Lzp1T14uCfaEcHRbPaXIMQR5opXed7g= 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=FfcpbHjf DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 868FD4BB5889 Received: by mail-vk1-xa2f.google.com with SMTP id 71dfb90a1353d-57513733658so1112323e0c.3 for ; Tue, 19 May 2026 06:26:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1779197172; x=1779801972; 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=/ggw9uySRmuKDL3zvjJX+6R+vVK2WBCmJn0RrVYd58I=; b=FfcpbHjfK73hm3c+Hrm0dYtgVVhk10jmI1FMhiYQLxfVdjXhDHUs2x8fLIQp6PkFeu 9svOVQhPI3gDF1f5Q3y0DkkDYwe/rYHMEMeDuP+bY7DwY7LnlYfxFQ/R2dQ1QC5E+POQ ZHx5tefh+9tVpn9ZoirmOzXptRHQ/w0MgJsbhQAoZtBDVaXZDC8yuN3x8gqSmcJL3PjD ouofr6WP7qjKs1N8nyp14+29smDxuP2qBb4IxWqlzudKSf5KNG+PKmzPYUMLOB0d3UIs T48O8Fj26MCIFeDZE4fl4QgC4T8rmuPNqp/mX7inYXVzACbnqyqvUjpNHCjZ81DcZM2Y jNLg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779197172; x=1779801972; 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=/ggw9uySRmuKDL3zvjJX+6R+vVK2WBCmJn0RrVYd58I=; b=MLBKwM2DBAUQJGHWrAh+x4JUktrdfipY8ROXbYnoCT1g4flYCj1Uth8mTFRCSfkYXy XRi4An+V8igEgAPhWZiRU3MzhWm4uwwQ7Gp33Zp9CsTZBuSx8yV9EgJVWCtUBoRY61sC 9epC46TJ99kCykOJPS507ZCFh3NmJRCt3B2jzbEdJ5wGXx9Vdxb/FHMeP31jPbQNbE1i S57ffJ44F7POsUdb7aNtf+RBklvyfQp3JGq21MKSl9m6dUJzjyZhdzdR6SLUTetUgu7G BuCrQa1Tfnakq8vhwfi91J6er1ileyUIiPdM9GfcaGFluNP6MtW3dRueFUC4nq4M4VHD FvAQ== X-Gm-Message-State: AOJu0Yz2YUg3VPvfnfKue41Wjcmt6Vt8jXZ/BYN1kp058socY4YyfYz5 6d7lsjBfT+4dFHVsaBmqzloLljvZSbZRqB6xx1PV3TZHMoTc68nwC82r64w6/FxyNd19x0EXmDF lAYOk X-Gm-Gg: Acq92OFIvVg3WuS8aconfQHUhXG/z/JXbu5g45w8iqdAf1pzNU0HdSEv70xU8Z6AojJ WWTzhBCnffCtQgNC4J2UjuBJ1PEnjN+fA6QDQRAO97zbGmaCXrIEQdGtkgm4RYCTu+tzaVz6Va7 6pJoWpOIdN2gg3/FaRo2X1UpufjLyKgbwtwE1OPXHeTkQURkxtovhNTTLcDduIW2yFGfJ/bCRya ULlm/IzL0Be0w9x7uXhzKryhk1rKjIGjHiMGnlUNHAKmfiura9AMw7uk7wFUYEsphs+xWkMZYhK mhXGG4WRnf9EuKN997QInArebi3ZQmuoKGE8nE32/cvgu5jVoOHt7WG7LdPuedzT8l55wojn1F9 qBm8RT+sa1b/RHn4n6RsYuR/Gyq/iFHhQDUsBH+pY1bI8gqu8UeVK+M2u1y3ZhKpYaQQU8kTb6a iUuH1KBsGMRMJ6nid9JCXgOpSrMm2VaQZ3OeU4BYqt0TFTmg== X-Received: by 2002:a05:6122:242:b0:56d:4511:9367 with SMTP id 71dfb90a1353d-5760bc6fadbmr9308325e0c.0.1779197171467; Tue, 19 May 2026 06:26:11 -0700 (PDT) Received: from mandiga.. ([2804:1b3:a7c1:d905:f9d7:1c9a:81ca:c01c]) by smtp.gmail.com with ESMTPSA id 71dfb90a1353d-5760faa5645sm8182177e0c.14.2026.05.19.06.26.09 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 19 May 2026 06:26:10 -0700 (PDT) From: Adhemerval Zanella To: libc-alpha@sourceware.org Cc: "H . J . Lu" Subject: [PATCH v3 5/6] elf: Use dl_scratch_buffer for DST expansion in _dl_map_object_deps Date: Tue, 19 May 2026 10:23:55 -0300 Message-ID: <20260519132555.3024936-6-adhemerval.zanella@linaro.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260519132555.3024936-1-adhemerval.zanella@linaro.org> References: <20260519132555.3024936-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 | 122 ++++++++++++++++++++++++++++++++++ elf/tst-dst-needed-wrap-mod.c | 21 ++++++ 5 files changed, 240 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 00d1a558d89..5476dd84abe 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 \ @@ -2891,6 +2894,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..d7fb7cd835a --- /dev/null +++ b/elf/tst-dst-needed-minstack.c @@ -0,0 +1,122 @@ +/* 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 = support_small_thread_stack_size (true); + + pthread_attr_t attr; + xpthread_attr_init (&attr); + xpthread_attr_setstacksize (&attr, stacksize); + pthread_t thr = xpthread_create (&attr, minstack_thread, NULL); + xpthread_join (thr); + xpthread_attr_destroy (&attr); + + free (deep_wrap_path); + return 0; +} + +#include 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 Tue May 19 13:23:56 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Adhemerval Zanella Netto X-Patchwork-Id: 135272 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 87A564BB588A for ; Tue, 19 May 2026 13:28:12 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 87A564BB588A 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=JmDNl4go X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-vk1-xa2d.google.com (mail-vk1-xa2d.google.com [IPv6:2607:f8b0:4864:20::a2d]) by sourceware.org (Postfix) with ESMTPS id 83A814BB24F8 for ; Tue, 19 May 2026 13:26:14 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 83A814BB24F8 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 83A814BB24F8 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:20::a2d ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1779197174; cv=none; b=XSKGV/O8COU0AQwgw6kZ3zd9+xqRMII2+g5zHCKUuO079/837iCCcO8Ste7U1AbyNmnZGXgBEXk+8hJRAsZLUhcrBV9p9XAKehGnhH8+Wk2I1rLQ7o9/34ALa0rQb//sDH1kYGR+3npAjptcVKgzBs8K+JYrorVcdxtIqPK9uq8= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1779197174; c=relaxed/simple; bh=EXwCDj6cGOrl350FwPkSH3xttp3EHmjLCSInxiQxw8s=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=ErY8vATBHYDaE9IG5TjanfTF5U8ZGwTGGFSnibjdUvBrYna+QYI6VeG0Ix9OlQYh2kg9Ljfj4ZXA64rU471pmGqKzu5DQsWK/In1j6dV99divtVEoxWCSD/lLHr9lvUmWECLuzf/m32nLoAY9KcEt08GmwolDJV6tM6UnbzLHlw= 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=JmDNl4go DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 83A814BB24F8 Received: by mail-vk1-xa2d.google.com with SMTP id 71dfb90a1353d-57533363201so974388e0c.0 for ; Tue, 19 May 2026 06:26:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1779197173; x=1779801973; 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=7ns507tMg+aRWlxB4lVH1LtkVFURtyVB8NdmNiQ3ZxA=; b=JmDNl4goDK9oqkVCFBhy39zzNdeUALGceyM+IGrwG/6QTSd/Gv+oTbpArAq8+OKh3Q o8osQWWpvO8KqElDHddcwgj9N/eAneOx3b/vuqemoSDvXLXh9NJ7/qXZo4vbW1rL9MuB gNKq8Bh4jv5Jrq1J8BfKio7OixV8QjHwmN4wcMKBLor4OHI6Mj4RDCUzFdvpvoygl/GN mWasvtEtPnNlr43u3VwffyfmEUetNzHzXexYjtz7IDUPm4wBIsAMxYFrtRiXU8Is3PMh wLMiOxFIfvJLTZUSExDr6wRIFreFnTrOcQ5sgtRGqC6vu0GLCqssIloxEcqZQxddPaH9 x5PA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779197173; x=1779801973; 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=7ns507tMg+aRWlxB4lVH1LtkVFURtyVB8NdmNiQ3ZxA=; b=f87Mk/JkDNpHfe59LDvgu6y4NrOqg6izWRruJhDD7a4Tb7SK7bIqALXlMOt/lw8Ni1 MA1ZhYs4cR/mIOlAt6IAx7EHYekR+567g1F78ol1B/j2Blo5lr1c9v8GtMsNnQOkyHaZ KMQCF9Vftkb//4EkwkJequYsn4kx6Hrd9K6jd0CzV52F4aAVZ5/HrEe847HQLr7NkuEr ttmrUsudPQIVXPf1O6rlp8yBiwNYstwkN6p/OEWVdCewCSOlfHDs7pdYgdj6ZK36TvPG YTyN2VCBziRA5q1Ex5CQBYxoE90BbvVUwrNo/8RExCab0HC3DgLqxOMnhI2RauWQ5HTL IO+A== X-Gm-Message-State: AOJu0Ywl5LuqZVENBH8n1fn54lDo9wu/NEqTvuTB3EThrUNYqPGKr3Oj kW/VK7wSEqY4KKcsQn3oPFiEIJ9dNywdcy1/T3NBuvcsAj5SVlf7bbsjd0O0z6n0uxIEo2nCNz1 E+BMj X-Gm-Gg: Acq92OG3fmaVndsxTEw0aj5/+YZFgq+y4YBpYFg0w8gtzJBJxxAg5UnrPWFPprIwIsw 1qCgqp3MYsyLcyPbGbLzvjciAtjcBB30mRoCuPSMCEDJNi6mdery7pt0N5kXsHn4Uxofgj8b2ZF y3B/XBsZtlxXRP901VTfGXfFXLbXlAhB4C3Y3/e3+PVMX7QzAM6rQbNn85SzgDqtgppN5AyW+Fs 11Tpf6GcQD52XbEcfYlrCFkyk+ckGQ3lIfLhXqnWjU7J2yiqjOE89K6Y09N//nbRg1HG0Bg83h6 2op9biCUxwh6b1v2a8aGBN8jIQFCMLpXKQlDmFSRprai1vmN9NS/2z4rOLNAKiinJdqYhss4Nde jCEUjOZTvEA+LbgiiuRHS763/QNf2igKQqPCG0ceABa+GQDz1Kt6ZyQudtwWFgUKDBmlam5N5J0 ysra75CY/eZJHkncwkGVUOsT5OoDTCxF8hVSA= X-Received: by 2002:a05:6122:4690:b0:573:a779:62cf with SMTP id 71dfb90a1353d-5760beaed3bmr8280109e0c.7.1779197173324; Tue, 19 May 2026 06:26:13 -0700 (PDT) Received: from mandiga.. ([2804:1b3:a7c1:d905:f9d7:1c9a:81ca:c01c]) by smtp.gmail.com with ESMTPSA id 71dfb90a1353d-5760faa5645sm8182177e0c.14.2026.05.19.06.26.11 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 19 May 2026 06:26:12 -0700 (PDT) From: Adhemerval Zanella To: libc-alpha@sourceware.org Cc: "H . J . Lu" Subject: [PATCH v3 6/6] elf: Use dl_scratch_buffer for LD_LIBRARY_PATH copy in _dl_init_paths Date: Tue, 19 May 2026 10:23:56 -0300 Message-ID: <20260519132555.3024936-7-adhemerval.zanella@linaro.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260519132555.3024936-1-adhemerval.zanella@linaro.org> References: <20260519132555.3024936-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 | 1 + elf/dl-load.c | 53 +++++++++++++---- elf/tst-dl-llp-stack.c | 129 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 12 deletions(-) create mode 100644 elf/tst-dl-llp-stack.c diff --git a/elf/Makefile b/elf/Makefile index 5476dd84abe..aef13b73ca5 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 \ 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..2fe85acd535 --- /dev/null +++ b/elf/tst-dl-llp-stack.c @@ -0,0 +1,129 @@ +/* Test that a long search 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, then spawns a child whose dynamic-linker search path is + artificially long (16 pages of synthetic colon-separated entries). + + 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 +#include + +static int restart; +#define CMDLINE_OPTIONS \ + { "restart", no_argument, &restart, 1 }, + +enum { llp_entries = 16 }; + +/* Return a newly malloc'd string of the form "PREFIX[:/ddd...]{16}" + where each synthetic entry is ENTRY_LEN bytes long. PREFIX is the + ld.so --library-path value from support_spawn_wrap and supplies the + real build directories so the rtld can still resolve libc. */ +static char * +build_long_library_path (const char *prefix, size_t entry_len) +{ + size_t prefix_len = strlen (prefix); + size_t junk_len = (size_t) llp_entries * (1 + entry_len); + char *out = xmalloc (prefix_len + junk_len + 1); + char *p = stpcpy (out, prefix); + for (int i = 0; i < llp_entries; i++) + { + *p++ = ':'; + *p++ = '/'; + memset (p, 'd', entry_len - 1); + p += entry_len - 1; + } + *p = '\0'; + return out; +} + +static int +do_test (void) +{ + if (restart) + return 0; + + char *binary = xasprintf ("%s/elf/tst-dl-llp-stack", support_objdir_root); + char *child_argv_in[] = { + binary, (char *) "--direct", (char *) "--restart", NULL + }; + + struct support_spawn_wrapped *w + = support_spawn_wrap (binary, child_argv_in, NULL, + support_spawn_wrap_force); + + /* 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 entry_len = (size_t) page_size - 1; + size_t stack_limit = (size_t) 24 * page_size; + + /* Extend the wrapped --library-path value with the synthetic junk. + Build a fresh argv whose slot 2 points at our long-paths string; + all other slots alias into the wrapped argv, which keeps ownership + of those strings. */ + TEST_VERIFY_EXIT (w->argv[0] != NULL && w->argv[1] != NULL + && w->argv[2] != NULL + && strcmp (w->argv[1], "--library-path") == 0); + char *long_paths = build_long_library_path (w->argv[2], entry_len); + + size_t nargs; + for (nargs = 0; w->argv[nargs] != NULL; nargs++) + ; + char **child_argv = xcalloc (nargs + 1, sizeof (*child_argv)); + for (size_t i = 0; i < nargs; i++) + child_argv[i] = (i == 2) ? long_paths : (char *) w->argv[i]; + child_argv[nargs] = 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); + + struct support_capture_subprocess proc + = support_capture_subprogram (w->path, child_argv, + (char *const *) w->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 (child_argv); + free (long_paths); + support_spawn_wrapped_free (w); + free (binary); + return 0; +} + +#include