Skip to content

Commit f94c34d

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 <nrwahl@protonmail.com>
1 parent 9e34af8 commit f94c34d

File tree

1 file changed

+67
-50
lines changed

1 file changed

+67
-50
lines changed

daemons/execd/remoted_pidone.c

Lines changed: 67 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -83,78 +83,95 @@ 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+
* The assignment must be contained within a single line. A trailing comment is
91+
* ignored. This function handles backslashes, single quotes, and double quotes
92+
* in a manner similar to a POSIX shell.
93+
*
94+
* \param[in] line Line containing an environment variable assignment statement
95+
*/
8696
static void
87-
load_env_var_line(char *line)
97+
load_env_var_line(const char *line)
8898
{
99+
gchar *stripped = NULL;
89100
gchar *name = NULL;
90101
gchar *value = NULL;
91-
gchar *end = NULL;
92-
gchar *comment = NULL;
93102

94-
// Strip leading and trailing whitespace
95-
g_strstrip(line);
103+
int rc = pcmk_rc_ok;
104+
const char *reason = NULL;
105+
106+
gint argc = 0;
107+
gchar **argv = NULL;
108+
GError *error = NULL;
96109

97-
if ((pcmk__scan_nvpair(line, &name, &value) != pcmk_rc_ok)
98-
|| !valid_env_var_name(name)) {
110+
stripped = g_strstrip(g_strdup(line));
111+
if (pcmk__str_empty(stripped) || (stripped[0] == '#')) {
112+
// Empty line, nothing to do
99113
goto done;
100114
}
101115

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++);
115-
116-
if (*end != quote) {
117-
// Matching closing quote wasn't found
118-
goto done;
119-
}
120-
121-
// Discard closing quote and advance to check for trailing garbage
122-
*end++ = '\0';
116+
rc = pcmk__scan_nvpair(stripped, &name, &value);
117+
if (rc != pcmk_rc_ok) {
118+
reason = pcmk_rc_str(rc);
119+
goto done;
120+
}
123121

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++);
122+
if (!valid_env_var_name(name)) {
123+
reason = "invalid environment variable name";
124+
goto done;
129125
}
130126

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.
127+
/* g_shell_parse_argv() does the following in a manner similar to the shell:
128+
* * tokenizes the value
129+
* * strips a trailing '#' comment if one exists
130+
* * handles backslashes, single quotes, and double quotes
131+
*
132+
* The value is valid only if it consists zero or one token after stripping
133+
* the comment (that is, if argc is 0 or 1) and does not have mismatched
134+
* quotes.
135+
*
136+
* For example, the following line is valid, and argc will be set to 1.
137+
* (Note that value is everything after "VAR=".)
138+
*
139+
* VAR=' some value "with quotes"' # comment
140+
*
141+
* We will set VAR to " some value \"with quotes\"".
142+
*
143+
* The following line is invalid, and argc will be set to 2.
144+
*
145+
* VAR=' some value "with quotes"' more # comment
146+
*
147+
* In this case, we will not set a value for VAR.
135148
*/
136-
137-
// Strip trailing comment beginning with '#'
138-
comment = strchr(end, '#');
139-
if (comment != NULL) {
140-
*comment = '\0';
149+
if (!g_shell_parse_argv(value, &argc, &argv, &error)) {
150+
reason = error->message;
151+
goto done;
141152
}
142-
143-
// Strip any remaining trailing whitespace from value
144-
g_strchomp(end);
145-
146-
if (*end != '\0') {
147-
// Found garbage after value
153+
if (argc > 1) {
154+
reason = "garbage after value";
148155
goto done;
149156
}
150157

151-
// Don't overwrite (bundle options take precedence)
158+
/* Don't overwrite (bundle options take precedence). argv[0] contains the
159+
* value after stripping the trailing comment and processing quotes and
160+
* backslashes.
161+
*/
152162
// coverity[tainted_string] Can't easily be changed right now
153-
setenv(name, value, 0);
163+
setenv(name, ((argc == 1)? argv[0] : ""), 0);
154164

155165
done:
166+
if (reason != NULL) {
167+
crm_warn("Failed to perform environment variable assignment '%s': %s",
168+
line, reason);
169+
}
170+
g_free(stripped);
156171
g_free(name);
157172
g_free(value);
173+
g_strfreev(argv);
174+
g_clear_error(&error);
158175
}
159176

160177
#define CONTAINER_ENV_FILE "/etc/pacemaker/pcmk-init.env"

0 commit comments

Comments
 (0)