Skip to content

Commit a473d15

Browse files
authored
Merge pull request #3833 from nrwahl2/nrwahl2-coverity
pacemaker-remoted load_env_var refactors, and assorted minor changes
2 parents ffc11c7 + bb71085 commit a473d15

File tree

7 files changed

+218
-197
lines changed

7 files changed

+218
-197
lines changed

configure.ac

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1099,7 +1099,6 @@ dnl ========================================================================
10991099
cc_temp_flags "$CFLAGS $WERROR"
11001100

11011101
# Optional headers (inclusion of these should be conditional in C code)
1102-
AC_CHECK_HEADERS([linux/swab.h])
11031102
AC_CHECK_HEADERS([sys/signalfd.h])
11041103
AC_CHECK_HEADERS([uuid/uuid.h])
11051104
AC_CHECK_HEADERS([security/pam_appl.h pam/pam_appl.h])

daemons/execd/remoted_pidone.c

Lines changed: 125 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -61,135 +61,152 @@ static struct {
6161

6262
/*!
6363
* \internal
64-
* \brief Check a line of text for a valid environment variable name
64+
* \brief Check whether a string is a valid environment variable name
6565
*
66-
* \param[in] line Text to check
67-
* \param[out] first First character of valid name if found, NULL otherwise
68-
* \param[out] last Last character of valid name if found, NULL otherwise
66+
* \param[in] name String to check
6967
*
70-
* \return TRUE if valid name found, FALSE otherwise
68+
* \return \c true if \p name is a valid name, or \c false otherwise
7169
* \note It's reasonable to impose limitations on environment variable names
7270
* beyond what C or setenv() does: We only allow names that contain only
7371
* [a-zA-Z0-9_] characters and do not start with a digit.
7472
*/
7573
static bool
76-
find_env_var_name(char *line, char **first, char **last)
74+
valid_env_var_name(const gchar *name)
7775
{
78-
// Skip leading whitespace
79-
*first = line;
80-
while (isspace(**first)) {
81-
++*first;
76+
if (!isalpha(*name) && (*name != '_')) {
77+
// Invalid first character
78+
return false;
8279
}
8380

84-
if (isalpha(**first) || (**first == '_')) { // Valid first character
85-
*last = *first;
86-
while (isalnum(*(*last + 1)) || (*(*last + 1) == '_')) {
87-
++*last;
81+
// The rest of the characters must be alphanumeric or underscores
82+
for (name++; isalnum(*name) || (*name == '_'); name++);
83+
return *name == '\0';
84+
}
85+
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+
*/
103+
static void
104+
load_env_var_line(const char *line)
105+
{
106+
gint argc = 0;
107+
gchar **argv = NULL;
108+
GError *error = NULL;
109+
110+
gchar *name = NULL;
111+
gchar *value = NULL;
112+
113+
int rc = pcmk_rc_ok;
114+
const char *reason = NULL;
115+
const char *value_to_set = NULL;
116+
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+
*/
122+
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;
88128
}
89-
return TRUE;
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;
135+
}
136+
137+
rc = pcmk__scan_nvpair(line, &name, &value);
138+
if (rc != pcmk_rc_ok) {
139+
reason = pcmk_rc_str(rc);
140+
goto done;
141+
}
142+
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;
90148
}
91149

92-
*first = *last = NULL;
93-
return FALSE;
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 = "";
158+
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];
163+
}
164+
165+
// Don't overwrite (bundle options take precedence)
166+
setenv(name, value_to_set, 0);
167+
168+
done:
169+
if (reason != NULL) {
170+
crm_warn("Failed to perform environment variable assignment '%s': %s",
171+
line, reason);
172+
}
173+
g_strfreev(argv);
174+
g_clear_error(&error);
175+
g_free(name);
176+
g_free(value);
94177
}
95178

