Skip to content

Commit cf33fe1

Browse files
committed
Low: libcrmcommon: Standardize remote environment variable parsing
Our bespoke code has been fragile and hard to understand at a glance. There were certainly corner cases involving nested quotes that were not handled in the way the shell would handle them. Now we use g_shell_parse_argv() instead, to mimic how the shell would process the value in a "NAME=VALUE" assignment statement. Signed-off-by: Reid Wahl <[email protected]>
1 parent 9e34af8 commit cf33fe1

File tree

2 files changed

+75
-50
lines changed

2 files changed

+75
-50
lines changed

daemons/execd/remoted_pidone.c

Lines changed: 70 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -83,76 +83,96 @@ valid_env_var_name(const gchar *name)
8383
return *name == '\0';
8484
}
8585

86+
/*!
87+
* \internal
88+
* \brief Read one environment variable assignment and set the value
89+
*
90+
* Empty lines and trailing comments are ignored. This function handles
91+
* backslashes, single quotes, and double quotes in a manner similar to a POSIX
92+
* shell.
93+
*
94+
* This function has at least two limitations compared to a shell:
95+
* * An assignment must be contained within a single line.
96+
* * Only one assignment per line is supported.
97+
*
98+
* It would be possible to get rid of these limitations, but it doesn't seem
99+
* worth the trouble of implementation and testing.
100+
*
101+
* \param[in] line Line containing an environment variable assignment statement
102+
*/
86103
static void
87-
load_env_var_line(char *line)
104+
load_env_var_line(const char *line)
88105
{
106+
gint argc = 0;
107+
gchar **argv = NULL;
108+
GError *error = NULL;
109+
89110
gchar *name = NULL;
90111
gchar *value = NULL;
91-
gchar *end = NULL;
92-
gchar *comment = NULL;
93112

94-
// Strip leading and trailing whitespace
95-
g_strstrip(line);
113+
int rc = pcmk_rc_ok;
114+
const char *reason = NULL;
115+
const char *value_to_set = NULL;
96116

97-
if ((pcmk__scan_nvpair(line, &name, &value) != pcmk_rc_ok)
98-
|| !valid_env_var_name(name)) {
99-
goto done;
100-
}
101-
102-
if ((*value == '\'') || (*value == '"')) {
103-
const char quote = *value;
104-
105-
// Strip the leading quote
106-
*value = ' ';
107-
g_strchug(value);
108-
109-
/* Value is remaining characters up to next non-backslashed matching
110-
* quote character.
111-
*/
112-
for (end = value;
113-
(*end != '\0') && ((*end != quote) || (*(end - 1) == '\\'));
114-
end++);
117+
/* g_shell_parse_argv() does the following in a manner similar to the shell:
118+
* * tokenizes the value
119+
* * strips a trailing '#' comment if one exists
120+
* * handles backslashes, single quotes, and double quotes
121+
*/
115122

116-
if (*end != quote) {
117-
// Matching closing quote wasn't found
118-
goto done;
123+
// Ensure the line contains zero or one token besides an optional comment
124+
if (!g_shell_parse_argv(line, &argc, NULL, &error)) {
125+
// Empty line (or only space/comment) means nothing to do and no error
126+
if (error->code != G_SHELL_ERROR_EMPTY_STRING) {
127+
reason = error->message;
119128
}
120-
121-
// Discard closing quote and advance to check for trailing garbage
122-
*end++ = '\0';
123-
124-
} else {
125-
// Value is remaining characters up to next non-backslashed whitespace
126-
for (end = value;
127-
(*end != '\0') && (!isspace(*end) || (*(end - 1) == '\\'));
128-
end++);
129+
goto done;
130+
}
131+
if (argc != 1) {
132+
// "argc != 1" for sanity; should imply "argc > 1" by now
133+
reason = "line contains garbage";
134+
goto done;
129135
}
130136

131-
/* We have a valid name and value, and end is now the character after the
132-
* closing quote or the first whitespace after the unquoted value. Make sure
133-
* the rest of the line, if any, is just optional whitespace followed by a
134-
* comment.
135-
*/
137+
rc = pcmk__scan_nvpair(line, &name, &value);
138+
if (rc != pcmk_rc_ok) {
139+
reason = pcmk_rc_str(rc);
140+
goto done;
141+
}
136142

137-
// Strip trailing comment beginning with '#'
138-
comment = strchr(end, '#');
139-
if (comment != NULL) {
140-
*comment = '\0';
143+
// Leading whitespace is allowed and ignored. A quoted name is invalid.
144+
g_strchug(name);
145+
if (!valid_env_var_name(name)) {
146+
reason = "invalid environment variable name";
147+
goto done;
141148
}
142149

143-
// Strip any remaining trailing whitespace from value
144-
g_strchomp(end);
150+
/* Parse the value as the shell would do (stripping outermost quotes, etc.).
151+
* Also sanity-check that the value either is empty or consists of one
152+
* token. Anything malformed should have been caught by now.
153+
*/
154+
if (!g_shell_parse_argv(value, &argc, &argv, &error)) {
155+
// Parse error should mean value is empty
156+
CRM_CHECK(error->code == G_SHELL_ERROR_EMPTY_STRING, goto done);
157+
value_to_set = "";
145158

146-
if (*end != '\0') {
147-
// Found garbage after value
148-
goto done;
159+
} else {
160+
// value wasn't empty, so it should contain one token
161+
CRM_CHECK(argc == 1, goto done);
162+
value_to_set = argv[0];
149163
}
150164

151165
// Don't overwrite (bundle options take precedence)
152166
// coverity[tainted_string] Can't easily be changed right now
153-
setenv(name, value, 0);
167+
setenv(name, value_to_set, 0);
154168

155169
done:
170+
if (reason != NULL) {
171+
crm_warn("Failed to perform environment variable assignment '%s': %s",
172+
line, reason);
173+
}
174+
g_strfreev(argv);
175+
g_clear_error(&error);
156176
g_free(name);
157177
g_free(value);
158178
}

lib/common/nvpair.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ pcmk_free_nvpairs(GSList *nvpairs)
124124
int
125125
pcmk__scan_nvpair(const gchar *input, gchar **name, gchar **value)
126126
{
127+
/* @COMPAT Consider rejecting leading (and possibly trailing) whitespace in
128+
* value and stripping outer quotes from value (for example,
129+
* using g_shell_unquote()). This would affect stonith_admin and
130+
* crm_resource and would simplify remoted_spawn_pidone()'s helpers.
131+
*/
127132
gchar **nvpair = NULL;
128133
int rc = pcmk_rc_ok;
129134

0 commit comments

Comments
 (0)