]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
posix: Reset wordexp_t fields with WRDE_REUSE (CVE-2025-15281 / BZ 33814)
authorAdhemerval Zanella <adhemerval.zanella@linaro.org>
Thu, 15 Jan 2026 13:32:19 +0000 (10:32 -0300)
committerAdhemerval Zanella <adhemerval.zanella@linaro.org>
Tue, 20 Jan 2026 14:34:12 +0000 (11:34 -0300)
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>
posix/Makefile
posix/tst-wordexp-reuse.c [new file with mode: 0644]
posix/wordexp.c

index 03229768e9f88024857f03e052b21af7f4061a15..71a19061686600b73c9bd24e934ffd96998e0647 100644 (file)
@@ -328,6 +328,7 @@ tests := \
   tst-wait4 \
   tst-waitid \
   tst-wordexp-nocmd \
+  tst-wordexp-reuse \
   tstgetopt \
   # tests
 
@@ -458,6 +459,8 @@ generated += \
   tst-rxspencer-no-utf8.mtrace \
   tst-vfork3-mem.out \
   tst-vfork3.mtrace \
+  tst-wordexp-reuse-mem.out \
+  tst-wordexp-reuse.mtrace \
   # generated
 endif
 endif
@@ -496,6 +499,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
 endif
@@ -789,3 +793,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 (file)
index 0000000..3926b9f
--- /dev/null
@@ -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>
index 167a62d80cc2615e5e80742db2a258df71371901..4a8541add495311af3c4105b634e2320d5622ee8 100644 (file)
@@ -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)