Skip to content

Commit cfbf4a4

Browse files
committed
Refactor: remoted: Clean up load_env_vars()
This makes two Coverity false positives (tainted name and value passed to setenv()) go away. It also makes the function's logic easier to follow. We now use getline() instead of fgets(). Instead of reading into a fixed-size buffer, getline() allocates a buffer large enough to hold the line, and it calls realloc() as needed. This is not an execution path where performance is critical enough for this to matter. getline() also returns the number of characters read, so that if there is a '\0' character within a line, the caller can tell that the end of line has not yet been reached. This doesn't seem to matter for our use case, since we don't want to allow null bytes inside a name or value. We also make liberal use of continue statements to avoid nesting where possible, and this allows us to get rid of the places where we set value to NULL. This will be painful to review, so sorry in advance. Signed-off-by: Reid Wahl <nrwahl@protonmail.com>
1 parent 5819586 commit cfbf4a4

File tree

1 file changed

+86
-71
lines changed

1 file changed

+86
-71
lines changed

daemons/execd/remoted_pidone.c

Lines changed: 86 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -93,103 +93,118 @@ find_env_var_name(char *line, char **first, char **last)
9393
return FALSE;
9494
}
9595

96+
#define CONTAINER_ENV_FILE "/etc/pacemaker/pcmk-init.env"
97+
9698
static void
97-
load_env_vars(const char *filename)
99+
load_env_vars(void)
98100
{
99101
/* We haven't forked or initialized logging yet, so don't leave any file
100102
* descriptors open, and don't log -- silently ignore errors.
101103
*/
102-
FILE *fp = fopen(filename, "r");
103-
char line[LINE_MAX] = { 0, };
104+
FILE *fp = fopen(CONTAINER_ENV_FILE, "r");
105+
char *line = NULL;
106+
size_t buf_size = 0;
104107

105108
if (fp == NULL) {
106109
return;
107110
}
108111

109-
while (fgets(line, LINE_MAX, fp) != NULL) {
112+
while (getline(&line, &buf_size, fp) != -1) {
110113
char *name = NULL;
111114
char *end = NULL;
112-
char *value = NULL;
113115
char *quote = NULL;
116+
char *value = NULL;
117+
char *value_end = NULL;
114118

115119
// Look for valid name immediately followed by equals sign
116-
if (find_env_var_name(line, &name, &end) && (*++end == '=')) {
120+
if (!find_env_var_name(line, &name, &end) || (*++end != '=')) {
121+
continue;
122+
}
117123

118-
// Null-terminate name, and advance beyond equals sign
119-
*end++ = '\0';
124+
// Null-terminate name, and advance beyond equals sign
125+
*end++ = '\0';
126+
127+
// Check whether value is quoted
128+
if ((*end == '\'') || (*end == '"')) {
129+
quote = end++;
130+
}
120131

121-
// Check whether value is quoted
122-
if ((*end == '\'') || (*end == '"')) {
123-
quote = end++;
132+
// Set beginning of value
133+
value = end;
134+
135+
// Find end of value
136+
if (quote != NULL) {
137+
/* Value is remaining characters up to next non-backslashed matching
138+
* quote character
139+
*
140+
* @TODO This breaks when the value ends with a literal escaped
141+
* backslash. For example: VAR='value\\'.
142+
*/
143+
while (((*end != *quote) || (*(end - 1) == '\\'))
144+
&& (*end != '\0')) {
145+
end++;
124146
}
125-
value = end;
126-
127-
if (quote != NULL) {
128-
/* Value is remaining characters up to next non-backslashed
129-
* matching quote character.
130-
*/
131-
while (((*end != *quote) || (*(end - 1) == '\\'))
132-
&& (*end != '\0')) {
133-
end++;
134-
}
135-
if (*end == *quote) {
136-
// Null-terminate value, and advance beyond close quote
137-
*end++ = '\0';
138-
} else {
139-
// Matching closing quote wasn't found
140-
value = NULL;
141-
}
142-
143-
} else {
144-
/* Value is remaining characters up to next non-backslashed
145-
* whitespace.
146-
*/
147-
while ((!isspace(*end) || (*(end - 1) == '\\'))
148-
&& (*end != '\0')) {
149-
end++;
150-
}
151-
152-
if (end == (line + LINE_MAX - 1)) {
153-
// Line was too long
154-
value = NULL;
155-
}
156-
// Do NOT null-terminate value (yet)
147+
148+
if (*end != *quote) {
149+
// Matching closing quote wasn't found
150+
continue;
157151
}
158152

159-
/* We have a valid name and value, and end is now the character
160-
* after the closing quote or the first whitespace after the
161-
* unquoted value. Make sure the rest of the line is just whitespace
162-
* or a comment.
153+
/* Null-terminate value at the closing quote and advance the end
154+
* pointer beyond it.
155+
*/
156+
*end++ = '\0';
157+
158+
} else {
159+
/* Value is remaining characters up to next non-backslashed
160+
* whitespace
161+
*
162+
* @TODO This breaks when the value ends with a literal escaped
163+
* backslash and is followed by a space.
163164
*/
164-
if (value != NULL) {
165-
char *value_end = end;
166-
167-
while (isspace(*end) && (*end != '\n')) {
168-
end++;
169-
}
170-
if ((*end == '\n') || (*end == '#')) {
171-
if (quote == NULL) {
172-
// Now we can null-terminate an unquoted value
173-
*value_end = '\0';
174-
}
175-
176-
// Don't overwrite (bundle options take precedence)
177-
setenv(name, value, 0);
178-
179-
} else {
180-
value = NULL;
181-
}
165+
while ((!isspace(*end) || (*(end - 1) == '\\'))
166+
&& (*end != '\0')) {
167+
end++;
182168
}
183-
}
184169

185-
if ((value == NULL) && (strchr(line, '\n') == NULL)) {
186-
// Eat remainder of line beyond LINE_MAX
187-
if (fscanf(fp, "%*[^\n]\n") == EOF) {
188-
value = NULL; // Don't care, make compiler happy
170+
if (*end == '\0') {
171+
// Malformed line (contains null terminator before newline)
172+
continue;
189173
}
190174
}
175+
176+
/* We have a valid name and value, and end is now the character after
177+
* the closing quote or the first whitespace after the unquoted value.
178+
* Make sure the rest of the line is just whitespace or a comment.
179+
*/
180+
value_end = end;
181+
182+
while (isspace(*end) && (*end != '\n')) {
183+
end++;
184+
}
185+
186+
if ((*end != '\n') && (*end != '#')) {
187+
// Found garbage after value
188+
continue;
189+
}
190+
191+
// Null-terminate value (it's safe to do it again for a quoted value)
192+
*value_end = '\0';
193+
194+
// Don't overwrite (bundle options take precedence)
195+
setenv(name, value, 0);
196+
}
197+
198+
// getline() returns -1 on EOF or error
199+
if (errno != 0) {
200+
int rc = errno;
201+
202+
crm_err("Error while reading environment variables from "
203+
CONTAINER_ENV_FILE ": %s",
204+
pcmk_rc_str(rc));
191205
}
192206
fclose(fp);
207+
free(line);
193208
}
194209

195210
void
@@ -221,7 +236,7 @@ remoted_spawn_pidone(int argc, char **argv, char **envp)
221236
* To allow for that, look for a special file containing a shell-like syntax
222237
* of name/value pairs, and export those into the environment.
223238
*/
224-
load_env_vars("/etc/pacemaker/pcmk-init.env");
239+
load_env_vars();
225240

226241
if (strcmp(pid1, "vars") == 0) {
227242
return;

0 commit comments

Comments
 (0)