From 9520284df9ed49d3e5df2e861baa2990be584656 Mon Sep 17 00:00:00 2001 From: Florian Weimer Date: Thu, 12 Feb 2026 15:11:11 +0100 Subject: [PATCH] CVE-2025-15281: wordexp WRDE_REUSE uninitialized memory read Resolves: RHEL-142788 --- glibc-RHEL-142788-1.patch | 172 ++++++++++++++++++++++++++++++++++++++ glibc-RHEL-142788-2.patch | 29 +++++++ 2 files changed, 201 insertions(+) create mode 100644 glibc-RHEL-142788-1.patch create mode 100644 glibc-RHEL-142788-2.patch diff --git a/glibc-RHEL-142788-1.patch b/glibc-RHEL-142788-1.patch new file mode 100644 index 0000000..be5ed54 --- /dev/null +++ b/glibc-RHEL-142788-1.patch @@ -0,0 +1,172 @@ +commit 80cc58ea2de214f85b0a1d902a3b668ad2ecb302 +Author: Adhemerval Zanella +Date: Thu Jan 15 10:32:19 2026 -0300 + + 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 + +Conflicts: + posix/Makefile + (Makefile not sorted downstream) + +diff --git a/posix/Makefile b/posix/Makefile +index 562e8cb85fdb6f43..2dfd687430b205a8 100644 +--- a/posix/Makefile ++++ b/posix/Makefile +@@ -113,7 +113,8 @@ tests := test-errno tstgetopt testfnm runtests runptests \ + tst-cpuset-dynamic \ + tst-cpuset-static \ + tst-spawn6 \ +- tst-regcomp-bracket-free ++ tst-regcomp-bracket-free \ ++ tst-wordexp-reuse + + # Test for the glob symbol version that was replaced in glibc 2.27. + ifeq ($(have-GLIBC_2.26)$(build-shared),yesyes) +@@ -160,6 +161,7 @@ generated += $(addprefix wordexp-test-result, 1 2 3 4 5 6 7 8 9 10) \ + bug-glob2.mtrace bug-glob2-mem.out tst-vfork3-mem.out \ + tst-vfork3.mtrace getconf.speclist tst-fnmatch-mem.out \ + tst-fnmatch.mtrace bug-regex36.mtrace \ ++ tst-wordexp-reuse-mem.out tst-wordexp-reuse.mtrace \ + testcases.h ptestcases.h + + ifeq ($(run-built-tests),yes) +@@ -178,7 +180,8 @@ tests-special += $(objpfx)bug-regex2-mem.out $(objpfx)bug-regex14-mem.out \ + $(objpfx)tst-boost-mem.out $(objpfx)tst-getconf.out \ + $(objpfx)bug-glob2-mem.out $(objpfx)tst-vfork3-mem.out \ + $(objpfx)tst-fnmatch-mem.out $(objpfx)bug-regex36-mem.out \ +- $(objpfx)tst-glob-tilde-mem.out $(objpfx)bug-ga2-mem.out ++ $(objpfx)tst-glob-tilde-mem.out $(objpfx)bug-ga2-mem.out \ ++ $(objpfx)tst-wordexp-reuse.out + endif + + include ../Rules +@@ -455,3 +458,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 0000000000000000..3926b9f5576750ac +--- /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 1f3b09f721bbee5c..b23608d4ee1cccd8 100644 +--- a/posix/wordexp.c ++++ b/posix/wordexp.c +@@ -2220,7 +2220,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) diff --git a/glibc-RHEL-142788-2.patch b/glibc-RHEL-142788-2.patch new file mode 100644 index 0000000..b459da1 --- /dev/null +++ b/glibc-RHEL-142788-2.patch @@ -0,0 +1,29 @@ +commit bed2db02f3183e93f21d506786c5f884a1dec9e7 +Author: Florian Weimer +Date: Mon Jan 26 17:12:37 2026 +0100 + + posix: Run tst-wordexp-reuse-mem test + + The test was not properly scheduled for execution with a Makefile + dependency. + + Fixes commit 80cc58ea2de214f85b0a1d902a3b668ad2ecb302 ("posix: Reset + wordexp_t fields with WRDE_REUSE (CVE-2025-15281 / BZ 33814"). + +Conflicts: + posix/Makefile + (Makefile not sorted downstream) + +diff --git a/posix/Makefile b/posix/Makefile +index 2dfd687430b205a8..837fc26868ed74a8 100644 +--- a/posix/Makefile ++++ b/posix/Makefile +@@ -181,7 +181,7 @@ tests-special += $(objpfx)bug-regex2-mem.out $(objpfx)bug-regex14-mem.out \ + $(objpfx)bug-glob2-mem.out $(objpfx)tst-vfork3-mem.out \ + $(objpfx)tst-fnmatch-mem.out $(objpfx)bug-regex36-mem.out \ + $(objpfx)tst-glob-tilde-mem.out $(objpfx)bug-ga2-mem.out \ +- $(objpfx)tst-wordexp-reuse.out ++ $(objpfx)tst-wordexp-reuse-mem.out + endif + + include ../Rules