/* * OpenVPN -- An application to securely tunnel IP networks * over a single TCP/UDP port, with support for SSL/TLS-based * session authentication and key exchange, * packet encryption, packet authentication, and * packet compression. * * Copyright (C) 2002-2021 OpenVPN Technologies, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #elif defined(_MSC_VER) #include "config-msvc.h" #endif #include "syshead.h" #include "buffer.h" #include "error.h" #include "platform.h" #include "win32.h" #include "memdbg.h" #include "run_command.h" /* contains an SSEC_x value defined in platform.h */ static int script_security_level = SSEC_BUILT_IN; /* GLOBAL */ int script_security(void) { return script_security_level; } void script_security_set(int level) { script_security_level = level; } /* * Generate an error message based on the status code returned by openvpn_execve(). */ static const char * system_error_message(int stat, struct gc_arena *gc) { struct buffer out = alloc_buf_gc(256, gc); switch (stat) { case OPENVPN_EXECVE_NOT_ALLOWED: buf_printf(&out, "disallowed by script-security setting"); break; #ifdef _WIN32 case OPENVPN_EXECVE_ERROR: buf_printf(&out, "external program did not execute -- "); /* fall through */ default: buf_printf(&out, "returned error code %d", stat); break; #else /* ifdef _WIN32 */ case OPENVPN_EXECVE_ERROR: buf_printf(&out, "external program fork failed"); break; default: if (!WIFEXITED(stat)) { buf_printf(&out, "external program did not exit normally"); } else { const int cmd_ret = WEXITSTATUS(stat); if (!cmd_ret) { buf_printf(&out, "external program exited normally"); } else if (cmd_ret == OPENVPN_EXECVE_FAILURE) { buf_printf(&out, "could not execute external program"); } else { buf_printf(&out, "external program exited with error status: %d", cmd_ret); } } break; #endif /* ifdef _WIN32 */ } return (const char *)out.data; } bool openvpn_execve_allowed(const unsigned int flags) { if (flags & S_SCRIPT) { return script_security() >= SSEC_SCRIPTS; } else { return script_security() >= SSEC_BUILT_IN; } } #ifndef _WIN32 /* * Run execve() inside a fork(). Designed to replicate the semantics of system() but * in a safer way that doesn't require the invocation of a shell or the risks * associated with formatting and parsing a command line. * Returns the exit status of child, OPENVPN_EXECVE_NOT_ALLOWED if openvpn_execve_allowed() * returns false, or OPENVPN_EXECVE_ERROR on other errors. */ int openvpn_execve(const struct argv *a, const struct env_set *es, const unsigned int flags) { struct gc_arena gc = gc_new(); int ret = OPENVPN_EXECVE_ERROR; static bool warn_shown = false; if (a && a->argv[0]) { #if defined(ENABLE_FEATURE_EXECVE) if (openvpn_execve_allowed(flags)) { const char *cmd = a->argv[0]; char *const *argv = a->argv; char *const *envp = (char *const *)make_env_array(es, true, &gc); pid_t pid; pid = fork(); if (pid == (pid_t)0) /* child side */ { execve(cmd, argv, envp); exit(OPENVPN_EXECVE_FAILURE); } else if (pid < (pid_t)0) /* fork failed */ { msg(M_ERR, "openvpn_execve: unable to fork"); } else /* parent side */ { if (waitpid(pid, &ret, 0) != pid) { ret = OPENVPN_EXECVE_ERROR; } } } else { ret = OPENVPN_EXECVE_NOT_ALLOWED; if (!warn_shown && (script_security() < SSEC_SCRIPTS)) { msg(M_WARN, SCRIPT_SECURITY_WARNING); warn_shown = true; } } #else /* if defined(ENABLE_FEATURE_EXECVE) */ msg(M_WARN, "openvpn_execve: execve function not available"); #endif /* if defined(ENABLE_FEATURE_EXECVE) */ } else { msg(M_FATAL, "openvpn_execve: called with empty argv"); } gc_free(&gc); return ret; } #endif /* ifndef _WIN32 */ /* * Wrapper around openvpn_execve */ bool openvpn_execve_check(const struct argv *a, const struct env_set *es, const unsigned int flags, const char *error_message) { struct gc_arena gc = gc_new(); const int stat = openvpn_execve(a, es, flags); int ret = false; if (platform_system_ok(stat)) { ret = true; } else { if (error_message) { msg(((flags & S_FATAL) ? M_FATAL : M_WARN), "%s: %s", error_message, system_error_message(stat, &gc)); } } gc_free(&gc); return ret; } /* * Run execve() inside a fork(), duping stdout. Designed to replicate the semantics of popen() but * in a safer way that doesn't require the invocation of a shell or the risks * associated with formatting and parsing a command line. */ int openvpn_popen(const struct argv *a, const struct env_set *es) { struct gc_arena gc = gc_new(); int ret = -1; static bool warn_shown = false; if (a && a->argv[0]) { #if defined(ENABLE_FEATURE_EXECVE) if (script_security() >= SSEC_BUILT_IN) { const char *cmd = a->argv[0]; char *const *argv = a->argv; char *const *envp = (char *const *)make_env_array(es, true, &gc); pid_t pid; int pipe_stdout[2]; if (pipe(pipe_stdout) == 0) { pid = fork(); if (pid == (pid_t)0) /* child side */ { close(pipe_stdout[0]); /* Close read end */ dup2(pipe_stdout[1],1); execve(cmd, argv, envp); exit(OPENVPN_EXECVE_FAILURE); } else if (pid > (pid_t)0) /* parent side */ { int status = 0; close(pipe_stdout[1]); /* Close write end */ waitpid(pid, &status, 0); ret = pipe_stdout[0]; } else /* fork failed */ { close(pipe_stdout[0]); close(pipe_stdout[1]); msg(M_ERR, "openvpn_popen: unable to fork %s", cmd); } } else { msg(M_WARN, "openvpn_popen: unable to create stdout pipe for %s", cmd); ret = -1; } } else if (!warn_shown && (script_security() < SSEC_SCRIPTS)) { msg(M_WARN, SCRIPT_SECURITY_WARNING); warn_shown = true; } #else /* if defined(ENABLE_FEATURE_EXECVE) */ msg(M_WARN, "openvpn_popen: execve function not available"); #endif /* if defined(ENABLE_FEATURE_EXECVE) */ } else { msg(M_FATAL, "openvpn_popen: called with empty argv"); } gc_free(&gc); return ret; }