1    	/*
2    	 * Copyright 2017-2023 the Pacemaker project contributors
3    	 *
4    	 * The version control history for this file may have further details.
5    	 *
6    	 * This source code is licensed under the GNU Lesser General Public License
7    	 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8    	 */
9    	
10   	#include <crm_internal.h>
11   	
12   	#include <stdio.h>
13   	#include <ctype.h>
14   	#include <stdlib.h>
15   	#include <signal.h>
16   	#include <unistd.h>
17   	#include <sys/types.h>
18   	#include <sys/wait.h>
19   	
20   	#include <crm/crm.h>
21   	#include "pacemaker-execd.h"
22   	
23   	static pid_t main_pid = 0;
24   	
25   	static void
26   	sigdone(void)
27   	{
28   	    exit(CRM_EX_OK);
29   	}
30   	
31   	static void
32   	sigreap(void)
33   	{
34   	    pid_t pid = 0;
35   	    int status;
36   	
37   	    do {
38   	        /*
39   	         * Opinions seem to differ as to what to put here:
40   	         *  -1, any child process
41   	         *  0,  any child process whose process group ID is equal to that of the calling process
42   	         */
43   	        pid = waitpid(-1, &status, WNOHANG);
44   	        if (pid == main_pid) {
45   	            /* Exit when pacemaker-remote exits and use the same return code */
46   	            if (WIFEXITED(status)) {
47   	                exit(WEXITSTATUS(status));
48   	            }
49   	            exit(CRM_EX_ERROR);
50   	        }
51   	    } while (pid > 0);
52   	}
53   	
54   	static struct {
55   	    int sig;
56   	    void (*handler)(void);
57   	} sigmap[] = {
58   	    { SIGCHLD, sigreap },
59   	    { SIGINT,  sigdone },
60   	};
61   	
62   	/*!
63   	 * \internal
64   	 * \brief Check a line of text for a valid environment variable name
65   	 *
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
69   	 *
70   	 * \return TRUE if valid name found, FALSE otherwise
71   	 * \note It's reasonable to impose limitations on environment variable names
72   	 *       beyond what C or setenv() does: We only allow names that contain only
73   	 *       [a-zA-Z0-9_] characters and do not start with a digit.
74   	 */
75   	static bool
76   	find_env_var_name(char *line, char **first, char **last)
77   	{
78   	    // Skip leading whitespace
(1) Event parm_assign: Assigning: "*first" = "line", which taints "*first".
79   	    *first = line;
(2) Event path: Condition "*__ctype_b_loc()[(int)**first] & 8192 /* (unsigned short)_ISspace */", taking true branch.
(4) Event path: Condition "*__ctype_b_loc()[(int)**first] & 8192 /* (unsigned short)_ISspace */", taking false branch.
80   	    while (isspace(**first)) {
81   	        ++*first;
(3) Event path: Jumping back to the beginning of the loop.
82   	    }
83   	
(5) Event path: Condition "*__ctype_b_loc()[(int)**first] & 1024 /* (unsigned short)_ISalpha */", taking true branch.
84   	    if (isalpha(**first) || (**first == '_')) { // Valid first character
85   	        *last = *first;
(6) Event path: Condition "*__ctype_b_loc()[(int)*last[1]] & 8 /* (unsigned short)_ISalnum */", taking false branch.
(7) Event path: Condition "*last[1] == '_'", taking false branch.
86   	        while (isalnum(*(*last + 1)) || (*(*last + 1) == '_')) {
87   	            ++*last;
88   	        }
89   	        return TRUE;
90   	    }
91   	
92   	    *first = *last = NULL;
93   	    return FALSE;
94   	}
95   	
96   	static void
97   	load_env_vars(const char *filename)
98   	{
99   	    /* We haven't forked or initialized logging yet, so don't leave any file
100  	     * descriptors open, and don't log -- silently ignore errors.
101  	     */
102  	    FILE *fp = fopen(filename, "r");
103  	
(1) Event path: Condition "fp != NULL", taking true branch.
104  	    if (fp != NULL) {
105  	        char line[LINE_MAX] = { '\0', };
106  	
(2) Event tainted_argument: Calling function "fgets" taints argument "*line".
(3) Event path: Condition "fgets(line, 2048, fp) != NULL", taking true branch.
Also see events: [tainted_data_transitive][tainted_string][remediation]
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
(4) Event tainted_data_transitive: Call to function "find_env_var_name" with tainted argument "line" transitively taints "*name". [details]
(5) Event path: Condition "find_env_var_name(line, &name, &end)", taking true branch.
(6) Event path: Condition "*++end == '='", taking true branch.
Also see events: [tainted_argument][tainted_string][remediation]
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
(7) Event path: Condition "*end == '\''", taking true branch.
120  	                if ((*end == '\'') || (*end == '"')) {
121  	                    quote = end++;
122  	                }
123  	                value = end;
124  	
(8) Event path: Condition "quote", taking true branch.
125  	                if (quote) {
126  	                    /* Value is remaining characters up to next non-backslashed
127  	                     * matching quote character.
128  	                     */
(9) Event path: Condition "*end != *quote", taking true branch.
(10) Event path: Condition "*end != 0", taking true branch.
(12) Event path: Condition "*end != *quote", taking false branch.
(13) Event path: Condition "*(end - 1) == '\\'", taking false branch.
129  	                    while (((*end != *quote) || (*(end - 1) == '\\'))
130  	                           && (*end != '\0')) {
131  	                        end++;
(11) Event path: Jumping back to the beginning of the loop.
132  	                    }
(14) Event path: Condition "*end == *quote", taking true branch.
133  	                    if (*end == *quote) {
134  	                        // Null-terminate value, and advance beyond close quote
135  	                        *end++ = '\0';
(15) Event path: Falling through to end of if statement.
136  	                    } else {
137  	                        // Matching closing quote wasn't found
138  	                        value = NULL;
139  	                    }
140  	
(16) Event path: Falling through to end of if statement.
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  	                 */
(17) Event path: Condition "value", taking true branch.
162  	                if (value) {
163  	                    char *value_end = end;
164  	
(18) Event path: Condition "*__ctype_b_loc()[(int)*end] & 8192 /* (unsigned short)_ISspace */", taking true branch.
(19) Event path: Condition "*end != 10", taking false branch.
165  	                    while (isspace(*end) && (*end != '\n')) {
166  	                        ++end;
167  	                    }
(20) Event path: Condition "*end == 10", taking true branch.
168  	                    if ((*end == '\n') || (*end == '#')) {
(21) Event path: Condition "quote == NULL", taking false branch.
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)
CID (unavailable; MK=ac5f1f4a8024df88ee0fcd68f3d8187d) (#1 of 2): Use of untrusted string value (TAINTED_STRING):
(22) Event tainted_string: Passing tainted string "*name" to "setenv", which cannot accept tainted data.
(23) Event remediation: Ensure tainted data is properly sanitized, for instance by using a whitelist of permissible characters.
Also see events: [tainted_argument][tainted_data_transitive]
175  	                        setenv(name, value, 0);
176  	
177  	                    } else {
178  	                        value = NULL;
179  	                    }
180  	                }
181  	            }
182  	
183  	            if ((value == NULL) && (strchr(line, '\n') == NULL)) {
184  	                // Eat remainder of line beyond LINE_MAX
185  	                if (fscanf(fp, "%*[^\n]\n") == EOF) {
186  	                    value = NULL; // Don't care, make compiler happy
187  	                }
188  	            }
189  	        }
190  	        fclose(fp);
191  	    }
192  	}
193  	
194  	void
195  	remoted_spawn_pidone(int argc, char **argv, char **envp)
196  	{
197  	    sigset_t set;
198  	
199  	    /* This environment variable exists for two purposes:
200  	     * - For testing, setting it to "full" enables full PID 1 behavior even
201  	     *   when PID is not 1
202  	     * - Setting to "vars" enables just the loading of environment variables
203  	     *   from /etc/pacemaker/pcmk-init.env, which could be useful for testing or
204  	     *   containers with a custom PID 1 script that launches pacemaker-remoted.
205  	     */
206  	    const char *pid1 = "default";
207  	
208  	    if (getpid() != 1) {
209  	        pid1 = pcmk__env_option(PCMK__ENV_REMOTE_PID1);
210  	        if (!pcmk__str_any_of(pid1, "full", "vars", NULL)) {
211  	            // Default, unset, or invalid
212  	            return;
213  	        }
214  	    }
215  	
216  	    /* When a container is launched, it may be given specific environment
217  	     * variables, which for Pacemaker bundles are given in the bundle
218  	     * configuration. However, that does not allow for host-specific values.
219  	     * To allow for that, look for a special file containing a shell-like syntax
220  	     * of name/value pairs, and export those into the environment.
221  	     */
222  	    load_env_vars("/etc/pacemaker/pcmk-init.env");
223  	
224  	    if (strcmp(pid1, "vars") == 0) {
225  	        return;
226  	    }
227  	
228  	    /* Containers can be expected to have /var/log, but they may not have
229  	     * /var/log/pacemaker, so use a different default if no value has been
230  	     * explicitly configured in the container's environment.
231  	     */
232  	    if (pcmk__env_option(PCMK__ENV_LOGFILE) == NULL) {
233  	        pcmk__set_env_option(PCMK__ENV_LOGFILE, "/var/log/pcmk-init.log", true);
234  	    }
235  	
236  	    sigfillset(&set);
237  	    sigprocmask(SIG_BLOCK, &set, 0);
238  	
239  	    main_pid = fork();
240  	    switch (main_pid) {
241  	        case 0:
242  	            sigprocmask(SIG_UNBLOCK, &set, NULL);
243  	            setsid();
244  	            setpgid(0, 0);
245  	
246  	            // Child remains as pacemaker-remoted
247  	            return;
248  	        case -1:
249  	            crm_err("fork failed: %s", pcmk_rc_str(errno));
250  	    }
251  	
252  	    /* Parent becomes the reaper of zombie processes */
253  	    /* Safe to initialize logging now if needed */
254  	
255  	#  ifdef HAVE_PROGNAME
256  	    /* Differentiate ourselves in the 'ps' output */
257  	    {
258  	        char *p;
259  	        int i, maxlen;
260  	        char *LastArgv = NULL;
261  	        const char *name = "pcmk-init";
262  	
263  	        for (i = 0; i < argc; i++) {
264  	            if (!i || (LastArgv + 1 == argv[i]))
265  	                LastArgv = argv[i] + strlen(argv[i]);
266  	        }
267  	
268  	        for (i = 0; envp[i] != NULL; i++) {
269  	            if ((LastArgv + 1) == envp[i]) {
270  	                LastArgv = envp[i] + strlen(envp[i]);
271  	            }
272  	        }
273  	
274  	        maxlen = (LastArgv - argv[0]) - 2;
275  	
276  	        i = strlen(name);
277  	
278  	        /* We can overwrite individual argv[] arguments */
279  	        snprintf(argv[0], maxlen, "%s", name);
280  	
281  	        /* Now zero out everything else */
282  	        p = &argv[0][i];
283  	        while (p < LastArgv) {
284  	            *p++ = '\0';
285  	        }
286  	        argv[1] = NULL;
287  	    }
288  	#  endif // HAVE_PROGNAME
289  	
290  	    while (1) {
291  	        int sig;
292  	        size_t i;
293  	
294  	        sigwait(&set, &sig);
295  	        for (i = 0; i < PCMK__NELEM(sigmap); i++) {
296  	            if (sigmap[i].sig == sig) {
297  	                sigmap[i].handler();
298  	                break;
299  	            }
300  	        }
301  	    }
302  	}
303