|
| 1 | +From ce65d944e38a20cb70af2a48a4b8aa5d8fabe1cc Mon Sep 17 00:00:00 2001 |
| 2 | +From: Adhemerval Zanella <adhemerval.zanella@linaro.org> |
| 3 | +Date: Thu, 15 Jan 2026 10:32:19 -0300 |
| 4 | +Subject: [PATCH 1/1] posix: Reset wordexp_t fields with WRDE_REUSE |
| 5 | + (CVE-2025-15281 / BZ 33814) |
| 6 | + |
| 7 | +The wordexp fails to properly initialize the input wordexp_t when |
| 8 | +WRDE_REUSE is used. The wordexp_t struct is properly freed, but |
| 9 | +reuses the old wc_wordc value and updates the we_wordv in the |
| 10 | +wrong position. A later wordfree will then call free with an |
| 11 | +invalid pointer. |
| 12 | + |
| 13 | +Checked on x86_64-linux-gnu and i686-linux-gnu. |
| 14 | + |
| 15 | +Reviewed-by: Carlos O'Donell <carlos@redhat.com> |
| 16 | +(cherry picked from commit 80cc58ea2de214f85b0a1d902a3b668ad2ecb302) |
| 17 | + |
| 18 | +Upstream Patch Reference: https://sourceware.org/git/?p=glibc.git;a=patch;h=ce65d944e38a20cb70af2a48a4b8aa5d8fabe1cc;hp=831f63b94ceb92fb14c0d1a7ddad35a0d1404c71 |
| 19 | +--- |
| 20 | + NEWS | 6 +++ |
| 21 | + posix/Makefile | 10 +++++ |
| 22 | + posix/tst-wordexp-reuse.c | 89 +++++++++++++++++++++++++++++++++++++++ |
| 23 | + posix/wordexp.c | 2 + |
| 24 | + 4 files changed, 107 insertions(+) |
| 25 | + create mode 100644 posix/tst-wordexp-reuse.c |
| 26 | + |
| 27 | +diff --git a/NEWS b/NEWS |
| 28 | +index faa7ec18..d8fbec32 100644 |
| 29 | +--- a/NEWS |
| 30 | ++++ b/NEWS |
| 31 | +@@ -199,6 +199,10 @@ Security related changes: |
| 32 | + corresponds to the / directory through an unprivileged mount |
| 33 | + namespace. Reported by Qualys. |
| 34 | + |
| 35 | ++ GLIBC-SA-2026-0003 |
| 36 | ++ wordexp with WRDE_REUSE and WRDE_APPEND may return uninitialized |
| 37 | ++ memory (CVE-2025-15281) |
| 38 | ++ |
| 39 | + The following bugs are resolved with this release: |
| 40 | + |
| 41 | + [12889] nptl: Race condition in pthread_kill |
| 42 | +@@ -335,6 +339,8 @@ The following bugs are resolved with this release: |
| 43 | + [28837] libc: FAIL: socket/tst-socket-timestamp-compat |
| 44 | + [28847] locale: Empty mon_decimal_point in LC_MONETARY results in non- |
| 45 | + empty mon_decimal_point_wc |
| 46 | ++ [33814] glob: wordexp with WRDE_REUSE and WRDE_APPEND may return |
| 47 | ++ uninitialized memory |
| 48 | + |
| 49 | + |
| 50 | + Version 2.34 |
| 51 | +diff --git a/posix/Makefile b/posix/Makefile |
| 52 | +index 9b30b53a..bc068ed9 100644 |
| 53 | +--- a/posix/Makefile |
| 54 | ++++ b/posix/Makefile |
| 55 | +@@ -109,6 +109,7 @@ tests := test-errno tstgetopt testfnm runtests runptests \ |
| 56 | + tst-glob-tilde test-ssize-max tst-spawn4 bug-regex37 \ |
| 57 | + bug-regex38 tst-regcomp-truncated tst-spawn-chdir \ |
| 58 | + tst-wordexp-nocmd tst-execveat tst-spawn5 \ |
| 59 | ++ tst-wordexp-reuse \ |
| 60 | + tst-sched_getaffinity tst-spawn6 |
| 61 | + |
| 62 | + # Test for the glob symbol version that was replaced in glibc 2.27. |
| 63 | +@@ -156,6 +157,7 @@ generated += $(addprefix wordexp-test-result, 1 2 3 4 5 6 7 8 9 10) \ |
| 64 | + bug-glob2.mtrace bug-glob2-mem.out tst-vfork3-mem.out \ |
| 65 | + tst-vfork3.mtrace getconf.speclist tst-fnmatch-mem.out \ |
| 66 | + tst-fnmatch.mtrace bug-regex36.mtrace \ |
| 67 | ++ tst-wordexp-reuse-mem.out tst-wordexp-reuse.mtrace \ |
| 68 | + testcases.h ptestcases.h |
| 69 | + |
| 70 | + ifeq ($(run-built-tests),yes) |
| 71 | +@@ -174,6 +176,7 @@ tests-special += $(objpfx)bug-regex2-mem.out $(objpfx)bug-regex14-mem.out \ |
| 72 | + $(objpfx)tst-boost-mem.out $(objpfx)tst-getconf.out \ |
| 73 | + $(objpfx)bug-glob2-mem.out $(objpfx)tst-vfork3-mem.out \ |
| 74 | + $(objpfx)tst-fnmatch-mem.out $(objpfx)bug-regex36-mem.out \ |
| 75 | ++ $(objpfx)tst-wordexp-reuse.out \ |
| 76 | + $(objpfx)tst-glob-tilde-mem.out $(objpfx)bug-ga2-mem.out |
| 77 | + endif |
| 78 | + |
| 79 | +@@ -451,3 +454,10 @@ $(objpfx)posix-conf-vars-def.h: $(..)scripts/gen-posix-conf-vars.awk \ |
| 80 | + $(make-target-directory) |
| 81 | + $(AWK) -f $(filter-out Makefile, $^) > $@.tmp |
| 82 | + mv -f $@.tmp $@ |
| 83 | ++ |
| 84 | ++tst-wordexp-reuse-ENV += MALLOC_TRACE=$(objpfx)tst-wordexp-reuse.mtrace \ |
| 85 | ++ LD_PRELOAD=$(common-objpfx)/malloc/libc_malloc_debug.so |
| 86 | ++ |
| 87 | ++$(objpfx)tst-wordexp-reuse-mem.out: $(objpfx)tst-wordexp-reuse.out \ |
| 88 | ++ $(common-objpfx)malloc/mtrace $(objpfx)tst-wordexp-reuse.mtrace > $@; \ |
| 89 | ++ $(evaluate-test) |
| 90 | +diff --git a/posix/tst-wordexp-reuse.c b/posix/tst-wordexp-reuse.c |
| 91 | +new file mode 100644 |
| 92 | +index 00000000..c2c12fd1 |
| 93 | +--- /dev/null |
| 94 | ++++ b/posix/tst-wordexp-reuse.c |
| 95 | +@@ -0,0 +1,89 @@ |
| 96 | ++/* Test for wordexp with WRDE_REUSE flag. |
| 97 | ++ Copyright (C) 2026 Free Software Foundation, Inc. |
| 98 | ++ This file is part of the GNU C Library. |
| 99 | ++ |
| 100 | ++ The GNU C Library is free software; you can redistribute it and/or |
| 101 | ++ modify it under the terms of the GNU Lesser General Public |
| 102 | ++ License as published by the Free Software Foundation; either |
| 103 | ++ version 2.1 of the License, or (at your option) any later version. |
| 104 | ++ |
| 105 | ++ The GNU C Library is distributed in the hope that it will be useful, |
| 106 | ++ but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 107 | ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 108 | ++ Lesser General Public License for more details. |
| 109 | ++ |
| 110 | ++ You should have received a copy of the GNU Lesser General Public |
| 111 | ++ License along with the GNU C Library; if not, see |
| 112 | ++ <https://www.gnu.org/licenses/>. */ |
| 113 | ++ |
| 114 | ++#include <wordexp.h> |
| 115 | ++#include <mcheck.h> |
| 116 | ++ |
| 117 | ++#include <support/check.h> |
| 118 | ++ |
| 119 | ++static int |
| 120 | ++do_test (void) |
| 121 | ++{ |
| 122 | ++ mtrace (); |
| 123 | ++ |
| 124 | ++ { |
| 125 | ++ wordexp_t p = { 0 }; |
| 126 | ++ TEST_COMPARE (wordexp ("one", &p, 0), 0); |
| 127 | ++ TEST_COMPARE (p.we_wordc, 1); |
| 128 | ++ TEST_COMPARE_STRING (p.we_wordv[0], "one"); |
| 129 | ++ TEST_COMPARE (wordexp ("two", &p, WRDE_REUSE), 0); |
| 130 | ++ TEST_COMPARE (p.we_wordc, 1); |
| 131 | ++ TEST_COMPARE_STRING (p.we_wordv[0], "two"); |
| 132 | ++ wordfree (&p); |
| 133 | ++ } |
| 134 | ++ |
| 135 | ++ { |
| 136 | ++ wordexp_t p = { .we_offs = 2 }; |
| 137 | ++ TEST_COMPARE (wordexp ("one", &p, 0), 0); |
| 138 | ++ TEST_COMPARE (p.we_wordc, 1); |
| 139 | ++ TEST_COMPARE_STRING (p.we_wordv[0], "one"); |
| 140 | ++ TEST_COMPARE (wordexp ("two", &p, WRDE_REUSE | WRDE_DOOFFS), 0); |
| 141 | ++ TEST_COMPARE (p.we_wordc, 1); |
| 142 | ++ TEST_COMPARE_STRING (p.we_wordv[p.we_offs + 0], "two"); |
| 143 | ++ wordfree (&p); |
| 144 | ++ } |
| 145 | ++ |
| 146 | ++ { |
| 147 | ++ wordexp_t p = { 0 }; |
| 148 | ++ TEST_COMPARE (wordexp ("one", &p, 0), 0); |
| 149 | ++ TEST_COMPARE (p.we_wordc, 1); |
| 150 | ++ TEST_COMPARE_STRING (p.we_wordv[0], "one"); |
| 151 | ++ TEST_COMPARE (wordexp ("two", &p, WRDE_REUSE | WRDE_APPEND), 0); |
| 152 | ++ TEST_COMPARE (p.we_wordc, 1); |
| 153 | ++ TEST_COMPARE_STRING (p.we_wordv[0], "two"); |
| 154 | ++ wordfree (&p); |
| 155 | ++ } |
| 156 | ++ |
| 157 | ++ { |
| 158 | ++ wordexp_t p = { .we_offs = 2 }; |
| 159 | ++ TEST_COMPARE (wordexp ("one", &p, WRDE_DOOFFS), 0); |
| 160 | ++ TEST_COMPARE (p.we_wordc, 1); |
| 161 | ++ TEST_COMPARE_STRING (p.we_wordv[p.we_offs + 0], "one"); |
| 162 | ++ TEST_COMPARE (wordexp ("two", &p, WRDE_REUSE |
| 163 | ++ | WRDE_DOOFFS), 0); |
| 164 | ++ TEST_COMPARE (p.we_wordc, 1); |
| 165 | ++ TEST_COMPARE_STRING (p.we_wordv[p.we_offs + 0], "two"); |
| 166 | ++ wordfree (&p); |
| 167 | ++ } |
| 168 | ++ |
| 169 | ++ { |
| 170 | ++ wordexp_t p = { .we_offs = 2 }; |
| 171 | ++ TEST_COMPARE (wordexp ("one", &p, WRDE_DOOFFS), 0); |
| 172 | ++ TEST_COMPARE (p.we_wordc, 1); |
| 173 | ++ TEST_COMPARE_STRING (p.we_wordv[p.we_offs + 0], "one"); |
| 174 | ++ TEST_COMPARE (wordexp ("two", &p, WRDE_REUSE |
| 175 | ++ | WRDE_DOOFFS | WRDE_APPEND), 0); |
| 176 | ++ TEST_COMPARE (p.we_wordc, 1); |
| 177 | ++ TEST_COMPARE_STRING (p.we_wordv[p.we_offs + 0], "two"); |
| 178 | ++ wordfree (&p); |
| 179 | ++ } |
| 180 | ++ |
| 181 | ++ return 0; |
| 182 | ++} |
| 183 | ++ |
| 184 | ++#include <support/test-driver.c> |
| 185 | +diff --git a/posix/wordexp.c b/posix/wordexp.c |
| 186 | +index d4cb9c73..25f5b509 100644 |
| 187 | +--- a/posix/wordexp.c |
| 188 | ++++ b/posix/wordexp.c |
| 189 | +@@ -2219,7 +2219,9 @@ wordexp (const char *words, wordexp_t *pwordexp, int flags) |
| 190 | + { |
| 191 | + /* Minimal implementation of WRDE_REUSE for now */ |
| 192 | + wordfree (pwordexp); |
| 193 | ++ old_word.we_wordc = 0; |
| 194 | + old_word.we_wordv = NULL; |
| 195 | ++ pwordexp->we_wordc = 0; |
| 196 | + } |
| 197 | + |
| 198 | + if ((flags & WRDE_APPEND) == 0) |
| 199 | +-- |
| 200 | +2.45.4 |
| 201 | + |
0 commit comments