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 <carlos@redhat.com>
This commit is contained in:
Adhemerval Zanella 2026-01-15 10:32:19 -03:00
parent c42baf0c08
commit 80cc58ea2d
3 changed files with 102 additions and 0 deletions

View File

@ -328,6 +328,7 @@ tests := \
tst-wait4 \ tst-wait4 \
tst-waitid \ tst-waitid \
tst-wordexp-nocmd \ tst-wordexp-nocmd \
tst-wordexp-reuse \
tstgetopt \ tstgetopt \
# tests # tests
@ -458,6 +459,8 @@ generated += \
tst-rxspencer-no-utf8.mtrace \ tst-rxspencer-no-utf8.mtrace \
tst-vfork3-mem.out \ tst-vfork3-mem.out \
tst-vfork3.mtrace \ tst-vfork3.mtrace \
tst-wordexp-reuse-mem.out \
tst-wordexp-reuse.mtrace \
# generated # generated
endif endif
endif endif
@ -496,6 +499,7 @@ tests-special += \
$(objpfx)tst-pcre-mem.out \ $(objpfx)tst-pcre-mem.out \
$(objpfx)tst-rxspencer-no-utf8-mem.out \ $(objpfx)tst-rxspencer-no-utf8-mem.out \
$(objpfx)tst-vfork3-mem.out \ $(objpfx)tst-vfork3-mem.out \
$(objpfx)tst-wordexp-reuse.out \
# tests-special # tests-special
endif endif
endif endif
@ -789,3 +793,10 @@ $(objpfx)posix-conf-vars-def.h: $(..)scripts/gen-posix-conf-vars.awk \
$(make-target-directory) $(make-target-directory)
$(AWK) -f $(filter-out Makefile, $^) > $@.tmp $(AWK) -f $(filter-out Makefile, $^) > $@.tmp
mv -f $@.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)

89
posix/tst-wordexp-reuse.c Normal file
View File

@ -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>

View File

@ -2216,7 +2216,9 @@ wordexp (const char *words, wordexp_t *pwordexp, int flags)
{ {
/* Minimal implementation of WRDE_REUSE for now */ /* Minimal implementation of WRDE_REUSE for now */
wordfree (pwordexp); wordfree (pwordexp);
old_word.we_wordc = 0;
old_word.we_wordv = NULL; old_word.we_wordv = NULL;
pwordexp->we_wordc = 0;
} }
if ((flags & WRDE_APPEND) == 0) if ((flags & WRDE_APPEND) == 0)