[PATCH 2/3] posix: Fix spurious wordexp WRDE_NOSPACE for empty expansions (BZ 34629)
Adhemerval Zanella
adhemerval.zanella@linaro.org
Fri Sep 11 16:59:00 GMT 2026
w_addstr returns NULL both when it runs out of memory and when the
string being appended is empty and the word has not been started yet
started.
Several callers treat a NULL result as an allocation failure
unconditionally, so a legitimate expansion to an empty string is
reported as WRDE_NOSPACE. For instance:
wordexp ("\"$var\"", &we, 0) with var set and null
wordexp ("\"$var\"x", &we, 0) likewise
wordexp ("\"$*\"", &we, 0) with no positional parameters
wordexp ("~", &we, 0) with HOME set and null
wordexp ("\"$@\"", &we, 0) with more than two positional
parameters, the first one being null
Add a w_addstr_status helper that does the same, and use it in parse_tilde
and in the quoted path of parse_param
Checked on x86_64-linux-gnu and i686-linux-gnu.
---
posix/wordexp-test.c | 10 ++++++++++
posix/wordexp.c | 41 +++++++++++++++++++++++++++++------------
2 files changed, 39 insertions(+), 12 deletions(-)
diff --git a/posix/wordexp-test.c b/posix/wordexp-test.c
index 86be38e5966..6c10bd3458a 100644
--- a/posix/wordexp-test.c
+++ b/posix/wordexp-test.c
@@ -95,6 +95,16 @@ struct test_case_struct
{ 0, NULL, "contin\\\nuation", 0, 1, { "continuation", }, IFS },
{ 0, NULL, "explicit ''", 0, 2, { "explicit", "", }, IFS },
{ 0, NULL, "explicit \"\"", 0, 2, { "explicit", "", }, IFS },
+
+ /* Quoted expansion of a parameter that is set but null. No field
+ splitting is done, so the result is a quoted null string rather than
+ no field at all. */
+ { 0, "", "\"$var\"", 0, 1, { "", }, IFS },
+ { 0, "", "\"${var}\"", 0, 1, { "", }, IFS },
+ { 0, "", "\"$var$var\"", 0, 1, { "", }, IFS },
+ { 0, "", "x\"$var\"", 0, 1, { "x", }, IFS },
+ { 0, "", "\"a${var}b\"", 0, 1, { "ab", }, IFS },
+ { 0, "", "one \"$var\" two", 0, 3, { "one", "", "two", }, IFS },
{ 0, NULL, "explicit ``", 0, 1, { "explicit", }, IFS },
/* Simple command substitution */
diff --git a/posix/wordexp.c b/posix/wordexp.c
index 0ad005c0f38..7741a85c23a 100644
--- a/posix/wordexp.c
+++ b/posix/wordexp.c
@@ -137,6 +137,18 @@ w_addstr (char *buffer, size_t *actlen, size_t *maxlen, const char *str)
return w_addmem (buffer, actlen, maxlen, str, len);
}
+/* w_addstr returns NULL both when it runs out of memory and when STR is
+ empty and *WORD has not been started yet. Only the former is a failure. */
+static bool
+w_addstr_status (char **word, size_t *word_length, size_t *max_length,
+ const char *str)
+{
+ bool empty = *str == '\0';
+
+ *word = w_addstr (*word, word_length, max_length, str);
+ return *word != NULL || empty;
+}
+
static int
w_addword (wordexp_t *pwordexp, char *word)
{
@@ -292,8 +304,7 @@ parse_tilde (char **word, size_t *word_length, size_t *max_length,
home = getenv ("HOME");
if (home != NULL)
{
- *word = w_addstr (*word, word_length, max_length, home);
- if (*word == NULL)
+ if (!w_addstr_status (word, word_length, max_length, home))
return WRDE_NOSPACE;
}
else
@@ -313,8 +324,7 @@ parse_tilde (char **word, size_t *word_length, size_t *max_length,
if (result == 0 && tpwd != NULL && pwd.pw_dir != NULL)
{
- *word = w_addstr (*word, word_length, max_length, pwd.pw_dir);
- if (*word == NULL)
+ if (!w_addstr_status (word, word_length, max_length, pwd.pw_dir))
{
scratch_buffer_free (&tmpbuf);
return WRDE_NOSPACE;
@@ -359,21 +369,24 @@ parse_tilde (char **word, size_t *word_length, size_t *max_length,
user = tmpbuf.data;
}
+ bool ok;
if (result == 0 && tpwd != NULL && pwd.pw_dir)
- *word = w_addstr (*word, word_length, max_length, pwd.pw_dir);
+ ok = w_addstr_status (word, word_length, max_length, pwd.pw_dir);
else
{
/* (invalid login name) */
*word = w_addchar (*word, word_length, max_length, '~');
- if (*word != NULL)
- *word = w_addstr (*word, word_length, max_length, user);
+ ok = *word != NULL
+ && w_addstr_status (word, word_length, max_length, user);
}
scratch_buffer_free (&tmpbuf);
*offset = i - 1;
+ if (!ok)
+ return WRDE_NOSPACE;
}
- return *word ? 0 : WRDE_NOSPACE;
+ return 0;
}
@@ -1476,10 +1489,13 @@ envsubst:
{
int p;
- /* Append first parameter to current word. */
+ /* Append first parameter to current word. When the word is
+ still empty and it is the first parameter, w_addword turns it
+ into an empty word. */
value = w_addstr (*word, word_length, max_length,
__libc_argv[1]);
- if (value == NULL || w_addword (pwordexp, value))
+ if ((value == NULL && __libc_argv[1][0] != '\0')
+ || w_addword (pwordexp, value))
goto no_space;
for (p = 2; __libc_argv[p + 1]; p++)
@@ -1907,11 +1923,12 @@ envsubst:
if (quoted || !pwordexp)
{
/* Quoted - no field split */
- *word = w_addstr (*word, word_length, max_length, value);
+ bool ok = w_addstr_status (word, word_length, max_length, value);
+
if (free_value)
free (value);
- return *word ? 0 : WRDE_NOSPACE;
+ return ok ? 0 : WRDE_NOSPACE;
}
else
{
--
2.53.0
More information about the Libc-alpha
mailing list