1 /*
2  *  OpenVPN -- An application to securely tunnel IP networks
3  *             over a single TCP/UDP port, with support for SSL/TLS-based
4  *             session authentication and key exchange,
5  *             packet encryption, packet authentication, and
6  *             packet compression.
7  *
8  *  Copyright (C) 2002-2017 OpenVPN Technologies, Inc. <sales@openvpn.net>
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License version 2
12  *  as published by the Free Software Foundation.
13  *
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License along
20  *  with this program; if not, write to the Free Software Foundation, Inc.,
21  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22  */
23 
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #elif defined(_MSC_VER)
27 #include "config-msvc.h"
28 #endif
29 
30 #include "syshead.h"
31 
32 #include "buffer.h"
33 #include "error.h"
34 #include "platform.h"
35 #include "win32.h"
36 
37 #include "memdbg.h"
38 
39 #include "run_command.h"
40 
41 /* contains an SSEC_x value defined in platform.h */
42 static int script_security_level = SSEC_BUILT_IN; /* GLOBAL */
43 
44 int
script_security(void)45 script_security(void)
46 {
47     return script_security_level;
48 }
49 
50 void
script_security_set(int level)51 script_security_set(int level)
52 {
53     script_security_level = level;
54 }
55 
56 /*
57  * Generate an error message based on the status code returned by openvpn_execve().
58  */
59 static const char *
system_error_message(int stat,struct gc_arena * gc)60 system_error_message(int stat, struct gc_arena *gc)
61 {
62     struct buffer out = alloc_buf_gc(256, gc);
63 
64     switch (stat)
65     {
66         case OPENVPN_EXECVE_NOT_ALLOWED:
67             buf_printf(&out, "disallowed by script-security setting");
68             break;
69 
70 #ifdef _WIN32
71         case OPENVPN_EXECVE_ERROR:
72             buf_printf(&out, "external program did not execute -- ");
73         /* fall through */
74 
75         default:
76             buf_printf(&out, "returned error code %d", stat);
77             break;
78 #else  /* ifdef _WIN32 */
79 
80         case OPENVPN_EXECVE_ERROR:
81             buf_printf(&out, "external program fork failed");
82             break;
83 
84         default:
85             if (!WIFEXITED(stat))
86             {
87                 buf_printf(&out, "external program did not exit normally");
88             }
89             else
90             {
91                 const int cmd_ret = WEXITSTATUS(stat);
92                 if (!cmd_ret)
93                 {
94                     buf_printf(&out, "external program exited normally");
95                 }
96                 else if (cmd_ret == OPENVPN_EXECVE_FAILURE)
97                 {
98                     buf_printf(&out, "could not execute external program");
99                 }
100                 else
101                 {
102                     buf_printf(&out, "external program exited with error status: %d", cmd_ret);
103                 }
104             }
105             break;
106 #endif /* ifdef _WIN32 */
107     }
108     return (const char *)out.data;
109 }
110 
111 bool
openvpn_execve_allowed(const unsigned int flags)112 openvpn_execve_allowed(const unsigned int flags)
113 {
114     if (flags & S_SCRIPT)
115     {
116         return script_security() >= SSEC_SCRIPTS;
117     }
118     else
119     {
120         return script_security() >= SSEC_BUILT_IN;
121     }
122 }
123 
124 
125 #ifndef _WIN32
126 /*
127  * Run execve() inside a fork().  Designed to replicate the semantics of system() but
128  * in a safer way that doesn't require the invocation of a shell or the risks
129  * associated with formatting and parsing a command line.
130  * Returns the exit status of child, OPENVPN_EXECVE_NOT_ALLOWED if openvpn_execve_allowed()
131  * returns false, or OPENVPN_EXECVE_ERROR on other errors.
132  */
133 int
openvpn_execve(const struct argv * a,const struct env_set * es,const unsigned int flags)134 openvpn_execve(const struct argv *a, const struct env_set *es, const unsigned int flags)
135 {
136     struct gc_arena gc = gc_new();
137     int ret = OPENVPN_EXECVE_ERROR;
138     static bool warn_shown = false;
139 
140     if (a && a->argv[0])
141     {
142 #if defined(ENABLE_FEATURE_EXECVE)
143         if (openvpn_execve_allowed(flags))
144         {
145             const char *cmd = a->argv[0];
146             char *const *argv = a->argv;
147             char *const *envp = (char *const *)make_env_array(es, true, &gc);
148             pid_t pid;
149 
150             pid = fork();
151             if (pid == (pid_t)0) /* child side */
152             {
153                 execve(cmd, argv, envp);
154                 exit(OPENVPN_EXECVE_FAILURE);
155             }
156             else if (pid < (pid_t)0) /* fork failed */
157             {
158                 msg(M_ERR, "openvpn_execve: unable to fork");
159             }
160             else /* parent side */
161             {
162                 if (waitpid(pid, &ret, 0) != pid)
163                 {
164                     ret = OPENVPN_EXECVE_ERROR;
165                 }
166             }
167         }
168         else
169         {
170             ret = OPENVPN_EXECVE_NOT_ALLOWED;
171             if (!warn_shown && (script_security() < SSEC_SCRIPTS))
172             {
173                 msg(M_WARN, SCRIPT_SECURITY_WARNING);
174                 warn_shown = true;
175             }
176         }
177 #else  /* if defined(ENABLE_FEATURE_EXECVE) */
178         msg(M_WARN, "openvpn_execve: execve function not available");
179 #endif /* if defined(ENABLE_FEATURE_EXECVE) */
180     }
181     else
182     {
183         msg(M_FATAL, "openvpn_execve: called with empty argv");
184     }
185 
186     gc_free(&gc);
187     return ret;
188 }
189 #endif /* ifndef _WIN32 */
190 
191 /*
192  * Wrapper around openvpn_execve
193  */
194 int
openvpn_execve_check(const struct argv * a,const struct env_set * es,const unsigned int flags,const char * error_message)195 openvpn_execve_check(const struct argv *a, const struct env_set *es, const unsigned int flags, const char *error_message)
196 {
197     struct gc_arena gc = gc_new();
198     const int stat = openvpn_execve(a, es, flags);
199     int ret = false;
200 
201     if (flags & S_EXITCODE)
202     {
203         ret = platform_ret_code(stat);
204         if (ret != -1)
205         {
206             goto done;
207         }
208     }
209     else if (platform_system_ok(stat))
210     {
211         ret = true;
212         goto done;
213     }
214     if (error_message)
215     {
216         msg(((flags & S_FATAL) ? M_FATAL : M_WARN), "%s: %s",
217             error_message,
218             system_error_message(stat, &gc));
219     }
220 done:
221     gc_free(&gc);
222 
223     return ret;
224 }
225 
226 /*
227  * Run execve() inside a fork(), duping stdout.  Designed to replicate the semantics of popen() but
228  * in a safer way that doesn't require the invocation of a shell or the risks
229  * associated with formatting and parsing a command line.
230  */
231 int
openvpn_popen(const struct argv * a,const struct env_set * es)232 openvpn_popen(const struct argv *a,  const struct env_set *es)
233 {
234     struct gc_arena gc = gc_new();
235     int ret = -1;
236 
237     if (a && a->argv[0])
238     {
239 #if defined(ENABLE_FEATURE_EXECVE)
240         static bool warn_shown = false;
241         if (script_security() >= SSEC_BUILT_IN)
242         {
243             const char *cmd = a->argv[0];
244             char *const *argv = a->argv;
245             char *const *envp = (char *const *)make_env_array(es, true, &gc);
246             pid_t pid;
247             int pipe_stdout[2];
248 
249             if (pipe(pipe_stdout) == 0)
250             {
251                 pid = fork();
252                 if (pid == (pid_t)0)       /* child side */
253                 {
254                     close(pipe_stdout[0]);         /* Close read end */
255                     dup2(pipe_stdout[1],1);
256                     execve(cmd, argv, envp);
257                     exit(OPENVPN_EXECVE_FAILURE);
258                 }
259                 else if (pid > (pid_t)0)       /* parent side */
260                 {
261                     int status = 0;
262 
263                     close(pipe_stdout[1]);        /* Close write end */
264                     waitpid(pid, &status, 0);
265                     ret = pipe_stdout[0];
266                 }
267                 else       /* fork failed */
268                 {
269                     close(pipe_stdout[0]);
270                     close(pipe_stdout[1]);
271                     msg(M_ERR, "openvpn_popen: unable to fork %s", cmd);
272                 }
273             }
274             else
275             {
276                 msg(M_WARN, "openvpn_popen: unable to create stdout pipe for %s", cmd);
277                 ret = -1;
278             }
279         }
280         else if (!warn_shown && (script_security() < SSEC_SCRIPTS))
281         {
282             msg(M_WARN, SCRIPT_SECURITY_WARNING);
283             warn_shown = true;
284         }
285 #else  /* if defined(ENABLE_FEATURE_EXECVE) */
286         msg(M_WARN, "openvpn_popen: execve function not available");
287 #endif /* if defined(ENABLE_FEATURE_EXECVE) */
288     }
289     else
290     {
291         msg(M_FATAL, "openvpn_popen: called with empty argv");
292     }
293 
294     gc_free(&gc);
295     return ret;
296 }
297