From da11d653495bca04a6e7e655e39166e5d77b01dc Mon Sep 17 00:00:00 2001 From: Maximilian Bosch Date: Sun, 27 Sep 2026 10:32:29 +0200 Subject: [PATCH] glibc: 2.44-25 -> 2.44-50 Fixes CVE-2026-8674, CVE-2026-95818, CVE-2026-19499, CVE-2026-19542, CVE-2026-80489, CVE-2026-77117. Closes #563466 Closes #564529 Closes #566198 --- .../libraries/glibc/2.44-master.patch | 2615 +++++++++++++++++ pkgs/development/libraries/glibc/common.nix | 4 +- 2 files changed, 2617 insertions(+), 2 deletions(-) diff --git a/pkgs/development/libraries/glibc/2.44-master.patch b/pkgs/development/libraries/glibc/2.44-master.patch index 08c0e1da3143..e87450d69a73 100644 --- a/pkgs/development/libraries/glibc/2.44-master.patch +++ b/pkgs/development/libraries/glibc/2.44-master.patch @@ -4136,3 +4136,2618 @@ index a3bd24b71f..98797e0887 100644 } strong_alias (__fmodf, __ieee754_fmodf) versioned_symbol (libm, __fmodf, fmodf, GLIBC_2_43); + +commit d6ff274313d79feb864cc10eb775b91c817a67e9 +Author: Florian Weimer +Date: Fri Aug 14 13:41:16 2026 +0200 + + misc: Fix out-of-bounds array write in tdelete (bug 34506) + + Allocate the maximum array sizes directly, instead of resizing + the arrays as needed. This eliminates alloca usage from the + function, and fixes the out-of-bounds accesses. The asserts + guard against the bug coming back if the balancing of the tree + turns out not to work correctly. + + Reviewed-by: Adhemerval Zanella + (cherry picked from commit e2789c46e3bfdcd67a82bea9946b315c179e83d3) + +diff --git a/misc/tsearch.c b/misc/tsearch.c +index 9b2eb34b25..e517dfa712 100644 +--- a/misc/tsearch.c ++++ b/misc/tsearch.c +@@ -85,6 +85,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -406,12 +407,13 @@ __tdelete (const void *key, void **vrootp, __compar_fn_t compar) + int cmp; + node *rootp = (node *) vrootp; + node root, unchained; +- /* Stack of nodes so we remember the parents without recursion. It's +- _very_ unlikely that there are paths longer than 40 nodes. The tree +- would need to have around 250.000 nodes. */ +- int stacksize = 40; ++ /* Stack of nodes so we remember the parents without recursion. The ++ stack size is a conservative approximation of the maximum height ++ of a red-black tree, based on size of the address space. ++ Actual numbers are closer to 57 (32 bit) and 117 (63 bit). */ ++ enum { stacksize = 2 * UINTPTR_WIDTH }; + int sp = 0; +- node **nodestack = alloca (sizeof (node *) * stacksize); ++ node *nodestack[stacksize]; + + if (rootp == NULL) + return NULL; +@@ -424,14 +426,7 @@ __tdelete (const void *key, void **vrootp, __compar_fn_t compar) + root = DEREFNODEPTR(rootp); + while ((cmp = (*compar) (key, root->key)) != 0) + { +- if (sp == stacksize) +- { +- node **newstack; +- stacksize += 20; +- newstack = alloca (sizeof (node *) * stacksize); +- nodestack = memcpy (newstack, nodestack, sp * sizeof (node *)); +- } +- ++ assert (sp < stacksize); + nodestack[sp++] = rootp; + p = DEREFNODEPTR(rootp); + if (cmp < 0) +@@ -470,13 +465,7 @@ __tdelete (const void *key, void **vrootp, __compar_fn_t compar) + node upn; + for (;;) + { +- if (sp == stacksize) +- { +- node **newstack; +- stacksize += 20; +- newstack = alloca (sizeof (node *) * stacksize); +- nodestack = memcpy (newstack, nodestack, sp * sizeof (node *)); +- } ++ assert (sp < stacksize); + nodestack[sp++] = parentp; + parentp = up; + upn = DEREFNODEPTR(up); +@@ -541,6 +530,7 @@ __tdelete (const void *key, void **vrootp, __compar_fn_t compar) + SETNODEPTR(pp,q); + /* Make sure pp is right if the case below tries to use + it. */ ++ assert (sp < stacksize); + nodestack[sp++] = pp = LEFTPTR(q); + q = RIGHT(p); + } +@@ -625,6 +615,7 @@ __tdelete (const void *key, void **vrootp, __compar_fn_t compar) + SETLEFT(p,RIGHT(q)); + SETRIGHT(q,p); + SETNODEPTR(pp,q); ++ assert (sp < stacksize); + nodestack[sp++] = pp = RIGHTPTR(q); + q = LEFT(p); + } + +commit ae9225d55963c4420c49ccfa3f2fafc416f92032 +Author: Andreas Schwab +Date: Mon Aug 24 18:00:26 2026 +0200 + + m68k: remove sysdeps/m68k/m680x0/fpu/w_fmod_compat.c (bug 34559) + + This was missed from commit 6deadd4eb6. + + (cherry picked from commit ff1c5580610dee6d743169fc0f6684505d372dc9) + +diff --git a/sysdeps/m68k/m680x0/fpu/w_fmod_compat.c b/sysdeps/m68k/m680x0/fpu/w_fmod_compat.c +deleted file mode 100644 +index 57f38091e6..0000000000 +--- a/sysdeps/m68k/m680x0/fpu/w_fmod_compat.c ++++ /dev/null +@@ -1,15 +0,0 @@ +-/* m68k provides an optimized __ieee752_fmod. */ +-#include +-#ifdef SHARED +-# undef SHLIB_COMPAT +-# define SHLIB_COMPAT(a, b, c) 1 +-# undef LIBM_SVID_COMPAT +-# define LIBM_SVID_COMPAT 1 +-# undef compat_symbol +-# define compat_symbol(a, b, c, d) +-#include +-libm_alias_double (__fmod_compat, fmod) +-#else +-#include +-#include +-#endif + +commit 9c48b91ee34431f19fca47bf31e2f2ef77c119d5 +Author: Adhemerval Zanella +Date: Thu Aug 13 08:53:06 2026 -0300 + + posix: Remove unnecessary overflow check in wordexp (BZ 34090) + + The WRDE_APPEND path duplicates the caller's we_wordv array, which + already holds we_offs + we_wordc + 1 pointers. Follow-up to commit + e2cefe16c37. + + Checked on x86_64-linux-gnu and i686-linux-gnu. + + (cherry picked from commit 53ec26f1736aee747b353aaea0667b1ebdd5cae7) + +diff --git a/posix/wordexp.c b/posix/wordexp.c +index 8fdc8b8caf..9a49e102ee 100644 +--- a/posix/wordexp.c ++++ b/posix/wordexp.c +@@ -35,7 +35,6 @@ + #include + #include <_itoa.h> + #include +-#include + + /* + * This is a recursive-descent-style word expansion routine. +@@ -2269,16 +2268,14 @@ wordexp (const char *words, wordexp_t *pwordexp, int flags) + { + /* WRDE_APPEND with an existing word list: duplicate the array so that + realloc during parsing does not invalidate the caller's pointer. The +- strings themselves are shared. */ +- size_t num_p; +- char **dup; +- if (INT_ADD_WRAPV (pwordexp->we_offs, pwordexp->we_wordc, &num_p) +- || INT_ADD_WRAPV (num_p, 1, &num_p)) +- return WRDE_NOSPACE; +- dup = __libc_reallocarray (NULL, num_p, sizeof *dup); ++ strings themselves are shared an the array already holds ++ 'we_offs + we_wordc + 1 pointers' (so the size computation cannot ++ overflow). */ ++ size_t num_p = pwordexp->we_offs + pwordexp->we_wordc + 1; ++ char **dup = malloc (num_p * sizeof (char *)); + if (dup == NULL) + return WRDE_NOSPACE; +- memcpy (dup, pwordexp->we_wordv, num_p * sizeof *dup); ++ memcpy (dup, pwordexp->we_wordv, num_p * sizeof (char *)); + saved_wordv = pwordexp->we_wordv; + pwordexp->we_wordv = dup; + } + +commit 63b53df549451a5d69fcba6d7612ea99f517e8e3 +Author: Florian Weimer +Date: Thu Aug 27 13:34:54 2026 +0200 + + stdlib: Fix right-justification in strfmon (bug 34510, CVE-2026-19499) + + The memmove call did not take into account that __printf_buffer_pad + updated the buffer pointers. + + Fixes commit e88b9f0e5cc50cab57a299dc7efe1a4eb385161d + ("stdio-common: Convert vfprintf and related functions to buffers"), + which went into glibc 2.37. + + Reviewed-by: Adhemerval Zanella + (cherry picked from commit b090cf226ff65b913e41536f1f573f500855615c) + +diff --git a/stdlib/Makefile b/stdlib/Makefile +index addf7dc99f..16948eb512 100644 +--- a/stdlib/Makefile ++++ b/stdlib/Makefile +@@ -347,6 +347,7 @@ tests := \ + tst-stdc_leading_zeros \ + tst-stdc_trailing_ones \ + tst-stdc_trailing_zeros \ ++ tst-strfmon-bug34510 \ + tst-strfmon_l \ + tst-strfrom \ + tst-strfrom-locale \ +diff --git a/stdlib/strfmon_l.c b/stdlib/strfmon_l.c +index f864289480..c39babaeae 100644 +--- a/stdlib/strfmon_l.c ++++ b/stdlib/strfmon_l.c +@@ -549,7 +549,8 @@ __vstrfmon_l_buffer (struct __printf_buffer *buf, locale_t loc, + /* Now test whether the output width is filled. */ + if (buf->write_ptr - startp < width) + { +- size_t pad_width = width - (buf->write_ptr - startp); ++ size_t written_width = buf->write_ptr - startp; ++ size_t pad_width = width - written_width; + __printf_buffer_pad (buf, ' ', pad_width); + if (__printf_buffer_has_failed (buf)) + /* Implies length check. */ +@@ -558,7 +559,7 @@ __vstrfmon_l_buffer (struct __printf_buffer *buf, locale_t loc, + Otherwise move the field contents in place. */ + if (!left) + { +- memmove (startp + pad_width, startp, buf->write_ptr - startp); ++ memmove (startp + pad_width, startp, written_width); + memset (startp, ' ', pad_width); + } + } +diff --git a/stdlib/tst-strfmon-bug34510.c b/stdlib/tst-strfmon-bug34510.c +new file mode 100644 +index 0000000000..b187bde1f4 +--- /dev/null ++++ b/stdlib/tst-strfmon-bug34510.c +@@ -0,0 +1,33 @@ ++/* Test handling of right-padding in strfmon (bug 34510, CVE-2026-19499). ++ 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 ++ ++static int ++do_test (void) ++{ ++ struct support_next_to_fault ntf = support_next_to_fault_allocate (100); ++ TEST_COMPARE (strfmon (ntf.buffer, ntf.length, "%100n", 1.23), -1); ++ TEST_COMPARE (errno, E2BIG); ++ return 0; ++} ++ ++#include + +commit 6f9b2bfa500bf5d1cff5d990adfff4b71298dadd +Author: Florian Weimer +Date: Fri Aug 28 10:26:07 2026 +0200 + + iconvdata: SHIFT_JISX0213 decoding lacks pending character reset (CVE-2026-77117) + + This fixes bug 34556. + + Reviewed-by: Carlos O'Donell + (cherry picked from commit 68d94bbe50b7577d48998107d632ef3a0df050e3) + +diff --git a/iconvdata/shift_jisx0213.c b/iconvdata/shift_jisx0213.c +index 61c9c3ce6d..e9179f605e 100644 +--- a/iconvdata/shift_jisx0213.c ++++ b/iconvdata/shift_jisx0213.c +@@ -226,6 +226,9 @@ + STANDARD_FROM_LOOP_ERR_HANDLER (1); \ + } \ + } \ ++ else \ ++ /* There was a pending character. Clear it. */ \ ++ *statep = 0; \ + \ + put32 (outptr, ch); \ + outptr += 4; \ + +commit cb61572ea3f773e1e1978f6c412cc36a30acdb0c +Author: Florian Weimer +Date: Fri Aug 28 10:26:07 2026 +0200 + + iconvdata: EUC_JISX0213 decoding lacks pending character reset (CVE-2026-80489) + + This fixes bug 34568. + + Reviewed-by: Carlos O'Donell + (cherry picked from commit 4dafa087ff5fe7df45bd37dc727e988da6b8c935) + +diff --git a/iconvdata/euc-jisx0213.c b/iconvdata/euc-jisx0213.c +index 5572bbdb7b..61c22231e1 100644 +--- a/iconvdata/euc-jisx0213.c ++++ b/iconvdata/euc-jisx0213.c +@@ -224,6 +224,9 @@ + STANDARD_FROM_LOOP_ERR_HANDLER (1); \ + } \ + } \ ++ else \ ++ /* There was a pending character. Clear it. */ \ ++ *statep = 0; \ + \ + put32 (outptr, ch); \ + outptr += 4; \ + +commit ca54198decdc3be693b010687f8de716a750b0e4 +Author: Florian Weimer +Date: Fri Aug 28 10:26:07 2026 +0200 + + iconvdata: Test case for bug 34556, bug 34568 + + Assisted-by: LLM + Reviewed-by: Carlos O'Donell + (cherry picked from commit 35efcffa97553df071bc37ab31fd7dc2c634e7da) + +diff --git a/iconvdata/Makefile b/iconvdata/Makefile +index fbb0067302..ba2eec2b48 100644 +--- a/iconvdata/Makefile ++++ b/iconvdata/Makefile +@@ -76,7 +76,8 @@ tests = bug-iconv1 bug-iconv2 tst-loading tst-e2big tst-iconv4 bug-iconv4 \ + tst-iconv6 bug-iconv5 bug-iconv6 tst-iconv7 bug-iconv8 bug-iconv9 \ + bug-iconv10 bug-iconv11 bug-iconv12 tst-iconv-big5-hkscs-to-2ucs4 \ + bug-iconv13 bug-iconv14 bug-iconv15 \ +- tst-iconv-iso-2022-cn-ext tst-bug33980 ++ tst-iconv-iso-2022-cn-ext tst-bug33980 \ ++ tst-jisx0213-progress + ifeq ($(have-thread-library),yes) + tests += bug-iconv3 + endif +@@ -335,6 +336,8 @@ $(objpfx)tst-iconv-iso-2022-cn-ext.out: $(addprefix $(objpfx), $(gconv-modules)) + $(addprefix $(objpfx),$(modules.so)) + $(objpfx)tst-bug33980.out: $(addprefix $(objpfx), $(gconv-modules)) \ + $(addprefix $(objpfx),$(modules.so)) ++$(objpfx)tst-jisx0213-progress.out: \ ++ $(addprefix $(objpfx), $(gconv-modules)) $(addprefix $(objpfx),$(modules.so)) + + $(objpfx)iconv-test.out: run-iconv-test.sh \ + $(addprefix $(objpfx), $(gconv-modules)) \ +diff --git a/iconvdata/tst-jisx0213-progress.c b/iconvdata/tst-jisx0213-progress.c +new file mode 100644 +index 0000000000..7b2073be1f +--- /dev/null ++++ b/iconvdata/tst-jisx0213-progress.c +@@ -0,0 +1,124 @@ ++/* Test JISX0213 combining character conversion progress (bug 34556, bug 34568). ++ 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 ++ . */ ++ ++/* Certain JISX0213 byte sequences map to a combining sequence, for ++ example U+304B (HIRAGANA LETTER KA) followed by U+309A (COMBINING ++ SEMI-VOICED SOUND MARK). When converting to internal encoding ++ (actually UTF-32) with a small output buffer, the first code point ++ is emitted and the second is queued in the converter state. This ++ test verifies that the queued code point is consumed exactly once ++ on retry, so that the conversion makes progress and terminates. */ ++ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++static void ++test_one (const char *charset, const char *input, size_t outbufsize) ++{ ++ printf ("info: %s: testing output buffer size %zu\n", charset, outbufsize); ++ ++ /* Expected UTF-32 output. */ ++ static const wchar_t expected[] = { 0x304b, 0x309a, 'A' }; ++ ++ /* Use WCHAR_T encoding to avoid the BOM. */ ++ iconv_t cd = iconv_open ("WCHAR_T", charset); ++ TEST_VERIFY_EXIT (cd != (iconv_t) -1); ++ ++ char result[64]; ++ size_t result_len = 0; ++ ++ char *inptr = (char *) input; ++ size_t inleft = strlen (input); ++ ++ char outbuf[64]; ++ ++ int iterations = 0; ++ while (inleft > 0) ++ { ++ char *outptr = outbuf; ++ size_t outleft = outbufsize; ++ size_t inleft_before = inleft; ++ ++ size_t ret = iconv (cd, &inptr, &inleft, &outptr, &outleft); ++ size_t produced = outptr - outbuf; ++ ++ TEST_VERIFY_EXIT (result_len + produced <= sizeof (result)); ++ memcpy (result + result_len, outbuf, produced); ++ result_len += produced; ++ ++ if (ret == (size_t) -1 && errno == E2BIG) ++ { ++ if (produced == 0 && inleft == inleft_before) ++ { ++ /* Output buffer too small for a single code point. */ ++ TEST_VERIFY_EXIT (outbufsize < 4); ++ break; ++ } ++ /* Bound iterations to detect non-progress bugs. */ ++ if (++iterations < 10) ++ continue; ++ else ++ { ++ FAIL ("%s: no progress", charset); ++ goto out; ++ } ++ } ++ if (ret == (size_t) -1) ++ FAIL_EXIT1 ("outbufsize %zu: iconv: %m", outbufsize); ++ break; ++ } ++ ++ /* Flush pending converter state. */ ++ { ++ char *outptr = outbuf; ++ size_t outleft = outbufsize; ++ size_t ret = iconv (cd, NULL, NULL, &outptr, &outleft); ++ TEST_VERIFY (ret == 0); ++ size_t produced = outptr - outbuf; ++ memcpy (result + result_len, outbuf, produced); ++ result_len += produced; ++ } ++ ++ if (outbufsize >= 4) ++ { ++ TEST_COMPARE (inleft, 0); ++ TEST_COMPARE_BLOB (result, result_len, ++ expected, sizeof (expected)); ++ } ++ ++ out: ++ TEST_VERIFY_EXIT (iconv_close (cd) == 0); ++} ++ ++static int ++do_test (void) ++{ ++ for (size_t outbufsize = 1; outbufsize <= 16; outbufsize++) ++ { ++ test_one ("EUC-JISX0213", "\244\367A", outbufsize); ++ test_one ("SHIFT_JISX0213", "\202\365A", outbufsize); ++ } ++ return 0; ++} ++ ++#include + +commit 890f1c13f0de88678db16f8f189d43b51b77b8dc +Author: Magnus Lindholm +Date: Mon Aug 3 17:51:59 2026 +0200 + + alpha: Fix stack alignment in makecontext + + The Alpha ABI requires the stack pointer to be 16-byte aligned. + However, __makecontext did not realign it after reserving space for + arguments. Depending on uc_stack.ss_size, this could leave the stack + only 8-byte aligned. + + Round the new stack pointer down to a 16-byte boundary after reserving + the argument area. + + This fixes stdlib/tst-makecontext2. + + Signed-off-by: Magnus Lindholm + Reviewed-by: Adhemerval Zanella + (cherry picked from commit 4a07bb292f921c10e71fbf48c4a7f44391feb06c) + +diff --git a/sysdeps/unix/sysv/linux/alpha/makecontext.S b/sysdeps/unix/sysv/linux/alpha/makecontext.S +index b0479315d5..1de0dd2653 100644 +--- a/sysdeps/unix/sysv/linux/alpha/makecontext.S ++++ b/sysdeps/unix/sysv/linux/alpha/makecontext.S +@@ -38,6 +38,9 @@ ENTRY(__makecontext) + s8addq $1, 0, $2 + subq $8, $2, $8 + ++ /* The Alpha ABI requires a 16-byte-aligned stack pointer. */ ++ bic $8, 15, $8 ++ + /* Copy all parameters. Switch statement header here. */ + ldah $3, $jumptable($29) !gprelhigh + cmple $18, 6, $1 + +commit 3258c3fd9167ac5e1bfbb62b55710a139382d6a0 +Author: Matt Turner +Date: Mon Aug 3 19:55:06 2026 -0400 + + alpha: add the denormal trap enable bit to FE_NOMASK_ENV + + FE_NOMASK_ENV is the floating-point environment in which no exception is + masked, so it must enable every exception that FE_ALL_EXCEPT covers. On + Alpha that includes the GNU extension FE_DENORMAL, whose SWCR trap enable + bit is IEEE_TRAP_ENABLE_DNO (bit 6). + + The constant only set bits 1 through 5 (INV, DZE, OVF, UNF and INE), so + after fesetenv (FE_NOMASK_ENV) a subsequent fegetexcept () returned + 0x3e0000 rather than FE_ALL_EXCEPT (0x7e0000), and denormal exceptions + stayed masked. Set bit 6 as well. + + Fixes math/test-fenv-return on alpha. + + Reviewed-by: Adhemerval Zanella + (cherry picked from commit 8e41f6008df3b5d56df199e8b4d19891666857a4) + +diff --git a/sysdeps/alpha/fpu/bits/fenv.h b/sysdeps/alpha/fpu/bits/fenv.h +index b1135f1f0a..530f298914 100644 +--- a/sysdeps/alpha/fpu/bits/fenv.h ++++ b/sysdeps/alpha/fpu/bits/fenv.h +@@ -121,7 +121,7 @@ typedef unsigned long int fenv_t; + + #ifdef __USE_GNU + /* Floating-point environment where none of the exceptions are masked. */ +-# define FE_NOMASK_ENV ((const fenv_t *) 0x880000000000003eUL) ++# define FE_NOMASK_ENV ((const fenv_t *) 0x880000000000007eUL) + + /* Floating-point environment with (processor-dependent) non-IEEE floating + point. In this case, mapping denormals to zero. */ + +commit 8587edfc9642b46232292b5722fec955851aa7c2 +Author: Matt Turner +Date: Mon Aug 3 19:55:09 2026 -0400 + + alpha: expect test-float32x-float64-div to fail + + _Float32x and _Float64 are both binary64 on Alpha, so this narrowing + divide is a plain divide and the hardware alone decides whether to signal + underflow. + + IEEE 754 determines tininess after rounding from the result rounded as if + the exponent range were unbounded, while Alpha determines it from the + delivered result. The two differ for a quotient that is tiny but rounds + up to the smallest normal, as in DBL_MIN / (1 + 2^-52) under a rounding + mode that rounds away from zero: the binade below DBL_MIN has a finer + spacing than the subnormals, so the unbounded rounding stays below + DBL_MIN and the result is tiny, but the delivered result is DBL_MIN and + looks normal. Alpha signals no underflow for it. + + Nothing in software can correct this. The hardware detects no underflow, + so no software completion trap is taken and the kernel emulation never + runs, and as the operation is not really narrowing there is no wider + intermediate for libm to examine. + + Reviewed-by: Adhemerval Zanella + (cherry picked from commit a32db99a38c5f0c64b414af270830c3b6e3719c1) + +diff --git a/sysdeps/alpha/Makefile b/sysdeps/alpha/Makefile +index 60a369e255..faa59ab27a 100644 +--- a/sysdeps/alpha/Makefile ++++ b/sysdeps/alpha/Makefile +@@ -53,6 +53,20 @@ CFLAGS-s_lrint.c += -mieee-with-inexact + CFLAGS-test-misc.c += -mieee-with-inexact + # Avoid "conflicting types for built-in function" warnings + CFLAGS-s_isnan.c += -fno-builtin-isnanf ++ ++# _Float32x and _Float64 are both binary64 on Alpha, so this narrowing ++# divide is a plain divide and the hardware alone decides whether to ++# signal underflow. IEEE 754 determines tininess after rounding from the ++# result rounded as if the exponent range were unbounded, but Alpha ++# determines it from the delivered result. For a quotient that is tiny ++# but rounds up to the smallest normal -- DBL_MIN / (1 + 2^-52) under a ++# rounding mode that rounds away from zero -- the unbounded rounding ++# stays below DBL_MIN, because that binade has a finer spacing than the ++# subnormals, while the delivered result is DBL_MIN and looks normal. ++# Alpha raises no underflow for it. No trap is taken, so the kernel ++# emulation cannot correct this, and the operation has no wider ++# intermediate for libm to examine. ++test-xfail-test-float32x-float64-div = yes + endif + + # Build everything with full IEEE math support, and with dynamic rounding; + +commit 2d5421ffca8893534d5e02ad38c28acd8e778fa3 +Author: H.J. Lu +Date: Wed Aug 26 15:18:35 2026 +0800 + + Add check-symbol-version.awk + + commit 6deadd4eb6ab4f59d116b2d7ad97be0d0848cb7f + Author: Adhemerval Zanella + Date: Wed Oct 8 10:55:05 2025 -0300 + + didn't remove sysdeps/m68k/m680x0/fpu/w_fmod_compat.c. As the result, + due to a linker bug: + + https://sourceware.org/bugzilla/show_bug.cgi?id=34550 + + there were 2 default versions of fmod in m68k libm: + + 996: 0001433c 174 FUNC WEAK DEFAULT 12 fmod@@GLIBC_2.0 + 997: 000307d4 214 FUNC GLOBAL DEFAULT 12 fmod@@GLIBC_2.43 + + Add check-symbol-version.awk to verify that versioned symbols only have + one default version in dynamic symbol table. + + Signed-off-by: H.J. Lu + Reviewed-by: Sam James + (cherry picked from commit 0e57c798c68eaf229a9fa8cb02179e43d4f8581a) + +diff --git a/elf/Makefile b/elf/Makefile +index d279a5135c..54a4339148 100644 +--- a/elf/Makefile ++++ b/elf/Makefile +@@ -1432,6 +1432,7 @@ tests-special += \ + $(objpfx)check-execstack.out \ + $(objpfx)check-initfini.out \ + $(objpfx)check-localplt.out \ ++ $(objpfx)check-symbol-version.out \ + $(objpfx)check-textrel.out \ + $(objpfx)check-wx-segment.out \ + # tests-special +@@ -2377,6 +2378,12 @@ $(objpfx)check-initfini.out: $(..)scripts/check-initfini.awk \ + $(evaluate-test) + generated += check-initfini.out + ++$(objpfx)check-symbol-version.out: $(..)scripts/check-symbol-version.awk \ ++ $(all-built-dso:=.dynsym) ++ LC_ALL=C $(AWK) -f $^ > $@; \ ++ $(evaluate-test) ++generated += check-symbol-version.out ++ + $(objpfx)tst-dlopenrpath: $(objpfx)tst-dlopenrpathmod.so + CFLAGS-tst-dlopenrpath.c += -DPFX=\"$(objpfx)\" + LDFLAGS-tst-dlopenrpathmod.so += -Wl,-rpath,\$$ORIGIN/test-subdir +diff --git a/scripts/check-symbol-version.awk b/scripts/check-symbol-version.awk +new file mode 100644 +index 0000000000..2ddaa0edc9 +--- /dev/null ++++ b/scripts/check-symbol-version.awk +@@ -0,0 +1,67 @@ ++# 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 awk script expects to get command-line files that are each ++# the output of 'readelf -W --dyn-syms' on a single shared object. ++# It exits successfully (0) if there are versioned symbols with more ++# than one default version in dynamic symbol table. ++# It fails (1) if any did contain versioned symbols with more than one ++# default version in dynamic symbol table. ++# It fails (2) if the input did not take the expected form. ++ ++BEGIN { result = incorrect = sanity = 0 } ++ ++function check_one(name) { ++ if (!sanity) { ++ print name ": *** input did not look like readelf -d output"; ++ result = 2; ++ } else { ++ ok = 1; ++ if (incorrect) { ++ print name ": *** incorrect dynamic symbol table"; ++ result = result ? result : 1; ++ ok = 0; ++ } ++ if (ok) ++ print name ": OK"; ++ } ++ ++ delete version ++ incorrect = sanity = 0 ++} ++ ++FILENAME != lastfile { ++ if (lastfile) ++ check_one(lastfile); ++ lastfile = FILENAME; ++} ++ ++$1 == "Symbol" && $2 == "table" && $3 == "'.dynsym'" { sanity = 1 } ++$8 ~ "@@" { ++ split($8,a,"@"); ++ if (version[a[1]] != "") { ++ incorrect = 1; ++ print a[1] " has 2 default versions: " version[a[1]] ", " a[3]; ++ } else { ++ version[a[1]] = a[3]; ++ } ++} ++ ++END { ++ check_one(lastfile); ++ exit(result); ++} + +commit 93606c5c1d2a571abf5d2e8af66e4f0aee9c67ba +Author: Florian Weimer +Date: Tue Aug 18 21:48:34 2026 +0200 + + powerpc: Fix non-atomic stack pointer update in fortified longjmp (bug 34530) + + After commit 78f1f0e39cd41d28ae771eb3498bc33780c85cfd ("Consolidate + the C pointer guard and align the assembly implementations"), + PTR_DEMANGLE3 on POWER no longer atomically updates the destination + register. The fortified longjmp relies on atomic update of the + stack pointer (r1) in sysdeps/powerpc/powerpc64/__longjmp-common.S + and parallel files: + + #ifdef PTR_DEMANGLE + # ifdef CHECK_SP + PTR_DEMANGLE3 (r22, r22, r25) + # else + PTR_DEMANGLE3 (r1, r22, r25) + # endif + #endif + + Fix this by using PTR_DEMANGLE instead of PTR_DEMANGLE3. Remove + PTR_MANGLE3 and PTR_DEMANGLE3 as unused. + + An alternate fix would store the pointer guard cookie rotated, + but this would go against the unification in the commit that + introduced the regression. + + Reviewed-by: Adhemerval Zanella + (cherry picked from commit 24d188a2a149b9933e17240e454f917e065cdf06) + +diff --git a/sysdeps/powerpc/powerpc32/__longjmp-common.S b/sysdeps/powerpc/powerpc32/__longjmp-common.S +index eb228bf4fe..9da86330f7 100644 +--- a/sysdeps/powerpc/powerpc32/__longjmp-common.S ++++ b/sysdeps/powerpc/powerpc32/__longjmp-common.S +@@ -30,11 +30,12 @@ ENTRY (__longjmp_symbol) + + #if defined PTR_DEMANGLE || defined CHECK_SP + lwz r24,(JB_GPR1*4)(r3) ++# ifdef PTR_DEMANGLE ++ PTR_DEMANGLE (r24, r25) ++# endif + # ifdef CHECK_SP +-# ifdef PTR_DEMANGLE +- PTR_DEMANGLE3 (r24, r24, r25) +-# endif + CHECK_SP (r24) ++# endif + mr r1,r24 + # endif + #else +@@ -49,9 +50,6 @@ ENTRY (__longjmp_symbol) + lwz r19,((JB_GPRS+19-14)*4)(r3) + lwz r20,((JB_GPRS+20-14)*4)(r3) + #ifdef PTR_DEMANGLE +-# ifndef CHECK_SP +- PTR_DEMANGLE3 (r1, r24, r25) +-# endif + PTR_DEMANGLE2 (r0, r25) + #endif + /* longjmp/longjmp_target probe expects longjmp first argument (4@3), +diff --git a/sysdeps/powerpc/powerpc32/fpu/__longjmp-common.S b/sysdeps/powerpc/powerpc32/fpu/__longjmp-common.S +index c5acc1610c..8e383cc997 100644 +--- a/sysdeps/powerpc/powerpc32/fpu/__longjmp-common.S ++++ b/sysdeps/powerpc/powerpc32/fpu/__longjmp-common.S +@@ -114,13 +114,13 @@ L(no_vmx): + #endif + #if defined PTR_DEMANGLE || defined CHECK_SP + lwz r24,(JB_GPR1*4)(r3) ++# ifdef PTR_DEMANGLE ++ PTR_DEMANGLE (r24, r25) ++# endif + # ifdef CHECK_SP +-# ifdef PTR_DEMANGLE +- PTR_DEMANGLE3 (r24, r24, r25) +-# endif + CHECK_SP (r24) +- mr r1,r24 + # endif ++ mr r1,r24 + #else + lwz r1,(JB_GPR1*4)(r3) + #endif +@@ -140,9 +140,6 @@ L(no_vmx): + lwz r20,((JB_GPRS+6)*4)(r3) + lfd fp20,((JB_FPRS+6*2)*4)(r3) + #ifdef PTR_DEMANGLE +-# ifndef CHECK_SP +- PTR_DEMANGLE3 (r1, r24, r25) +-# endif + PTR_DEMANGLE2 (r0, r25) + #endif + /* longjmp/longjmp_target probe expects longjmp first argument (4@3), +diff --git a/sysdeps/powerpc/powerpc64/__longjmp-common.S b/sysdeps/powerpc/powerpc64/__longjmp-common.S +index 99b2718677..fe8488fcac 100644 +--- a/sysdeps/powerpc/powerpc64/__longjmp-common.S ++++ b/sysdeps/powerpc/powerpc64/__longjmp-common.S +@@ -115,19 +115,15 @@ L(no_vmx): + #endif + #if defined PTR_DEMANGLE || defined CHECK_SP + ld r22,(JB_GPR1*8)(r3) +-#else +- ld r1,(JB_GPR1*8)(r3) +-#endif +-#ifdef PTR_DEMANGLE +-# ifdef CHECK_SP +- PTR_DEMANGLE3 (r22, r22, r25) +-# else +- PTR_DEMANGLE3 (r1, r22, r25) ++# ifdef PTR_DEMANGLE ++ PTR_DEMANGLE (r22, r25) + # endif +-#endif +-#ifdef CHECK_SP ++# ifdef CHECK_SP + CHECK_SP (r22) ++# endif + mr r1,r22 ++#else ++ ld r1,(JB_GPR1*8)(r3) + #endif + ld r2,(JB_GPR2*8)(r3) + ld r0,(JB_LR*8)(r3) +diff --git a/sysdeps/unix/sysv/linux/powerpc/pointer_guard-asm.h b/sysdeps/unix/sysv/linux/powerpc/pointer_guard-asm.h +index 962ad10e59..cb3e031467 100644 +--- a/sysdeps/unix/sysv/linux/powerpc/pointer_guard-asm.h ++++ b/sysdeps/unix/sysv/linux/powerpc/pointer_guard-asm.h +@@ -65,10 +65,6 @@ + # define PTR_MANGLE2(reg, tmpreg) \ + xor reg,tmpreg,reg; \ + PTR_ROT_MANGLE (reg, reg) +-# define PTR_MANGLE3(destreg, reg, tmpreg) \ +- PTR_GUARD_LOAD (tmpreg); \ +- xor destreg,tmpreg,reg; \ +- PTR_ROT_MANGLE (destreg, destreg) + # define PTR_DEMANGLE(reg, tmpreg) \ + PTR_GUARD_LOAD (tmpreg); \ + PTR_ROT_DEMANGLE (reg, reg); \ +@@ -76,10 +72,6 @@ + # define PTR_DEMANGLE2(reg, tmpreg) \ + PTR_ROT_DEMANGLE (reg, reg); \ + xor reg,tmpreg,reg +-# define PTR_DEMANGLE3(destreg, reg, tmpreg) \ +- PTR_GUARD_LOAD (tmpreg); \ +- PTR_ROT_DEMANGLE (destreg, reg); \ +- xor destreg,tmpreg,destreg + #endif + + #endif /* POINTER_GUARD_ASM_H */ + +commit 5c479454d1232f71c78fa21584e90a2f57883407 +Author: Adhemerval Zanella +Date: Mon Aug 24 13:38:02 2026 -0300 + + powerpc: Fix preprocessor conditional in soft-float __longjmp (bug 34530) + + Commit 24d188a2a149b9933e17240e454f917e065cdf06 left a stray #endif in + the powerpc32 soft-float __longjmp-common.S. Remove it, matching the + fpu variant. + + Checked with a build for powerpc32-linux-gnu-soft. + + (cherry picked from commit 5f5bc63a4d8a7eec7f11e56664ca76455d837b22) + +diff --git a/sysdeps/powerpc/powerpc32/__longjmp-common.S b/sysdeps/powerpc/powerpc32/__longjmp-common.S +index 9da86330f7..f1da933704 100644 +--- a/sysdeps/powerpc/powerpc32/__longjmp-common.S ++++ b/sysdeps/powerpc/powerpc32/__longjmp-common.S +@@ -37,7 +37,6 @@ ENTRY (__longjmp_symbol) + CHECK_SP (r24) + # endif + mr r1,r24 +-# endif + #else + lwz r1,(JB_GPR1*4)(r3) + #endif + +commit 0b4e41fc51e6aba6216a908961b49b0622b47fa0 +Author: Dongkyun Son +Date: Fri Sep 4 21:28:41 2026 +0900 + + libio: Fix CVE-2026-18374 heap buffer overflow in ccs= handling + + When fopen() is called with a ,ccs= parameter whose value becomes empty + after strip(), the code must reject it with EINVAL instead of attempting + to use it. The original upstr() fallback could read past the ',' delimiter + and cause a heap buffer overflow. + + The fix checks if the charset specification is empty after strip() and + returns EINVAL immediately, preventing the overflow and following the + approach described in BZ #34574. + + CVE-2026-18374 - CVSS 4.9 (AV:L/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L) + + Reported-by: AISLE in partnership with Red Hat + Signed-off-by: Dongkyun Son + Reviewed-by: Florian Weimer + (cherry picked from commit 9765a538ebf8661a6e5578e01e35a3dd30db7eb4) + +diff --git a/libio/fileops.c b/libio/fileops.c +index 9348d7c3a1..5a249725ee 100644 +--- a/libio/fileops.c ++++ b/libio/fileops.c +@@ -355,12 +355,14 @@ _IO_new_file_fopen (FILE *fp, const char *filename, const char *mode, + *((char *) __mempcpy (ccs, cs + 5, endp - (cs + 5))) = '\0'; + strip (ccs, ccs); + +- if (__wcsmbs_named_conv (&fcts, ccs[2] == '\0' +- ? upstr (ccs, cs + 5) : ccs) != 0) ++ /* After stripping, ccs[2] == '\0' means the charset name is empty. ++ This is not a valid charset and would cause problems downstream. ++ Reject it with EINVAL (BZ #34574, CVE-2026-18374). */ ++ if (ccs[2] == '\0' || __wcsmbs_named_conv (&fcts, ccs) != 0) + { +- /* Something went wrong, we cannot load the conversion modules. +- This means we cannot proceed since the user explicitly asked +- for these. */ ++ /* Either the charset name is empty after strip(), or conversion ++ modules cannot be loaded. This means we cannot proceed since ++ the user explicitly asked for character conversion. */ + (void) _IO_file_close_it (fp); + free (ccs); + __set_errno (EINVAL); + +commit 30950ce64dbc29db0aedf1d46b6b8eb70b360f0c +Author: Shamil Abdulaev +Date: Thu Sep 3 20:19:42 2026 +0300 + + libio: Add test for fopen with an empty ", ccs=" value [BZ #34574] + + This goes on top of the fix for CVE-2026-18374. The test runs the + reproducer from the bug report, plus "w,ccs=" and "w,ccs=,", and + expects NULL with errno set to EINVAL. + + Signed-off-by: Shamil Abdulaev + Reviewed-by: Florian Weimer + (cherry picked from commit cca93e5d88d3d4ed073c03100467696f652269e7) + +diff --git a/libio/Makefile b/libio/Makefile +index 616107ee10..9aec016cca 100644 +--- a/libio/Makefile ++++ b/libio/Makefile +@@ -110,6 +110,7 @@ tests = \ + tst-fgetwc \ + tst-fgetws \ + tst-file-init-race \ ++ tst-fopen-ccs-empty \ + tst-fopenloc2 \ + tst-fputws \ + tst-freopen \ +diff --git a/libio/tst-fopen-ccs-empty.c b/libio/tst-fopen-ccs-empty.c +new file mode 100644 +index 0000000000..64723965e1 +--- /dev/null ++++ b/libio/tst-fopen-ccs-empty.c +@@ -0,0 +1,62 @@ ++/* Test fopen with an empty ",ccs=" value in the mode string (bug 34574). ++ 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 ++ ++static void ++check_fopen_fails (const char *path, const char *mode) ++{ ++ errno = 0; ++ FILE *fp = fopen (path, mode); ++ TEST_VERIFY (fp == NULL); ++ TEST_COMPARE (errno, EINVAL); ++ if (fp != NULL) ++ fclose (fp); ++} ++ ++static int ++do_test (void) ++{ ++ char *path; ++ xclose (create_temp_file ("tst-fopen-ccs-empty", &path)); ++ ++ /* The value is blank and the mode string continues well past it. */ ++ enum { size = 1024 * 1024 }; ++ char *mode = xmalloc (size); ++ memset (mode, 'X', size); ++ mode[size - 1] = '\0'; ++ static const char prefix[] = "w,ccs= ,"; ++ memcpy (mode, prefix, sizeof (prefix) - 1); ++ check_fopen_fails (path, mode); ++ free (mode); ++ ++ check_fopen_fails (path, "w,ccs="); ++ check_fopen_fails (path, "w,ccs=,"); ++ ++ free (path); ++ return 0; ++} ++ ++#include + +commit b4f51887c48ac82acfdb13f96d9eab5d120cb2b7 +Author: Hemanth Kumar M D +Date: Mon Sep 7 01:59:06 2026 -0700 + + nptl: Skip pretty-printer tests without python3 [BZ #34507] + + The tests-printers-out rule in Rules wraps $(PYTHON) through + $(test-wrapper-env). Unlike ordinary tests, which wrap a freshly built + target binary, this wraps python3, a build-host tool. When cross-testing + with test-wrapper set (e.g. via scripts/cross-test-ssh.sh) the whole + command is forwarded to the target; if the target lacks python3 the shell + returns 127 and evaluate-test.sh reports the six nptl pretty-printer + tests as FAIL instead of UNSUPPORTED. + + scripts/test_printers_common.py already exits UNSUPPORTED (77) when its + dependencies are missing, but that is unreachable when python3 itself is + absent. + + Guard the invocation with a "command -v" check so the recipe exits 77 + (UNSUPPORTED) when python3 is not found. Native builds are unaffected, + as configure requires python3. + + Signed-off-by: Hemanth Kumar M D + Suggested-by: Adhemerval Zanella Netto + Reviewed-by: Adhemerval Zanella + + (cherry picked from commit c958d789db3bd8dbfb93868d8a975d13a3d66396) + +diff --git a/NEWS b/NEWS +index 697049efd1..a9a89bf6bc 100644 +--- a/NEWS ++++ b/NEWS +@@ -18,6 +18,8 @@ The following bugs are resolved with this release: + [34441] math: math: sinh() returns wrong results for some inputs with + |x| > 36.736801 + [34465] math: math: x86_64 tanh ifunc selection wrong ++ [34507] nptl: Pretty-printer tests FAIL instead of UNSUPPORTED when ++ cross-testing without python3 on target + [34509] libc: [m68k] Regression: Perl locks up after upgrading glibc + to 2.43 + +diff --git a/Rules b/Rules +index 71495028fb..cfdbb459c8 100644 +--- a/Rules ++++ b/Rules +@@ -467,8 +467,9 @@ py-env := PYTHONPATH=$(py-const-dir):$(..)scripts:$${PYTHONPATH} + # The pretty printer files and test_common_printers.py must be present for all. + $(tests-printers-out): $(objpfx)%.out: $(objpfx)% %.py %.c $(pretty-printers) \ + $(..)scripts/test_printers_common.py +- $(test-wrapper-env) $(py-env) \ +- $(PYTHON) $*.py $*.c $(objpfx)$* $(pretty-printers) > $@; \ ++ $(test-wrapper-env) $(py-env) sh -c \ ++ 'command -v $(firstword $(PYTHON)) > /dev/null 2>&1 || exit 77; \ ++ exec $(PYTHON) $*.py $*.c $(objpfx)$* $(pretty-printers)' > $@; \ + $(evaluate-test) + endif + + +commit b84047b5b792e1f028f4d82acbfa9ee9f6e89069 +Author: Shamil Abdulaev +Date: Fri Sep 11 14:59:48 2026 +0200 + + elf: Do not load cache extensions from an old-format ld.so.cache [BZ #34600] + + Since b9957a70b8 the cache extensions are loaded in + _dl_maybe_load_ldsocache. A cache written by "ldconfig -c old" has no + new-format header, so tmp_cache_new is NULL there and + cache_extension_load dereferences it. Because __tunables_init loads the + cache unconditionally, ld.so crashes at startup of every dynamically + linked program. An old-format cache cannot carry extensions, so return + early instead. + + Checked on x86_64-linux-gnu. + + Signed-off-by: Shamil Abdulaev + Reviewed-by: Florian Weimer + (cherry picked from commit fa91c51bb7bdd47ba4a1bb0ee7d66be101032ba5) + +diff --git a/elf/Makefile b/elf/Makefile +index 54a4339148..f1a8a75fdc 100644 +--- a/elf/Makefile ++++ b/elf/Makefile +@@ -306,6 +306,7 @@ tst-ifunc-resolver-protector-static-non-pie-no-pie = yes + + tests-container := \ + tst-dl-cache-long-path \ ++ tst-dl-cache-old-format \ + tst-ldconfig-bad-aux-cache \ + tst-ldconfig-ld_so_conf-update \ + # tests-container +@@ -3043,9 +3044,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) ++# Both cache tests reuse the trivial module already built for tst-dl-path-buf. + $(objpfx)tst-dl-cache-long-path.out: $(objpfx)tst-dl-path-buf-mod.so ++$(objpfx)tst-dl-cache-old-format.out: $(objpfx)tst-dl-path-buf-mod.so + + LDFLAGS-tst-dst-needed-leaf-mod.so = \ + -Wl,-soname,\$$ORIGIN/\$$ORIGIN/\$$ORIGIN/\$$ORIGIN/\$$ORIGIN/leaf.so +diff --git a/elf/tst-dl-cache-old-format.c b/elf/tst-dl-cache-old-format.c +new file mode 100644 +index 0000000000..985aed99ec +--- /dev/null ++++ b/elf/tst-dl-cache-old-format.c +@@ -0,0 +1,144 @@ ++/* Test that ld.so accepts an ld.so.cache written in the old format. ++ 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 ++ . */ ++ ++/* A cache written by "ldconfig -c old" carries no new-format header, so ++ the loader must not look for cache extensions in it. The test writes ++ such a cache, then starts a dynamically linked child, which is where ++ an unpatched loader dies before main, and then dlopens a module that ++ is only reachable through the cache, which covers the reload path. */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* ldconfig only indexes file names starting with "lib", so the module is ++ deployed under a lib-prefixed name 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 ++ ++#define MOD_DIR "/tst-dl-cache-old-format" ++ ++/* Copied from sysdeps/generic/dl-cache.h, which cannot be included here ++ because it relies on libc-internal attributes. */ ++#define CACHE_MAGIC_OLD "ld.so-1.7.0" ++#define CACHE_MAGIC_NEW "glibc-ld.so.cache1.1" ++ ++/* Set in the re-executed child, whose only job is to reach main. */ ++static int restart; ++#define CMDLINE_OPTIONS \ ++ { "restart", no_argument, &restart, 1 }, ++ ++static void ++run_ldconfig (void *closure __attribute__ ((unused))) ++{ ++ char *prog = xasprintf ("%s/ldconfig", support_install_rootsbindir); ++ char *args[] = { prog, (char *) "-c", (char *) "old", NULL }; ++ execv (args[0], args); ++ FAIL_EXIT1 ("execv (%s): %m", prog); ++} ++ ++/* ldconfig accepts an unknown -c argument without complaining and falls ++ back to the new format, so verify that the cache on disk really is the ++ old format and carries no new-format header anywhere. */ ++static void ++check_cache_is_old_format (void) ++{ ++ char *path = xasprintf ("%s/ld.so.cache", support_sysconfdir_prefix); ++ int fd = xopen (path, O_RDONLY, 0); ++ struct stat64 st; ++ xfstat64 (fd, &st); ++ size_t length = st.st_size; ++ char *contents = xmalloc (length); ++ xread (fd, contents, length); ++ xclose (fd); ++ ++ TEST_COMPARE_BLOB (contents, sizeof CACHE_MAGIC_OLD - 1, ++ CACHE_MAGIC_OLD, sizeof CACHE_MAGIC_OLD - 1); ++ TEST_VERIFY (memmem (contents, length, CACHE_MAGIC_NEW, ++ sizeof CACHE_MAGIC_NEW - 1) == NULL); ++ ++ free (contents); ++ free (path); ++} ++ ++static void ++do_prepare (int argc, char **argv) ++{ ++ if (restart) ++ return; ++ ++ xmkdirp (MOD_DIR, 0777); ++ add_temp_file (MOD_DIR); ++ char *src = xasprintf ("%s/elf/" MOD_BUILT, support_objdir_root); ++ support_copy_file (src, MOD_DIR "/" MOD_DEPLOYED); ++ add_temp_file (MOD_DIR "/" MOD_DEPLOYED); ++ free (src); ++ ++ char *conf = xasprintf ("%s/ld.so.conf", support_sysconfdir_prefix); ++ support_write_file_string (conf, MOD_DIR "\n"); ++ free (conf); ++ ++ xmkdirp ("/var/cache/ldconfig", 0777); ++ struct support_capture_subprocess result ++ = support_capture_subprocess (run_ldconfig, NULL); ++ support_capture_subprocess_check (&result, "ldconfig", 0, sc_allow_none); ++ support_capture_subprocess_free (&result); ++ ++ check_cache_is_old_format (); ++} ++#define PREPARE do_prepare ++ ++static int ++do_test (int argc, char **argv) ++{ ++ if (restart) ++ /* Reaching main is the entire check: the loader read the old-format ++ cache while starting this process. */ ++ return 0; ++ ++ char *spargv[] = { argv[0], (char *) "--direct", (char *) "--restart", ++ NULL }; ++ struct support_capture_subprocess result ++ = support_capture_subprogram (spargv[0], spargv, NULL); ++ support_capture_subprocess_check (&result, "restart", 0, sc_allow_none); ++ support_capture_subprocess_free (&result); ++ ++ /* This process still has the cache from its own startup mapped, so the ++ dlopen exercises the reload of the replaced file as well. */ ++ void *handle = xdlopen (MOD_DEPLOYED, RTLD_NOW | RTLD_LOCAL); ++ unsigned int (*value) (void) = xdlsym (handle, MOD_SYMBOL); ++ TEST_COMPARE (value (), MOD_EXPECTED); ++ xdlclose (handle); ++ ++ return 0; ++} ++ ++#define TEST_FUNCTION_ARGV do_test ++#include +diff --git a/elf/tst-dl-cache-old-format.root/postclean.req b/elf/tst-dl-cache-old-format.root/postclean.req +new file mode 100644 +index 0000000000..e69de29bb2 +diff --git a/sysdeps/generic/dl-cache.h b/sysdeps/generic/dl-cache.h +index 972ab32b86..b1de245438 100644 +--- a/sysdeps/generic/dl-cache.h ++++ b/sysdeps/generic/dl-cache.h +@@ -321,6 +321,9 @@ cache_extension_load (const struct cache_file_new *cache, + struct cache_extension_all_loaded *loaded) + { + memset (loaded, 0, sizeof (*loaded)); ++ if (cache == NULL) ++ /* Old-format cache without a new-format header, so no extensions. */ ++ return true; + if (cache->extension_offset == 0) + /* No extensions present. This is not a format error. */ + return true; + +commit b14c4b0bc95c25f9718807be07ad9a13442d2b6f +Author: Sam James +Date: Sat Aug 29 16:54:46 2026 +0100 + + io: drop nonnull attribute for fchmodat, faccessat, fchownat's path argument [BZ #34313] + + Since Linux 6.11, AT_EMPTY_PATH can be used for a NULL path argument, so + the nonnull attribute is no longer sound. Drop it. + + This bug was worked around in gnulib's 6db27b4dd36eda618db20e997ff56bbed7fce3cb. + + See also 55618e13968a60b89e5b226a23afaa8f17c5ef48 which fixed fstatat + in glibc. + + Bug: https://sourceware.org/PR34313 + +diff --git a/io/sys/stat.h b/io/sys/stat.h +index 3069e187b0..ff70b69f59 100644 +--- a/io/sys/stat.h ++++ b/io/sys/stat.h +@@ -370,7 +370,7 @@ extern int fchmod (int __fd, __mode_t __mode) __THROW; + the directory FD is open on. */ + extern int fchmodat (int __fd, const char *__file, __mode_t __mode, + int __flag) +- __THROW __nonnull ((2)) __wur; ++ __THROW __wur; + #endif /* Use ATFILE. */ + + +diff --git a/posix/unistd.h b/posix/unistd.h +index 06a6a88b52..dc5c2ec009 100644 +--- a/posix/unistd.h ++++ b/posix/unistd.h +@@ -307,7 +307,7 @@ extern int execveat (int __fd, const char *__path, char *const __argv[], + If AT_EACCESS is set in FLAG, then use effective IDs like `eaccess', + otherwise use real IDs like `access'. */ + extern int faccessat (int __fd, const char *__file, int __type, int __flag) +- __THROW __nonnull ((2)) __wur; ++ __THROW __wur; + #endif /* Use GNU. */ + + +@@ -510,7 +510,7 @@ extern int lchown (const char *__file, __uid_t __owner, __gid_t __group) + on. */ + extern int fchownat (int __fd, const char *__file, __uid_t __owner, + __gid_t __group, int __flag) +- __THROW __nonnull ((2)) __wur; ++ __THROW __wur; + #endif /* Use GNU. */ + + /* Change the process's working directory to PATH. */ + +commit d19791b12ae5f054187ebe93a653c0ef90447a4f +Author: H.J. Lu +Date: Wed Sep 16 08:22:35 2026 +0800 + + x86-64: Link tst-shstk-legacy-1{f,g} with -Wl,--no-as-needed + + Link tst-shstk-legacy-1f and tst-shstk-legacy-1g with -Wl,--no-as-needed + so that tst-shstk-legacy-mod-1.so is added to DT_NEEDED. This fixes + BZ #31877. + + Signed-off-by: H.J. Lu + Reviewed-by: Aurelien Jarno + (cherry picked from commit 1db83d1652d35115525960021e4fb5b76dd1c633) + +diff --git a/sysdeps/x86_64/Makefile b/sysdeps/x86_64/Makefile +index ff0463d7ca..dfb344d74a 100644 +--- a/sysdeps/x86_64/Makefile ++++ b/sysdeps/x86_64/Makefile +@@ -467,10 +467,12 @@ $(objpfx)tst-shstk-legacy-1e-static.out: \ + $(SHELL) $< $(common-objpfx) 2> $@; \ + $(evaluate-test) + tst-shstk-legacy-1f-ENV = GLIBC_TUNABLES=glibc.cpu.hwcaps=SHSTK ++LDFLAGS-tst-shstk-legacy-1f = -Wl,--no-as-needed + $(objpfx)tst-shstk-legacy-1f: $(objpfx)tst-shstk-legacy-mod-1.so + $(objpfx)tst-shstk-legacy-mod-1.so: \ + $(objpfx)tst-shstk-legacy-mod-1.os \ + $(objpfx)tst-shstk-legacy-1-extra.os ++LDFLAGS-tst-shstk-legacy-1g = -Wl,--no-as-needed + $(objpfx)tst-shstk-legacy-1g: $(objpfx)tst-shstk-legacy-mod-1.so + $(objpfx)tst-shstk-legacy-1g.out: \ + $(..)/sysdeps/x86_64/tst-shstk-legacy-1g.sh $(objpfx)tst-shstk-legacy-1g + +commit d7179269b79eb7394e980cb8ebd810629c686f0f +Author: Sam James +Date: Thu Sep 17 02:04:11 2026 +0100 + + Revert "io: drop nonnull attribute for fchmodat, faccessat, fchownat's path argument [BZ #34313]" + + This reverts commit b14c4b0bc95c25f9718807be07ad9a13442d2b6f. + +diff --git a/io/sys/stat.h b/io/sys/stat.h +index ff70b69f59..3069e187b0 100644 +--- a/io/sys/stat.h ++++ b/io/sys/stat.h +@@ -370,7 +370,7 @@ extern int fchmod (int __fd, __mode_t __mode) __THROW; + the directory FD is open on. */ + extern int fchmodat (int __fd, const char *__file, __mode_t __mode, + int __flag) +- __THROW __wur; ++ __THROW __nonnull ((2)) __wur; + #endif /* Use ATFILE. */ + + +diff --git a/posix/unistd.h b/posix/unistd.h +index dc5c2ec009..06a6a88b52 100644 +--- a/posix/unistd.h ++++ b/posix/unistd.h +@@ -307,7 +307,7 @@ extern int execveat (int __fd, const char *__path, char *const __argv[], + If AT_EACCESS is set in FLAG, then use effective IDs like `eaccess', + otherwise use real IDs like `access'. */ + extern int faccessat (int __fd, const char *__file, int __type, int __flag) +- __THROW __wur; ++ __THROW __nonnull ((2)) __wur; + #endif /* Use GNU. */ + + +@@ -510,7 +510,7 @@ extern int lchown (const char *__file, __uid_t __owner, __gid_t __group) + on. */ + extern int fchownat (int __fd, const char *__file, __uid_t __owner, + __gid_t __group, int __flag) +- __THROW __wur; ++ __THROW __nonnull ((2)) __wur; + #endif /* Use GNU. */ + + /* Change the process's working directory to PATH. */ + +commit e58294a5a7e37971ab3827efbd0469ac6a19e4b8 +Author: Sam James +Date: Sat Aug 29 16:54:46 2026 +0100 + + io: drop nonnull attribute for fchmodat, faccessat, fchownat's path argument [BZ #34313] + + Since Linux 6.11, AT_EMPTY_PATH can be used for a NULL path argument, so + the nonnull attribute is no longer sound. Drop it. + + This bug was worked around in gnulib's 6db27b4dd36eda618db20e997ff56bbed7fce3cb. + + See also 55618e13968a60b89e5b226a23afaa8f17c5ef48 which fixed fstatat + in glibc. + + Bug: https://sourceware.org/PR34313 + Reviewed-by: Paul Eggert + (cherry picked from commit 5bcfeca12b295908453c9045965842feb9e63ced) + +diff --git a/io/sys/stat.h b/io/sys/stat.h +index 3069e187b0..ff70b69f59 100644 +--- a/io/sys/stat.h ++++ b/io/sys/stat.h +@@ -370,7 +370,7 @@ extern int fchmod (int __fd, __mode_t __mode) __THROW; + the directory FD is open on. */ + extern int fchmodat (int __fd, const char *__file, __mode_t __mode, + int __flag) +- __THROW __nonnull ((2)) __wur; ++ __THROW __wur; + #endif /* Use ATFILE. */ + + +diff --git a/posix/unistd.h b/posix/unistd.h +index 06a6a88b52..dc5c2ec009 100644 +--- a/posix/unistd.h ++++ b/posix/unistd.h +@@ -307,7 +307,7 @@ extern int execveat (int __fd, const char *__path, char *const __argv[], + If AT_EACCESS is set in FLAG, then use effective IDs like `eaccess', + otherwise use real IDs like `access'. */ + extern int faccessat (int __fd, const char *__file, int __type, int __flag) +- __THROW __nonnull ((2)) __wur; ++ __THROW __wur; + #endif /* Use GNU. */ + + +@@ -510,7 +510,7 @@ extern int lchown (const char *__file, __uid_t __owner, __gid_t __group) + on. */ + extern int fchownat (int __fd, const char *__file, __uid_t __owner, + __gid_t __group, int __flag) +- __THROW __nonnull ((2)) __wur; ++ __THROW __wur; + #endif /* Use GNU. */ + + /* Change the process's working directory to PATH. */ + +commit e01330dcec96ddc3495bb3c62fe59e4fc9bbe083 +Author: Sam James +Date: Sat Aug 29 17:02:29 2026 +0100 + + fcntl: drop nonnull attribute for openat, openat2's path argument [BZ #34313] + + .. and openat64. + + Linux 7.2 (31cf44efa6df72a524b40adefb80539f3a4e13ba) allows openat, openat2 + to take a NULL path with the new O_EMPTYPATH flag, so the nonnull attribute + is no longer sound. Drop it. + + Bug: https://sourceware.org/PR34313 + Reviewed-by: Paul Eggert + (cherry picked from commit f750ffab2f76ae10575cde110b4069ae7009667f) + +diff --git a/include/fcntl.h b/include/fcntl.h +index be435047bc..86890a5b3d 100644 +--- a/include/fcntl.h ++++ b/include/fcntl.h +@@ -20,11 +20,9 @@ extern int __fcntl (int __fd, int __cmd, ...); + libc_hidden_proto (__fcntl) + extern int __fcntl64 (int __fd, int __cmd, ...) attribute_hidden; + libc_hidden_proto (__fcntl64) +-extern int __openat (int __fd, const char *__file, int __oflag, ...) +- __nonnull ((2)); ++extern int __openat (int __fd, const char *__file, int __oflag, ...); + libc_hidden_proto (__openat) +-extern int __openat64 (int __fd, const char *__file, int __oflag, ...) +- __nonnull ((2)); ++extern int __openat64 (int __fd, const char *__file, int __oflag, ...); + libc_hidden_proto (__openat64) + + extern int __open_2 (const char *__path, int __oflag); +diff --git a/io/bits/fcntl2.h b/io/bits/fcntl2.h +index f076cc3367..074c16b204 100644 +--- a/io/bits/fcntl2.h ++++ b/io/bits/fcntl2.h +@@ -135,18 +135,14 @@ open64 (__fortify_clang_overload_arg (const char *, ,__path), int __oflag, + + #ifdef __USE_ATFILE + # ifndef __USE_FILE_OFFSET64 +-extern int __openat_2 (int __fd, const char *__path, int __oflag) +- __nonnull ((2)); ++extern int __openat_2 (int __fd, const char *__path, int __oflag); + extern int __REDIRECT (__openat_alias, (int __fd, const char *__path, +- int __oflag, ...), openat) +- __nonnull ((2)); ++ int __oflag, ...), openat); + # else + extern int __REDIRECT (__openat_2, (int __fd, const char *__path, +- int __oflag), __openat64_2) +- __nonnull ((2)); ++ int __oflag), __openat64_2); + extern int __REDIRECT (__openat_alias, (int __fd, const char *__path, +- int __oflag, ...), openat64) +- __nonnull ((2)); ++ int __oflag, ...), openat64); + # endif + + # ifdef __va_arg_pack_len +diff --git a/io/fcntl.h b/io/fcntl.h +index d0ad4d6652..3d90cc9adb 100644 +--- a/io/fcntl.h ++++ b/io/fcntl.h +@@ -230,19 +230,17 @@ extern int open64 (const char *__file, int __oflag, ...) __nonnull ((1)); + This function is a cancellation point and therefore not marked with + __THROW. */ + # ifndef __USE_FILE_OFFSET64 +-extern int openat (int __fd, const char *__file, int __oflag, ...) +- __nonnull ((2)); ++extern int openat (int __fd, const char *__file, int __oflag, ...); + # else + # ifdef __REDIRECT + extern int __REDIRECT (openat, (int __fd, const char *__file, int __oflag, +- ...), openat64) __nonnull ((2)); ++ ...), openat64); + # else + # define openat openat64 + # endif + # endif + # ifdef __USE_LARGEFILE64 +-extern int openat64 (int __fd, const char *__file, int __oflag, ...) +- __nonnull ((2)); ++extern int openat64 (int __fd, const char *__file, int __oflag, ...); + # endif + #endif + +diff --git a/sysdeps/unix/sysv/linux/bits/fcntl-linux-fortify.h b/sysdeps/unix/sysv/linux/bits/fcntl-linux-fortify.h +index 4c8f3a874e..b12b5b75da 100644 +--- a/sysdeps/unix/sysv/linux/bits/fcntl-linux-fortify.h ++++ b/sysdeps/unix/sysv/linux/bits/fcntl-linux-fortify.h +@@ -25,7 +25,7 @@ + extern int __REDIRECT (__openat2_alias, (int __dfd, const char *__filename, + const struct open_how *__how, + size_t __usize), openat2) +- __nonnull ((2, 3)); ++ __nonnull ((3)); + + #if !__fortify_use_clang + __errordecl (__openat2_invalid_size, +diff --git a/sysdeps/unix/sysv/linux/bits/fcntl-linux.h b/sysdeps/unix/sysv/linux/bits/fcntl-linux.h +index 587b815124..6aaa6cc4e7 100644 +--- a/sysdeps/unix/sysv/linux/bits/fcntl-linux.h ++++ b/sysdeps/unix/sysv/linux/bits/fcntl-linux.h +@@ -485,7 +485,7 @@ extern int open_by_handle_at (int __mountdirfd, struct file_handle *__handle, + extern int openat2 (int __dfd, const char * __filename, + const struct open_how * __how, + __SIZE_TYPE__ __usize) +- __nonnull ((2, 3)); ++ __nonnull ((3)); + + #endif /* use GNU */ + + +commit 1f5026241027260e9280039698bca031484c82ad +Author: Adhemerval Zanella +Date: Mon Sep 14 17:08:03 2026 -0300 + + resolv: Fix assertion failure on search list truncation [BZ 31026, CVE-2026-8674] + + update_from_conf copies the search list into the 256-byte + resp->defdname and truncates it when an entry does not fit, then + asserts that resolv_conf_matches accepts the result. + + The truncation check there compared the accumulated size against + sizeof (resp->dnsrch) (the pointer array) instead of resp->defdname, + and the empty-list case did not account for a first entry that does + not fit at all. A long search domain in resolv.conf or LOCALDOMAIN + thus aborts any process using the resolver. + + Check whether the entry fits in the remaining defdname space, matching + alloc_buffer_copy_string, and also accept an empty resp->dnsrch when + the first entry is too long. Add tests covering both cases through + the search and domain directives. + + Checked on x86_64-linux-gnu and i686-linux-gnu. + Reviewed-by: Florian Weimer + + (cherry picked from commit 506ea57086bfb9ce3daff1c14246a1cb532aba0a) + +diff --git a/resolv/resolv_conf.c b/resolv/resolv_conf.c +index d33f32ac1a..a688d4223b 100644 +--- a/resolv/resolv_conf.c ++++ b/resolv/resolv_conf.c +@@ -281,8 +281,12 @@ resolv_conf_matches (const struct __res_state *resp, + { + if (resp->dnsrch[0] == NULL) + { +- /* Empty search list. No default domain name. */ +- return conf->search_list_size == 0 && resp->defdname[0] == '\0'; ++ /* Empty search list, or the first entry does not fit in ++ resp->defdname. No default domain name. */ ++ return resp->defdname[0] == '\0' ++ && (conf->search_list_size == 0 ++ || (strlen (conf->search_list[0]) + 1 ++ > sizeof (resp->defdname))); + } + + if (resp->dnsrch[0] != resp->defdname) +@@ -309,11 +313,12 @@ resolv_conf_matches (const struct __res_state *resp, + } + else + { +- /* resp->dnsrch is truncated if the number of elements +- exceeds MAXDNSRCH, or if the combined storage space for +- the search list exceeds what can be stored in +- resp->defdname. */ +- if (i == MAXDNSRCH || search_list_size > sizeof (resp->dnsrch)) ++ /* resp->dnsrch is truncated if the number of elements exceeds ++ MAXDNSRCH, or if conf->search_list[i] does not fit in the ++ remaining space of resp->defdname. */ ++ if (i == MAXDNSRCH ++ || (search_list_size + strlen (conf->search_list[i]) + 1 ++ > sizeof (resp->defdname))) + break; + /* Otherwise, a mismatch indicates a match failure. */ + return false; +diff --git a/resolv/tst-resolv-res_init-skeleton.c b/resolv/tst-resolv-res_init-skeleton.c +index 1e4c59dac9..e51a532c7e 100644 +--- a/resolv/tst-resolv-res_init-skeleton.c ++++ b/resolv/tst-resolv-res_init-skeleton.c +@@ -724,6 +724,41 @@ struct test_case test_cases[] = + "nameserver 192.0.2.1\n" + "; nameserver[0]: [192.0.2.1]:53\n" + }, ++/* Search list entries which do not fit in the legacy 256-byte ++ resp->defdname buffer (bug 31026). LONG244 is 244 characters long, ++ so it does not fit after "example.com\0" (12 bytes). LONG256 is 256 ++ characters long, so it does not fit even as the first entry. */ ++#define LBL63 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ++#define LONG244 LBL63 "." LBL63 "." LBL63 "." \ ++ "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ++#define LONG256 LBL63 "." LBL63 "." LBL63 "." LBL63 "a" ++ {.name = "search list truncated at long entry after short entry", ++ .conf = "nameserver 192.0.2.1\n" ++ "search example.com " LONG244 "\n", ++ .expected = "search example.com\n" ++ "; search[0]: example.com\n" ++ "; search[1]: " LONG244 "\n" ++ "nameserver 192.0.2.1\n" ++ "; nameserver[0]: [192.0.2.1]:53\n" ++ }, ++ {.name = "search list truncated at long first entry", ++ .conf = "nameserver 192.0.2.1\n" ++ "search " LONG256 " example.com\n", ++ .expected = "; search[0]: " LONG256 "\n" ++ "; search[1]: example.com\n" ++ "nameserver 192.0.2.1\n" ++ "; nameserver[0]: [192.0.2.1]:53\n" ++ }, ++ {.name = "long first entry from the domain directive", ++ .conf = "nameserver 192.0.2.1\n" ++ "domain " LONG256 "\n", ++ .expected = "; search[0]: " LONG256 "\n" ++ "nameserver 192.0.2.1\n" ++ "; nameserver[0]: [192.0.2.1]:53\n" ++ }, ++#undef LONG256 ++#undef LONG244 ++#undef LBL63 + {.name = "trust-ad flag", + .conf = "options trust-ad\n" + "nameserver 192.0.2.1\n", + +commit 6b48c0b29c359dfa84c09c32373a48c8f663c304 +Author: Adhemerval Zanella +Date: Mon Aug 17 15:15:32 2026 -0300 + + elf: Open the normalized $ORIGIN rpath in AT_SECURE programs (BZ 34360) + + For AT_SECURE programs the loader honors $ORIGIN in DT_RPATH only when the + expansion is rooted in a trusted directory, but it validated the lexically + normalized path while opening the raw expansion. As "a/b/../c" only names + "a/c" when "b" is not a symlink, an attacker who controls a component of + $ORIGIN -- e.g. by hard-linking the setuid binary into an attacker-owned + directory -- can make the opened path escape the trusted directory even + though the check passed, loading an attacker-controlled object. + + Normalize the expansion in place and open that, so the path that is opened + is exactly the path that was validated. _dl_normalize_path rewrites the + string in place without ever advancing its write cursor past its read + cursor or appending, so it stays within the original storage. + + Add elf/tst-origin-secure as a regression test. + + Reviewed-by: Florian Weimer + (cherry picked from commit ed0c137b97eb940b4b64981e84ed806d3276edd9) + +diff --git a/elf/Makefile b/elf/Makefile +index f1a8a75fdc..a9cafed56d 100644 +--- a/elf/Makefile ++++ b/elf/Makefile +@@ -567,6 +567,7 @@ tests-internal += \ + tst-audit19a \ + tst-create_format1 \ + tst-dl-hwcaps_split \ ++ tst-dl-path-normalize \ + tst-dl_find_object \ + tst-dl_find_object-threads \ + tst-dlmopen2 \ +@@ -592,6 +593,7 @@ tests-container += \ + tst-dlopen-self-container \ + tst-dlopen-tlsmodid-container \ + tst-ldconfig-cache \ ++ tst-origin-secure \ + tst-pldd \ + tst-preload-pthread-libc \ + tst-ptrguard-static-dlopen \ +@@ -600,6 +602,7 @@ tests-container += \ + # tests-container + + test-srcs = \ ++ tst-origin-secure-victim \ + tst-pathopt \ + tst-sprof-basic \ + # tests-srcs +@@ -840,6 +843,7 @@ modules-names += \ + libtracemod3-1 \ + libtracemod4-1 \ + libtracemod5-1 \ ++ libtst-origin-secure-mod \ + ltglobmod1 \ + ltglobmod2 \ + neededobj1 \ +@@ -1035,6 +1039,7 @@ modules-names += \ + tst-nodeps2-mod \ + tst-non-directory-mod \ + tst-null-argv-lib \ ++ tst-origin-secure-evilmod \ + tst-p_alignmod-base \ + tst-p_alignmod3 \ + tst-ptrguard-static-dlopen-mod \ +@@ -3773,3 +3778,16 @@ $(objpfx)tst-dl-debug-exclude.out: tst-dl-debug-exclude.sh \ + $(objpfx)tst-recursive-tls > $@; \ + $(evaluate-test) + endif ++ ++LDFLAGS-libtst-origin-secure-mod.so += -Wl,-soname,libtst-origin-secure-mod.so ++LDFLAGS-tst-origin-secure-evilmod.so += -Wl,-soname,libtst-origin-secure-mod.so ++$(objpfx)tst-origin-secure-victim: $(objpfx)libtst-origin-secure-mod.so ++# The number of "../" here must match the layout invariants described in ++# tst-origin-secure.c. ++LDFLAGS-tst-origin-secure-victim += \ ++ -Wl,--no-as-needed \ ++ -Wl,-rpath,\$$ORIGIN/sub/../../../../..$(slibdir)/tst-origin-secure \ ++ -Wl,--disable-new-dtags ++$(objpfx)tst-origin-secure.out: $(objpfx)tst-origin-secure-victim \ ++ $(objpfx)libtst-origin-secure-mod.so \ ++ $(objpfx)tst-origin-secure-evilmod.so +diff --git a/elf/dl-load.c b/elf/dl-load.c +index 95404adae9..471478649b 100644 +--- a/elf/dl-load.c ++++ b/elf/dl-load.c +@@ -34,6 +34,7 @@ + #include + #include + #include ++#include + + #include "dynamic-link.h" + #include "get-dynamic-info.h" +@@ -91,67 +92,30 @@ static const size_t system_dirs_len[] = + }; + #define nsystem_dirs_len array_length (system_dirs_len) + ++/* Return true if the normalized path NPATH of length NLEN is rooted in one of ++ the trusted system directories. The system_dirs entries carry a trailing ++ '/'; NPATH matches an entry when it shares the entry's leading component ++ sequence and then either ends or continues with '/'. For instance, ++ "/lib64" and "/lib64/x" match "/lib64/" but "/lib64x" does not. */ + static bool +-is_trusted_path_normalize (const char *path, size_t len) ++path_is_trusted (const char *npath, size_t nlen) + { +- if (len == 0) +- return false; +- +- 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') +- { +- if (path[0] == '/') +- { +- if (path[1] == '.') +- { +- if (path[2] == '.' && (path[3] == '/' || path[3] == '\0')) +- { +- while (wnp > npath && *--wnp != '/') +- ; +- path += 3; +- continue; +- } +- else if (path[2] == '/' || path[2] == '\0') +- { +- path += 2; +- continue; +- } +- } +- +- if (wnp > npath && wnp[-1] == '/') +- { +- ++path; +- continue; +- } +- } +- +- *wnp++ = *path++; +- } +- +- 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. */ +- result = true; +- break; +- } ++ /* Compare against the entry without its trailing '/'. */ ++ size_t dirlen = system_dirs_len[idx] - 1; ++ ++ if (nlen >= dirlen ++ && memcmp (trun, npath, dirlen) == 0 ++ && (npath[dirlen] == '/' || npath[dirlen] == '\0')) ++ return true; + + trun += system_dirs_len[idx] + 1; + } + +- dl_scratch_buffer_free (&scratch); +- return result; ++ return false; + } + + /* Given a substring starting at INPUT, just after the DST '$' start +@@ -335,16 +299,21 @@ _dl_dst_substitute (struct link_map *l, const char *input, char *result) + checked for trust, the authors of the binaries themselves are + trusted to have designed this correctly. Only $ORIGIN is tested in + this way because it may be manipulated in some ways with hard +- links. */ +- if (__glibc_unlikely (check_for_trusted) +- && !is_trusted_path_normalize (result, wp - result)) +- { +- *result = '\0'; +- return result; +- } ++ links. ++ ++ _dl_normalize_path replaces the expansion with its normalized form ++ in place, so that the path that is opened is exactly the path that ++ was validated. */ + + *wp = '\0'; + ++ if (__glibc_unlikely (check_for_trusted)) ++ { ++ size_t nlen = _dl_normalize_path (result); ++ if (!path_is_trusted (result, nlen)) ++ *result = '\0'; ++ } ++ + return result; + } + +diff --git a/elf/dl-path-normalize.h b/elf/dl-path-normalize.h +new file mode 100644 +index 0000000000..ca4b14698c +--- /dev/null ++++ b/elf/dl-path-normalize.h +@@ -0,0 +1,118 @@ ++/* In-place lexical path normalization for the dynamic loader. ++ 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 ++ . */ ++ ++#ifndef _DL_PATH_NORMALIZE_H ++#define _DL_PATH_NORMALIZE_H ++ ++#include ++#include ++ ++/* Lexically normalize the null-terminated PATH in place and return the ++ length of the result (excluding the terminating NUL byte): ++ ++ - Runs of '/' are collapsed to a single '/'. ++ ++ - "." components are removed. ++ ++ - A ".." component removes the preceding component if there is one and it ++ is not itself a preserved "..". In an absolute path a surplus ".." at ++ the root is dropped ("/../a" normalizes to "/a"), in a relative path ++ leading ".." components are preserved ("../a" stays "../a", "a/../../b" ++ normalizes to "../b"). ++ ++ - The result has no trailing '/' except for the root path "/" itself ++ ("/a/" normalizes to "/a"). ++ ++ - The result is empty if and only if every component cancels or is removed ++ ("", ".", "a/.." all normalize to ""). ++ ++ The PATH is written in place, and the internal write cursor never runs ++ ahead of the read cursor. Only bytes within the strlen (PATH) + 1 storage ++ are accessed. */ ++static inline size_t ++_dl_normalize_path (char *path) ++{ ++ /* The root '/' of an absolute path is not removed. */ ++ char *pstart = path + (path[0] == '/'); ++ const char *rnp = pstart; ++ char *wnp = pstart; ++ /* End of the prefix a ".." may not remove. Either the root '/', or, for ++ relative paths, the original start of the string extended by any ++ preserved leading ".." components. */ ++ char *limit = pstart; ++ ++ while (*rnp != '\0') ++ { ++ /* Collapse consecutive separators. */ ++ if (*rnp == '/') ++ { ++ ++rnp; ++ continue; ++ } ++ ++ /* [RNP, REND) is the next input component. */ ++ const char *rend = rnp; ++ while (*rend != '\0' && *rend != '/') ++ ++rend; ++ size_t clen = rend - rnp; ++ ++ /* Drop '.' component. */ ++ if (clen == 1 && rnp[0] == '.') ++ ; ++ else if (clen == 2 && rnp[0] == '.' && rnp[1] == '.') ++ { ++ if (wnp > limit) ++ { ++ /* Remove the last component along with the '/' separating it ++ from its predecessor (the root '/' of an absolute path is ++ retained). */ ++ while (wnp > limit && wnp[-1] != '/') ++ --wnp; ++ if (wnp > pstart) ++ --wnp; ++ } ++ else if (pstart == path) ++ { ++ /* No component is left and the original path is relative: ++ keep the unresolvable ".." (it becomes part of the ++ preserved prefix). */ ++ if (wnp > pstart) ++ *wnp++ = '/'; ++ *wnp++ = '.'; ++ *wnp++ = '.'; ++ limit = wnp; ++ } ++ /* Otherwise the path is absolute and the surplus ".." at the ++ root is dropped ("/../a" normalizes to "/a"). */ ++ } ++ else ++ { ++ if (wnp > pstart) ++ *wnp++ = '/'; ++ memmove (wnp, rnp, clen); ++ wnp += clen; ++ } ++ ++ rnp = rend; ++ } ++ ++ *wnp = '\0'; ++ return wnp - path; ++} ++ ++#endif /* _DL_PATH_NORMALIZE_H */ +diff --git a/elf/libtst-origin-secure-mod.c b/elf/libtst-origin-secure-mod.c +new file mode 100644 +index 0000000000..8d7f372094 +--- /dev/null ++++ b/elf/libtst-origin-secure-mod.c +@@ -0,0 +1,25 @@ ++/* Module for tst-origin-secure (the "good" copy). ++ 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 "tst-origin-secure.h" ++ ++int ++origin_secure_id (void) ++{ ++ return ORIGIN_SECURE_ID_TRUSTED; ++} +diff --git a/elf/tst-dl-path-normalize.c b/elf/tst-dl-path-normalize.c +new file mode 100644 +index 0000000000..f6d45d654f +--- /dev/null ++++ b/elf/tst-dl-path-normalize.c +@@ -0,0 +1,142 @@ ++/* Unit tests for dl-path-normalize.h. ++ 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 ++ ++static void ++check_one_guarded (const char *input, const char *expected, bool before) ++{ ++ size_t size = strlen (input) + 1; ++ struct support_next_to_fault ntf ++ = before ? support_next_to_fault_allocate_before (size) ++ : support_next_to_fault_allocate (size); ++ memcpy (ntf.buffer, input, size); ++ ++ size_t len = _dl_normalize_path (ntf.buffer); ++ ++ TEST_COMPARE (len, strlen (ntf.buffer)); ++ TEST_COMPARE_STRING (ntf.buffer, expected); ++ ++ support_next_to_fault_free (&ntf); ++} ++ ++static void ++check_one (const char *input, const char *expected) ++{ ++ /* Check that _dl_normalize_path does not access the string outside the ++ input argument. It checks for both over-runs and under-runs (the ++ latter for the case of '..' expansions). */ ++ check_one_guarded (input, expected, false); ++ check_one_guarded (input, expected, true); ++} ++ ++static int ++do_test (void) ++{ ++ /* Absolute paths. */ ++ check_one ("/", "/"); ++ check_one ("//", "/"); ++ check_one ("///", "/"); ++ check_one ("////", "/"); ++ check_one ("/a", "/a"); ++ check_one ("/a/", "/a"); ++ check_one ("/a//", "/a"); ++ check_one ("//a//b//", "/a/b"); ++ check_one ("/.", "/"); ++ check_one ("/./", "/"); ++ check_one ("/./a", "/a"); ++ check_one ("/a/./b", "/a/b"); ++ check_one ("/a/.", "/a"); ++ check_one ("/..", "/"); ++ check_one ("/../", "/"); ++ check_one ("/../a", "/a"); ++ check_one ("/a/..", "/"); ++ check_one ("/a/../", "/"); ++ check_one ("/a/../..", "/"); ++ check_one ("/a/../b", "/b"); ++ check_one ("/a/../../b", "/b"); ++ check_one ("/a/b/../../c", "/c"); ++ check_one ("/a/b/../c", "/a/c"); ++ check_one ("/a/b/c/../..", "/a"); ++ check_one ("/usr/lib/../lib64", "/usr/lib64"); ++ check_one ("/usr/lib/../lib64/", "/usr/lib64"); ++ check_one ("/usr/lib/..//lib64/", "/usr/lib64"); ++ check_one ("/usr/lib/../../lib64/", "/lib64"); ++ ++ /* "." and ".." are special only as complete components. */ ++ check_one ("/a..", "/a.."); ++ check_one ("/..a", "/..a"); ++ check_one ("/a/...", "/a/..."); ++ check_one ("/.../a", "/.../a"); ++ check_one ("/a./b", "/a./b"); ++ check_one (".a", ".a"); ++ check_one ("a.", "a."); ++ check_one ("..a", "..a"); ++ check_one ("...", "..."); ++ ++ /* Relative paths. */ ++ check_one ("", ""); ++ check_one (".", ""); ++ check_one ("./", ""); ++ check_one ("..", ".."); ++ check_one ("../", ".."); ++ check_one ("a", "a"); ++ check_one ("a/", "a"); ++ check_one ("a//b", "a/b"); ++ check_one ("a..", "a.."); ++ check_one ("./a", "a"); ++ check_one ("./.", ""); ++ check_one ("./..", ".."); ++ ++ check_one ("a/..", ""); ++ check_one ("a/../", ""); ++ check_one ("ab/..", ""); ++ check_one (".a/..", ""); ++ check_one ("a./..", ""); ++ check_one (".../..", ""); ++ check_one ("a/./..", ""); ++ check_one ("a/b/..", "a"); ++ check_one ("abc/def/..", "abc"); ++ ++ /* Appending a component to an emptied relative output must not produce a ++ leading '/' (the path must stay relative). */ ++ check_one ("a/../b", "b"); ++ check_one ("a/.././b", "b"); ++ check_one ("a/../lib64/b", "lib64/b"); ++ ++ /* Leading ".." components of a relative path are preserved and stack ++ instead of cancelling each other; ordinary components may follow and be ++ removed again afterwards. */ ++ check_one ("../a", "../a"); ++ check_one ("../..", "../.."); ++ check_one ("../../..", "../../.."); ++ check_one ("../../a", "../../a"); ++ check_one ("../a/..", ".."); ++ check_one ("../../a/..", "../.."); ++ check_one ("a/../../b", "../b"); ++ check_one ("a/b/../../..", ".."); ++ ++ return 0; ++} ++ ++#include +diff --git a/elf/tst-origin-secure-evilmod.c b/elf/tst-origin-secure-evilmod.c +new file mode 100644 +index 0000000000..0913e32e21 +--- /dev/null ++++ b/elf/tst-origin-secure-evilmod.c +@@ -0,0 +1,28 @@ ++/* Module for tst-origin-secure (the attacker-controlled copy). ++ 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 "tst-origin-secure.h" ++ ++/* If the victim reports this copy, the loader opened the un-normalized rpath ++ and resolved it through the attacker's symlink -- i.e. the trusted-path ++ check was bypassed (bug 34360). */ ++int ++origin_secure_id (void) ++{ ++ return ORIGIN_SECURE_ID_ATTACKER; ++} +diff --git a/elf/tst-origin-secure-victim.c b/elf/tst-origin-secure-victim.c +new file mode 100644 +index 0000000000..601265aaa4 +--- /dev/null ++++ b/elf/tst-origin-secure-victim.c +@@ -0,0 +1,43 @@ ++/* Victim program for tst-origin-secure. ++ 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 "tst-origin-secure.h" ++ ++extern int __libc_enable_secure; ++ ++/* Report both which module was loaded and whether the loader ran in secure ++ mode, so the driver can tell a genuine trusted-path bypass apart from a run ++ that simply was not secure. ++ ++ The exit status is the combination of the ORIGIN_SECURE_STATUS_* bits: ++ ++ _NONE trusted copy, not secure ++ _ATTACKER attacker copy, not secure (the control run) ++ _SECURE trusted copy, secure (a fixed loader) ++ _SECURE | _ATTACKER attacker copy, secure (the bug: the raw ++ rpath was opened) */ ++int ++main (void) ++{ ++ int status = ORIGIN_SECURE_STATUS_NONE; ++ if (origin_secure_id () == ORIGIN_SECURE_ID_ATTACKER) ++ status |= ORIGIN_SECURE_STATUS_ATTACKER; ++ if (__libc_enable_secure != 0) ++ status |= ORIGIN_SECURE_STATUS_SECURE; ++ return status; ++} +diff --git a/elf/tst-origin-secure.c b/elf/tst-origin-secure.c +new file mode 100644 +index 0000000000..f304823b49 +--- /dev/null ++++ b/elf/tst-origin-secure.c +@@ -0,0 +1,203 @@ ++/* Test that AT_SECURE $ORIGIN rpath entries are looked up using the ++ normalized (trusted) path, not the raw expansion (bug 34360). ++ ++ 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 ++ . */ ++ ++ ++/* For a SUID/SGID program the loader only honors $ORIGIN in DT_RPATH when ++ the normalized expansion is rooted in a trusted directory. If the loader ++ opens the un-normalized string (that contains "../"), it might disagree ++ as soon as a path component is a symbolic link. ++ ++ This test builds the executable with the rpath: ++ ++ $ORIGIN/sub/../../../../..SLIBDIR/tst-origin-secure ++ ++ and runs it as BASE/a/b/victim, so $ORIGIN is BASE/a/b. Lexically the ++ five "../" pop $ORIGIN/sub back to "/" (BASE is /tmp/tst-origin-secure, ++ so $ORIGIN/sub is the five components tmp, tst-origin-secure, a, b, sub), ++ and the entry normalizes to the trusted SLIBDIR/tst-origin-secure. But ++ "sub" is a symlink pointing six levels deep under BASE, so opening the raw ++ string makes the kernel resolve the "../" through the symlink and land in ++ BASE/x1 SLIBDIR/tst-origin-secure instead. ++ ++ The "../" count in the rpath (see the Makefile) is therefore ++ depth(BASE) + 2 (for the "a/b" of $ORIGIN) + 1 (for "sub"); it is ++ independent of SLIBDIR, which is appended whole on both the raw and the ++ normalized side. ++ ++ A trusted copy of the module (ORIGIN_SECURE_ID_TRUSTED) is installed in ++ SLIBDIR/tst-origin-secure; an attacker copy (ORIGIN_SECURE_ID_ATTACKER) is ++ placed at the symlink-diverted location. The trusted subdirectory is ++ rooted under SLIBDIR (so it passes the trusted-path check) but is not ++ itself a default loader search directory. ++ ++ The victim reports, in its exit status, both which module it loaded and ++ whether it ran in secure mode. ++ ++ Secure mode is forced with glibc.rtld.enable_secure=1 so that no real ++ SUID/SGID binary is required. */ ++ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include "tst-origin-secure.h" ++ ++#define BASE "/tmp/tst-origin-secure" ++#define SONAME "libtst-origin-secure-mod.so" ++/* Subdirectory of the trusted SLIBDIR that the rpath normalizes to. It is ++ trusted (rooted under SLIBDIR) but not a default search directory. */ ++#define SUBDIR "tst-origin-secure" ++ ++static int ++run_victim (char *const *envp) ++{ ++ const char *victim = BASE "/a/b/victim"; ++ char *const argv[] = { (char *) victim, NULL }; ++ ++ struct support_capture_subprocess res ++ = support_capture_subprogram (victim, argv, envp); ++ /* The victim itself prints nothing; forward any loader diagnostics to ++ the test log. */ ++ if (res.err.length > 0) ++ printf ("info: victim stderr: %s\n", res.err.buffer); ++ int status = res.status; ++ support_capture_subprocess_free (&res); ++ return WIFEXITED (status) ? WEXITSTATUS (status) : -1; ++} ++ ++/* With SLIBDIR "/lib64" the container layout is: ++ ++ /lib64/tst-origin-secure/libtst-origin-secure-mod.so trusted copy (id 1) ++ BASE/a/b/victim the executable ++ BASE/a/b/sub -> BASE/x1/x2/x3/x4/x5/x6 six levels deep ++ BASE/x1/lib64/tst-origin-secure/libtst-origin-secure-mod.so ++ attacker copy (id 2) ++ BASE/x1/x2/x3/x4/x5/x6/ the symlink target ++ ++ The victim's rpath is $ORIGIN/sub + five "../" + /lib64/tst-origin-secure. */ ++static void ++do_prepare (int argc, char **argv) ++{ ++ const char *slibdir = support_slibdir_prefix; ++ const char *objelf = support_objdir_root; ++ ++ char *good_src = xasprintf ("%s/elf/libtst-origin-secure-mod.so", objelf); ++ char *evil_src = xasprintf ("%s/elf/tst-origin-secure-evilmod.so", objelf); ++ char *victim_src = xasprintf ("%s/elf/tst-origin-secure-victim", objelf); ++ ++ xmkdirp (BASE "/a/b", 0755); ++ xmkdirp (BASE "/x1/x2/x3/x4/x5/x6", 0755); ++ ++ /* Where the trusted copy lives (reached only via the normalized rpath, ++ SLIBDIR/SUBDIR) ... */ ++ char *good_dir = xasprintf ("%s/%s", slibdir, SUBDIR); ++ char *good_dst = xasprintf ("%s/%s", good_dir, SONAME); ++ xmkdirp (good_dir, 0755); ++ /* ... and where the raw, symlink-diverted lookup lands. */ ++ char *evil_dir = xasprintf ("%s/x1%s/%s", BASE, slibdir, SUBDIR); ++ char *evil_dst = xasprintf ("%s/%s", evil_dir, SONAME); ++ xmkdirp (evil_dir, 0755); ++ ++ /* support_copy_file preserves the source mode, so the victim stays ++ executable and the modules readable; no chmod is needed. */ ++ support_copy_file (good_src, good_dst); ++ support_copy_file (evil_src, evil_dst); ++ support_copy_file (victim_src, BASE "/a/b/victim"); ++ ++ unlink (BASE "/a/b/sub"); ++ xsymlink (BASE "/x1/x2/x3/x4/x5/x6", BASE "/a/b/sub"); ++ ++ free (good_src); ++ free (evil_src); ++ free (victim_src); ++ free (good_dir); ++ free (good_dst); ++ free (evil_dir); ++ free (evil_dst); ++} ++#define PREPARE do_prepare ++ ++static int ++do_test (void) ++{ ++ /* Control run: in normal mode $ORIGIN is honored without the trusted check, ++ so the raw rpath resolves through "sub" and the attacker copy is ++ loaded. */ ++ { ++ char *const env[] = { NULL }; ++ int rc = run_victim (env); ++ if (rc != ORIGIN_SECURE_STATUS_ATTACKER) ++ FAIL_EXIT1 ("control run returned status %d, expected %d (attacker " ++ "copy, not secure): the $ORIGIN layout does not reproduce " ++ "the divergence between the raw and the normalized rpath", ++ rc, ORIGIN_SECURE_STATUS_ATTACKER); ++ } ++ ++ /* Secure run: force AT_SECURE. A fixed loader normalizes the rpath to the ++ trusted SLIBDIR/SUBDIR and loads the trusted copy; a loader with the bug ++ opens the raw path, resolves "sub", and loads the attacker copy. */ ++ { ++ char *const env[] = { (char *) "GLIBC_TUNABLES=glibc.rtld.enable_secure=1", ++ NULL }; ++ int rc = run_victim (env); ++ switch (rc) ++ { ++ /* Secure, trusted copy loaded via the normalized rpath: fixed. */ ++ case ORIGIN_SECURE_STATUS_SECURE: ++ break; ++ ++ /* Secure, attacker copy loaded: the raw rpath was opened. */ ++ case ORIGIN_SECURE_STATUS_SECURE | ORIGIN_SECURE_STATUS_ATTACKER: ++ FAIL_EXIT1 ("secure-mode loader resolved the un-normalized rpath " ++ "through the attacker symlink (bug 34360)"); ++ ++ /* Not secure, attacker copy: exactly what the control run produced, so ++ the tunable did not engage and this run says nothing about the ++ trusted-path handling. */ ++ case ORIGIN_SECURE_STATUS_ATTACKER: ++ FAIL_UNSUPPORTED ("glibc.rtld.enable_secure=1 did not enable " ++ "secure mode (victim status %d)", rc); ++ ++ /* Not secure, yet the trusted copy was loaded, which is reachable only ++ through the normalized rpath, and only a secure loader normalizes it. ++ Fail rather than report UNSUPPORTED. */ ++ case ORIGIN_SECURE_STATUS_NONE: ++ FAIL_EXIT1 ("secure run loaded the trusted copy but the victim " ++ "reports not being secure: __libc_enable_secure is no " ++ "longer a valid proxy for secure mode"); ++ ++ /* Neither copy loaded: since the trusted copy is reachable only through ++ the normalized rpath, this means the rpath entry was not honored at ++ all. */ ++ default: ++ FAIL_EXIT1 ("secure run did not load the module via the normalized " ++ "rpath (victim status %d)", rc); ++ } ++ } ++ ++ return 0; ++} ++ ++#include +diff --git a/elf/tst-origin-secure.h b/elf/tst-origin-secure.h +new file mode 100644 +index 0000000000..2e2b944607 +--- /dev/null ++++ b/elf/tst-origin-secure.h +@@ -0,0 +1,41 @@ ++/* Definitions shared by the tst-origin-secure test, its victim and modules. ++ 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 ++ . */ ++ ++#ifndef _TST_ORIGIN_SECURE_H ++#define _TST_ORIGIN_SECURE_H 1 ++ ++enum ++ { ++ ORIGIN_SECURE_ID_TRUSTED = 1, /* The copy installed in the trusted ++ SLIBDIR. */ ++ ORIGIN_SECURE_ID_ATTACKER = 2, /* The copy reachable only by resolving the ++ "sub" symlink. */ ++ }; ++ ++extern int origin_secure_id (void); ++ ++enum ++ { ++ ORIGIN_SECURE_STATUS_NONE = 0, ++ ORIGIN_SECURE_STATUS_ATTACKER = 1 << 0, /* The victim loaded attacker ++ rather than the trusted. */ ++ ORIGIN_SECURE_STATUS_SECURE = 1 << 1, /* The loader ran the victim in ++ secure mode. */ ++ }; ++ ++#endif +diff --git a/elf/tst-origin-secure.root/postclean.req b/elf/tst-origin-secure.root/postclean.req +new file mode 100644 +index 0000000000..e69de29bb2 + +commit 1848099f063e99d4ffecbd7667766d54862398b9 +Author: Mark Wielaard +Date: Fri Sep 18 00:24:34 2026 +0200 + + stdlib: Don't call clearenv from __libc_setenv_freemem + + Since commit 7a61e7f557a9 ("stdlib: Make getenv thread-safe in more + cases") clearenv doesn't call any deallocation functions anymore. + __libc_setenv_freemem (called from __libc_freeres) now clears all + backing arrays. So there is no reason anymore to call clearenv from + __libc_setenv_freemem. + + Tested against valgrind memcheck with --run-libc-freeres=yes which is + the default. + + Reviewed-by: Florian Weimer + (cherry picked from commit b837aae83df8fe80c8977b5ed5c538aebd2b152a) + +diff --git a/stdlib/setenv.c b/stdlib/setenv.c +index 1f7a4498b0..cbf83f8f8e 100644 +--- a/stdlib/setenv.c ++++ b/stdlib/setenv.c +@@ -384,9 +384,6 @@ clearenv (void) + void + __libc_setenv_freemem (void) + { +- /* Remove all traces. */ +- clearenv (); +- + /* Clear all backing arrays. */ + while (__environ_array_list != NULL) + { diff --git a/pkgs/development/libraries/glibc/common.nix b/pkgs/development/libraries/glibc/common.nix index ab415d5b932f..e47148d725d2 100644 --- a/pkgs/development/libraries/glibc/common.nix +++ b/pkgs/development/libraries/glibc/common.nix @@ -51,7 +51,7 @@ let version = "2.44"; - patchSuffix = "-25"; + patchSuffix = "-50"; sha256 = "sha256-N/YA8r7zxegwAUcFlWiyouQKetbMxlzpQlVtSUKcxmc="; in @@ -69,7 +69,7 @@ stdenv.mkDerivation ( /* No tarballs for stable upstream branch, only https://sourceware.org/git/glibc.git and using git would complicate bootstrapping. $ git fetch --all -p && git checkout origin/release/2.42/master && git describe - glibc-2.44-25-gafd131806b + glibc-2.44-50-g1848099f06 $ git show --minimal --reverse glibc-2.44.. ':!ADVISORIES' > 2.44-master.patch To compare the archive contents zdiff can be used.