From d5409a1be010699794264162c551ba60f05ee6c3 Mon Sep 17 00:00:00 2001 From: Adhemerval Zanella Date: Thu, 15 Jan 2026 10:32:19 -0300 Subject: [PATCH] posix: Reset wordexp_t fields with WRDE_REUSE (CVE-2025-15281 / BZ 33814) The wordexp fails to properly initialize the input wordexp_t when WRDE_REUSE is used. The wordexp_t struct is properly freed, but reuses the old wc_wordc value and updates the we_wordv in the wrong position. A later wordfree will then call free with an invalid pointer. Checked on x86_64-linux-gnu and i686-linux-gnu. Reviewed-by: Carlos O'Donell (cherry picked from commit 80cc58ea2de214f85b0a1d902a3b668ad2ecb302) --- NEWS | 6 +++ posix/Makefile | 11 +++++ posix/tst-wordexp-reuse.c | 89 +++++++++++++++++++++++++++++++++++++++ posix/wordexp.c | 2 + 4 files changed, 108 insertions(+) create mode 100644 posix/tst-wordexp-reuse.c diff --git a/NEWS b/NEWS index 7f3049367f..c81099e8d4 100644 --- a/NEWS +++ b/NEWS @@ -46,6 +46,10 @@ Security related changes: information, which may lead to a buffer overflow if the message string size aligns to page size. + GLIBC-SA-2026-0003 + wordexp with WRDE_REUSE and WRDE_APPEND may return uninitialized + memory (CVE-2025-15281) + The following bugs are resolved with this release: [27821] ungetc: Fix backup buffer leak on program exit @@ -77,6 +81,8 @@ The following bugs are resolved with this release: [32582] Fix underallocation of abort_msg_s struct (CVE-2025-0395) [32987] elf: Fix subprocess status handling for tst-dlopen-sgid [33185] Fix double-free after allocation failure in regcomp + [33814] glob: wordexp with WRDE_REUSE and WRDE_APPEND may return + uninitialized memory Version 2.38 diff --git a/posix/Makefile b/posix/Makefile index 1fc0f565af..7db396fbe6 100644 --- a/posix/Makefile +++ b/posix/Makefile @@ -328,6 +328,7 @@ tests := \ tst-wait4 \ tst-waitid \ tst-wordexp-nocmd \ + tst-wordexp-reuse \ tstgetopt \ # tests @@ -453,6 +454,8 @@ generated += \ tst-rxspencer-no-utf8.mtrace \ tst-vfork3-mem.out \ tst-vfork3.mtrace \ + tst-wordexp-reuse-mem.out \ + tst-wordexp-reuse.mtrace \ wordexp-tst.out \ # generated @@ -484,6 +487,7 @@ tests-special += \ $(objpfx)tst-pcre-mem.out \ $(objpfx)tst-rxspencer-no-utf8-mem.out \ $(objpfx)tst-vfork3-mem.out \ + $(objpfx)tst-wordexp-reuse.out \ # tests-special endif @@ -765,3 +769,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 $@ + +tst-wordexp-reuse-ENV += MALLOC_TRACE=$(objpfx)tst-wordexp-reuse.mtrace \ + LD_PRELOAD=$(common-objpfx)/malloc/libc_malloc_debug.so + +$(objpfx)tst-wordexp-reuse-mem.out: $(objpfx)tst-wordexp-reuse.out + $(common-objpfx)malloc/mtrace $(objpfx)tst-wordexp-reuse.mtrace > $@; \ + $(evaluate-test) diff --git a/posix/tst-wordexp-reuse.c b/posix/tst-wordexp-reuse.c new file mode 100644 index 0000000000..3926b9f557 --- /dev/null +++ b/posix/tst-wordexp-reuse.c @@ -0,0 +1,89 @@ +/* Test for wordexp with WRDE_REUSE 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 + +static int +do_test (void) +{ + mtrace (); + + { + wordexp_t p = { 0 }; + TEST_COMPARE (wordexp ("one", &p, 0), 0); + TEST_COMPARE (p.we_wordc, 1); + TEST_COMPARE_STRING (p.we_wordv[0], "one"); + TEST_COMPARE (wordexp ("two", &p, WRDE_REUSE), 0); + TEST_COMPARE (p.we_wordc, 1); + TEST_COMPARE_STRING (p.we_wordv[0], "two"); + wordfree (&p); + } + + { + wordexp_t p = { .we_offs = 2 }; + TEST_COMPARE (wordexp ("one", &p, 0), 0); + TEST_COMPARE (p.we_wordc, 1); + TEST_COMPARE_STRING (p.we_wordv[0], "one"); + TEST_COMPARE (wordexp ("two", &p, WRDE_REUSE | WRDE_DOOFFS), 0); + TEST_COMPARE (p.we_wordc, 1); + TEST_COMPARE_STRING (p.we_wordv[p.we_offs + 0], "two"); + wordfree (&p); + } + + { + wordexp_t p = { 0 }; + TEST_COMPARE (wordexp ("one", &p, 0), 0); + TEST_COMPARE (p.we_wordc, 1); + TEST_COMPARE_STRING (p.we_wordv[0], "one"); + TEST_COMPARE (wordexp ("two", &p, WRDE_REUSE | WRDE_APPEND), 0); + TEST_COMPARE (p.we_wordc, 1); + TEST_COMPARE_STRING (p.we_wordv[0], "two"); + wordfree (&p); + } + + { + wordexp_t p = { .we_offs = 2 }; + TEST_COMPARE (wordexp ("one", &p, WRDE_DOOFFS), 0); + TEST_COMPARE (p.we_wordc, 1); + TEST_COMPARE_STRING (p.we_wordv[p.we_offs + 0], "one"); + TEST_COMPARE (wordexp ("two", &p, WRDE_REUSE + | WRDE_DOOFFS), 0); + TEST_COMPARE (p.we_wordc, 1); + TEST_COMPARE_STRING (p.we_wordv[p.we_offs + 0], "two"); + wordfree (&p); + } + + { + wordexp_t p = { .we_offs = 2 }; + TEST_COMPARE (wordexp ("one", &p, WRDE_DOOFFS), 0); + TEST_COMPARE (p.we_wordc, 1); + TEST_COMPARE_STRING (p.we_wordv[p.we_offs + 0], "one"); + TEST_COMPARE (wordexp ("two", &p, WRDE_REUSE + | WRDE_DOOFFS | WRDE_APPEND), 0); + TEST_COMPARE (p.we_wordc, 1); + TEST_COMPARE_STRING (p.we_wordv[p.we_offs + 0], "two"); + wordfree (&p); + } + + return 0; +} + +#include diff --git a/posix/wordexp.c b/posix/wordexp.c index 994d79161f..5c5863b51b 100644 --- a/posix/wordexp.c +++ b/posix/wordexp.c @@ -2216,7 +2216,9 @@ wordexp (const char *words, wordexp_t *pwordexp, int flags) { /* Minimal implementation of WRDE_REUSE for now */ wordfree (pwordexp); + old_word.we_wordc = 0; old_word.we_wordv = NULL; + pwordexp->we_wordc = 0; } if ((flags & WRDE_APPEND) == 0)