CVE-2026-6368: wordexp WRDE_APPEND state rollback on error (RHEL-238480)
Resolves: RHEL-238480 RPM-Skip-Release: yes
This commit is contained in:
parent
02c8aa5efe
commit
e956e857dd
572
glibc-RHEL-238480.patch
Normal file
572
glibc-RHEL-238480.patch
Normal file
@ -0,0 +1,572 @@
|
||||
commit e2cefe16c37a617df9f11407cb00a272a6098823
|
||||
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
|
||||
Date: Mon Apr 13 16:33:30 2026 -0300
|
||||
|
||||
posix: Fix wordexp WRDE_APPEND to preserve state on non-NOSPACE errors (BZ 34090, CVE-2026-6368)
|
||||
|
||||
The previous implementation saved a copy of the wordexp_t struct at
|
||||
entry and blindly restored it on error via (*pwordexp = old_word).
|
||||
This is incorrect when WRDE_APPEND is set because w_addword may have
|
||||
called realloc on we_wordv during partial processing before the error
|
||||
was detected. If realloc relocated the buffer, the saved we_wordv
|
||||
pointer is dangling; restoring it causes a use-after-free in the
|
||||
caller (e.g. via wordfree), and the relocated buffer is leaked.
|
||||
|
||||
Fix this by duplicating the we_wordv pointer array at entry when
|
||||
WRDE_APPEND is set, so that all subsequent realloc calls inside
|
||||
w_addword operate on the copy.
|
||||
|
||||
This change also fixes a POSIX conformance issue: if the WRDE_APPEND
|
||||
flag is specified, pwordexp->we_wordc and pwordexp->we_wordv shall
|
||||
not be modified.
|
||||
|
||||
Also fix two pre-existing error return paths in the '"' and '\'' cases
|
||||
that returned directly from w_addword failures instead of going through
|
||||
do_error, which would leak the saved array (and previously would also
|
||||
skip the word cleanup).
|
||||
|
||||
Checked on x86_64-linux-gnu and i686-linux-gnu.
|
||||
|
||||
Reviewed-by: DJ Delorie <dj@redhat.com>
|
||||
|
||||
Conflicts:
|
||||
posix/Makefile
|
||||
context for old testcase order
|
||||
posix/wordexp.c
|
||||
context and line numbers
|
||||
|
||||
diff --git a/posix/Makefile b/posix/Makefile
|
||||
index d9f897faa1..10d70ae0ae 100644
|
||||
--- a/posix/Makefile
|
||||
+++ b/posix/Makefile
|
||||
@@ -97,6 +97,7 @@ tests := test-errno tstgetopt testfnm r
|
||||
tst-sysconf-empty-chroot tst-glob_symlinks tst-fexecve \
|
||||
tst-glob-tilde test-ssize-max tst-spawn4 bug-regex37 \
|
||||
bug-regex38 tst-regcomp-truncated tst-regcomp-bracket-free \
|
||||
+ tst-wordexp-append \
|
||||
tst-wordexp-reuse
|
||||
tests-internal := bug-regex5 bug-regex20 bug-regex33 \
|
||||
tst-rfc3484 tst-rfc3484-2 tst-rfc3484-3 \
|
||||
diff --git a/posix/tst-wordexp-append.c b/posix/tst-wordexp-append.c
|
||||
new file mode 100644
|
||||
index 0000000000..87f388f0a7
|
||||
--- /dev/null
|
||||
+++ b/posix/tst-wordexp-append.c
|
||||
@@ -0,0 +1,393 @@
|
||||
+/* Test for wordexp with WRDE_APPEND 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 <string.h>
|
||||
+#include <stdio.h>
|
||||
+#include <stdlib.h>
|
||||
+#include <malloc.h>
|
||||
+
|
||||
+#include <support/check.h>
|
||||
+#include <support/support.h>
|
||||
+
|
||||
+static unsigned int relocating_reallocs;
|
||||
+
|
||||
+/* w_addword grows we_wordv with realloc, make every call guaranteed to
|
||||
+ relocate the block. This makes BZ 34090 regression more deterministic. */
|
||||
+void *
|
||||
+realloc (void *ptr, size_t size)
|
||||
+{
|
||||
+ if (ptr == NULL)
|
||||
+ return malloc (size);
|
||||
+ if (size == 0)
|
||||
+ {
|
||||
+ free (ptr);
|
||||
+ return NULL;
|
||||
+ }
|
||||
+
|
||||
+ void *new = malloc (size);
|
||||
+ if (new == NULL)
|
||||
+ return NULL;
|
||||
+
|
||||
+ /* Copy only what is valid in the old block to avoid reading past it. */
|
||||
+ size_t old = malloc_usable_size (ptr);
|
||||
+ memcpy (new, ptr, old < size ? old : size);
|
||||
+ /* Clobber the old block so that a stale we_wordv pointer restored on the
|
||||
+ error path reads garbage instead of the old contents, which might
|
||||
+ otherwise survive intact and mask the bug. */
|
||||
+ memset (ptr, 0x5a, old);
|
||||
+ free (ptr);
|
||||
+ relocating_reallocs++;
|
||||
+ return new;
|
||||
+}
|
||||
+
|
||||
+/* Verify that all words in we match the expected NULL-terminated
|
||||
+ array. */
|
||||
+static void
|
||||
+check_words (const wordexp_t *we, const char *const *expected)
|
||||
+{
|
||||
+ size_t i;
|
||||
+ for (i = 0; expected[i] != NULL; i++)
|
||||
+ {
|
||||
+ TEST_VERIFY (i < we->we_wordc);
|
||||
+ TEST_COMPARE_STRING (we->we_wordv[we->we_offs + i], expected[i]);
|
||||
+ }
|
||||
+ TEST_COMPARE (we->we_wordc, i);
|
||||
+}
|
||||
+
|
||||
+#define CHECK_WORDS(we, ...) \
|
||||
+ do { \
|
||||
+ const char *const expected_[] = { __VA_ARGS__, NULL }; \
|
||||
+ check_words (we, expected_); \
|
||||
+ } while (0)
|
||||
+
|
||||
+/* Test 1: WRDE_APPEND + WRDE_BADCHAR preserves we_wordc. */
|
||||
+static void
|
||||
+test_append_badchar_preserves_count (void)
|
||||
+{
|
||||
+ printf ("info: test_append_badchar_preserves_count\n");
|
||||
+ wordexp_t we = { 0 };
|
||||
+
|
||||
+ TEST_COMPARE (wordexp ("one two three", &we, 0), 0);
|
||||
+ TEST_COMPARE (we.we_wordc, 3);
|
||||
+
|
||||
+ size_t saved_count = we.we_wordc;
|
||||
+
|
||||
+ /* ')' triggers WRDE_BADCHAR and "extra" would be a new word if the
|
||||
+ expansion succeeded, exercising the w_addword path before the error
|
||||
+ is detected. */
|
||||
+ TEST_COMPARE (wordexp ("extra )", &we, WRDE_APPEND), WRDE_BADCHAR);
|
||||
+ TEST_COMPARE (we.we_wordc, saved_count);
|
||||
+
|
||||
+ wordfree (&we);
|
||||
+}
|
||||
+
|
||||
+/* Test 2: WRDE_APPEND + WRDE_BADCHAR preserves the we_wordv pointer even
|
||||
+ when internal realloc would move the buffer. */
|
||||
+static void
|
||||
+test_append_badchar_preserves_pointer (void)
|
||||
+{
|
||||
+ printf ("info: test_append_badchar_preserves_pointer\n");
|
||||
+ wordexp_t we = { 0 };
|
||||
+
|
||||
+ /* Use many words so that the initial we_wordv allocation is
|
||||
+ non-trivial and a later realloc is more likely to move it. */
|
||||
+ TEST_COMPARE (wordexp ("a b c d e f g h", &we, 0), 0);
|
||||
+ TEST_COMPARE (we.we_wordc, 8);
|
||||
+
|
||||
+ char **saved_wordv = we.we_wordv;
|
||||
+ size_t saved_count = we.we_wordc;
|
||||
+ unsigned int saved_reallocs = relocating_reallocs;
|
||||
+
|
||||
+ /* The interposed realloc guarantees the internal we_wordv buffer moves
|
||||
+ during parsing, so the pointer-stability check below is meaningful. */
|
||||
+ TEST_COMPARE (wordexp ("append )", &we, WRDE_APPEND), WRDE_BADCHAR);
|
||||
+ /* Verify that a relocating realloc actually happened during the failed
|
||||
+ call, otherwise the pointer-stability check is vacuous. */
|
||||
+ TEST_VERIFY (relocating_reallocs > saved_reallocs);
|
||||
+ TEST_COMPARE (we.we_wordc, saved_count);
|
||||
+ TEST_VERIFY (we.we_wordv == saved_wordv);
|
||||
+
|
||||
+ wordfree (&we);
|
||||
+}
|
||||
+
|
||||
+/* Test 3: After a failed WRDE_APPEND the original words are still accessible
|
||||
+ and correct. */
|
||||
+static void
|
||||
+test_append_badchar_words_intact (void)
|
||||
+{
|
||||
+ printf ("info: test_append_badchar_words_intact\n");
|
||||
+ wordexp_t we = { 0 };
|
||||
+
|
||||
+ TEST_COMPARE (wordexp ("alpha beta gamma", &we, 0), 0);
|
||||
+ CHECK_WORDS (&we, "alpha", "beta", "gamma");
|
||||
+
|
||||
+ TEST_COMPARE (wordexp ("delta )", &we, WRDE_APPEND), WRDE_BADCHAR);
|
||||
+
|
||||
+ /* Words must still be intact. */
|
||||
+ CHECK_WORDS (&we, "alpha", "beta", "gamma");
|
||||
+ /* The NULL terminator must still be present. */
|
||||
+ TEST_VERIFY (we.we_wordv[we.we_offs + we.we_wordc] == NULL);
|
||||
+
|
||||
+ wordfree (&we);
|
||||
+}
|
||||
+
|
||||
+/* Test 4: Successful WRDE_APPEND still works (regression test). */
|
||||
+static void
|
||||
+test_append_success (void)
|
||||
+{
|
||||
+ printf ("info: test_append_success\n");
|
||||
+ wordexp_t we = { 0 };
|
||||
+
|
||||
+ TEST_COMPARE (wordexp ("hello", &we, 0), 0);
|
||||
+ TEST_COMPARE (we.we_wordc, 1);
|
||||
+
|
||||
+ char **saved_wordv = we.we_wordv;
|
||||
+
|
||||
+ TEST_COMPARE (wordexp ("world", &we, WRDE_APPEND), 0);
|
||||
+ TEST_COMPARE (we.we_wordc, 2);
|
||||
+ /* A successful append works on a fresh copy of the array, so the
|
||||
+ caller-visible pointer must have changed. */
|
||||
+ TEST_VERIFY (we.we_wordv != saved_wordv);
|
||||
+ CHECK_WORDS (&we, "hello", "world");
|
||||
+
|
||||
+ wordfree (&we);
|
||||
+}
|
||||
+
|
||||
+/* Test 5: Successful append after a failed append — the implementation must
|
||||
+ recover and allow further use of the wordexp_t. */
|
||||
+static void
|
||||
+test_append_success_after_failure (void)
|
||||
+{
|
||||
+ printf ("info: test_append_success_after_failure\n");
|
||||
+ wordexp_t we = { 0 };
|
||||
+
|
||||
+ TEST_COMPARE (wordexp ("first", &we, 0), 0);
|
||||
+ CHECK_WORDS (&we, "first");
|
||||
+
|
||||
+ TEST_COMPARE (wordexp ("bad |", &we, WRDE_APPEND), WRDE_BADCHAR);
|
||||
+
|
||||
+ /* State must be exactly as before the failed call. */
|
||||
+ CHECK_WORDS (&we, "first");
|
||||
+
|
||||
+ /* A subsequent successful append must work. */
|
||||
+ TEST_COMPARE (wordexp ("second third", &we, WRDE_APPEND), 0);
|
||||
+ CHECK_WORDS (&we, "first", "second", "third");
|
||||
+
|
||||
+ wordfree (&we);
|
||||
+}
|
||||
+
|
||||
+/* Test 6: Multiple consecutive failed appends do not corrupt state. */
|
||||
+static void
|
||||
+test_append_multiple_failures (void)
|
||||
+{
|
||||
+ printf ("info: test_append_multiple_failures\n");
|
||||
+ wordexp_t we = { 0 };
|
||||
+
|
||||
+ TEST_COMPARE (wordexp ("keep this", &we, 0), 0);
|
||||
+ CHECK_WORDS (&we, "keep", "this");
|
||||
+
|
||||
+ size_t saved_count = we.we_wordc;
|
||||
+ char **saved_wordv = we.we_wordv;
|
||||
+
|
||||
+ /* Each of these bad characters must leave the state unchanged. */
|
||||
+ TEST_COMPARE (wordexp ("x )", &we, WRDE_APPEND), WRDE_BADCHAR);
|
||||
+ TEST_COMPARE (wordexp ("x |", &we, WRDE_APPEND), WRDE_BADCHAR);
|
||||
+ TEST_COMPARE (wordexp ("x ;", &we, WRDE_APPEND), WRDE_BADCHAR);
|
||||
+ TEST_COMPARE (wordexp ("x &", &we, WRDE_APPEND), WRDE_BADCHAR);
|
||||
+ TEST_COMPARE (wordexp ("x <", &we, WRDE_APPEND), WRDE_BADCHAR);
|
||||
+ TEST_COMPARE (wordexp ("x >", &we, WRDE_APPEND), WRDE_BADCHAR);
|
||||
+
|
||||
+ TEST_COMPARE (we.we_wordc, saved_count);
|
||||
+ TEST_VERIFY (we.we_wordv == saved_wordv);
|
||||
+ CHECK_WORDS (&we, "keep", "this");
|
||||
+
|
||||
+ wordfree (&we);
|
||||
+}
|
||||
+
|
||||
+/* Test 7: WRDE_APPEND with WRDE_SYNTAX error (unterminated quote) also
|
||||
+ preserves state. */
|
||||
+static void
|
||||
+test_append_syntax_error (void)
|
||||
+{
|
||||
+ printf ("info: test_append_syntax_error\n");
|
||||
+ wordexp_t we = { 0 };
|
||||
+
|
||||
+ TEST_COMPARE (wordexp ("original", &we, 0), 0);
|
||||
+ CHECK_WORDS (&we, "original");
|
||||
+
|
||||
+ char **saved_wordv = we.we_wordv;
|
||||
+ size_t saved_count = we.we_wordc;
|
||||
+
|
||||
+ /* Unterminated double quote triggers WRDE_SYNTAX. */
|
||||
+ TEST_COMPARE (wordexp ("\"unterminated", &we, WRDE_APPEND), WRDE_SYNTAX);
|
||||
+
|
||||
+ TEST_COMPARE (we.we_wordc, saved_count);
|
||||
+ TEST_VERIFY (we.we_wordv == saved_wordv);
|
||||
+ CHECK_WORDS (&we, "original");
|
||||
+
|
||||
+ wordfree (&we);
|
||||
+}
|
||||
+
|
||||
+/* Test 8: Error without WRDE_APPEND still works (regression test for the
|
||||
+ non-APPEND code path in do_error). */
|
||||
+static void
|
||||
+test_no_append_error (void)
|
||||
+{
|
||||
+ printf ("info: test_no_append_error\n");
|
||||
+ wordexp_t we = { 0 };
|
||||
+
|
||||
+ /* Simple failure without WRDE_APPEND. */
|
||||
+ TEST_COMPARE (wordexp ("bad |", &we, 0), WRDE_BADCHAR);
|
||||
+
|
||||
+ /* After failure without WRDE_APPEND the struct should be safe to
|
||||
+ reuse — start fresh. */
|
||||
+ TEST_COMPARE (wordexp ("ok", &we, 0), 0);
|
||||
+ CHECK_WORDS (&we, "ok");
|
||||
+
|
||||
+ wordfree (&we);
|
||||
+}
|
||||
+
|
||||
+/* Test 9: WRDE_BADCHAR on the very first character (no partial words added
|
||||
+ before the error). */
|
||||
+static void
|
||||
+test_append_badchar_immediate (void)
|
||||
+{
|
||||
+ printf ("info: test_append_badchar_immediate\n");
|
||||
+ wordexp_t we = { 0 };
|
||||
+
|
||||
+ TEST_COMPARE (wordexp ("hello world", &we, 0), 0);
|
||||
+ CHECK_WORDS (&we, "hello", "world");
|
||||
+
|
||||
+ char **saved_wordv = we.we_wordv;
|
||||
+ size_t saved_count = we.we_wordc;
|
||||
+
|
||||
+ /* The bad character is the very first byte — no w_addword call happens
|
||||
+ before the error. */
|
||||
+ TEST_COMPARE (wordexp ("|", &we, WRDE_APPEND), WRDE_BADCHAR);
|
||||
+ TEST_COMPARE (we.we_wordc, saved_count);
|
||||
+ TEST_VERIFY (we.we_wordv == saved_wordv);
|
||||
+
|
||||
+ wordfree (&we);
|
||||
+}
|
||||
+
|
||||
+/* Test 10: WRDE_APPEND into an empty wordexp_t (initial call uses WRDE_APPEND
|
||||
+ with a zeroed struct — unusual but allowed). */
|
||||
+static void
|
||||
+test_append_into_empty (void)
|
||||
+{
|
||||
+ printf ("info: test_append_into_empty\n");
|
||||
+ wordexp_t we = { 0 };
|
||||
+
|
||||
+ /* First call with WRDE_APPEND on a zeroed struct. The implementation
|
||||
+ must handle we_wordv == NULL gracefully. */
|
||||
+ TEST_COMPARE (wordexp ("solo", &we, WRDE_APPEND), 0);
|
||||
+ TEST_COMPARE (we.we_wordc, 1);
|
||||
+ CHECK_WORDS (&we, "solo");
|
||||
+
|
||||
+ wordfree (&we);
|
||||
+}
|
||||
+
|
||||
+/* Verify that the leading we_offs slots are all NULL. */
|
||||
+static void
|
||||
+check_offs_null (const wordexp_t *we)
|
||||
+{
|
||||
+ for (size_t i = 0; i < we->we_offs; i++)
|
||||
+ TEST_VERIFY (we->we_wordv[i] == NULL);
|
||||
+}
|
||||
+
|
||||
+/* Test 11: successful WRDE_APPEND with WRDE_DOOFFS and a non-zero we_offs.
|
||||
+ The leading offset slots must stay NULL and words must land at
|
||||
+ we_wordv[we_offs + i] across both the initial and the appended call. */
|
||||
+static void
|
||||
+test_dooffs_append_success (void)
|
||||
+{
|
||||
+ printf ("info: test_dooffs_append_success\n");
|
||||
+ wordexp_t we = { 0 };
|
||||
+ we.we_offs = 2;
|
||||
+
|
||||
+ TEST_COMPARE (wordexp ("one two", &we, WRDE_DOOFFS), 0);
|
||||
+ TEST_COMPARE (we.we_offs, 2);
|
||||
+ check_offs_null (&we);
|
||||
+ CHECK_WORDS (&we, "one", "two");
|
||||
+
|
||||
+ TEST_COMPARE (wordexp ("three", &we, WRDE_APPEND | WRDE_DOOFFS), 0);
|
||||
+ TEST_COMPARE (we.we_offs, 2);
|
||||
+ check_offs_null (&we);
|
||||
+ CHECK_WORDS (&we, "one", "two", "three");
|
||||
+ /* The NULL terminator must sit right after the last word. */
|
||||
+ TEST_VERIFY (we.we_wordv[we.we_offs + we.we_wordc] == NULL);
|
||||
+
|
||||
+ wordfree (&we);
|
||||
+}
|
||||
+
|
||||
+/* Test 12: failed WRDE_APPEND with WRDE_DOOFFS preserves we_wordc, the
|
||||
+ we_wordv pointer, the words and the leading NULL offset slots. This
|
||||
+ exercises the we_offs arithmetic in the array duplication and in the
|
||||
+ error-path cleanup (we_wordv[we_offs + --we_wordc]). */
|
||||
+static void
|
||||
+test_dooffs_append_error_preserves_state (void)
|
||||
+{
|
||||
+ printf ("info: test_dooffs_append_error_preserves_state\n");
|
||||
+ wordexp_t we = { 0 };
|
||||
+ we.we_offs = 3;
|
||||
+
|
||||
+ TEST_COMPARE (wordexp ("alpha beta", &we, WRDE_DOOFFS), 0);
|
||||
+ check_offs_null (&we);
|
||||
+ CHECK_WORDS (&we, "alpha", "beta");
|
||||
+
|
||||
+ char **saved_wordv = we.we_wordv;
|
||||
+ size_t saved_count = we.we_wordc;
|
||||
+ unsigned int saved_reallocs = relocating_reallocs;
|
||||
+
|
||||
+ /* "gamma" is a partial word added via w_addword (forcing a relocating
|
||||
+ realloc of we_wordv) before ')' triggers WRDE_BADCHAR. */
|
||||
+ TEST_COMPARE (wordexp ("gamma )", &we, WRDE_APPEND | WRDE_DOOFFS),
|
||||
+ WRDE_BADCHAR);
|
||||
+ TEST_VERIFY (relocating_reallocs > saved_reallocs);
|
||||
+
|
||||
+ TEST_COMPARE (we.we_offs, 3);
|
||||
+ TEST_COMPARE (we.we_wordc, saved_count);
|
||||
+ TEST_VERIFY (we.we_wordv == saved_wordv);
|
||||
+ check_offs_null (&we);
|
||||
+ CHECK_WORDS (&we, "alpha", "beta");
|
||||
+ TEST_VERIFY (we.we_wordv[we.we_offs + we.we_wordc] == NULL);
|
||||
+
|
||||
+ wordfree (&we);
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+do_test (void)
|
||||
+{
|
||||
+ test_append_badchar_preserves_count ();
|
||||
+ test_append_badchar_preserves_pointer ();
|
||||
+ test_append_badchar_words_intact ();
|
||||
+ test_append_success ();
|
||||
+ test_append_success_after_failure ();
|
||||
+ test_append_multiple_failures ();
|
||||
+ test_append_syntax_error ();
|
||||
+ test_no_append_error ();
|
||||
+ test_append_badchar_immediate ();
|
||||
+ test_append_into_empty ();
|
||||
+ test_dooffs_append_success ();
|
||||
+ test_dooffs_append_error_preserves_state ();
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+#include <support/test-driver.c>
|
||||
diff --git a/posix/wordexp.c b/posix/wordexp.c
|
||||
index f0f69ee85d..8fdc8b8caf 100644
|
||||
--- a/posix/wordexp.c
|
||||
+++ b/posix/wordexp.c
|
||||
@@ -48,6 +48,7 @@
|
||||
/* Undefine the following line for the production version. */
|
||||
/* #define NDEBUG 1 */
|
||||
#include <assert.h>
|
||||
+#include <intprops.h>
|
||||
|
||||
/* Get some device information. */
|
||||
#include <device-nrs.h>
|
||||
@@ -2249,6 +2250,12 @@ wordexp (const char *words, wordexp_t *p
|
||||
char ifs_white[4];
|
||||
wordexp_t old_word = *pwordexp;
|
||||
|
||||
+ /* When WRDE_APPEND is set we work on a copy of the we_wordv array so that
|
||||
+ the caller's original pointer is never invalidated by realloc inside
|
||||
+ w_addword. The saved_wordv keeps the original; on success we free it,
|
||||
+ on non-NOSPACE error we free the working copy and restore the original. */
|
||||
+ char **saved_wordv = NULL;
|
||||
+
|
||||
if (flags & WRDE_REUSE)
|
||||
{
|
||||
/* Minimal implementation of WRDE_REUSE for now */
|
||||
@@ -2283,6 +2290,23 @@ wordexp (const char *words, wordexp_t *p
|
||||
pwordexp->we_offs = 0;
|
||||
}
|
||||
}
|
||||
+ else if (pwordexp->we_wordv != NULL)
|
||||
+ {
|
||||
+ /* WRDE_APPEND with an existing word list: duplicate the array so that
|
||||
+ realloc during parsing does not invalidate the caller's pointer. The
|
||||
+ strings themselves are shared. */
|
||||
+ size_t num_p;
|
||||
+ char **dup;
|
||||
+ if (INT_ADD_WRAPV (pwordexp->we_offs, pwordexp->we_wordc, &num_p)
|
||||
+ || INT_ADD_WRAPV (num_p, 1, &num_p))
|
||||
+ return WRDE_NOSPACE;
|
||||
+ dup = __libc_reallocarray (NULL, num_p, sizeof *dup);
|
||||
+ if (dup == NULL)
|
||||
+ return WRDE_NOSPACE;
|
||||
+ memcpy (dup, pwordexp->we_wordv, num_p * sizeof *dup);
|
||||
+ saved_wordv = pwordexp->we_wordv;
|
||||
+ pwordexp->we_wordv = dup;
|
||||
+ }
|
||||
|
||||
/* Find out what the field separators are.
|
||||
* There are two types: whitespace and non-whitespace.
|
||||
@@ -2363,7 +2387,7 @@ wordexp (const char *words, wordexp_t *p
|
||||
error = w_addword (pwordexp, NULL);
|
||||
|
||||
if (error)
|
||||
- return error;
|
||||
+ goto do_error;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -2381,7 +2405,7 @@ wordexp (const char *words, wordexp_t *p
|
||||
error = w_addword (pwordexp, NULL);
|
||||
|
||||
if (error)
|
||||
- return error;
|
||||
+ goto do_error;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -2447,10 +2471,18 @@ wordexp (const char *words, wordexp_t *p
|
||||
|
||||
/* There was a word separator at the end */
|
||||
if (word == NULL) /* i.e. w_newword */
|
||||
- return 0;
|
||||
+ {
|
||||
+ free (saved_wordv);
|
||||
+ return 0;
|
||||
+ }
|
||||
|
||||
- /* There was no field separator at the end */
|
||||
- return w_addword (pwordexp, word);
|
||||
+ /* There was no field separator at the end. The only possible error
|
||||
+ from w_addword is WRDE_NOSPACE. */
|
||||
+ error = w_addword (pwordexp, word);
|
||||
+ if (error != 0)
|
||||
+ goto do_error;
|
||||
+ free (saved_wordv);
|
||||
+ return 0;
|
||||
|
||||
do_error:
|
||||
/* Error:
|
||||
@@ -2461,11 +2493,30 @@ do_error:
|
||||
free (word);
|
||||
|
||||
if (error == WRDE_NOSPACE)
|
||||
- return WRDE_NOSPACE;
|
||||
+ {
|
||||
+ /* we_wordc and we_wordv are updated to reflect any words that were
|
||||
+ successfully expanded. The old array is obsolete. */
|
||||
+ free (saved_wordv);
|
||||
+ return WRDE_NOSPACE;
|
||||
+ }
|
||||
|
||||
- if ((flags & WRDE_APPEND) == 0)
|
||||
- wordfree (pwordexp);
|
||||
+ if (flags & WRDE_APPEND)
|
||||
+ {
|
||||
+ /* POSIX 2024 states that for in other error cases, if the WRDE_APPEND
|
||||
+ flag was specified, we_wordc and we_wordv shall not be modified.
|
||||
+
|
||||
+ Free strings appended during this call, discard the working copy of
|
||||
+ we_wordv, and restore the caller's original pointer. */
|
||||
+ while (pwordexp->we_wordc > old_word.we_wordc)
|
||||
+ free (pwordexp->we_wordv[pwordexp->we_offs + --pwordexp->we_wordc]);
|
||||
+ free (pwordexp->we_wordv);
|
||||
+ pwordexp->we_wordv = saved_wordv;
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ wordfree (pwordexp);
|
||||
+ *pwordexp = old_word;
|
||||
+ }
|
||||
|
||||
- *pwordexp = old_word;
|
||||
return error;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user