aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdhemerval Zanella <adhemerval.zanella@linaro.org>2026-01-15 10:32:19 -0300
committerAdhemerval Zanella <adhemerval.zanella@linaro.org>2026-01-20 14:05:25 -0300
commitce65d944e38a20cb70af2a48a4b8aa5d8fabe1cc (patch)
treee904b32820713a3f92c225143e075b1fcab7247f
parent831f63b94ceb92fb14c0d1a7ddad35a0d1404c71 (diff)
downloadglibc-release/2.39/master.zip
glibc-release/2.39/master.tar.gz
glibc-release/2.39/master.tar.bz2
posix: Reset wordexp_t fields with WRDE_REUSE (CVE-2025-15281 / BZ 33814)release/2.39/master
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 <carlos@redhat.com> (cherry picked from commit 80cc58ea2de214f85b0a1d902a3b668ad2ecb302)
-rw-r--r--NEWS6
-rw-r--r--posix/Makefile11
-rw-r--r--posix/tst-wordexp-reuse.c89
-rw-r--r--posix/wordexp.c2
4 files changed, 108 insertions, 0 deletions
diff --git a/NEWS b/NEWS
index bf1887b..d330ea1 100644
--- a/NEWS
+++ b/NEWS
@@ -38,6 +38,10 @@ The following CVEs were fixed in this release:
assert: Buffer overflow when printing assertion failure message
(CVE-2025-0395)
+ 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:
[19341] ctype: Fallback initialization of TLS using relocations
@@ -109,6 +113,8 @@ The following bugs are resolved with this release:
[33234] Use TLS initial-exec model for __libc_tsd_CTYPE_* thread variables
[33361] nss: Group merge does not react to ERANGE during merge
[33601] aarch64: Do not link conform tests with -Wl,-z,force-bti
+ [33814] glob: wordexp with WRDE_REUSE and WRDE_APPEND may return
+ uninitialized memory
Version 2.39
diff --git a/posix/Makefile b/posix/Makefile
index 18ddb8c..25e2794 100644
--- a/posix/Makefile
+++ b/posix/Makefile
@@ -326,6 +326,7 @@ tests := \
tst-wait4 \
tst-waitid \
tst-wordexp-nocmd \
+ tst-wordexp-reuse \
tstgetopt \
# tests
@@ -446,6 +447,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
@@ -477,6 +480,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
@@ -758,3 +762,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 0000000..3926b9f
--- /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
+ <https://www.gnu.org/licenses/>. */
+
+#include <wordexp.h>
+#include <mcheck.h>
+
+#include <support/check.h>
+
+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 <support/test-driver.c>
diff --git a/posix/wordexp.c b/posix/wordexp.c
index a7362ef..4cd2364 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)