From patchwork Wed Jul 1 18:51:37 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Adhemerval Zanella Netto X-Patchwork-Id: 138237 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 2E34C4BA2E1D for ; Wed, 1 Jul 2026 18:52:28 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 2E34C4BA2E1D Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=JaCUhMki X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-pj1-x1031.google.com (mail-pj1-x1031.google.com [IPv6:2607:f8b0:4864:20::1031]) by sourceware.org (Postfix) with ESMTPS id C87DF4BA2E27 for ; Wed, 1 Jul 2026 18:51:52 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org C87DF4BA2E27 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=linaro.org Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=linaro.org ARC-Filter: OpenARC Filter v1.0.0 sourceware.org C87DF4BA2E27 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:20::1031 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1782931913; cv=none; b=mIk8hBosV2Iewr3znfDRdg/VJGnC8LeDqe+8+jhlFwL4tDTxu0XfjvbXuFlrz0CrnyDOdcqahrnRmAmwbcvt0k/OiwWjJNM6FgcMi2Nq0U9m2UoCP4gLxKwf+pL4yezcXcOrYeBygfPsqs5ceEdR6VXwpn8VescUdCQNLfIVYlw= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1782931913; c=relaxed/simple; bh=jb9AgQOA/J933Ak1nyi+gz+6l/ZHnm9tTTkgaw+Kaxs=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=V7QA2nacYYOe2cyhwMnda3Cqzdjb/cEvfgmAEhyaSrRLj/4PHo5wMeWm4TLfbkwLrr+zkEXlfwd17om9ueCB7r7tNr6hzyRb45xa9eIHoIgV8hC0bGqY/wxpNIE8ol6xMbs4KGP2BsBf5f50zeyq0ZyPKf/6moKfO806lb3/v0k= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=JaCUhMki DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org C87DF4BA2E27 Received: by mail-pj1-x1031.google.com with SMTP id 98e67ed59e1d1-36b9d265355so616021a91.2 for ; Wed, 01 Jul 2026 11:51:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1782931912; x=1783536712; darn=sourceware.org; h=content-transfer-encoding:content-type:mime-version:message-id:date :subject:cc:to:from:from:to:cc:subject:date:message-id:reply-to :content-type; bh=3L06KEs0rRXV/V2nbRGG5My7n6kudeFdVVBhIUY2CDg=; b=JaCUhMkieS12R18WrOBX1By5iJ/hl4rh4K0wxVALz+ba6W77x6X/LyAKwB9r+jCTFc zftiKiDhNifECmztoWQ1WzLyI3ppqxgKVSheREEwgU99hqnR/7xqLqNLCZ9PADt41S5j eXkmqD18vXRHz0yC9S47WIh8suSmIbcwgp1MfPT3WDhAGq9Q9swLmZzi1zJFbDBf6niH TczEPvAC5mFqTY/RLoG7Uv6VHA51oj9VbMmTOuILG3gMBqf0rwDgO3X3ICOnoDX0cDxH OxQRIaxJKXFvDe9H8Mo9EoXvl5gm+bfEyUYObW84pMXWeOekVhctFJQPdf3+BqJQ1qpj a1mQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782931912; x=1783536712; h=content-transfer-encoding:content-type:mime-version:message-id:date :subject:cc:to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject :date:message-id:reply-to:content-type; bh=3L06KEs0rRXV/V2nbRGG5My7n6kudeFdVVBhIUY2CDg=; b=YVBPGFpia+080xGSOHuljIJnrlTpnwPnfVPTr0BV5uzNwcfsIlnFWJ9KxOBuHxZNWW 94HwiMNX1RECeW5Cgw1b5/ffvc/V2J15/AZhYICPgBsDhrEWbib0OfBAX7JnTRcfxLs8 2jlPC2yZRnAauNaGVzUlsmxA3VaOiCrNT0zeBwS7waPKW/Xvhuxle3LN79rsP739rr5s mcvIkvzhWOjFMY65i9cDn/8ipfezJbRI+B3dFRw3NChteCaM1zcwiPeuAjgUFm0HvMpD MLNl+ImhcnpsY1p2+g5F8qdTwjbiB69pnAFKV77+trAZ3u+tqvHcjGBGnWWOy8+ikwOs AB3g== X-Gm-Message-State: AOJu0Ywd49I3MLSuu1lQQ1i5lvJ/cott5eNnkGGdXGqXuf/xeR/xNSQr z4PpNwHSg0eZFWBOvF96NHcW1nTakkfjzIXumI4EjxlHZr8pWW/U/ouc+4JieB8QGQ3n2zIPs2u KDZEa X-Gm-Gg: AfdE7cnFof7MUurwJzhOgK7oM7mUTMfs14nJ8NW4Lsh37OMN8fhWVUx5pBrZKfirxpc I/UtDmIqGGMiJut/5B0eUyWtSLXrsURb/WKV8XzT/IuxbGA42/ugv9fTu9nddz6NEdkqhQjlYMX gw1Df1F59XxqE1BBi68Me2sWI+/KSFGRJ8iG7L0nUqkARdJdKN9Ii9SxLcshj7Dbn+kYzPhHWu4 ylROxuCKgTurAp997UwhkK1b92B+4yc6uVMe7TD2VQGLFHKFRYscB1+wVjfml+WBipOfulpd1mm MavGgK+am9L2/kojkfY2u/cHS6tFt0zc356SJrrIvb2ewkC9HrDl+p+oWAyVxOTbdAxtKN2lhWN bVmdq15Tjd9+uKD4E009Fhd6dzZ3PVNLFpTj7DM3H38MRfXu5mT1QGR7deznWgf12ASL348gtnm wEAIrkibw+VAD+EXmCo63/17U= X-Received: by 2002:a17:90b:5910:b0:37e:2505:cde9 with SMTP id 98e67ed59e1d1-380ba84484emr1977877a91.13.1782931911179; Wed, 01 Jul 2026 11:51:51 -0700 (PDT) Received: from mandiga.. ([2804:1b3:a7c1:7042:74f7:c4ab:8c63:c2a]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-30f0bb7fd75sm460071eec.19.2026.07.01.11.51.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Jul 2026 11:51:50 -0700 (PDT) From: Adhemerval Zanella To: libc-alpha@sourceware.org Cc: Florian Weimer Subject: [PATCH v2] posix: Fix wordexp WRDE_APPEND to preserve state on non-NOSPACE errors (BZ 34090, CVE-2026-6368) Date: Wed, 1 Jul 2026 15:51:37 -0300 Message-ID: <20260701185147.355016-1-adhemerval.zanella@linaro.org> X-Mailer: git-send-email 2.43.0 MIME-Version: 1.0 X-Spam-Status: No, score=-12.6 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_NONE, TXREP shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org 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. --- Changes from v1: * Use aninterposed realloc to make the test more deterministic. * Add WRDE_DOOFFS tests. --- posix/Makefile | 1 + posix/tst-wordexp-append.c | 375 +++++++++++++++++++++++++++++++++++++ posix/wordexp.c | 64 ++++++- 3 files changed, 432 insertions(+), 8 deletions(-) create mode 100644 posix/tst-wordexp-append.c diff --git a/posix/Makefile b/posix/Makefile index d9f897faa16..10d70ae0ae2 100644 --- a/posix/Makefile +++ b/posix/Makefile @@ -330,6 +330,7 @@ tests := \ tst-wait3 \ tst-wait4 \ tst-waitid \ + tst-wordexp-append \ tst-wordexp-nocmd \ tst-wordexp-reuse \ tstgetopt \ diff --git a/posix/tst-wordexp-append.c b/posix/tst-wordexp-append.c new file mode 100644 index 00000000000..6aad7a0a8ed --- /dev/null +++ b/posix/tst-wordexp-append.c @@ -0,0 +1,375 @@ +/* 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 + . */ + +#include +#include +#include +#include +#include + +#include +#include + +/* 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); + free (ptr); + 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, int line) +{ + 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_, __LINE__); \ + } 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; + + /* 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); + 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); + + TEST_COMPARE (wordexp ("world", &we, WRDE_APPEND), 0); + TEST_COMPARE (we.we_wordc, 2); + 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; + + /* "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_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 diff --git a/posix/wordexp.c b/posix/wordexp.c index f0f69ee85d5..5f3941fa845 100644 --- a/posix/wordexp.c +++ b/posix/wordexp.c @@ -35,6 +35,7 @@ #include #include <_itoa.h> #include +#include /* * This is a recursive-descent-style word expansion routine. @@ -2224,6 +2225,12 @@ wordexp (const char *words, wordexp_t *pwordexp, int flags) 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 */ @@ -2258,6 +2265,23 @@ wordexp (const char *words, wordexp_t *pwordexp, int flags) 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. @@ -2338,7 +2362,7 @@ wordexp (const char *words, wordexp_t *pwordexp, int flags) error = w_addword (pwordexp, NULL); if (error) - return error; + goto do_error; } break; @@ -2356,7 +2380,7 @@ wordexp (const char *words, wordexp_t *pwordexp, int flags) error = w_addword (pwordexp, NULL); if (error) - return error; + goto do_error; } break; @@ -2422,10 +2446,15 @@ wordexp (const char *words, wordexp_t *pwordexp, int flags) /* 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); + error = w_addword (pwordexp, word); + free (saved_wordv); + return error; do_error: /* Error: @@ -2436,11 +2465,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; }