|
| 1 | +From bf108a5fe1a992e5a6057fefa5d3d54f06300d3b 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] posix: Reset wordexp_t fields with WRDE_REUSE (CVE-2025-15281 |
| 5 | + / 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 | +Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> |
| 18 | +Upstream-reference: https://github.com/bminor/glibc/commit/d5409a1be010699794264162c551ba60f05ee6c3.patch |
| 19 | +--- |
| 20 | + posix/Makefile | 11 +++++ |
| 21 | + posix/tst-wordexp-reuse.c | 89 +++++++++++++++++++++++++++++++++++++++ |
| 22 | + posix/wordexp.c | 2 + |
| 23 | + 3 files changed, 102 insertions(+) |
| 24 | + create mode 100644 posix/tst-wordexp-reuse.c |
| 25 | + |
| 26 | +diff --git a/posix/Makefile b/posix/Makefile |
| 27 | +index 1fc0f565..7db396fb 100644 |
| 28 | +--- a/posix/Makefile |
| 29 | ++++ b/posix/Makefile |
| 30 | +@@ -328,6 +328,7 @@ tests := \ |
| 31 | + tst-wait4 \ |
| 32 | + tst-waitid \ |
| 33 | + tst-wordexp-nocmd \ |
| 34 | ++ tst-wordexp-reuse \ |
| 35 | + tstgetopt \ |
| 36 | + # tests |
| 37 | + |
| 38 | +@@ -453,6 +454,8 @@ generated += \ |
| 39 | + tst-rxspencer-no-utf8.mtrace \ |
| 40 | + tst-vfork3-mem.out \ |
| 41 | + tst-vfork3.mtrace \ |
| 42 | ++ tst-wordexp-reuse-mem.out \ |
| 43 | ++ tst-wordexp-reuse.mtrace \ |
| 44 | + wordexp-tst.out \ |
| 45 | + # generated |
| 46 | + |
| 47 | +@@ -484,6 +487,7 @@ tests-special += \ |
| 48 | + $(objpfx)tst-pcre-mem.out \ |
| 49 | + $(objpfx)tst-rxspencer-no-utf8-mem.out \ |
| 50 | + $(objpfx)tst-vfork3-mem.out \ |
| 51 | ++ $(objpfx)tst-wordexp-reuse.out \ |
| 52 | + # tests-special |
| 53 | + endif |
| 54 | + |
| 55 | +@@ -765,3 +769,10 @@ $(objpfx)posix-conf-vars-def.h: $(..)scripts/gen-posix-conf-vars.awk \ |
| 56 | + $(make-target-directory) |
| 57 | + $(AWK) -f $(filter-out Makefile, $^) > $@.tmp |
| 58 | + mv -f $@.tmp $@ |
| 59 | ++ |
| 60 | ++tst-wordexp-reuse-ENV += MALLOC_TRACE=$(objpfx)tst-wordexp-reuse.mtrace \ |
| 61 | ++ LD_PRELOAD=$(common-objpfx)/malloc/libc_malloc_debug.so |
| 62 | ++ |
| 63 | ++$(objpfx)tst-wordexp-reuse-mem.out: $(objpfx)tst-wordexp-reuse.out |
| 64 | ++ $(common-objpfx)malloc/mtrace $(objpfx)tst-wordexp-reuse.mtrace > $@; \ |
| 65 | ++ $(evaluate-test) |
| 66 | +diff --git a/posix/tst-wordexp-reuse.c b/posix/tst-wordexp-reuse.c |
| 67 | +new file mode 100644 |
| 68 | +index 00000000..3926b9f5 |
| 69 | +--- /dev/null |
| 70 | ++++ b/posix/tst-wordexp-reuse.c |
| 71 | +@@ -0,0 +1,89 @@ |
| 72 | ++/* Test for wordexp with WRDE_REUSE flag. |
| 73 | ++ Copyright (C) 2026 Free Software Foundation, Inc. |
| 74 | ++ This file is part of the GNU C Library. |
| 75 | ++ |
| 76 | ++ The GNU C Library is free software; you can redistribute it and/or |
| 77 | ++ modify it under the terms of the GNU Lesser General Public |
| 78 | ++ License as published by the Free Software Foundation; either |
| 79 | ++ version 2.1 of the License, or (at your option) any later version. |
| 80 | ++ |
| 81 | ++ The GNU C Library is distributed in the hope that it will be useful, |
| 82 | ++ but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 83 | ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 84 | ++ Lesser General Public License for more details. |
| 85 | ++ |
| 86 | ++ You should have received a copy of the GNU Lesser General Public |
| 87 | ++ License along with the GNU C Library; if not, see |
| 88 | ++ <https://www.gnu.org/licenses/>. */ |
| 89 | ++ |
| 90 | ++#include <wordexp.h> |
| 91 | ++#include <mcheck.h> |
| 92 | ++ |
| 93 | ++#include <support/check.h> |
| 94 | ++ |
| 95 | ++static int |
| 96 | ++do_test (void) |
| 97 | ++{ |
| 98 | ++ mtrace (); |
| 99 | ++ |
| 100 | ++ { |
| 101 | ++ wordexp_t p = { 0 }; |
| 102 | ++ TEST_COMPARE (wordexp ("one", &p, 0), 0); |
| 103 | ++ TEST_COMPARE (p.we_wordc, 1); |
| 104 | ++ TEST_COMPARE_STRING (p.we_wordv[0], "one"); |
| 105 | ++ TEST_COMPARE (wordexp ("two", &p, WRDE_REUSE), 0); |
| 106 | ++ TEST_COMPARE (p.we_wordc, 1); |
| 107 | ++ TEST_COMPARE_STRING (p.we_wordv[0], "two"); |
| 108 | ++ wordfree (&p); |
| 109 | ++ } |
| 110 | ++ |
| 111 | ++ { |
| 112 | ++ wordexp_t p = { .we_offs = 2 }; |
| 113 | ++ TEST_COMPARE (wordexp ("one", &p, 0), 0); |
| 114 | ++ TEST_COMPARE (p.we_wordc, 1); |
| 115 | ++ TEST_COMPARE_STRING (p.we_wordv[0], "one"); |
| 116 | ++ TEST_COMPARE (wordexp ("two", &p, WRDE_REUSE | WRDE_DOOFFS), 0); |
| 117 | ++ TEST_COMPARE (p.we_wordc, 1); |
| 118 | ++ TEST_COMPARE_STRING (p.we_wordv[p.we_offs + 0], "two"); |
| 119 | ++ wordfree (&p); |
| 120 | ++ } |
| 121 | ++ |
| 122 | ++ { |
| 123 | ++ wordexp_t p = { 0 }; |
| 124 | ++ TEST_COMPARE (wordexp ("one", &p, 0), 0); |
| 125 | ++ TEST_COMPARE (p.we_wordc, 1); |
| 126 | ++ TEST_COMPARE_STRING (p.we_wordv[0], "one"); |
| 127 | ++ TEST_COMPARE (wordexp ("two", &p, WRDE_REUSE | WRDE_APPEND), 0); |
| 128 | ++ TEST_COMPARE (p.we_wordc, 1); |
| 129 | ++ TEST_COMPARE_STRING (p.we_wordv[0], "two"); |
| 130 | ++ wordfree (&p); |
| 131 | ++ } |
| 132 | ++ |
| 133 | ++ { |
| 134 | ++ wordexp_t p = { .we_offs = 2 }; |
| 135 | ++ TEST_COMPARE (wordexp ("one", &p, WRDE_DOOFFS), 0); |
| 136 | ++ TEST_COMPARE (p.we_wordc, 1); |
| 137 | ++ TEST_COMPARE_STRING (p.we_wordv[p.we_offs + 0], "one"); |
| 138 | ++ TEST_COMPARE (wordexp ("two", &p, WRDE_REUSE |
| 139 | ++ | WRDE_DOOFFS), 0); |
| 140 | ++ TEST_COMPARE (p.we_wordc, 1); |
| 141 | ++ TEST_COMPARE_STRING (p.we_wordv[p.we_offs + 0], "two"); |
| 142 | ++ wordfree (&p); |
| 143 | ++ } |
| 144 | ++ |
| 145 | ++ { |
| 146 | ++ wordexp_t p = { .we_offs = 2 }; |
| 147 | ++ TEST_COMPARE (wordexp ("one", &p, WRDE_DOOFFS), 0); |
| 148 | ++ TEST_COMPARE (p.we_wordc, 1); |
| 149 | ++ TEST_COMPARE_STRING (p.we_wordv[p.we_offs + 0], "one"); |
| 150 | ++ TEST_COMPARE (wordexp ("two", &p, WRDE_REUSE |
| 151 | ++ | WRDE_DOOFFS | WRDE_APPEND), 0); |
| 152 | ++ TEST_COMPARE (p.we_wordc, 1); |
| 153 | ++ TEST_COMPARE_STRING (p.we_wordv[p.we_offs + 0], "two"); |
| 154 | ++ wordfree (&p); |
| 155 | ++ } |
| 156 | ++ |
| 157 | ++ return 0; |
| 158 | ++} |
| 159 | ++ |
| 160 | ++#include <support/test-driver.c> |
| 161 | +diff --git a/posix/wordexp.c b/posix/wordexp.c |
| 162 | +index 994d7916..5c5863b5 100644 |
| 163 | +--- a/posix/wordexp.c |
| 164 | ++++ b/posix/wordexp.c |
| 165 | +@@ -2216,7 +2216,9 @@ wordexp (const char *words, wordexp_t *pwordexp, int flags) |
| 166 | + { |
| 167 | + /* Minimal implementation of WRDE_REUSE for now */ |
| 168 | + wordfree (pwordexp); |
| 169 | ++ old_word.we_wordc = 0; |
| 170 | + old_word.we_wordv = NULL; |
| 171 | ++ pwordexp->we_wordc = 0; |
| 172 | + } |
| 173 | + |
| 174 | + if ((flags & WRDE_APPEND) == 0) |
| 175 | +-- |
| 176 | +2.45.4 |
| 177 | + |
0 commit comments