179+
#define CONTAINER_ENV_FILE "/etc/pacemaker/pcmk-init.env"
180+
96181
static void
97-
load_env_vars(const char *filename)
182+
load_env_vars(void)
98183
{
99184
/* We haven't forked or initialized logging yet, so don't leave any file
100185
* descriptors open, and don't log -- silently ignore errors.
101186
*/
102-
FILE *fp = fopen(filename, "r");
103-
104-
if (fp != NULL) {
105-
char line[LINE_MAX] = { '\0', };
106-
107-
while (fgets(line, LINE_MAX, fp) != NULL) {
108-
char *name = NULL;
109-
char *end = NULL;
110-
char *value = NULL;
111-
char *quote = NULL;
112-
113-
// Look for valid name immediately followed by equals sign
114-
if (find_env_var_name(line, &name, &end) && (*++end == '=')) {
115-
116-
// Null-terminate name, and advance beyond equals sign
117-
*end++ = '\0';
118-
119-
// Check whether value is quoted
120-
if ((*end == '\'') || (*end == '"')) {
121-
quote = end++;
122-
}
123-
value = end;
124-
125-
if (quote) {
126-
/* Value is remaining characters up to next non-backslashed
127-
* matching quote character.
128-
*/
129-
while (((*end != *quote) || (*(end - 1) == '\\'))
130-
&& (*end != '\0')) {
131-
end++;
132-
}
133-
if (*end == *quote) {
134-
// Null-terminate value, and advance beyond close quote
135-
*end++ = '\0';
136-
} else {
137-
// Matching closing quote wasn't found
138-
value = NULL;
139-
}
140-
141-
} else {
142-
/* Value is remaining characters up to next non-backslashed
143-
* whitespace.
144-
*/
145-
while ((!isspace(*end) || (*(end - 1) == '\\'))
146-
&& (*end != '\0')) {
147-
++end;
148-
}
149-
150-
if (end == (line + LINE_MAX - 1)) {
151-
// Line was too long
152-
value = NULL;
153-
}
154-
// Do NOT null-terminate value (yet)
155-
}
156-
157-
/* We have a valid name and value, and end is now the character
158-
* after the closing quote or the first whitespace after the
159-
* unquoted value. Make sure the rest of the line is just
160-
* whitespace or a comment.
161-
*/
162-
if (value) {
163-
char *value_end = end;
164-
165-
while (isspace(*end) && (*end != '\n')) {
166-
++end;
167-
}
168-
if ((*end == '\n') || (*end == '#')) {
169-
if (quote == NULL) {
170-
// Now we can null-terminate an unquoted value
171-
*value_end = '\0';
172-
}
173-
174-
// Don't overwrite (bundle options take precedence)
175-
// coverity[tainted_string] This can't easily be changed right now
176-
setenv(name, value, 0);
177-
178-
} else {
179-
value = NULL;
180-
}
181-
}
182-
}
187+
FILE *fp = fopen(CONTAINER_ENV_FILE, "r");
188+
char *line = NULL;
189+
size_t buf_size = 0;
183190

184-
if ((value == NULL) && (strchr(line, '\n') == NULL)) {
185-
// Eat remainder of line beyond LINE_MAX
186-
if (fscanf(fp, "%*[^\n]\n") == EOF) {
187-
value = NULL; // Don't care, make compiler happy
188-
}
189-
}
190-
}
191-
fclose(fp);
191+
if (fp == NULL) {
192+
return;
193+
}
194+
195+
while (getline(&line, &buf_size, fp) != -1) {
196+
load_env_var_line(line);
197+
errno = 0;
198+
}
199+
200+
// getline() returns -1 on EOF (expected) or error
201+
if (errno != 0) {
202+
int rc = errno;
203+
204+
crm_err("Error while reading environment variables from "
205+
CONTAINER_ENV_FILE ": %s",
206+
pcmk_rc_str(rc));
192207
}
208+
fclose(fp);
209+
free(line);
193210
}
194211

195212
void
@@ -221,7 +238,7 @@ remoted_spawn_pidone(int argc, char **argv)
221238
* To allow for that, look for a special file containing a shell-like syntax
222239
* of name/value pairs, and export those into the environment.
223240
*/
224-
load_env_vars("/etc/pacemaker/pcmk-init.env");
241+
load_env_vars();
225242

226243
if (strcmp(pid1, "vars") == 0) {
227244
return;

daemons/fenced/fenced_commands.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -744,10 +744,13 @@ schedule_stonith_command(async_command_t *cmd, fenced_device_t *device)
744744
delay_base = delay_max;
745745
}
746746
if (delay_max > 0) {
747-
cmd->start_delay +=
748-
// coverity[dont_call] It doesn't matter here if rand() is predictable
749-
((delay_max != delay_base)?(rand() % (delay_max - delay_base)):0)
750-
+ delay_base;
747+
cmd->start_delay += delay_base;
748+
749+
// Add random offset so that delay_base <= cmd->start_delay <= delay_max
750+
if (delay_max > delay_base) {
751+
// coverity[dont_call] Doesn't matter that rand() is predictable
752+
cmd->start_delay += rand() % (delay_max - delay_base + 1);
753+
}
751754
}
752755

753756
if (cmd->start_delay > 0) {

doc/sphinx/Pacemaker_Explained/collective.rst

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,12 +1104,13 @@ greater than zero.
11041104
* Environment variables whose value is the same regardless of the underlying host
11051105
may be set using the container element's ``options`` attribute.
11061106
* If you want variables to have host-specific values, you can use the
1107-
:ref:`storage-mapping <s-bundle-storage>` element to map a file on the host as
1108-
``/etc/pacemaker/pcmk-init.env`` in the container *(since 2.0.3)*.
1109-
Pacemaker Remote will parse this file as a shell-like format, with
1110-
variables set as NAME=VALUE, ignoring blank lines and comments starting
1111-
with "#".
1112-
1107+
:ref:`storage-mapping <s-bundle-storage>` element to map a file on the host
1108+
as |PCMK_INIT_ENV_FILE| in the container *(since 2.0.3)*. Pacemaker Remote
1109+
will parse this file in a manner similar to a POSIX shell, with variables
1110+
set as NAME=VALUE, ignoring blank lines and comments starting with ``#``.
1111+
An assignment may not span multiple lines, and multiple assignments per
1112+
line are not supported.
1113+
11131114
.. important::
11141115

11151116
When a bundle has a ``primitive``, Pacemaker on all cluster nodes must be able to

lib/common/nvpair.c

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,7 @@ pcmk_free_nvpairs(GSList *nvpairs)
114114
*
115115
* \param[in] input Input string, likely from the command line
116116
* \param[out] name Everything before the first \c '=' in the input string
117-
* \param[out] value Everything after the first \c '=' in the input string,
118-
* minus trailing newlines
117+
* \param[out] value Everything after the first \c '=' in the input string
119118
*
120119
* \return Standard Pacemaker return code
121120
*
@@ -125,6 +124,11 @@ pcmk_free_nvpairs(GSList *nvpairs)
125124
int
126125
pcmk__scan_nvpair(const gchar *input, gchar **name, gchar **value)
127126
{
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+
*/
128132
gchar **nvpair = NULL;
129133
int rc = pcmk_rc_ok;
130134

@@ -134,19 +138,15 @@ pcmk__scan_nvpair(const gchar *input, gchar **name, gchar **value)
134138

135139
nvpair = g_strsplit(input, "=", 2);
136140

137-
/* Check whether nvpair is well-formed (short-circuits if input was split
138-
* into fewer than 2 tokens)
139-
*/
140-
if (pcmk__str_empty(nvpair[0]) || pcmk__str_empty(nvpair[1])) {
141+
// Check whether nvpair is well-formed: two tokens and non-empty name
142+
if ((g_strv_length(nvpair) != 2) || pcmk__str_empty(nvpair[0])) {
141143
rc = pcmk_rc_bad_nvpair;
142144
goto done;
143145
}
144146

147+
// name and value take ownership of the strings in nvpair
145148
*name = nvpair[0];
146149
*value = nvpair[1];
147-
pcmk__trim((char *) *value);
148-
149-
// name and value took ownership
150150
nvpair[0] = NULL;
151151
nvpair[1] = NULL;
152152

0 commit comments

Comments
 (0)