diff options
| author | Adhemerval Zanella <adhemerval.zanella@linaro.org> | 2026-01-15 10:32:19 -0300 |
|---|---|---|
| committer | Adhemerval Zanella <adhemerval.zanella@linaro.org> | 2026-01-20 17:34:22 -0300 |
| commit | 2b656ff94d72f93c84d8da2e7c76456c1994f02e (patch) | |
| tree | f61364e186b6a0eea904721f481103fe6503bcca | |
| parent | 96863dee262225cfb79f9fe45e06fd188319c7b8 (diff) | |
| download | glibc-release/2.34/master.zip glibc-release/2.34/master.tar.gz glibc-release/2.34/master.tar.bz2 | |
posix: Reset wordexp_t fields with WRDE_REUSE (CVE-2025-15281 / BZ 33814)release/2.34/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-- | NEWS | 5 | ||||
| -rw-r--r-- | posix/Makefile | 19 | ||||
| -rw-r--r-- | posix/tst-wordexp-reuse.c | 89 | ||||
| -rw-r--r-- | posix/wordexp.c | 2 |
4 files changed, 111 insertions, 4 deletions
@@ -65,6 +65,9 @@ Security related changes: information, which may lead to a buffer overflow if the message string size aligns to page size. + 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: [11053] regex: Wrong results with backreferences @@ -180,6 +183,8 @@ The following bugs are resolved with this release: [32582] Fix underallocation of abort_msg_s struct (CVE-2025-0395) [32987] elf: Fix subprocess status handling for tst-dlopen-sgid [33185] Fix double-free after allocation failure in regcomp + [33814] glob: wordexp with WRDE_REUSE and WRDE_APPEND may return + uninitialized memory Version 2.34 diff --git a/posix/Makefile b/posix/Makefile index 7c7002f..3edac30 100644 --- a/posix/Makefile +++ b/posix/Makefile @@ -108,7 +108,8 @@ tests := test-errno tstgetopt testfnm runtests runptests \ tst-glob-tilde test-ssize-max tst-spawn4 bug-regex37 \ bug-regex38 tst-regcomp-truncated tst-spawn-chdir \ tst-wordexp-nocmd tst-execveat tst-spawn5 \ - tst-sched_getaffinity tst-regcomp-bracket-free + tst-sched_getaffinity 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) @@ -155,11 +156,13 @@ 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 \ - testcases.h ptestcases.h + testcases.h ptestcases.h tst-wordexp-reuse-mem.out \ + tst-wordexp-reuse.mtrace ifeq ($(run-built-tests),yes) ifeq (yes,$(build-shared)) -tests-special += $(objpfx)globtest.out $(objpfx)wordexp-tst.out +tests-special += $(objpfx)globtest.out $(objpfx)wordexp-tst.out \ + $(objpfx)wordexp-tst.out endif endif @@ -173,7 +176,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 @@ -439,3 +443,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 1f3b09f..b23608d 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) |
