Version in base suite: 2.41-12+deb13u4 Base version: glibc_2.41-12+deb13u4 Target version: glibc_2.41-12+deb13u5 Base file: /srv/ftp-master.debian.org/ftp/pool/main/g/glibc/glibc_2.41-12+deb13u4.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/g/glibc/glibc_2.41-12+deb13u5.dsc changelog | 34 patches/git-updates.diff | 3460 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 3454 insertions(+), 40 deletions(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpzuavp971/glibc_2.41-12+deb13u4.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpzuavp971/glibc_2.41-12+deb13u5.dsc: no acceptable signature found diff -Nru glibc-2.41/debian/changelog glibc-2.41/debian/changelog --- glibc-2.41/debian/changelog 2026-06-29 21:37:52.000000000 +0000 +++ glibc-2.41/debian/changelog 2026-10-10 17:56:39.000000000 +0000 @@ -1,3 +1,37 @@ +glibc (2.41-12+deb13u5) trixie; urgency=medium + + * debian/patches/git-updates.diff: update from upstream stable branch: + - Cache cpuid results in ld.so for Intel CPUs. + - Fix a buffer overread in ns_sprintrrf with corrupted RDATA field + (CVE-2026-6238). Closes: #1135231. + - Fix an out-of-bounds write in ns_sprintrrf when printing TSIG records + (CVE-2026-5435). Closes: #1135230. + - Fix stack overflow in wordexp tilde expansion (CVE-2026-6791). + - Fix wordexp WRDE_APPEND to preserve state on non-NOSPACE errors + (CVE-2026-6368). Closes: #1144252. + - Fix out-of-bounds stack array write in tdelete (CVE-2026-19542). + - Fix a buffer overflow in strfmon right-justification padding + (CVE-2026-19499). Closes: #1145891. + - Fix a hang when decoding SHIFT_JISX0213 to UTF-32 (CVE-2026-77117). + Closes: #1145880. + - Fix a hand when decoding EUC_JISX0213 to UTF-32 (CVE-2026-80489). + Closes: #1145987. + - Fix a heap buffer overlow in how fopen() parses the ,ccs= mode suffix + (CVE-2026-18374). Closes: #1146721. + - Keep needed last transition to new type in TZif files generated by zic. + - Fix an assertion failure in the DNS stub resolver with a long search + domain (CVE-2026-8674). Closes: #1148168. + - Fix a stack overflow in nscd that can be triggered by large DNS responses + from malicious servers (CVE-2026-89092). Closes: #1147395. + - Fix a TOCTOU race condition in the dynamic loader (CVE-2026-86805). + Closes: #1148727. + - Fix a stack overflow in the dynamic loader (CVE-2026-95818). Closes: + #1148728. + - Fix one byte overread in POWER8 version of strncasecmp() + (CVE-2026-97399). Closes: #1149647 + + -- Aurelien Jarno Sat, 10 Oct 2026 19:56:39 +0200 + glibc (2.41-12+deb13u4) trixie; urgency=medium * debian/patches/git-updates.diff: update from upstream stable branch: diff -Nru glibc-2.41/debian/patches/git-updates.diff glibc-2.41/debian/patches/git-updates.diff --- glibc-2.41/debian/patches/git-updates.diff 2026-06-29 21:37:52.000000000 +0000 +++ glibc-2.41/debian/patches/git-updates.diff 2026-10-10 17:56:39.000000000 +0000 @@ -1212,7 +1212,7 @@ + +#include diff --git a/elf/Makefile b/elf/Makefile -index 4b1d0d8741..d8a0c0c4ae 100644 +index 4b1d0d8741..8a5aa0123a 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -34,7 +34,6 @@ routines = \ @@ -1279,7 +1279,12 @@ unload3 \ unload4 \ unload5 \ -@@ -532,6 +541,8 @@ tests-internal += \ +@@ -528,10 +537,13 @@ 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 \ tst-hash-collision3 \ @@ -1288,7 +1293,7 @@ tst-ptrguard1 \ tst-stackguard1 \ tst-tls-surplus \ -@@ -543,6 +554,10 @@ tests-internal += \ +@@ -543,15 +555,21 @@ tests-internal += \ unload2 \ # tests-internal @@ -1299,7 +1304,18 @@ tests-container += \ tst-dlopen-self-container \ tst-dlopen-tlsmodid-container \ -@@ -567,9 +582,11 @@ tests-execstack-yes = \ ++ tst-origin-secure \ + tst-pldd \ + tst-preload-pthread-libc \ + tst-rootdir \ + # tests-container + + test-srcs = \ ++ tst-origin-secure-victim \ + tst-pathopt \ + tst-sprof-basic \ + # tests-srcs +@@ -567,9 +585,11 @@ tests-execstack-yes = \ tst-execstack \ tst-execstack-needed \ tst-execstack-prog \ @@ -1312,7 +1328,15 @@ # tests-execstack-static-yes ifeq (yes,$(run-built-tests)) tests-execstack-special-yes = \ -@@ -863,6 +880,7 @@ modules-names += \ +@@ -765,6 +785,7 @@ modules-names += \ + libtracemod3-1 \ + libtracemod4-1 \ + libtracemod5-1 \ ++ libtst-origin-secure-mod \ + ltglobmod1 \ + ltglobmod2 \ + neededobj1 \ +@@ -863,6 +884,7 @@ modules-names += \ tst-auditmanymod8 \ tst-auditmanymod9 \ tst-auditmod-tlsdesc \ @@ -1320,7 +1344,7 @@ tst-auditmod1 \ tst-auditmod11 \ tst-auditmod12 \ -@@ -905,6 +923,9 @@ modules-names += \ +@@ -905,6 +927,9 @@ modules-names += \ tst-dlmopen1mod \ tst-dlopen-auditdup-auditmod \ tst-dlopen-auditdupmod \ @@ -1330,7 +1354,15 @@ tst-dlopen-tlsreinitmod1 \ tst-dlopen-tlsreinitmod2 \ tst-dlopen-tlsreinitmod3 \ -@@ -1003,6 +1024,7 @@ modules-names += \ +@@ -956,6 +981,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-recursive-tlsmallocmod \ +@@ -1003,6 +1029,7 @@ modules-names += \ tst-tls22-mod1-gnu2 \ tst-tls22-mod2 \ tst-tls22-mod2-gnu2 \ @@ -1338,7 +1370,7 @@ tst-tlsalign-lib \ tst-tlsgap-mod0 \ tst-tlsgap-mod1 \ -@@ -1033,6 +1055,9 @@ modules-names += \ +@@ -1033,6 +1060,9 @@ modules-names += \ tst-unique2mod1 \ tst-unique2mod2 \ tst-unwind-ctor-lib \ @@ -1348,7 +1380,7 @@ unload2dep \ unload2mod \ unload3mod1 \ -@@ -1144,6 +1169,10 @@ tests-pie += \ +@@ -1144,6 +1174,10 @@ tests-pie += \ tst-pie1 \ tst-pie2 \ # tests-pie @@ -1359,7 +1391,7 @@ ifneq (,$(load-address-ldflag)) tests += \ tst-pie-address \ -@@ -1159,6 +1188,10 @@ tests += \ +@@ -1159,6 +1193,10 @@ tests += \ tests-static += \ tst-pie-address-static \ # tests-static @@ -1370,7 +1402,7 @@ LDFLAGS-tst-pie-address-static += \ $(load-address-ldflag)=$(pde-load-address) endif -@@ -1988,6 +2021,9 @@ $(objpfx)tst-execstack.out: $(objpfx)tst-execstack-mod.so +@@ -1988,6 +2026,9 @@ $(objpfx)tst-execstack.out: $(objpfx)tst-execstack-mod.so CPPFLAGS-tst-execstack.c += -DUSE_PTHREADS=0 LDFLAGS-tst-execstack = -Wl,-z,noexecstack LDFLAGS-tst-execstack-mod.so = -Wl,-z,execstack @@ -1380,7 +1412,7 @@ $(objpfx)tst-execstack-needed: $(objpfx)tst-execstack-mod.so LDFLAGS-tst-execstack-needed = -Wl,-z,noexecstack -@@ -1996,7 +2032,18 @@ LDFLAGS-tst-execstack-prog = -Wl,-z,execstack +@@ -1996,7 +2037,18 @@ LDFLAGS-tst-execstack-prog = -Wl,-z,execstack CFLAGS-tst-execstack-prog.c += -Wno-trampolines CFLAGS-tst-execstack-mod.c += -Wno-trampolines @@ -1399,7 +1431,7 @@ CFLAGS-tst-execstack-prog-static.c += -Wno-trampolines ifeq (yes,$(build-hardcoded-path-in-tests)) -@@ -2074,6 +2121,7 @@ $(objpfx)tst-array5-static-cmp.out: tst-array5-static.exp \ +@@ -2074,6 +2126,7 @@ $(objpfx)tst-array5-static-cmp.out: tst-array5-static.exp \ CFLAGS-tst-pie1.c += $(pie-ccflag) CFLAGS-tst-pie2.c += $(pie-ccflag) @@ -1407,7 +1439,7 @@ CFLAGS-tst-pie-address.c += $(pie-ccflag) $(objpfx)tst-piemod1.so: $(libsupport) -@@ -2206,6 +2254,13 @@ $(objpfx)tst-dlmopen3.out: $(objpfx)tst-dlmopen1mod.so +@@ -2206,6 +2259,13 @@ $(objpfx)tst-dlmopen3.out: $(objpfx)tst-dlmopen1mod.so $(objpfx)tst-dlmopen4.out: $(objpfx)tst-dlmopen1mod.so @@ -1421,7 +1453,7 @@ $(objpfx)tst-audit1.out: $(objpfx)tst-auditmod1.so tst-audit1-ENV = LD_AUDIT=$(objpfx)tst-auditmod1.so -@@ -3189,6 +3244,9 @@ $(objpfx)tst-audit-tlsdesc.out: $(objpfx)tst-auditmod-tlsdesc.so +@@ -3189,6 +3249,9 @@ $(objpfx)tst-audit-tlsdesc.out: $(objpfx)tst-auditmod-tlsdesc.so tst-audit-tlsdesc-ENV = LD_AUDIT=$(objpfx)tst-auditmod-tlsdesc.so $(objpfx)tst-audit-tlsdesc-dlopen.out: $(objpfx)tst-auditmod-tlsdesc.so tst-audit-tlsdesc-dlopen-ENV = LD_AUDIT=$(objpfx)tst-auditmod-tlsdesc.so @@ -1431,7 +1463,7 @@ $(objpfx)tst-dlmopen-twice.out: \ $(objpfx)tst-dlmopen-twice-mod1.so \ -@@ -3354,6 +3412,13 @@ tst-tls22-mod1-gnu2.so-no-z-defs = yes +@@ -3354,6 +3417,13 @@ tst-tls22-mod1-gnu2.so-no-z-defs = yes tst-tls22-mod2.so-no-z-defs = yes tst-tls22-mod2-gnu2.so-no-z-defs = yes @@ -1445,7 +1477,7 @@ ifeq ($(have-test-cc-cflags-fsemantic-interposition),yes) # Compiler may default to -fno-semantic-interposition. These modules # must be compiled with -fsemantic-interposition. -@@ -3392,3 +3457,28 @@ $(objpfx)tst-nolink-libc-2: $(objpfx)tst-nolink-libc.o +@@ -3392,3 +3462,41 @@ $(objpfx)tst-nolink-libc-2: $(objpfx)tst-nolink-libc.o -Wl,--dynamic-linker=$(objpfx)ld.so $(objpfx)tst-nolink-libc-2.out: $(objpfx)tst-nolink-libc-2 $(objpfx)ld.so $< > $@ 2>&1; $(evaluate-test) @@ -1474,6 +1506,19 @@ + $(objpfx)tst-dlopen-constructor-null-mod2.so +$(objpfx)tst-dlopen-constructor-null-mod2.so: \ + $(objpfx)tst-dlopen-constructor-null-mod1.so ++ ++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-close.c b/elf/dl-close.c index 47bd3dab81..83e4f012b2 100644 --- a/elf/dl-close.c @@ -1946,10 +1991,114 @@ ph < ph_end; ++ph) if (ph->p_type == DLFO_EH_SEGMENT_TYPE) diff --git a/elf/dl-load.c b/elf/dl-load.c -index f905578a65..7b21f1dffa 100644 +index f905578a65..0d141a61c1 100644 --- a/elf/dl-load.c +++ b/elf/dl-load.c -@@ -921,8 +921,7 @@ _dl_notify_new_object (int mode, Lmid_t nsid, struct link_map *l) +@@ -33,6 +33,7 @@ + #include + #include + #include ++#include + + /* Type for the buffer we put the ELF header and hopefully the program + header. This buffer does not really have to be too large. In most +@@ -114,54 +115,24 @@ 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; +- +- char *npath = (char *) alloca (len + 2); +- 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++ = '/'; +- + 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. */ ++ /* 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; +@@ -351,16 +322,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; + } + +@@ -921,8 +897,7 @@ _dl_notify_new_object (int mode, Lmid_t nsid, struct link_map *l) /* Notify the debugger we have added some objects. We need to call _dl_debug_initialize in a static program in case dynamic linking has not been used before. */ @@ -1959,7 +2108,7 @@ LIBC_PROBE (map_start, 2, nsid, r); } else -@@ -945,7 +944,7 @@ struct link_map * +@@ -945,7 +920,7 @@ struct link_map * _dl_map_object_from_fd (const char *name, const char *origname, int fd, struct filebuf *fbp, char *realname, struct link_map *loader, int l_type, int mode, @@ -1968,7 +2117,7 @@ { struct link_map *l = NULL; const ElfW(Ehdr) *header; -@@ -2180,7 +2179,7 @@ _dl_map_object (struct link_map *loader, const char *name, +@@ -2180,7 +2155,7 @@ _dl_map_object (struct link_map *loader, const char *name, void *stack_end = __libc_stack_end; return _dl_map_object_from_fd (name, origname, fd, &fb, realname, loader, @@ -2050,6 +2199,130 @@ } /* Never allow loading a DSO in a namespace which is empty. Such direct placements is only causing problems. Also don't allow +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/dl-reloc-static-pie.c b/elf/dl-reloc-static-pie.c index e34bf5f7ce..758bf9893e 100644 --- a/elf/dl-reloc-static-pie.c @@ -2128,6 +2401,37 @@ } /* When there is a DT_VERNEED entry with libc.so on DT_NEEDED, issue +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/rtld.c b/elf/rtld.c index 00bec15316..18c96bd5c1 100644 --- a/elf/rtld.c @@ -2490,6 +2794,154 @@ + + return LAV_CURRENT; +} +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-dlmopen4-nonpic.c b/elf/tst-dlmopen4-nonpic.c new file mode 100644 index 0000000000..ad4e409953 @@ -3176,6 +3628,348 @@ + 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 diff --git a/elf/tst-pie-bss-static.c b/elf/tst-pie-bss-static.c new file mode 100644 index 0000000000..5df542f9ee @@ -3746,24 +4540,27 @@ +run_program_env="$run_program_env LC_ALL=en_US.UTF-8" +run_test_array diff --git a/iconvdata/Makefile b/iconvdata/Makefile -index 5a2abeea24..cc689f63e9 100644 +index 5a2abeea24..36f48749d2 100644 --- a/iconvdata/Makefile +++ b/iconvdata/Makefile -@@ -76,7 +76,7 @@ tests = bug-iconv1 bug-iconv2 tst-loading tst-e2big tst-iconv4 bug-iconv4 \ +@@ -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-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 -@@ -333,6 +333,8 @@ $(objpfx)bug-iconv15.out: $(addprefix $(objpfx), $(gconv-modules)) \ +@@ -333,6 +334,10 @@ $(objpfx)bug-iconv15.out: $(addprefix $(objpfx), $(gconv-modules)) \ $(addprefix $(objpfx),$(modules.so)) $(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)) \ @@ -3780,6 +4577,20 @@ { \ /* The second character is not available. */ \ result = __GCONV_INCOMPLETE_INPUT; \ +diff --git a/iconvdata/euc-jisx0213.c b/iconvdata/euc-jisx0213.c +index 9c3f28da2d..f0305c113a 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; \ diff --git a/iconvdata/ibm1364.c b/iconvdata/ibm1364.c index 45c62acee5..244c61ad7a 100644 --- a/iconvdata/ibm1364.c @@ -3894,6 +4705,20 @@ \ if (__builtin_expect (ch, 0) == SO) \ { \ +diff --git a/iconvdata/shift_jisx0213.c b/iconvdata/shift_jisx0213.c +index 364298dcff..834fa81322 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; \ diff --git a/iconvdata/tst-bug33980.c b/iconvdata/tst-bug33980.c new file mode 100644 index 0000000000..c9693e0efe @@ -4053,6 +4878,136 @@ +} + +#include +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 diff --git a/include/ctype.h b/include/ctype.h index 493a6f80ce..a15e5b6678 100644 --- a/include/ctype.h @@ -4139,6 +5094,22 @@ #if __ELF_NATIVE_CLASS == 32 # define symbind symbind32 # define LA_SYMBIND "la_symbind32" +diff --git a/include/resolv.h b/include/resolv.h +index 4dbbac3800..d5ad9994b9 100644 +--- a/include/resolv.h ++++ b/include/resolv.h +@@ -70,6 +70,11 @@ libc_hidden_proto (__libc_res_nameinquery) + extern __typeof (__res_queriesmatch) __libc_res_queriesmatch; + libc_hidden_proto (__libc_res_queriesmatch) + ++extern const struct res_sym __p_class_syms[]; ++libresolv_hidden_proto (__p_class_syms) ++extern const struct res_sym __p_type_syms[]; ++libresolv_hidden_proto (__p_type_syms) ++ + /* Variant of res_hnok which operates on binary (but uncompressed) names. */ + bool __res_binary_hnok (const unsigned char *dn) attribute_hidden; + diff --git a/include/rpc/rpc.h b/include/rpc/rpc.h index f5cee6caef..ba967833ad 100644 --- a/include/rpc/rpc.h @@ -4178,7 +5149,7 @@ int __fcntl (int fd, int cmd, ...) diff --git a/libio/Makefile b/libio/Makefile -index e143ccdb2c..43ee8db06d 100644 +index e143ccdb2c..5b9929c3de 100644 --- a/libio/Makefile +++ b/libio/Makefile @@ -83,6 +83,7 @@ tests = \ @@ -4189,6 +5160,14 @@ bug-wmemstream1 \ bug-wsetpos \ test-fmemopen \ +@@ -104,6 +105,7 @@ tests = \ + tst-fgetc-after-eof \ + tst-fgetwc \ + tst-fgetws \ ++ tst-fopen-ccs-empty \ + tst-fopenloc2 \ + tst-fputws \ + tst-freopen \ diff --git a/libio/bug-wgenops-bz33998.c b/libio/bug-wgenops-bz33998.c new file mode 100644 index 0000000000..cc4067da99 @@ -4249,6 +5228,98 @@ +} + +#include +diff --git a/libio/fileops.c b/libio/fileops.c +index 775999deb3..18efe8e0c0 100644 +--- a/libio/fileops.c ++++ b/libio/fileops.c +@@ -306,12 +306,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); +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 diff --git a/libio/wgenops.c b/libio/wgenops.c index 0a11d1b1de..9e0b2c00ea 100644 --- a/libio/wgenops.c @@ -4869,6 +5940,84 @@ #define __MATHDECL_1_IMPL(type, function, suffix, args) \ extern type __MATH_PRECNAME(function,suffix) args __THROW #define __MATHDECL_1(type, function, suffix, args) \ +diff --git a/misc/tsearch.c b/misc/tsearch.c +index d15260baed..350fe15bf0 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); + } diff --git a/misc/tst-preadvwritev2-common.c b/misc/tst-preadvwritev2-common.c index ff1007d6d2..5182fcdce0 100644 --- a/misc/tst-preadvwritev2-common.c @@ -4984,6 +6133,228 @@ break; /* Record the ownership. */ +diff --git a/nscd/aicache.c b/nscd/aicache.c +index 6f47236627..1ac86b86d8 100644 +--- a/nscd/aicache.c ++++ b/nscd/aicache.c +@@ -93,7 +93,9 @@ addhstaiX (struct database_dyn *db, int fd, request_header *req, + int32_t ttl = INT32_MAX; + ssize_t total = 0; + char *key_copy = NULL; +- bool alloca_used = false; ++ /* If not NULL, dataset is a temporary dataset not inserted into the ++ database, and needs to be free'd. */ ++ void *dataset_alloc = NULL; + time_t timeout = MAX_TIMEOUT_VALUE; + + while (!no_more) +@@ -164,10 +166,12 @@ addhstaiX (struct database_dyn *db, int fd, request_header *req, + /* We cannot permanently add the result in the moment. But + we can provide the result as is. Store the data in some + temporary memory. */ +- dataset = (struct dataset *) alloca (total + req->key_len); ++ dataset = (struct dataset *) malloc (total + req->key_len); ++ if (dataset == NULL) ++ goto out; + + /* We cannot add this record to the permanent database. */ +- alloca_used = true; ++ dataset_alloc = dataset; + } + + /* Fill in the address and address families. */ +@@ -336,10 +340,12 @@ addhstaiX (struct database_dyn *db, int fd, request_header *req, + /* We cannot permanently add the result in the moment. But + we can provide the result as is. Store the data in some + temporary memory. */ +- dataset = (struct dataset *) alloca (total + req->key_len); ++ dataset = (struct dataset *) malloc (total + req->key_len); ++ if (dataset == NULL) ++ goto out; + + /* We cannot add this record to the permanent database. */ +- alloca_used = true; ++ dataset_alloc = dataset; + } + + /* Fill in the address and address families. */ +@@ -391,8 +397,7 @@ addhstaiX (struct database_dyn *db, int fd, request_header *req, + resp)) == 0) + { + /* The data has not changed. We will just bump the +- timeout value. Note that the new record has been +- allocated on the stack and need not be freed. */ ++ timeout value. */ + dh->timeout = dataset->head.timeout; + dh->ttl = dataset->head.ttl; + ++dh->nreloads; +@@ -410,7 +415,9 @@ addhstaiX (struct database_dyn *db, int fd, request_header *req, + key_copy = (char *) newp + (key_copy - (char *) dataset); + + dataset = memcpy (newp, dataset, total + req->key_len); +- alloca_used = false; ++ ++ free (dataset_alloc); ++ dataset_alloc = NULL; + } + + /* Mark the old record as obsolete. */ +@@ -488,7 +495,7 @@ next_nip: + out: + __resolv_context_put (ctx); + +- if (dataset != NULL && !alloca_used) ++ if (dataset != NULL && dataset_alloc == NULL) + { + /* If necessary, we also propagate the data to disk. */ + if (db->persistent) +@@ -514,6 +521,8 @@ next_nip: + scratch_buffer_free (&tmpbuf4); + scratch_buffer_free (&canonbuf); + ++ free (dataset_alloc); ++ + return timeout; + } + +diff --git a/nscd/hstcache.c b/nscd/hstcache.c +index 7b3a8ab1ea..11af5cb34a 100644 +--- a/nscd/hstcache.c ++++ b/nscd/hstcache.c +@@ -15,7 +15,6 @@ + You should have received a copy of the GNU General Public License + along with this program; if not, see . */ + +-#include + #include + #include + #include +@@ -187,7 +186,7 @@ cache_addhst (struct database_dyn *db, int fd, request_header *req, + /* Determine the I/O structure. */ + size_t h_name_len = strlen (hst->h_name) + 1; + size_t h_aliases_cnt; +- uint32_t *h_aliases_len; ++ uint32_t *h_aliases_len = NULL; + size_t h_addr_list_cnt; + char *addresses; + char *aliases; +@@ -201,7 +200,13 @@ cache_addhst (struct database_dyn *db, int fd, request_header *req, + for (cnt = 0; hst->h_aliases[cnt] != NULL; ++cnt) + ++h_aliases_cnt; + /* Determine the length of all aliases. */ +- h_aliases_len = (uint32_t *) alloca (h_aliases_cnt * sizeof (uint32_t)); ++ if (h_aliases_cnt > 0) ++ { ++ h_aliases_len = (uint32_t *) reallocarray (NULL, h_aliases_cnt, ++ sizeof (uint32_t)); ++ if (h_aliases_len == NULL) ++ return MAX_TIMEOUT_VALUE; ++ } + total = 0; + for (cnt = 0; cnt < h_aliases_cnt; ++cnt) + { +@@ -215,8 +220,11 @@ cache_addhst (struct database_dyn *db, int fd, request_header *req, + ++h_addr_list_cnt; + + if (h_addr_list_cnt == 0) +- /* Invalid entry. */ +- return MAX_TIMEOUT_VALUE; ++ { ++ /* Invalid entry. */ ++ free (h_aliases_len); ++ return MAX_TIMEOUT_VALUE; ++ } + + total += (sizeof (struct dataset) + + h_name_len +@@ -227,8 +235,10 @@ cache_addhst (struct database_dyn *db, int fd, request_header *req, + change. Allocate memory on the cache since it is likely + discarded anyway. If it turns out to be necessary to have a + new record we can still allocate real memory. */ +- bool alloca_used = false; + dataset = NULL; ++ /* If not NULL, dataset is a temporary dataset not inserted into ++ the database, and needs to be free'd. */ ++ void *dataset_alloc = NULL; + + /* If the record contains more than one IP address (used for + load balancing etc) don't cache the entry. This is something +@@ -244,10 +254,15 @@ cache_addhst (struct database_dyn *db, int fd, request_header *req, + /* We cannot permanently add the result in the moment. But + we can provide the result as is. Store the data in some + temporary memory. */ +- dataset = (struct dataset *) alloca (total + req->key_len); ++ dataset = (struct dataset *) malloc (total + req->key_len); ++ if (dataset == NULL) ++ { ++ free (h_aliases_len); ++ return MAX_TIMEOUT_VALUE; ++ } + + /* We cannot add this record to the permanent database. */ +- alloca_used = true; ++ dataset_alloc = dataset; + } + + timeout = datahead_init_pos (&dataset->head, total + req->key_len, +@@ -270,7 +285,8 @@ cache_addhst (struct database_dyn *db, int fd, request_header *req, + cp = dataset->strdata; + + cp = mempcpy (cp, hst->h_name, h_name_len); +- cp = mempcpy (cp, h_aliases_len, h_aliases_cnt * sizeof (uint32_t)); ++ if (h_aliases_cnt > 0) ++ cp = mempcpy (cp, h_aliases_len, h_aliases_cnt * sizeof (uint32_t)); + + /* The normal addresses first. */ + addresses = cp; +@@ -282,6 +298,8 @@ cache_addhst (struct database_dyn *db, int fd, request_header *req, + for (cnt = 0; cnt < h_aliases_cnt; ++cnt) + cp = mempcpy (cp, hst->h_aliases[cnt], h_aliases_len[cnt]); + ++ free (h_aliases_len); ++ + assert (cp + == dataset->strdata + total - offsetof (struct dataset, + strdata)); +@@ -307,8 +325,7 @@ cache_addhst (struct database_dyn *db, int fd, request_header *req, + dh->allocsize - offsetof (struct dataset, resp)) == 0) + { + /* The data has not changed. We will just bump the +- timeout value. Note that the new record has been +- allocated on the stack and need not be freed. */ ++ timeout value. */ + assert (h_addr_list_cnt == 1); + dh->ttl = dataset->head.ttl; + dh->timeout = dataset->head.timeout; +@@ -334,7 +351,9 @@ cache_addhst (struct database_dyn *db, int fd, request_header *req, + key_copy = (char *) newp + (key_copy - (char *) dataset); + + dataset = memcpy (newp, dataset, total + req->key_len); +- alloca_used = false; ++ ++ free (dataset_alloc); ++ dataset_alloc = NULL; + } + } + +@@ -362,7 +381,7 @@ cache_addhst (struct database_dyn *db, int fd, request_header *req, + the current cache handling cannot handle and it is more than + questionable whether it is worthwhile complicating the cache + handling just for handling such a special case. */ +- if (! alloca_used) ++ if (dataset_alloc == NULL) + { + /* If necessary, we also propagate the data to disk. */ + if (db->persistent) +@@ -393,6 +412,8 @@ cache_addhst (struct database_dyn *db, int fd, request_header *req, + + pthread_rwlock_unlock (&db->lock); + } ++ free (dataset_alloc); ++ dataset_alloc = NULL; + } + + if (__builtin_expect (!all_written, 0) && debug_level > 0) diff --git a/nss/Makefile b/nss/Makefile index 3ee51f309e..5dae3a0fca 100644 --- a/nss/Makefile @@ -5488,7 +6859,7 @@ + +#include diff --git a/posix/Makefile b/posix/Makefile -index a650abf598..9360bc7ff8 100644 +index a650abf598..5358a3f8c1 100644 --- a/posix/Makefile +++ b/posix/Makefile @@ -303,6 +303,7 @@ tests := \ @@ -5499,15 +6870,25 @@ tst-regcomp-truncated \ tst-regex \ tst-regex2 \ -@@ -325,6 +326,7 @@ tests := \ +@@ -324,7 +325,9 @@ tests := \ + tst-wait3 \ tst-wait4 \ tst-waitid \ ++ tst-wordexp-append \ tst-wordexp-nocmd \ + tst-wordexp-reuse \ tstgetopt \ # tests -@@ -453,6 +455,8 @@ generated += \ +@@ -352,6 +355,7 @@ tests-internal := \ + tests-container := \ + bug-ga2 \ + tst-vfork3 \ ++ tst-wordexp-tilde \ + # tests-container + + tests-time64 := \ +@@ -453,6 +457,8 @@ generated += \ tst-rxspencer-no-utf8.mtrace \ tst-vfork3-mem.out \ tst-vfork3.mtrace \ @@ -5516,7 +6897,7 @@ # generated endif endif -@@ -488,6 +492,7 @@ tests-special += \ +@@ -488,6 +494,7 @@ tests-special += \ $(objpfx)tst-pcre-mem.out \ $(objpfx)tst-rxspencer-no-utf8-mem.out \ $(objpfx)tst-vfork3-mem.out \ @@ -5524,7 +6905,7 @@ # tests-special endif endif -@@ -771,3 +776,10 @@ $(objpfx)posix-conf-vars-def.h: $(..)scripts/gen-posix-conf-vars.awk \ +@@ -771,3 +778,10 @@ $(objpfx)posix-conf-vars-def.h: $(..)scripts/gen-posix-conf-vars.awk \ $(make-target-directory) $(AWK) -f $(filter-out Makefile, $^) > $@.tmp mv -f $@.tmp $@ @@ -5771,6 +7152,405 @@ +} + +#include +diff --git a/posix/tst-wordexp-append.c b/posix/tst-wordexp-append.c +new file mode 100644 +index 0000000000..87f388f0a7 +--- /dev/null ++++ b/posix/tst-wordexp-append.c +@@ -0,0 +1,393 @@ ++/* Test for wordexp with WRDE_APPEND flag. ++ 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 ++ ++static unsigned int relocating_reallocs; ++ ++/* w_addword grows we_wordv with realloc, make every call guaranteed to ++ relocate the block. This makes BZ 34090 regression more deterministic. */ ++void * ++realloc (void *ptr, size_t size) ++{ ++ if (ptr == NULL) ++ return malloc (size); ++ if (size == 0) ++ { ++ free (ptr); ++ return NULL; ++ } ++ ++ void *new = malloc (size); ++ if (new == NULL) ++ return NULL; ++ ++ /* Copy only what is valid in the old block to avoid reading past it. */ ++ size_t old = malloc_usable_size (ptr); ++ memcpy (new, ptr, old < size ? old : size); ++ /* Clobber the old block so that a stale we_wordv pointer restored on the ++ error path reads garbage instead of the old contents, which might ++ otherwise survive intact and mask the bug. */ ++ memset (ptr, 0x5a, old); ++ free (ptr); ++ relocating_reallocs++; ++ return new; ++} ++ ++/* Verify that all words in we match the expected NULL-terminated ++ array. */ ++static void ++check_words (const wordexp_t *we, const char *const *expected) ++{ ++ size_t i; ++ for (i = 0; expected[i] != NULL; i++) ++ { ++ TEST_VERIFY (i < we->we_wordc); ++ TEST_COMPARE_STRING (we->we_wordv[we->we_offs + i], expected[i]); ++ } ++ TEST_COMPARE (we->we_wordc, i); ++} ++ ++#define CHECK_WORDS(we, ...) \ ++ do { \ ++ const char *const expected_[] = { __VA_ARGS__, NULL }; \ ++ check_words (we, expected_); \ ++ } while (0) ++ ++/* Test 1: WRDE_APPEND + WRDE_BADCHAR preserves we_wordc. */ ++static void ++test_append_badchar_preserves_count (void) ++{ ++ printf ("info: test_append_badchar_preserves_count\n"); ++ wordexp_t we = { 0 }; ++ ++ TEST_COMPARE (wordexp ("one two three", &we, 0), 0); ++ TEST_COMPARE (we.we_wordc, 3); ++ ++ size_t saved_count = we.we_wordc; ++ ++ /* ')' triggers WRDE_BADCHAR and "extra" would be a new word if the ++ expansion succeeded, exercising the w_addword path before the error ++ is detected. */ ++ TEST_COMPARE (wordexp ("extra )", &we, WRDE_APPEND), WRDE_BADCHAR); ++ TEST_COMPARE (we.we_wordc, saved_count); ++ ++ wordfree (&we); ++} ++ ++/* Test 2: WRDE_APPEND + WRDE_BADCHAR preserves the we_wordv pointer even ++ when internal realloc would move the buffer. */ ++static void ++test_append_badchar_preserves_pointer (void) ++{ ++ printf ("info: test_append_badchar_preserves_pointer\n"); ++ wordexp_t we = { 0 }; ++ ++ /* Use many words so that the initial we_wordv allocation is ++ non-trivial and a later realloc is more likely to move it. */ ++ TEST_COMPARE (wordexp ("a b c d e f g h", &we, 0), 0); ++ TEST_COMPARE (we.we_wordc, 8); ++ ++ char **saved_wordv = we.we_wordv; ++ size_t saved_count = we.we_wordc; ++ unsigned int saved_reallocs = relocating_reallocs; ++ ++ /* The interposed realloc guarantees the internal we_wordv buffer moves ++ during parsing, so the pointer-stability check below is meaningful. */ ++ TEST_COMPARE (wordexp ("append )", &we, WRDE_APPEND), WRDE_BADCHAR); ++ /* Verify that a relocating realloc actually happened during the failed ++ call, otherwise the pointer-stability check is vacuous. */ ++ TEST_VERIFY (relocating_reallocs > saved_reallocs); ++ TEST_COMPARE (we.we_wordc, saved_count); ++ TEST_VERIFY (we.we_wordv == saved_wordv); ++ ++ wordfree (&we); ++} ++ ++/* Test 3: After a failed WRDE_APPEND the original words are still accessible ++ and correct. */ ++static void ++test_append_badchar_words_intact (void) ++{ ++ printf ("info: test_append_badchar_words_intact\n"); ++ wordexp_t we = { 0 }; ++ ++ TEST_COMPARE (wordexp ("alpha beta gamma", &we, 0), 0); ++ CHECK_WORDS (&we, "alpha", "beta", "gamma"); ++ ++ TEST_COMPARE (wordexp ("delta )", &we, WRDE_APPEND), WRDE_BADCHAR); ++ ++ /* Words must still be intact. */ ++ CHECK_WORDS (&we, "alpha", "beta", "gamma"); ++ /* The NULL terminator must still be present. */ ++ TEST_VERIFY (we.we_wordv[we.we_offs + we.we_wordc] == NULL); ++ ++ wordfree (&we); ++} ++ ++/* Test 4: Successful WRDE_APPEND still works (regression test). */ ++static void ++test_append_success (void) ++{ ++ printf ("info: test_append_success\n"); ++ wordexp_t we = { 0 }; ++ ++ TEST_COMPARE (wordexp ("hello", &we, 0), 0); ++ TEST_COMPARE (we.we_wordc, 1); ++ ++ char **saved_wordv = we.we_wordv; ++ ++ TEST_COMPARE (wordexp ("world", &we, WRDE_APPEND), 0); ++ TEST_COMPARE (we.we_wordc, 2); ++ /* A successful append works on a fresh copy of the array, so the ++ caller-visible pointer must have changed. */ ++ TEST_VERIFY (we.we_wordv != saved_wordv); ++ CHECK_WORDS (&we, "hello", "world"); ++ ++ wordfree (&we); ++} ++ ++/* Test 5: Successful append after a failed append — the implementation must ++ recover and allow further use of the wordexp_t. */ ++static void ++test_append_success_after_failure (void) ++{ ++ printf ("info: test_append_success_after_failure\n"); ++ wordexp_t we = { 0 }; ++ ++ TEST_COMPARE (wordexp ("first", &we, 0), 0); ++ CHECK_WORDS (&we, "first"); ++ ++ TEST_COMPARE (wordexp ("bad |", &we, WRDE_APPEND), WRDE_BADCHAR); ++ ++ /* State must be exactly as before the failed call. */ ++ CHECK_WORDS (&we, "first"); ++ ++ /* A subsequent successful append must work. */ ++ TEST_COMPARE (wordexp ("second third", &we, WRDE_APPEND), 0); ++ CHECK_WORDS (&we, "first", "second", "third"); ++ ++ wordfree (&we); ++} ++ ++/* Test 6: Multiple consecutive failed appends do not corrupt state. */ ++static void ++test_append_multiple_failures (void) ++{ ++ printf ("info: test_append_multiple_failures\n"); ++ wordexp_t we = { 0 }; ++ ++ TEST_COMPARE (wordexp ("keep this", &we, 0), 0); ++ CHECK_WORDS (&we, "keep", "this"); ++ ++ size_t saved_count = we.we_wordc; ++ char **saved_wordv = we.we_wordv; ++ ++ /* Each of these bad characters must leave the state unchanged. */ ++ TEST_COMPARE (wordexp ("x )", &we, WRDE_APPEND), WRDE_BADCHAR); ++ TEST_COMPARE (wordexp ("x |", &we, WRDE_APPEND), WRDE_BADCHAR); ++ TEST_COMPARE (wordexp ("x ;", &we, WRDE_APPEND), WRDE_BADCHAR); ++ TEST_COMPARE (wordexp ("x &", &we, WRDE_APPEND), WRDE_BADCHAR); ++ TEST_COMPARE (wordexp ("x <", &we, WRDE_APPEND), WRDE_BADCHAR); ++ TEST_COMPARE (wordexp ("x >", &we, WRDE_APPEND), WRDE_BADCHAR); ++ ++ TEST_COMPARE (we.we_wordc, saved_count); ++ TEST_VERIFY (we.we_wordv == saved_wordv); ++ CHECK_WORDS (&we, "keep", "this"); ++ ++ wordfree (&we); ++} ++ ++/* Test 7: WRDE_APPEND with WRDE_SYNTAX error (unterminated quote) also ++ preserves state. */ ++static void ++test_append_syntax_error (void) ++{ ++ printf ("info: test_append_syntax_error\n"); ++ wordexp_t we = { 0 }; ++ ++ TEST_COMPARE (wordexp ("original", &we, 0), 0); ++ CHECK_WORDS (&we, "original"); ++ ++ char **saved_wordv = we.we_wordv; ++ size_t saved_count = we.we_wordc; ++ ++ /* Unterminated double quote triggers WRDE_SYNTAX. */ ++ TEST_COMPARE (wordexp ("\"unterminated", &we, WRDE_APPEND), WRDE_SYNTAX); ++ ++ TEST_COMPARE (we.we_wordc, saved_count); ++ TEST_VERIFY (we.we_wordv == saved_wordv); ++ CHECK_WORDS (&we, "original"); ++ ++ wordfree (&we); ++} ++ ++/* Test 8: Error without WRDE_APPEND still works (regression test for the ++ non-APPEND code path in do_error). */ ++static void ++test_no_append_error (void) ++{ ++ printf ("info: test_no_append_error\n"); ++ wordexp_t we = { 0 }; ++ ++ /* Simple failure without WRDE_APPEND. */ ++ TEST_COMPARE (wordexp ("bad |", &we, 0), WRDE_BADCHAR); ++ ++ /* After failure without WRDE_APPEND the struct should be safe to ++ reuse — start fresh. */ ++ TEST_COMPARE (wordexp ("ok", &we, 0), 0); ++ CHECK_WORDS (&we, "ok"); ++ ++ wordfree (&we); ++} ++ ++/* Test 9: WRDE_BADCHAR on the very first character (no partial words added ++ before the error). */ ++static void ++test_append_badchar_immediate (void) ++{ ++ printf ("info: test_append_badchar_immediate\n"); ++ wordexp_t we = { 0 }; ++ ++ TEST_COMPARE (wordexp ("hello world", &we, 0), 0); ++ CHECK_WORDS (&we, "hello", "world"); ++ ++ char **saved_wordv = we.we_wordv; ++ size_t saved_count = we.we_wordc; ++ ++ /* The bad character is the very first byte — no w_addword call happens ++ before the error. */ ++ TEST_COMPARE (wordexp ("|", &we, WRDE_APPEND), WRDE_BADCHAR); ++ TEST_COMPARE (we.we_wordc, saved_count); ++ TEST_VERIFY (we.we_wordv == saved_wordv); ++ ++ wordfree (&we); ++} ++ ++/* Test 10: WRDE_APPEND into an empty wordexp_t (initial call uses WRDE_APPEND ++ with a zeroed struct — unusual but allowed). */ ++static void ++test_append_into_empty (void) ++{ ++ printf ("info: test_append_into_empty\n"); ++ wordexp_t we = { 0 }; ++ ++ /* First call with WRDE_APPEND on a zeroed struct. The implementation ++ must handle we_wordv == NULL gracefully. */ ++ TEST_COMPARE (wordexp ("solo", &we, WRDE_APPEND), 0); ++ TEST_COMPARE (we.we_wordc, 1); ++ CHECK_WORDS (&we, "solo"); ++ ++ wordfree (&we); ++} ++ ++/* Verify that the leading we_offs slots are all NULL. */ ++static void ++check_offs_null (const wordexp_t *we) ++{ ++ for (size_t i = 0; i < we->we_offs; i++) ++ TEST_VERIFY (we->we_wordv[i] == NULL); ++} ++ ++/* Test 11: successful WRDE_APPEND with WRDE_DOOFFS and a non-zero we_offs. ++ The leading offset slots must stay NULL and words must land at ++ we_wordv[we_offs + i] across both the initial and the appended call. */ ++static void ++test_dooffs_append_success (void) ++{ ++ printf ("info: test_dooffs_append_success\n"); ++ wordexp_t we = { 0 }; ++ we.we_offs = 2; ++ ++ TEST_COMPARE (wordexp ("one two", &we, WRDE_DOOFFS), 0); ++ TEST_COMPARE (we.we_offs, 2); ++ check_offs_null (&we); ++ CHECK_WORDS (&we, "one", "two"); ++ ++ TEST_COMPARE (wordexp ("three", &we, WRDE_APPEND | WRDE_DOOFFS), 0); ++ TEST_COMPARE (we.we_offs, 2); ++ check_offs_null (&we); ++ CHECK_WORDS (&we, "one", "two", "three"); ++ /* The NULL terminator must sit right after the last word. */ ++ TEST_VERIFY (we.we_wordv[we.we_offs + we.we_wordc] == NULL); ++ ++ wordfree (&we); ++} ++ ++/* Test 12: failed WRDE_APPEND with WRDE_DOOFFS preserves we_wordc, the ++ we_wordv pointer, the words and the leading NULL offset slots. This ++ exercises the we_offs arithmetic in the array duplication and in the ++ error-path cleanup (we_wordv[we_offs + --we_wordc]). */ ++static void ++test_dooffs_append_error_preserves_state (void) ++{ ++ printf ("info: test_dooffs_append_error_preserves_state\n"); ++ wordexp_t we = { 0 }; ++ we.we_offs = 3; ++ ++ TEST_COMPARE (wordexp ("alpha beta", &we, WRDE_DOOFFS), 0); ++ check_offs_null (&we); ++ CHECK_WORDS (&we, "alpha", "beta"); ++ ++ char **saved_wordv = we.we_wordv; ++ size_t saved_count = we.we_wordc; ++ unsigned int saved_reallocs = relocating_reallocs; ++ ++ /* "gamma" is a partial word added via w_addword (forcing a relocating ++ realloc of we_wordv) before ')' triggers WRDE_BADCHAR. */ ++ TEST_COMPARE (wordexp ("gamma )", &we, WRDE_APPEND | WRDE_DOOFFS), ++ WRDE_BADCHAR); ++ TEST_VERIFY (relocating_reallocs > saved_reallocs); ++ ++ TEST_COMPARE (we.we_offs, 3); ++ TEST_COMPARE (we.we_wordc, saved_count); ++ TEST_VERIFY (we.we_wordv == saved_wordv); ++ check_offs_null (&we); ++ CHECK_WORDS (&we, "alpha", "beta"); ++ TEST_VERIFY (we.we_wordv[we.we_offs + we.we_wordc] == NULL); ++ ++ wordfree (&we); ++} ++ ++static int ++do_test (void) ++{ ++ test_append_badchar_preserves_count (); ++ test_append_badchar_preserves_pointer (); ++ test_append_badchar_words_intact (); ++ test_append_success (); ++ test_append_success_after_failure (); ++ test_append_multiple_failures (); ++ test_append_syntax_error (); ++ test_no_append_error (); ++ test_append_badchar_immediate (); ++ test_append_into_empty (); ++ test_dooffs_append_success (); ++ test_dooffs_append_error_preserves_state (); ++ ++ return 0; ++} ++ ++#include diff --git a/posix/tst-wordexp-reuse.c b/posix/tst-wordexp-reuse.c new file mode 100644 index 0000000000..3926b9f557 @@ -5866,11 +7646,330 @@ +} + +#include +diff --git a/posix/tst-wordexp-tilde.c b/posix/tst-wordexp-tilde.c +new file mode 100644 +index 0000000000..1661603681 +--- /dev/null ++++ b/posix/tst-wordexp-tilde.c +@@ -0,0 +1,244 @@ ++/* Test wordexp tilde expansion with large usernames (BZ 34091). ++ 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 ++#include ++ ++typedef void (*func_callback_t)(void); ++ ++static void ++subprocess_small_stack (void *closure) ++{ ++ struct rlimit rl; ++ TEST_COMPARE (getrlimit (RLIMIT_STACK, &rl), 0); ++ rl.rlim_cur = 512 * 1024; ++ TEST_COMPARE (setrlimit (RLIMIT_STACK, &rl), 0); ++ ++ func_callback_t func_test = closure; ++ func_test (); ++} ++ ++/* Build a string "~/tail" where is LEN bytes of the ++ character CH. The caller must free the result. */ ++static char * ++make_tilde_input (char ch, size_t len, const char *tail) ++{ ++ /* ~ + len + / + tail + \0 */ ++ size_t taillen = tail != NULL ? strlen (tail) : 0; ++ size_t total = 1 + len + 1 + taillen + 1; ++ char *buf = xmalloc (total); ++ buf[0] = '~'; ++ memset (buf + 1, ch, len); ++ buf[1 + len] = '/'; ++ if (tail != NULL) ++ memcpy (buf + 1 + len + 1, tail, taillen); ++ buf[total - 1] = '\0'; ++ return buf; ++} ++ ++/* Test 1: A very long username must not crash. The username will not match ++ any real user, so wordexp returns ~/rest. */ ++static void ++test_long_username (void) ++{ ++ printf ("info: test_long_username_no_crash\n"); ++ ++ static const char REST[] = "rest"; ++ ++ /* 1 MiB username — well beyond any reasonable stack frame. */ ++ const size_t long_len = 1024 * 1024; ++ char *input = make_tilde_input ('A', long_len, REST); ++ ++ wordexp_t we = { 0 }; ++ int ret = wordexp (input, &we, 0); ++ /* The (non-existent) username is invalid, so wordexp falls back to ++ literal output: ~AAA…/rest. */ ++ TEST_COMPARE (ret, 0); ++ TEST_COMPARE (we.we_wordc, 1); ++ ++ /* Verify prefix: '~' followed by long_len 'A's. */ ++ const char *result = we.we_wordv[0]; ++ TEST_COMPARE (result[0], '~'); ++ TEST_COMPARE (strlen (result), ++ 1 /* ~ */ + long_len + sizeof (REST)); ++ for (size_t j = 1; j <= long_len; j++) ++ if (result[j] != 'A') ++ { ++ printf (" mismatch at position %zu: expected 'A', got '%c'\n", ++ j, result[j]); ++ support_record_failure (); ++ break; ++ } ++ /* Verify the tail after the username. */ ++ TEST_COMPARE_STRING (result + 1 + long_len, "/rest"); ++ ++ wordfree (&we); ++ free (input); ++} ++ ++/* Test 2: A username that just exceeds the default scratch_buffer inline ++ size (1024 bytes) exercises the scratch_buffer_set_array_size growth path ++ without being excessively large. */ ++static void ++test_scratch_buffer_growth (void) ++{ ++ printf ("info: test_scratch_buffer_growth\n"); ++ ++ const size_t len = 2048; ++ char *input = make_tilde_input ('x', len, NULL); ++ ++ wordexp_t we = { 0 }; ++ int ret = wordexp (input, &we, 0); ++ TEST_COMPARE (ret, 0); ++ TEST_COMPARE (we.we_wordc, 1); ++ ++ /* ~xxx…/ — the trailing slash makes a separate empty component, but ++ wordexp merges it into the single token ~xxx…/. */ ++ const char *result = we.we_wordv[0]; ++ TEST_COMPARE (result[0], '~'); ++ for (size_t j = 1; j <= len; j++) ++ if (result[j] != 'x') ++ { ++ printf (" mismatch at position %zu\n", j); ++ support_record_failure (); ++ break; ++ } ++ TEST_COMPARE (result[1 + len], '/'); ++ ++ wordfree (&we); ++ free (input); ++} ++ ++/* Test 3: ~root still resolves to the correct home directory through the ++ __getpwnam_r path. */ ++static void ++test_known_user (void) ++{ ++ printf ("info: test_known_user\n"); ++ ++ /* Look up root's home directory for comparison. */ ++ struct passwd *pw = getpwnam ("root"); ++ if (pw == NULL || pw->pw_dir == NULL) ++ { ++ printf (" SKIP: cannot look up root\n"); ++ return; ++ } ++ ++ char *expected = xasprintf ("%s/file", pw->pw_dir); ++ ++ wordexp_t we = { 0 }; ++ TEST_COMPARE (wordexp ("~root/file", &we, 0), 0); ++ TEST_COMPARE (we.we_wordc, 1); ++ TEST_COMPARE_STRING (we.we_wordv[0], expected); ++ ++ wordfree (&we); ++ free (expected); ++} ++ ++/* Test 4: Bare tilde expands to $HOME. */ ++static void ++test_bare_tilde (void) ++{ ++ printf ("info: test_bare_tilde\n"); ++ ++ const char *home = getenv ("HOME"); ++ if (home == NULL) ++ { ++ printf (" SKIP: HOME is not set\n"); ++ return; ++ } ++ ++ wordexp_t we = { 0 }; ++ TEST_COMPARE (wordexp ("~", &we, 0), 0); ++ TEST_COMPARE (we.we_wordc, 1); ++ TEST_COMPARE_STRING (we.we_wordv[0], home); ++ ++ wordfree (&we); ++} ++ ++/* Test 5: Short non-existent username falls back to literal ~username output, ++ exercising the invalid-login-name path. */ ++static void ++test_unknown_user (void) ++{ ++ printf ("info: test_unknown_user\n"); ++ ++ /* Pick a username that is extremely unlikely to exist. */ ++ wordexp_t we = { 0 }; ++ TEST_COMPARE (wordexp ("~no_such_user_xyzzy42", &we, 0), 0); ++ TEST_COMPARE (we.we_wordc, 1); ++ TEST_COMPARE_STRING (we.we_wordv[0], "~no_such_user_xyzzy42"); ++ ++ wordfree (&we); ++} ++ ++/* Test 6: Tilde with username and WRDE_APPEND — exercises parse_tilde's ++ interaction with the WRDE_APPEND word list. */ ++static void ++test_tilde_with_append (void) ++{ ++ printf ("info: test_tilde_with_append\n"); ++ ++ const char *home = getenv ("HOME"); ++ if (home == NULL) ++ { ++ printf (" SKIP: HOME is not set\n"); ++ return; ++ } ++ ++ wordexp_t we = { 0 }; ++ TEST_COMPARE (wordexp ("first", &we, 0), 0); ++ ++ TEST_COMPARE (wordexp ("~/path", &we, WRDE_APPEND), 0); ++ TEST_COMPARE (we.we_wordc, 2); ++ TEST_COMPARE_STRING (we.we_wordv[0], "first"); ++ ++ char *expected = xasprintf ("%s/path", home); ++ TEST_COMPARE_STRING (we.we_wordv[1], expected); ++ ++ wordfree (&we); ++ free (expected); ++} ++ ++static int ++do_test (void) ++{ ++ test_known_user (); ++ test_bare_tilde (); ++ test_unknown_user (); ++ test_tilde_with_append (); ++ ++ support_isolate_in_subprocess (subprocess_small_stack, ++ test_long_username); ++ ++ support_isolate_in_subprocess (subprocess_small_stack, ++ test_scratch_buffer_growth); ++ ++ return 0; ++} ++ ++#include +diff --git a/posix/tst-wordexp-tilde.root/etc/group b/posix/tst-wordexp-tilde.root/etc/group +new file mode 100644 +index 0000000000..1dbf9013ee +--- /dev/null ++++ b/posix/tst-wordexp-tilde.root/etc/group +@@ -0,0 +1 @@ ++root:x:0: +diff --git a/posix/tst-wordexp-tilde.root/etc/nsswitch.conf b/posix/tst-wordexp-tilde.root/etc/nsswitch.conf +new file mode 100644 +index 0000000000..098a8d5938 +--- /dev/null ++++ b/posix/tst-wordexp-tilde.root/etc/nsswitch.conf +@@ -0,0 +1,3 @@ ++passwd: files ++group: files ++shadow: files +diff --git a/posix/tst-wordexp-tilde.root/etc/passwd b/posix/tst-wordexp-tilde.root/etc/passwd +new file mode 100644 +index 0000000000..eb85a552ad +--- /dev/null ++++ b/posix/tst-wordexp-tilde.root/etc/passwd +@@ -0,0 +1 @@ ++root:x:0:0:root:/root:/bin/sh diff --git a/posix/wordexp.c b/posix/wordexp.c -index a69b732801..9df4bb7424 100644 +index a69b732801..09c20cf5d4 100644 --- a/posix/wordexp.c +++ b/posix/wordexp.c -@@ -2216,7 +2216,9 @@ wordexp (const char *words, wordexp_t *pwordexp, int flags) +@@ -335,17 +335,29 @@ parse_tilde (char **word, size_t *word_length, size_t *max_length, + else + { + /* Look up user name in database to get home directory */ +- char *user = strndupa (&words[1 + *offset], i - (1 + *offset)); +- struct passwd pwd, *tpwd; +- int result; ++ size_t userlen = i - (1 + *offset); ++ /* tmpbuf contains both the user and the __getpwnam_r working area. */ + struct scratch_buffer tmpbuf; + scratch_buffer_init (&tmpbuf); ++ if (!scratch_buffer_set_array_size (&tmpbuf, userlen + 1, 1)) ++ return WRDE_NOSPACE; ++ char *user = tmpbuf.data; ++ memcpy (user, &words[1 + *offset], userlen); ++ user[userlen] = '\0'; + +- while ((result = __getpwnam_r (user, &pwd, tmpbuf.data, tmpbuf.length, ++ struct passwd pwd, *tpwd; ++ int result; ++ while ((result = __getpwnam_r (user, ++ &pwd, ++ tmpbuf.data + userlen + 1, ++ tmpbuf.length - userlen - 1, + &tpwd)) != 0 + && errno == ERANGE) +- if (!scratch_buffer_grow (&tmpbuf)) +- return WRDE_NOSPACE; ++ { ++ if (!scratch_buffer_grow_preserve (&tmpbuf)) ++ return WRDE_NOSPACE; ++ user = tmpbuf.data; ++ } + + if (result == 0 && tpwd != NULL && pwd.pw_dir) + *word = w_addstr (*word, word_length, max_length, pwd.pw_dir); +@@ -2212,11 +2224,19 @@ wordexp (const char *words, wordexp_t *pwordexp, int flags) + char ifs_white[4]; + wordexp_t old_word = *pwordexp; + ++ /* When WRDE_APPEND is set we work on a copy of the we_wordv array so that ++ the caller's original pointer is never invalidated by realloc inside ++ w_addword. The saved_wordv keeps the original; on success we free it, ++ on non-NOSPACE error we free the working copy and restore the original. */ ++ char **saved_wordv = NULL; ++ + if (flags & WRDE_REUSE) { /* Minimal implementation of WRDE_REUSE for now */ wordfree (pwordexp); @@ -5880,11 +7979,115 @@ } if ((flags & WRDE_APPEND) == 0) +@@ -2244,6 +2264,21 @@ wordexp (const char *words, wordexp_t *pwordexp, int flags) + pwordexp->we_offs = 0; + } + } ++ else if (pwordexp->we_wordv != NULL) ++ { ++ /* 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 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 (char *)); ++ saved_wordv = pwordexp->we_wordv; ++ pwordexp->we_wordv = dup; ++ } + + /* Find out what the field separators are. + * There are two types: whitespace and non-whitespace. +@@ -2324,7 +2359,7 @@ wordexp (const char *words, wordexp_t *pwordexp, int flags) + error = w_addword (pwordexp, NULL); + + if (error) +- return error; ++ goto do_error; + } + + break; +@@ -2342,7 +2377,7 @@ wordexp (const char *words, wordexp_t *pwordexp, int flags) + error = w_addword (pwordexp, NULL); + + if (error) +- return error; ++ goto do_error; + } + + break; +@@ -2408,10 +2443,18 @@ wordexp (const char *words, wordexp_t *pwordexp, int flags) + + /* There was a word separator at the end */ + if (word == NULL) /* i.e. w_newword */ +- return 0; ++ { ++ free (saved_wordv); ++ return 0; ++ } + +- /* There was no field separator at the end */ +- return w_addword (pwordexp, word); ++ /* There was no field separator at the end. The only possible error ++ from w_addword is WRDE_NOSPACE. */ ++ error = w_addword (pwordexp, word); ++ if (error != 0) ++ goto do_error; ++ free (saved_wordv); ++ return 0; + + do_error: + /* Error: +@@ -2422,11 +2465,30 @@ do_error: + free (word); + + if (error == WRDE_NOSPACE) +- return WRDE_NOSPACE; ++ { ++ /* we_wordc and we_wordv are updated to reflect any words that were ++ successfully expanded. The old array is obsolete. */ ++ free (saved_wordv); ++ return WRDE_NOSPACE; ++ } + +- if ((flags & WRDE_APPEND) == 0) +- wordfree (pwordexp); ++ if (flags & WRDE_APPEND) ++ { ++ /* POSIX 2024 states that for in other error cases, if the WRDE_APPEND ++ flag was specified, we_wordc and we_wordv shall not be modified. ++ ++ Free strings appended during this call, discard the working copy of ++ we_wordv, and restore the caller's original pointer. */ ++ while (pwordexp->we_wordc > old_word.we_wordc) ++ free (pwordexp->we_wordv[pwordexp->we_offs + --pwordexp->we_wordc]); ++ free (pwordexp->we_wordv); ++ pwordexp->we_wordv = saved_wordv; ++ } ++ else ++ { ++ wordfree (pwordexp); ++ *pwordexp = old_word; ++ } + +- *pwordexp = old_word; + return error; + } diff --git a/resolv/Makefile b/resolv/Makefile -index 48b16dc2b2..8fc444de13 100644 +index 48b16dc2b2..d40daec826 100644 --- a/resolv/Makefile +++ b/resolv/Makefile -@@ -98,8 +98,10 @@ tests += \ +@@ -92,14 +92,17 @@ tests += \ + tst-ns_name \ + tst-ns_name_compress \ + tst-ns_name_pton \ ++ tst-ns_sprintrr \ + tst-res_hconf_reorder \ + tst-res_hnok \ + tst-resolv-aliases \ tst-resolv-basic \ tst-resolv-binary \ tst-resolv-byaddr \ @@ -5895,7 +8098,7 @@ tst-resolv-network \ tst-resolv-noaaaa \ tst-resolv-noaaaa-vc \ -@@ -109,6 +111,7 @@ tests += \ +@@ -109,6 +112,7 @@ tests += \ tst-resolv-semi-failure \ tst-resolv-short-response \ tst-resolv-trailing \ @@ -5903,7 +8106,7 @@ # This test calls __res_context_send directly, which is not exported # from libresolv. -@@ -286,6 +289,8 @@ $(objpfx)tst-resolv-aliases: $(objpfx)libresolv.so $(shared-thread-library) +@@ -286,6 +290,8 @@ $(objpfx)tst-resolv-aliases: $(objpfx)libresolv.so $(shared-thread-library) $(objpfx)tst-resolv-basic: $(objpfx)libresolv.so $(shared-thread-library) $(objpfx)tst-resolv-binary: $(objpfx)libresolv.so $(shared-thread-library) $(objpfx)tst-resolv-byaddr: $(objpfx)libresolv.so $(shared-thread-library) @@ -5912,7 +8115,7 @@ $(objpfx)tst-resolv-edns: $(objpfx)libresolv.so $(shared-thread-library) $(objpfx)tst-resolv-network: $(objpfx)libresolv.so $(shared-thread-library) $(objpfx)tst-resolv-res_init: $(objpfx)libresolv.so -@@ -295,6 +300,8 @@ $(objpfx)tst-resolv-res_init-thread: $(objpfx)libresolv.so \ +@@ -295,6 +301,8 @@ $(objpfx)tst-resolv-res_init-thread: $(objpfx)libresolv.so \ $(shared-thread-library) $(objpfx)tst-resolv-invalid-cname: $(objpfx)libresolv.so \ $(shared-thread-library) @@ -5921,6 +8124,267 @@ $(objpfx)tst-resolv-noaaaa: $(objpfx)libresolv.so $(shared-thread-library) $(objpfx)tst-resolv-noaaaa-vc: $(objpfx)libresolv.so $(shared-thread-library) $(objpfx)tst-resolv-nondecimal: $(objpfx)libresolv.so $(shared-thread-library) +@@ -316,5 +324,6 @@ $(objpfx)tst-ns_name: $(objpfx)libresolv.so + $(objpfx)tst-ns_name.out: tst-ns_name.data + $(objpfx)tst-ns_name_compress: $(objpfx)libresolv.so + $(objpfx)tst-ns_name_pton: $(objpfx)libresolv.so ++$(objpfx)tst-ns_sprintrr: $(objpfx)libresolv.so + $(objpfx)tst-res_hnok: $(objpfx)libresolv.so + $(objpfx)tst-p_secstodate: $(objpfx)libresolv.so +diff --git a/resolv/ns_print.c b/resolv/ns_print.c +index cef2212fd2..f9dd086804 100644 +--- a/resolv/ns_print.c ++++ b/resolv/ns_print.c +@@ -78,6 +78,24 @@ ns_sprintrr(const ns_msg *handle, const ns_rr *rr, + } + libresolv_hidden_def (ns_sprintrr) + ++/* Writes the class/type symbol NUMBER to *BUF, using the name from ++ *SYMS if possible. If NUMBER is not found in *SYMS, print the ++ number with PREFIX. */ ++static int ++addsym (const struct res_sym *syms, int number, const char *prefix, ++ char **buf, size_t *buflen) ++{ ++ for (; syms->name != NULL; syms++) ++ if (number == syms->number) ++ { ++ T (addstr (" ", 1, buf, buflen)); ++ return addstr (syms->name, strlen (syms->name), buf, buflen); ++ } ++ char tmp[20]; ++ int len = snprintf (tmp, sizeof (tmp), " %s%d", prefix, number); ++ return addstr (tmp, len, buf, buflen); ++} ++ + /*% + * Convert the fields of an RR into presentation format. + * +@@ -97,7 +115,6 @@ ns_sprintrrf(const u_char *msg, size_t msglen, + + const char *comment; + char tmp[100]; +- char errbuf[40]; + int len, x; + + /* +@@ -128,11 +145,21 @@ ns_sprintrrf(const u_char *msg, size_t msglen, + /* + * TTL, Class, Type. + */ +- T(x = ns_format_ttl(ttl, buf, buflen)); +- addlen(x, &buf, &buflen); +- len = SPRINTF((tmp, " %s %s", p_class(class), p_type(type))); +- T(addstr(tmp, len, &buf, &buflen)); +- T(spaced = addtab(x + len, 16, spaced, &buf, &buflen)); ++ { ++ char *start = buf; ++ ++ T (x = ns_format_ttl (ttl, buf, buflen)); ++ addlen (x, &buf, &buflen); ++ T (addsym (__p_class_syms, class, "CLASS", &buf, &buflen)); ++ if (type == ns_t_a6) ++ /* A6 is not part of __p_type_syms, which is exported. ++ Adding A6 there would change its size. Handle it here. */ ++ T (addstr (" A6", 3, &buf, &buflen)); ++ else ++ T (addsym (__p_type_syms, type, "TYPE", &buf, &buflen)); ++ ++ T (spaced = addtab(buf - start, 16, spaced, &buf, &buflen)); ++ } + + /* + * RData. +@@ -140,8 +167,9 @@ ns_sprintrrf(const u_char *msg, size_t msglen, + switch (type) { + case ns_t_a: + if (rdlen != (size_t)NS_INADDRSZ) +- goto formerr; +- (void) inet_ntop(AF_INET, rdata, buf, buflen); ++ goto formerr; ++ if (inet_ntop (AF_INET, rdata, buf, buflen) == NULL) ++ return -1; + addlen(strlen(buf), &buf, &buflen); + break; + +@@ -307,16 +335,18 @@ ns_sprintrrf(const u_char *msg, size_t msglen, + } + + case ns_t_aaaa: +- if (rdlen != (size_t)NS_IN6ADDRSZ) +- goto formerr; +- (void) inet_ntop(AF_INET6, rdata, buf, buflen); ++ if (rdlen != (size_t)NS_IN6ADDRSZ) ++ goto formerr; ++ if (inet_ntop (AF_INET6, rdata, buf, buflen) == NULL) ++ return -1; + addlen(strlen(buf), &buf, &buflen); + break; + + case ns_t_loc: { + char t[255]; + +- /* XXX protocol format checking? */ ++ if (rdlen != 16) ++ goto formerr; + (void) loc_ntoa(rdata, t); + T(addstr(t, strlen(t), &buf, &buflen)); + break; +@@ -400,7 +430,8 @@ ns_sprintrrf(const u_char *msg, size_t msglen, + goto formerr; + + /* Address. */ +- (void) inet_ntop(AF_INET, rdata, buf, buflen); ++ if (inet_ntop (AF_INET, rdata, buf, buflen) == NULL) ++ return -1; + addlen(strlen(buf), &buf, &buflen); + rdata += NS_INADDRSZ; + +@@ -434,96 +465,6 @@ ns_sprintrrf(const u_char *msg, size_t msglen, + break; + } + +- case ns_t_cert: { +- u_int c_type, key_tag, alg; +- int n; +- unsigned int siz; +- char base64_cert[8192], tmp[40]; +- const char *leader; +- +- c_type = ns_get16(rdata); rdata += NS_INT16SZ; +- key_tag = ns_get16(rdata); rdata += NS_INT16SZ; +- alg = (u_int) *rdata++; +- +- len = SPRINTF((tmp, "%d %d %d ", c_type, key_tag, alg)); +- T(addstr(tmp, len, &buf, &buflen)); +- siz = (edata-rdata)*4/3 + 4; /* "+4" accounts for trailing \0 */ +- if (siz > sizeof(base64_cert) * 3/4) { +- const char *str = "record too long to print"; +- T(addstr(str, strlen(str), &buf, &buflen)); +- } +- else { +- len = b64_ntop(rdata, edata-rdata, base64_cert, siz); +- +- if (len < 0) +- goto formerr; +- else if (len > 15) { +- T(addstr(" (", 2, &buf, &buflen)); +- leader = "\n\t\t"; +- spaced = 0; +- } +- else +- leader = " "; +- +- for (n = 0; n < len; n += 48) { +- T(addstr(leader, strlen(leader), +- &buf, &buflen)); +- T(addstr(base64_cert + n, MIN(len - n, 48), +- &buf, &buflen)); +- } +- if (len > 15) +- T(addstr(" )", 2, &buf, &buflen)); +- } +- break; +- } +- +- case ns_t_tkey: { +- /* KJD - need to complete this */ +- u_long t; +- int mode, err, keysize; +- +- /* Algorithm name. */ +- T(addname(msg, msglen, &rdata, origin, &buf, &buflen)); +- T(addstr(" ", 1, &buf, &buflen)); +- +- /* Inception. */ +- t = ns_get32(rdata); rdata += NS_INT32SZ; +- len = SPRINTF((tmp, "%lu ", t)); +- T(addstr(tmp, len, &buf, &buflen)); +- +- /* Expiration. */ +- t = ns_get32(rdata); rdata += NS_INT32SZ; +- len = SPRINTF((tmp, "%lu ", t)); +- T(addstr(tmp, len, &buf, &buflen)); +- +- /* Mode , Error, Key Size. */ +- /* Priority, Weight, Port. */ +- mode = ns_get16(rdata); rdata += NS_INT16SZ; +- err = ns_get16(rdata); rdata += NS_INT16SZ; +- keysize = ns_get16(rdata); rdata += NS_INT16SZ; +- len = SPRINTF((tmp, "%u %u %u ", mode, err, keysize)); +- T(addstr(tmp, len, &buf, &buflen)); +- +- /* XXX need to dump key, print otherdata length & other data */ +- break; +- } +- +- case ns_t_tsig: { +- /* BEW - need to complete this */ +- int n; +- +- T(len = addname(msg, msglen, &rdata, origin, &buf, &buflen)); +- T(addstr(" ", 1, &buf, &buflen)); +- rdata += 8; /*%< time */ +- n = ns_get16(rdata); rdata += INT16SZ; +- rdata += n; /*%< sig */ +- n = ns_get16(rdata); rdata += INT16SZ; /*%< original id */ +- sprintf(buf, "%d", ns_get16(rdata)); +- rdata += INT16SZ; +- addlen(strlen(buf), &buf, &buflen); +- break; +- } +- + case ns_t_a6: { + struct in6_addr a; + int pbyte, pbit; +@@ -539,12 +480,14 @@ ns_sprintrrf(const u_char *msg, size_t msglen, + + /* address suffix: provided only when prefix len != 128 */ + if (pbit < 128) { +- if (rdata + pbyte >= edata) goto formerr; ++ unsigned int bytelen = sizeof(a) - pbyte; ++ if (edata - rdata < bytelen) goto formerr; + memset(&a, 0, sizeof(a)); +- memcpy(&a.s6_addr[pbyte], rdata, sizeof(a) - pbyte); +- (void) inet_ntop(AF_INET6, &a, buf, buflen); ++ memcpy(&a.s6_addr[pbyte], rdata, bytelen); ++ if (inet_ntop (AF_INET6, &a, buf, buflen) == NULL) ++ return -1; + addlen(strlen(buf), &buf, &buflen); +- rdata += sizeof(a) - pbyte; ++ rdata += bytelen; + } + + /* prefix name: provided only when prefix len > 0 */ +@@ -557,25 +500,18 @@ ns_sprintrrf(const u_char *msg, size_t msglen, + break; + } + +- case ns_t_opt: { +- len = SPRINTF((tmp, "%u bytes", class)); +- T(addstr(tmp, len, &buf, &buflen)); +- break; +- } +- + default: +- snprintf (errbuf, sizeof (errbuf), "unknown RR type %d", type); +- comment = errbuf; ++ comment = ""; + goto hexify; + } + return (buf - obuf); + formerr: +- comment = "RR format error"; ++ comment = " ; RR format error"; + hexify: { + int n, m; + char *p; + +- len = SPRINTF((tmp, "\\# %u%s\t; %s", (unsigned)(edata - rdata), ++ len = SPRINTF((tmp, "\\# %u%s%s", (unsigned)(edata - rdata), + rdlen != 0U ? " (" : "", comment)); + T(addstr(tmp, len, &buf, &buflen)); + while (rdata < edata) { diff --git a/resolv/nss_dns/dns-host.c b/resolv/nss_dns/dns-host.c index 14da73ee1d..1bc2e1df95 100644 --- a/resolv/nss_dns/dns-host.c @@ -5958,6 +8422,400 @@ } net_buffer.buf = orig_net_buffer = (querybuf *) alloca (1024); +diff --git a/resolv/res_debug.c b/resolv/res_debug.c +index 73af0c72fe..6bf9962916 100644 +--- a/resolv/res_debug.c ++++ b/resolv/res_debug.c +@@ -390,8 +390,6 @@ p_fqname(const u_char *cp, const u_char *msg, FILE *file) { + * that C_ANY is a qclass but not a class. (You can ask for records of class + * C_ANY, but you can't have any records of that class in the database.) + */ +-extern const struct res_sym __p_class_syms[]; +-libresolv_hidden_proto (__p_class_syms) + const struct res_sym __p_class_syms[] = { + {C_IN, (char *) "IN"}, + {C_CHAOS, (char *) "CHAOS"}, +@@ -426,8 +424,6 @@ const struct res_sym __p_update_section_syms[] attribute_hidden = { + * Names of RR types and qtypes. The list is incomplete because its + * size is part of the ABI. + */ +-extern const struct res_sym __p_type_syms[]; +-libresolv_hidden_proto (__p_type_syms) + const struct res_sym __p_type_syms[] = { + {ns_t_a, (char *) "A", (char *) "address"}, + {ns_t_ns, (char *) "NS", (char *) "name server"}, +diff --git a/resolv/resolv_conf.c b/resolv/resolv_conf.c +index dcf92ee90e..0418267044 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-ns_sprintrr.c b/resolv/tst-ns_sprintrr.c +new file mode 100644 +index 0000000000..34739b5924 +--- /dev/null ++++ b/resolv/tst-ns_sprintrr.c +@@ -0,0 +1,329 @@ ++/* Tests for the ns_sprintrr function. ++ 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 ++ ++/* Regions that test_one_record uses for input and output. */ ++static struct support_next_to_fault ntf_in; ++static struct support_next_to_fault ntf_out; ++ ++/* This is used by test_one_record to construct the packet. */ ++static const char packet_prefix[] = ++ /* DNS response with one question, one answer record. */ ++ "AA\x81\x80\0\1\0\1\0\0\0\0" ++ /* Question: www.example.org/IN/ANY. */ ++ "\3www\7example\3org\0\0\xff\0\1" ++ /* Response: compression reference. */ ++ "\xc0\x0c"; ++ ++/* Use ns_sprintrr to format a DNS record (starting with ++ packet_prefix) of type RTYPE, with a record payload of RDATALEN ++ bytes starting at RDATA. Check successful formatting against ++ EXPECTED. Try various truncated input and output buffers to catch ++ overreads and buffer overflows, using ntf_in and ntf_out above. */ ++static void ++test_one_record (uint16_t rtype, const char *rdata, size_t rdatalen, ++ const char *expected) ++{ ++ struct rr_header ++ { ++ uint16_t typ; ++ uint16_t cls; ++ uint32_t ttl; ++ uint16_t rdatalen; ++ uint16_t pad; ++ } hdr = ++ { ++ .typ = htons (rtype), ++ .cls = htons (ns_c_in), ++ .ttl = htonl (86400), /* One day. */ ++ .rdatalen = htons (rdatalen), ++ }; ++ enum { hdrlen = offsetof (struct rr_header, pad) }; ++ TEST_COMPARE (hdrlen, 10); ++ ++ /* Construct the packet from packet_prefix, hdr, and rdata. */ ++ unsigned char packet[512]; ++ size_t packetlen; ++ { ++ struct alloc_buffer buf = alloc_buffer_create (packet, sizeof (packet)); ++ alloc_buffer_copy_bytes (&buf, packet_prefix, sizeof (packet_prefix) - 1); ++ alloc_buffer_copy_bytes (&buf, &hdr, hdrlen); ++ alloc_buffer_copy_bytes (&buf, rdata, rdatalen); ++ packetlen = sizeof (packet) - alloc_buffer_size (&buf); ++ } ++ ++ /* Parse the record. */ ++ ns_msg msg; ++ TEST_COMPARE (ns_initparse (packet, packetlen, &msg), 0); ++ ns_rr rr; ++ TEST_COMPARE (ns_parserr (&msg, ns_s_an, 0, &rr), 0); ++ ++ /* Try sizes up to this limit. Go a bit beyond the expected size to ++ check for errors. */ ++ size_t max_result_size = strlen (expected) + 16; ++ ++ bool success = false; ++ for (size_t result_size = 1; result_size <= max_result_size; ++result_size) ++ { ++ char *result_start = ntf_out.buffer + ntf_out.length - result_size; ++ memset (result_start, 'X', result_size); ++ ++ /* ns_sprintrr was deprecated in 2.34. */ ++ DIAG_PUSH_NEEDS_COMMENT; ++ DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wdeprecated-declarations"); ++ int ret = ns_sprintrr (&msg, &rr, NULL, NULL, result_start, result_size); ++ DIAG_POP_NEEDS_COMMENT; ++ ++ if (ret > 0) ++ { ++ TEST_COMPARE_STRING (result_start, expected); ++ TEST_COMPARE (ret, strlen (expected)); ++ success = true; ++ } ++ else ++ { ++ TEST_VERIFY (!success); ++ TEST_COMPARE (ret, -1); ++ } ++ } ++ TEST_VERIFY (success); ++ ++ /* Test with truncated RDATA. */ ++ for (size_t rdata_size = 0; rdata_size <= rdatalen; ++rdata_size) ++ { ++ size_t truncated_packet_size = packetlen - rdatalen + rdata_size; ++ unsigned char *packet_start ++ = ((unsigned char *) ntf_in.buffer + ntf_in.length ++ - truncated_packet_size); ++ memcpy (packet_start, packet, truncated_packet_size); ++ /* Patch in the updated RDATA length field. */ ++ uint16_t new_rdatalen = htons (rdata_size); ++ memcpy (packet_start + truncated_packet_size - rdata_size - 2, ++ &new_rdatalen, 2); ++ ++ ns_msg msg; ++ TEST_COMPARE (ns_initparse (packet_start, truncated_packet_size, &msg), ++ 0); ++ ns_rr rr; ++ TEST_COMPARE (ns_parserr (&msg, ns_s_an, 0, &rr), 0); ++ ++ size_t result_size = strlen (expected) + 1; ++ char *result_start = ntf_out.buffer + ntf_out.length - result_size; ++ memset (result_start, 'X', result_size); ++ ++ /* ns_sprintrr was deprecated in 2.34. */ ++ DIAG_PUSH_NEEDS_COMMENT; ++ DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wdeprecated-declarations"); ++ int ret = ns_sprintrr (&msg, &rr, NULL, NULL, result_start, result_size); ++ DIAG_POP_NEEDS_COMMENT; ++ ++ /* This flag indicates whether the output is syntactically ++ correct. In some cases, truncation may still yield a valid ++ payload. */ ++ bool broken = rdata_size < rdatalen; ++ switch (rtype) ++ { ++ case ns_t_wks: ++ /* WKS records use all trailing bytes for the port bitmap. */ ++ broken = rdata_size < 5; ++ break; ++ case ns_t_nsap: ++ /* Uses all bytes that are available. */ ++ broken = false; ++ break; ++ case ns_t_txt: ++ /* Truncation produces a valid payload if it occurs right ++ after a complete string in the TXT payload. */ ++ broken = false; ++ for (size_t pos = 0; pos < rdata_size; ) ++ { ++ unsigned int slen = rdata[pos] & 0xff; ++ if (pos + 1 + slen > rdata_size) ++ { ++ broken = true; ++ break; ++ } ++ pos += 1 + slen; ++ } ++ break; ++ case ns_t_isdn: ++ /* The second field is optional. If it is present, it must ++ not be truncated. */ ++ broken = rdata_size < 6 || (rdata_size > 6 && rdata_size < rdatalen); ++ break; ++ case ns_t_a6: ++ /* The first A6 subtest contains a trailing domain name, ++ which is ignored and not formatted. */ ++ if (rdata_size > 0 && rdata[0] == 0) ++ broken = rdata_size < 17; ++ break; ++ case ns_t_cert: ++ case ns_t_tkey: ++ case ns_t_tsig: ++ /* Only generic printing, which does not validate anything. */ ++ broken = false; ++ break; ++ } ++ ++ if (broken) ++ { ++ if (strstr (result_start, "RR format error") != NULL) ++ /* No further checks if an error indicator has been added ++ to the output. */ ++ ; ++ else ++ TEST_COMPARE (ret, -1); ++ } ++ else ++ TEST_VERIFY (ret > 0); ++ } ++} ++ ++static int ++do_test (void) ++{ ++ ntf_in = support_next_to_fault_allocate (512); ++ ntf_out = support_next_to_fault_allocate (256); ++ ++#define T(rtype, rdata, expected) \ ++ test_one_record (rtype, rdata, sizeof (rdata) - 1, expected) ++ T (ns_t_a, "\xc0\0\2\1", "www.example.org.\t1D IN A\t\t192.0.2.1"); ++ T (ns_t_cname, "\4www1\4prod\xc0\x10", ++ "www.example.org.\t1D IN CNAME\twww1.prod.example.org."); ++ T (ns_t_hinfo, "\5first\6second", ++ "www.example.org.\t1D IN HINFO\t\"first\" \"second\""); ++ T (ns_t_isdn, "\5first\6second", ++ "www.example.org.\t1D IN ISDN\t\"first\" \"second\""); ++ /* Bug: Extra space at the end in the text representation of ISDN RRs. */ ++ T (ns_t_isdn, "\5first", "www.example.org.\t1D IN ISDN\t\"first\" "); ++ T (ns_t_soa, ++ "\2ns\xc0\x10\12hostmaster\xc0\x10" ++ "\0\0\0\1\0\0\0\2\0\0\0\3\0\0\0\4\0\0\0\5", ++ "www.example.org.\t1D IN SOA\tns.example.org. hostmaster.example.org. (\n" ++ "\t\t\t\t\t1\t\t; serial\n" ++ "\t\t\t\t\t2S\t\t; refresh\n" ++ "\t\t\t\t\t3S\t\t; retry\n" ++ "\t\t\t\t\t4S\t\t; expiry\n" ++ "\t\t\t\t\t5S )\t\t; minimum\n"); ++ T (ns_t_mx, "\0\xa\2mx\xc0\x10", ++ "www.example.org.\t1D IN MX\t10 mx.example.org."); ++ T (ns_t_px, "\0\xa\3px1\xc0\x10\3px2\xc0\x10", ++ "www.example.org.\t1D IN PX\t10 px1.example.org. px2.example.org."); ++ T (ns_t_x25, "\4X.25", ++ "www.example.org.\t1D IN X25\t\"X.25\""); ++ T (ns_t_txt, "\1A\2BC\3DEF", ++ "www.example.org.\t1D IN TXT\t\"A\" \"BC\" \"DEF\""); ++ T (ns_t_nsap, "", ++ "www.example.org.\t1D IN NSAP\t"); ++ T (ns_t_nsap, "\1", ++ "www.example.org.\t1D IN NSAP\t01"); ++ T (ns_t_nsap, "\1\2", ++ "www.example.org.\t1D IN NSAP\t01.02"); ++ T (ns_t_nsap, "\1\2\3", ++ "www.example.org.\t1D IN NSAP\t01.0203"); ++ T (ns_t_nsap, "\1\2\3\4", ++ "www.example.org.\t1D IN NSAP\t01.0203.04"); ++ T (ns_t_nsap, ++ "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32" ++ "\33\34\35\36\37\40\41\42\43\44\45\46\47\50\51\52\53\54\55\56\57\60\61" ++ "\62\63\64\65\66\67\70\71\72\73\74\75\76\77\100\101\102\103\104\105\106" ++ "\107\110\111\112\113\114\115\116\117\120\121\122\123\124\125\126\127" ++ "\130\131\132\133\134\135\136\137\140\141\142\143\144\145\146\147\150" ++ "\151\152\153\154\155\156\157\160\161\162\163\164\165\166\167\170\171" ++ "\172\173\174\175\176\177\200\201\202\203\204\205\206\207\210\211\212" ++ "\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233" ++ "\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254" ++ "\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275" ++ "\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316" ++ "\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337" ++ "\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360" ++ "\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377", ++ "www.example.org.\t1D IN NSAP\t" ++ "01.0203.0405.0607.0809.0A0B.0C0D.0E0F.1011.1213.1415.1617.1819.1A1B" ++ ".1C1D.1E1F.2021.2223.2425.2627.2829.2A2B.2C2D.2E2F.3031.3233.3435.3637" ++ ".3839.3A3B.3C3D.3E3F.4041.4243.4445.4647.4849.4A4B.4C4D.4E4F.5051.5253" ++ ".5455.5657.5859.5A5B.5C5D.5E5F.6061.6263.6465.6667.6869.6A6B.6C6D.6E6F" ++ ".7071.7273.7475.7677.7879.7A7B.7C7D.7E7F.8081.8283.8485.8687.8889.8A8B" ++ ".8C8D.8E8F.9091.9293.9495.9697.9899.9A9B.9C9D.9E9F.A0A1.A2A3.A4A5.A6A7" ++ ".A8A9.AAAB.ACAD.AEAF.B0B1.B2B3.B4B5.B6B7.B8B9.BABB.BCBD.BEBF.C0C1.C2C3" ++ ".C4C5.C6C7.C8C9.CACB.CCCD.CECF.D0D1.D2D3.D4D5.D6D7.D8D9.DADB.DCDD.DEDF" ++ ".E0E1.E2E3.E4E5.E6E7.E8E9.EAEB.ECED.EEEF.F0F1.F2F3.F4F5.F6F7.F8F9.FAFB" ++ ".FCFD.FEFF"); ++ T (ns_t_aaaa, "\x20\x01\x0d\xb8\0\0\0\0\0\0\0\0\0\0\x12\x34", ++ "www.example.org.\t1D IN AAAA\t2001:db8::1234"); ++ /* Example from RFC 1876. The loc_ntoa format is different from the ++ official text representation. */ ++ T (ns_t_loc, ++ "\000\063\026\023\211\027\055\320\160\276\025\360\000\230\215\040", ++ "www.example.org.\t1D IN LOC" ++ "\t42 21 54.000 N 71 06 18.000 W -24.00m 30.00m 10000.00m 10.00m"); ++ T (ns_t_naptr, ++ "\0\1\0\2\5flags\7service\2.*\5naptr\xc0\x10", ++ "www.example.org.\t1D IN NAPTR\t1 2 \"flags\" \"service\" \".*\"" ++ " naptr.example.org."); ++ T (ns_t_srv, ++ "\0\1\0\2\0\x50\4www1\xc0\x10", ++ "www.example.org.\t1D IN SRV\t1 2 80 www1.example.org."); ++ T (ns_t_rp, "\3rp1\xc0\x10\3rp2\xc0\x10", ++ "www.example.org.\t1D IN RP\trp1.example.org. rp2.example.org."); ++ T (ns_t_wks, "\xc0\0\2\1\6\0\0\0\0\0\0\0\0\0\0\200", ++ "www.example.org.\t1D IN WKS\t192.0.2.1 6 ( \n\t\t\t\t80 )"); ++ T (ns_t_cert, "\0\1\x04\xd2\0blob", ++ "www.example.org.\t1D IN CERT\t\\# 9 (\n" ++ "\t00 01 04 d2 00 62 6c 6f 62 )\t\t\t; .....blob"); ++ T (ns_t_tkey, "\4algo\0\0\0\0\1\0\0\0\2\0\3\0\4" ++ "\0\5\xa1\xa2\xa3\xa4\xa5\0\3\xb1\xb2\xb3", ++ "www.example.org.\t1D IN TYPE249\t\\# 30 (\n" ++ "\t04 61 6c 67 6f 00 00 00 00 01 00 00 00 02 00 03 ; .algo...........\n" ++ "\t00 04 00 05 a1 a2 a3 a4 a5 00 03 b1 b2 b3 )\t; .............."); ++ T (ns_t_tsig, "\4algo\0" ++ "\0\20\xdd\xcd\x64\x10\xe9\x21\x34\x1a\x8e\xe0\xa1\x9a\x30\xfc\x3b\xd1" ++ "\0\2\0\3\0\5other", ++ "www.example.org.\t1D IN TSIG\t\\# 35 (\n" ++ "\t04 61 6c 67 6f 00 00 10 dd cd 64 10 e9 21 34 1a ; .algo.....d..!4.\n" ++ "\t8e e0 a1 9a 30 fc 3b d1 00 02 00 03 00 05 6f 74 ; ....0.;.......ot\n" ++ "\t68 65 72 )\t\t\t\t\t; her"); ++ T (ns_t_a6, ++ "\0\x20\x01\x0d\xb8\0\0\0\0\0\0\0\0\0\0\x12\x34\6prefix\xc0\x10", ++ "www.example.org.\t1D IN A6\t0 2001:db8::1234"); ++ T (ns_t_a6, ++ "\0\x20\x01\x0d\xb8\0\0\0\0\0\0\0\0\0\0\x12\x35", ++ "www.example.org.\t1D IN A6\t0 2001:db8::1235"); ++ T (ns_t_a6, "\200\6prefix\xc0\x10", ++ "www.example.org.\t1D IN A6\t128 prefix.example.org."); ++ T (ns_t_a6, "\x20\0\0\0\0\0\0\0\0\0\0\x12\x36\6prefix\xc0\x10", ++ "www.example.org.\t1D IN A6\t32 ::1236 prefix.example.org."); ++#undef T ++ ++ support_next_to_fault_free (&ntf_in); ++ support_next_to_fault_free (&ntf_out); ++ return 0; ++} ++ ++#include diff --git a/resolv/tst-resolv-dns-section.c b/resolv/tst-resolv-dns-section.c new file mode 100644 index 0000000000..1171baef51 @@ -6411,6 +9269,52 @@ check_reverse (1, "name: 1.in-addr.arpa\n" "net: 0x00000001\n"); +diff --git a/resolv/tst-resolv-res_init-skeleton.c b/resolv/tst-resolv-res_init-skeleton.c +index 3ccbe71db9..4e9c57f3cb 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", diff --git a/stdio-common/Makefile b/stdio-common/Makefile index 840289afd9..72c18e5dc1 100644 --- a/stdio-common/Makefile @@ -6813,7 +9717,7 @@ { wchar_t *cp = (wchar_t *) realloc (*strptr, diff --git a/stdlib/Makefile b/stdlib/Makefile -index 1c4fa2382f..c9c8f702a2 100644 +index 1c4fa2382f..b655f0e5eb 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -282,6 +282,7 @@ tests := \ @@ -6824,7 +9728,15 @@ tst-getenv-thread \ tst-getenv-unsetenv \ tst-getrandom \ -@@ -377,6 +378,7 @@ tests-internal := \ +@@ -337,6 +338,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 \ +@@ -377,6 +379,7 @@ tests-internal := \ # tests-internal tests-static := \ @@ -6870,6 +9782,29 @@ char * getenv (const char *name) { +diff --git a/stdlib/strfmon_l.c b/stdlib/strfmon_l.c +index 5e22aac750..bd849ff470 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-getenv-static.c b/stdlib/tst-getenv-static.c new file mode 100644 index 0000000000..f5f484c83a @@ -6941,6 +9876,60 @@ exit (EXIT_SUCCESS); } } +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 +diff --git a/string/Makefile b/string/Makefile +index c83b195e2e..d7437cd915 100644 +--- a/string/Makefile ++++ b/string/Makefile +@@ -238,8 +238,8 @@ endif + # This test allocates a lot of memory and can run for a long time. + xtests += tst-strcoll-overflow + +-# This test runs for a long time. +-xtests += test-strncmp-nonarray ++# These tests run for a long time. ++xtests += test-strncasecmp-nonarray test-strncmp-nonarray + + # This test needs libdl. + ifeq (yes,$(build-shared)) diff --git a/string/strerror.c b/string/strerror.c index 3c02640816..daabec82d3 100644 --- a/string/strerror.c @@ -6952,6 +9941,16 @@ - return __strerror_l (errnum, __libc_tsd_get (locale_t, LOCALE)); + return __strerror_l (errnum, __libc_tsd_LOCALE); } +diff --git a/string/test-strncasecmp-nonarray.c b/string/test-strncasecmp-nonarray.c +new file mode 100644 +index 0000000000..d68e58d5ec +--- /dev/null ++++ b/string/test-strncasecmp-nonarray.c +@@ -0,0 +1,4 @@ ++#define TEST_IDENTIFIER strncasecmp ++#define TEST_NAME "strncasecmp" ++typedef char CHAR; ++#include "test-Xncmp-nonarray.c" diff --git a/sunrpc/rpc_thread.c b/sunrpc/rpc_thread.c index a04b7ec47f..e20f0a6230 100644 --- a/sunrpc/rpc_thread.c @@ -6998,6 +9997,100 @@ /* Deallocate the subprocess data captured by support_capture_subprocess. */ +diff --git a/support/next_to_fault.c b/support/next_to_fault.c +index eb2457d12d..b60d5a5744 100644 +--- a/support/next_to_fault.c ++++ b/support/next_to_fault.c +@@ -1,4 +1,4 @@ +-/* Memory allocation next to an unmapped page. ++/* Memory allocation either before or after an unmapped page. + Copyright (C) 2017-2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + +@@ -16,34 +16,58 @@ + License along with the GNU C Library; if not, see + . */ + ++#include + #include + #include + #include + #include + #include + +-struct support_next_to_fault +-support_next_to_fault_allocate (size_t size) ++static struct support_next_to_fault ++support_next_to_fault_allocate_any (size_t size, bool fault_after_alloc) + { + long page_size = sysconf (_SC_PAGE_SIZE); ++ long protect_offset = 0; ++ long buffer_offset = page_size; ++ + TEST_VERIFY_EXIT (page_size > 0); + struct support_next_to_fault result; + result.region_size = roundup (size, page_size) + page_size; + if (size + page_size <= size || result.region_size <= size) +- FAIL_EXIT1 ("support_next_to_fault_allocate (%zu): overflow", size); ++ FAIL_EXIT1 ("%s (%zu): overflow", __func__, size); + result.region_start + = xmmap (NULL, result.region_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1); +- /* Unmap the page after the allocation. */ +- xmprotect (result.region_start + (result.region_size - page_size), +- page_size, PROT_NONE); +- /* Align the allocation within the region so that it ends just +- before the PROT_NONE page. */ +- result.buffer = result.region_start + result.region_size - page_size - size; ++ ++ if (fault_after_alloc) ++ { ++ protect_offset = result.region_size - page_size; ++ buffer_offset = protect_offset - size; ++ } ++ ++ /* Unmap the page before or after the allocation. */ ++ xmprotect (result.region_start + protect_offset, page_size, PROT_NONE); ++ /* Align the allocation within the region so that it starts after or ends ++ just before the PROT_NONE page. */ ++ result.buffer = result.region_start + buffer_offset; + result.length = size; + return result; + } + ++/* Unmapped a page after the buffer */ ++struct support_next_to_fault ++support_next_to_fault_allocate (size_t size) ++{ ++ return support_next_to_fault_allocate_any (size, true); ++} ++ ++/* Unmapped a page before the buffer */ ++struct support_next_to_fault ++support_next_to_fault_allocate_before (size_t size) ++{ ++ return support_next_to_fault_allocate_any (size, false); ++} ++ + void + support_next_to_fault_free (struct support_next_to_fault *ntf) + { +diff --git a/support/next_to_fault.h b/support/next_to_fault.h +index 7112cc4c65..cec4b97332 100644 +--- a/support/next_to_fault.h ++++ b/support/next_to_fault.h +@@ -41,6 +41,11 @@ struct support_next_to_fault + fault). */ + struct support_next_to_fault support_next_to_fault_allocate (size_t size); + ++/* Allocate a buffer of SIZE bytes just *after* a page which is mapped ++ with PROT_NONE (so that under-running the buffer will cause a ++ fault). */ ++struct support_next_to_fault support_next_to_fault_allocate_before (size_t size); ++ + /* Deallocate the memory region allocated by + next_to_fault_allocate. */ + void support_next_to_fault_free (struct support_next_to_fault *); diff --git a/support/resolv_test.c b/support/resolv_test.c index ab37d3d58c..29e59da958 100644 --- a/support/resolv_test.c @@ -14605,6 +17698,69 @@ This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or +diff --git a/sysdeps/powerpc/powerpc64/power8/strcasecmp.S b/sysdeps/powerpc/powerpc64/power8/strcasecmp.S +index 7ce53407b8..838b8bf75f 100644 +--- a/sysdeps/powerpc/powerpc64/power8/strcasecmp.S ++++ b/sysdeps/powerpc/powerpc64/power8/strcasecmp.S +@@ -355,6 +355,14 @@ L(bytebybyte): + offset and string descriptors are only updated in the end + of loop unrolling. */ + ld rLOC, LOCALE_CTYPE_TOLOWER(rLOC) ++#ifdef USE_AS_STRNCASECMP ++ /* This entry point can be reached from the vector loop with ++ r5 == 0 (the last vector chunk ended exactly at the `n' ++ boundary). Do not read s1[0]/s2[0] in that case: those ++ bytes are s1[n]/s2[n], one past the caller's bound. */ ++ cmpdi cr7, r5, 0 ++ beq cr7, L(retnull) ++#endif + lbz rCHAR1, 0(rSTR1) /* Load char from s1 */ + lbz rCHAR2, 0(rSTR2) /* Load char from s2 */ + #ifdef USE_AS_STRNCASECMP +@@ -405,19 +413,23 @@ L(loop): + cmpw cr1, rLWR1, rLWR2 + crorc 4*cr1+eq,eq,4*cr1+eq + beq cr1, L(done) ++#ifdef USE_AS_STRNCASECMP ++ /* Test the remaining count before touching the next byte. */ ++ bdnz L(loop_load) ++ b L(lessthan4) ++L(loop_load): ++#endif + lbz rCHAR1, 0(rSTR1) /* Load char from s1 */ + lbz rCHAR2, 0(rSTR2) /* Load char from s2 */ +-#ifdef USE_AS_STRNCASECMP +- bdnz L(loop) +-#else + b L(loop) +-#endif + #ifdef USE_AS_STRNCASECMP + L(lessthan4): + clrldi r5, r5, 62 + cmpdi cr7, r5, 0 + beq cr7, L(retnull) + mtctr r5 ++ lbz rCHAR1, 0(rSTR1) ++ lbz rCHAR2, 0(rSTR2) + L(loop1): + cmpdi rCHAR1, 0 + sldi rADDR1, rCHAR1, 2 +@@ -427,11 +439,14 @@ L(loop1): + cmpw cr1, rLWR1, rLWR2 + crorc 4*cr1+eq,eq,4*cr1+eq + beq cr1, L(done) ++ bdnz L(loop1_load) ++ b L(retnull) ++L(loop1_load): + addi rSTR1, rSTR1, 1 + addi rSTR2, rSTR2, 1 + lbz rCHAR1, 0(rSTR1) + lbz rCHAR2, 0(rSTR2) +- bdnz L(loop1) ++ b L(loop1) + #endif + L(done): + subf r0, rLWR2, rLWR1 diff --git a/sysdeps/pthread/Makefile b/sysdeps/pthread/Makefile index a123e28a57..7fcbc72bc0 100644 --- a/sysdeps/pthread/Makefile @@ -16772,6 +19928,217 @@ CPU_FEATURE_UNSET (cpu_features, XSAVEC); } } +diff --git a/sysdeps/x86/dl-cacheinfo.h b/sysdeps/x86/dl-cacheinfo.h +index 6f9bb08a19..201d3ad278 100644 +--- a/sysdeps/x86/dl-cacheinfo.h ++++ b/sysdeps/x86/dl-cacheinfo.h +@@ -98,6 +98,15 @@ static const struct intel_02_cache_info + + #define nintel_02_known (sizeof (intel_02_known) / sizeof (intel_02_known [0])) + ++/* Cache for redundant cpuid queries in handle_intel, intel_check_word and ++ get_common_cache_info. Currently, this has only been shown to significantly ++ improve performance on a specific Intel CPU (Xeon 6430). */ ++struct intel_cpuid_cache ++{ ++ unsigned char leaf2_valid, leaf4_valid; /* Number of cached (sub)leaves. */ ++ unsigned int leaf2[4], leaf4[0x10][4]; ++}; ++ + static int + intel_02_known_compare (const void *p1, const void *p2) + { +@@ -118,7 +127,8 @@ static long int + __attribute__ ((noinline)) + intel_check_word (int name, unsigned int value, bool *has_level_2, + bool *no_level_2_or_3, +- const struct cpu_features *cpu_features) ++ const struct cpu_features *cpu_features, ++ struct intel_cpuid_cache *cache) + { + if ((value & 0x80000000) != 0) + /* The register value is reserved. */ +@@ -152,7 +162,21 @@ intel_check_word (int name, unsigned int value, bool *has_level_2, + unsigned int round = 0; + while (1) + { +- __cpuid_count (4, round, eax, ebx, ecx, edx); ++ if (round < cache->leaf4_valid) ++ /* Subleaf was queried before. Do not execute cpuid again. */ ++ eax = cache->leaf4[round][0], ebx = cache->leaf4[round][1], ++ ecx = cache->leaf4[round][2], edx = cache->leaf4[round][3]; ++ else if (round == cache->leaf4_valid ++ && round < sizeof(cache->leaf4)/sizeof(*cache->leaf4)) ++ { ++ /* Cache the cpuid result if we have space. */ ++ __cpuid_count (4, round, eax, ebx, ecx, edx); ++ cache->leaf4[round][0] = eax, cache->leaf4[round][1] = ebx; ++ cache->leaf4[round][2] = ecx, cache->leaf4[round][3] = edx; ++ cache->leaf4_valid++; ++ } ++ else ++ __cpuid_count (4, round, eax, ebx, ecx, edx); + + enum { null = 0, data = 1, inst = 2, uni = 3 } type = eax & 0x1f; + if (type == null) +@@ -247,7 +271,8 @@ intel_check_word (int name, unsigned int value, bool *has_level_2, + + + static long int __attribute__ ((noinline)) +-handle_intel (int name, const struct cpu_features *cpu_features) ++handle_intel (int name, const struct cpu_features *cpu_features, ++ struct intel_cpuid_cache *cache) + { + unsigned int maxidx = cpu_features->basic.max_cpuid; + +@@ -260,41 +285,33 @@ handle_intel (int name, const struct cpu_features *cpu_features) + long int result = 0; + bool no_level_2_or_3 = false; + bool has_level_2 = false; +- unsigned int eax; +- unsigned int ebx; +- unsigned int ecx; +- unsigned int edx; +- __cpuid (2, eax, ebx, ecx, edx); ++ int i; ++ ++ if (!cache->leaf2_valid) ++ { ++ __cpuid (2, cache->leaf2[0], cache->leaf2[1], ++ cache->leaf2[2], cache->leaf2[3]); ++ cache->leaf2_valid = 1; ++ } + + /* The low byte of EAX of CPUID leaf 2 should always return 1 and it + should be ignored. If it isn't 1, use CPUID leaf 4 instead. */ +- if ((eax & 0xff) != 1) ++ if ((cache->leaf2[0] & 0xff) != 1) + return intel_check_word (name, 0xff, &has_level_2, &no_level_2_or_3, +- cpu_features); +- else +- { +- eax &= 0xffffff00; +- +- /* Process the individual registers' value. */ +- result = intel_check_word (name, eax, &has_level_2, +- &no_level_2_or_3, cpu_features); +- if (result != 0) +- return result; ++ cpu_features, cache); + +- result = intel_check_word (name, ebx, &has_level_2, +- &no_level_2_or_3, cpu_features); +- if (result != 0) +- return result; +- +- result = intel_check_word (name, ecx, &has_level_2, +- &no_level_2_or_3, cpu_features); +- if (result != 0) +- return result; ++ /* Process all descriptors in leaf 2. */ ++ result = intel_check_word (name, cache->leaf2[0]&0xffffff00, &has_level_2, ++ &no_level_2_or_3, cpu_features, cache); ++ if (result != 0) ++ return result; + +- result = intel_check_word (name, edx, &has_level_2, +- &no_level_2_or_3, cpu_features); ++ for (i = 1; i < 4; i++) ++ { ++ result = intel_check_word (name, cache->leaf2[i], &has_level_2, ++ &no_level_2_or_3, cpu_features, cache); + if (result != 0) +- return result; ++ return result; + } + + if (name >= _SC_LEVEL2_CACHE_SIZE && name <= _SC_LEVEL3_CACHE_LINESIZE +@@ -611,7 +628,7 @@ handle_hygon (int name) + + static void + get_common_cache_info (long int *shared_ptr, long int * shared_per_thread_ptr, unsigned int *threads_ptr, +- long int core) ++ long int core, struct intel_cpuid_cache *cache) + { + unsigned int eax; + unsigned int ebx; +@@ -669,7 +686,14 @@ get_common_cache_info (long int *shared_ptr, long int * shared_per_thread_ptr, u + int check = 0x1 | (threads_l3 == 0) << 1; + do + { +- __cpuid_count (4, i++, eax, ebx, ecx, edx); ++ if (cache != NULL && i < cache->leaf4_valid) ++ eax = cache->leaf4[i][0], ebx = cache->leaf4[i][1], ++ ecx = cache->leaf4[i][2], edx = cache->leaf4[i][3]; ++ else ++ /* Do not attempt to cache queries at this point, ++ because get_common_cache_info is called last. */ ++ __cpuid_count (4, i, eax, ebx, ecx, edx); ++ i++; + + /* There seems to be a bug in at least some Pentium Ds + which sometimes fail to iterate all cache parameters. +@@ -849,35 +873,38 @@ dl_init_cacheinfo (struct cpu_features *cpu_features) + + if (cpu_features->basic.kind == arch_kind_intel) + { +- data = handle_intel (_SC_LEVEL1_DCACHE_SIZE, cpu_features); +- shared = handle_intel (_SC_LEVEL3_CACHE_SIZE, cpu_features); ++ struct intel_cpuid_cache cache; ++ cache.leaf2_valid = cache.leaf4_valid = 0; ++ ++ data = handle_intel (_SC_LEVEL1_DCACHE_SIZE, cpu_features, &cache); ++ shared = handle_intel (_SC_LEVEL3_CACHE_SIZE, cpu_features, &cache); + shared_per_thread = shared; + + level1_icache_size +- = handle_intel (_SC_LEVEL1_ICACHE_SIZE, cpu_features); ++ = handle_intel (_SC_LEVEL1_ICACHE_SIZE, cpu_features, &cache); + level1_icache_linesize +- = handle_intel (_SC_LEVEL1_ICACHE_LINESIZE, cpu_features); ++ = handle_intel (_SC_LEVEL1_ICACHE_LINESIZE, cpu_features, &cache); + level1_dcache_size = data; + level1_dcache_assoc +- = handle_intel (_SC_LEVEL1_DCACHE_ASSOC, cpu_features); ++ = handle_intel (_SC_LEVEL1_DCACHE_ASSOC, cpu_features, &cache); + level1_dcache_linesize +- = handle_intel (_SC_LEVEL1_DCACHE_LINESIZE, cpu_features); ++ = handle_intel (_SC_LEVEL1_DCACHE_LINESIZE, cpu_features, &cache); + level2_cache_size +- = handle_intel (_SC_LEVEL2_CACHE_SIZE, cpu_features); ++ = handle_intel (_SC_LEVEL2_CACHE_SIZE, cpu_features, &cache); + level2_cache_assoc +- = handle_intel (_SC_LEVEL2_CACHE_ASSOC, cpu_features); ++ = handle_intel (_SC_LEVEL2_CACHE_ASSOC, cpu_features, &cache); + level2_cache_linesize +- = handle_intel (_SC_LEVEL2_CACHE_LINESIZE, cpu_features); ++ = handle_intel (_SC_LEVEL2_CACHE_LINESIZE, cpu_features, &cache); + level3_cache_size = shared; + level3_cache_assoc +- = handle_intel (_SC_LEVEL3_CACHE_ASSOC, cpu_features); ++ = handle_intel (_SC_LEVEL3_CACHE_ASSOC, cpu_features, &cache); + level3_cache_linesize +- = handle_intel (_SC_LEVEL3_CACHE_LINESIZE, cpu_features); ++ = handle_intel (_SC_LEVEL3_CACHE_LINESIZE, cpu_features, &cache); + level4_cache_size +- = handle_intel (_SC_LEVEL4_CACHE_SIZE, cpu_features); ++ = handle_intel (_SC_LEVEL4_CACHE_SIZE, cpu_features, &cache); + + get_common_cache_info (&shared, &shared_per_thread, &threads, +- level2_cache_size); ++ level2_cache_size, &cache); + } + else if (cpu_features->basic.kind == arch_kind_zhaoxin) + { +@@ -898,7 +925,7 @@ dl_init_cacheinfo (struct cpu_features *cpu_features) + level3_cache_linesize = handle_zhaoxin (_SC_LEVEL3_CACHE_LINESIZE); + + get_common_cache_info (&shared, &shared_per_thread, &threads, +- level2_cache_size); ++ level2_cache_size, NULL); + } + else if (cpu_features->basic.kind == arch_kind_amd) + { diff --git a/sysdeps/x86/dl-diagnostics-cpu.c b/sysdeps/x86/dl-diagnostics-cpu.c index 7d0373602d..870b1268db 100644 --- a/sysdeps/x86/dl-diagnostics-cpu.c @@ -17492,3 +20859,16 @@ #include /* Some systems define `time_t' here. */ #ifdef TIME_WITH_SYS_TIME +diff --git a/timezone/zic.c b/timezone/zic.c +index d5d30163b0..17c6f906b2 100644 +--- a/timezone/zic.c ++++ b/timezone/zic.c +@@ -3234,7 +3234,7 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount) + startttisut); + if (usestart) { + addtt(starttime, type); +- if (useuntil && nonTZlimtime < starttime) { ++ if (nonTZlimtime < starttime) { + nonTZlimtime = starttime; + nonTZlimtype = type; + }