2.1_rc8 and earlier did implicit shell expansion on script

arguments since all scripts were called by system().
The security hardening changes made to 2.1_rc9 no longer
use system(), but rather use the safer execve or CreateProcess
system calls.  The security hardening also introduced a
backward incompatibility with 2.1_rc8 and earlier in that
script parameters were no longer shell-expanded, so
for example:

  client-connect "docc CLIENT-CONNECT"

would fail to work because execve would try to execute
a script called "docc CLIENT-CONNECT" instead of "docc"
with "CLIENT-CONNECT" as the first argument.

This patch fixes the issue, bringing the script argument
semantics back to pre 2.1_rc9 behavior in order to preserve
backward compatibility while still using execve or CreateProcess
to execute the script/executable.


git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@3311 e7ae566f-a301-0410-adde-c780ea21d3b5
This commit is contained in:
james 2008-09-06 09:42:17 +00:00
parent 0a838de8ad
commit b8fb090c16
9 changed files with 352 additions and 365 deletions

252
buffer.c
View File

@ -234,258 +234,6 @@ int openvpn_snprintf(char *str, size_t size, const char *format, ...)
return ret;
}
/*
* A printf-like function (that only recognizes a subset of standard printf
* format operators) that prints arguments to an argv list instead
* of a standard string. This is used to build up argv arrays for passing
* to execve.
*/
void
argv_init (struct argv *a)
{
a->argc = 0;
a->argv = NULL;
}
struct argv
argv_new (void)
{
struct argv ret;
argv_init (&ret);
return ret;
}
void
argv_reset (struct argv *a)
{
size_t i;
for (i = 0; i < a->argc; ++i)
free (a->argv[i]);
free (a->argv);
a->argc = 0;
a->argv = NULL;
}
size_t
argv_argc (const char *format)
{
char *term;
const char *f = format;
size_t argc = 0;
while ((term = argv_term (&f)) != NULL)
{
++argc;
free (term);
}
return argc;
}
struct argv
argv_insert_head (const struct argv *a, const char *head)
{
struct argv r;
size_t i;
r.argc = (a ? a->argc : 0) + 1;
ALLOC_ARRAY_CLEAR (r.argv, char *, r.argc + 1);
r.argv[0] = string_alloc (head, NULL);
if (a)
{
for (i = 0; i < a->argc; ++i)
r.argv[i+1] = string_alloc (a->argv[i], NULL);
}
return r;
}
char *
argv_term (const char **f)
{
const char *p = *f;
const char *term = NULL;
size_t termlen = 0;
if (*p == '\0')
return NULL;
while (true)
{
const int c = *p;
if (c == '\0')
break;
if (term)
{
if (!isspace (c))
++termlen;
else
break;
}
else
{
if (!isspace (c))
{
term = p;
termlen = 1;
}
}
++p;
}
*f = p;
if (term)
{
char *ret;
ASSERT (termlen > 0);
ret = malloc (termlen + 1);
check_malloc_return (ret);
memcpy (ret, term, termlen);
ret[termlen] = '\0';
return ret;
}
else
return NULL;
}
const char *
argv_str (const struct argv *a, struct gc_arena *gc, const unsigned int flags)
{
if (a->argv)
return print_argv ((const char **)a->argv, gc, flags);
else
return "";
}
void
argv_msg (const int msglev, const struct argv *a)
{
struct gc_arena gc = gc_new ();
msg (msglev, "%s", argv_str (a, &gc, 0));
gc_free (&gc);
}
void
argv_msg_prefix (const int msglev, const struct argv *a, const char *prefix)
{
struct gc_arena gc = gc_new ();
msg (msglev, "%s: %s", prefix, argv_str (a, &gc, 0));
gc_free (&gc);
}
void
argv_printf (struct argv *a, const char *format, ...)
{
va_list arglist;
va_start (arglist, format);
argv_printf_arglist (a, format, 0, arglist);
va_end (arglist);
}
void
argv_printf_cat (struct argv *a, const char *format, ...)
{
va_list arglist;
va_start (arglist, format);
argv_printf_arglist (a, format, APA_CAT, arglist);
va_end (arglist);
}
void
argv_printf_arglist (struct argv *a, const char *format, const unsigned int flags, va_list arglist)
{
char *term;
const char *f = format;
size_t argc = 0;
if (flags & APA_CAT)
{
char **old_argv = a->argv;
size_t i;
argc = a->argc;
a->argc += argv_argc (format);
ALLOC_ARRAY_CLEAR (a->argv, char *, a->argc + 1);
for (i = 0; i < argc; ++i)
a->argv[i] = old_argv[i];
free (old_argv);
}
else
{
argv_reset (a);
a->argc = argv_argc (format);
ALLOC_ARRAY_CLEAR (a->argv, char *, a->argc + 1);
}
while ((term = argv_term (&f)) != NULL)
{
ASSERT (argc < a->argc);
if (term[0] == '%')
{
if (!strcmp (term, "%s"))
{
char *s = va_arg (arglist, char *);
if (!s)
s = "";
a->argv[argc++] = string_alloc (s, NULL);
}
else if (!strcmp (term, "%d"))
{
char numstr[64];
openvpn_snprintf (numstr, sizeof (numstr), "%d", va_arg (arglist, int));
a->argv[argc++] = string_alloc (numstr, NULL);
}
else if (!strcmp (term, "%u"))
{
char numstr[64];
openvpn_snprintf (numstr, sizeof (numstr), "%u", va_arg (arglist, unsigned int));
a->argv[argc++] = string_alloc (numstr, NULL);
}
else if (!strcmp (term, "%s/%d"))
{
char numstr[64];
char *s = va_arg (arglist, char *);
if (!s)
s = "";
openvpn_snprintf (numstr, sizeof (numstr), "%d", va_arg (arglist, int));
{
const size_t len = strlen(s) + strlen(numstr) + 2;
char *combined = (char *) malloc (len);
check_malloc_return (combined);
strcpy (combined, s);
strcat (combined, "/");
strcat (combined, numstr);
a->argv[argc++] = combined;
}
}
else if (!strcmp (term, "%s%s"))
{
char *s1 = va_arg (arglist, char *);
char *s2 = va_arg (arglist, char *);
char *combined;
if (!s1) s1 = "";
if (!s2) s2 = "";
combined = (char *) malloc (strlen(s1) + strlen(s2) + 1);
check_malloc_return (combined);
strcpy (combined, s1);
strcat (combined, s2);
a->argv[argc++] = combined;
}
else
ASSERT (0);
free (term);
}
else
{
a->argv[argc++] = term;
}
}
ASSERT (argc == a->argc);
}
/*
* write a string to the end of a buffer that was
* truncated by buf_printf

View File

@ -60,6 +60,7 @@ struct buffer
/* used by argv_x functions */
struct argv {
size_t capacity;
size_t argc;
char **argv;
};
@ -292,37 +293,6 @@ int openvpn_snprintf(char *str, size_t size, const char *format, ...)
#endif
;
/*
* A printf-like function (that only recognizes a subset of standard printf
* format operators) that prints arguments to an argv list instead
* of a standard string. This is used to build up argv arrays for passing
* to execve.
*/
void argv_init (struct argv *a);
struct argv argv_new (void);
void argv_reset (struct argv *a);
size_t argv_argc (const char *format);
char *argv_term (const char **f);
const char *argv_str (const struct argv *a, struct gc_arena *gc, const unsigned int flags);
struct argv argv_insert_head (const struct argv *a, const char *head);
void argv_msg (const int msglev, const struct argv *a);
void argv_msg_prefix (const int msglev, const struct argv *a, const char *prefix);
#define APA_CAT (1<<0) /* concatentate onto existing struct argv list */
void argv_printf_arglist (struct argv *a, const char *format, const unsigned int flags, va_list arglist);
void argv_printf (struct argv *a, const char *format, ...)
#ifdef __GNUC__
__attribute__ ((format (printf, 2, 3)))
#endif
;
void argv_printf_cat (struct argv *a, const char *format, ...)
#ifdef __GNUC__
__attribute__ ((format (printf, 2, 3)))
#endif
;
/*
* remove/add trailing characters
*/

View File

@ -75,6 +75,7 @@
#define D_CLOSE LOGLEV(2, 22, 0) /* show socket and TUN/TAP close */
#define D_SHOW_OCC_HASH LOGLEV(2, 23, 0) /* show MD5 hash of option compatibility string */
#define D_PROXY LOGLEV(2, 24, 0) /* show http proxy control packets */
#define D_ARGV LOGLEV(2, 25, 0) /* show struct argv errors */
#define D_TLS_DEBUG_LOW LOGLEV(3, 20, 0) /* low frequency info from tls_session routines */
#define D_GREMLIN LOGLEV(3, 30, 0) /* show simulated outage info from gremlin module */

2
init.c
View File

@ -923,7 +923,7 @@ do_route (const struct options *options,
{
struct argv argv = argv_new ();
setenv_str (es, "script_type", "route-up");
argv_printf (&argv, "%s", options->route_script);
argv_printf (&argv, "%sc", options->route_script);
openvpn_execve_check (&argv, es, S_SCRIPT, "Route script failed");
argv_reset (&argv);
}

382
misc.c
View File

@ -220,7 +220,7 @@ run_up_down (const char *command,
ASSERT (arg);
setenv_str (es, "script_type", script_type);
argv_printf (&argv,
"%s %s %d %d %s %s %s",
"%sc %s %d %d %s %s %s",
command,
arg,
tun_mtu, link_mtu,
@ -1190,24 +1190,6 @@ absolute_pathname (const char *pathname)
return false;
}
/*
* Return the next largest power of 2
* or u if u is a power of 2.
*/
unsigned int
adjust_power_of_2 (unsigned int u)
{
unsigned int ret = 1;
while (ret < u)
{
ret <<= 1;
ASSERT (ret > 0);
}
return ret;
}
#ifdef HAVE_GETPASS
static FILE *
@ -1666,56 +1648,309 @@ openvpn_sleep (const int n)
sleep (n);
}
#if 0
/*
* Configure PATH. On Windows, sometimes PATH is not set correctly
* by default.
* Return the next largest power of 2
* or u if u is a power of 2.
*/
void
configure_path (void)
size_t
adjust_power_of_2 (size_t u)
{
#ifdef WIN32
FILE *fp;
fp = fopen ("c:\\windows\\system32\\route.exe", "rb");
if (fp)
size_t ret = 1;
while (ret < u)
{
const int bufsiz = 4096;
struct gc_arena gc = gc_new ();
struct buffer oldpath = alloc_buf_gc (bufsiz, &gc);
struct buffer newpath = alloc_buf_gc (bufsiz, &gc);
const char* delim = ";";
DWORD status;
fclose (fp);
status = GetEnvironmentVariable ("PATH", BPTR(&oldpath), (DWORD)BCAP(&oldpath));
#if 0
status = 0;
#endif
if (!status)
{
*BPTR(&oldpath) = '\0';
delim = "";
}
buf_printf (&newpath, "C:\\WINDOWS\\System32;C:\\WINDOWS;C:\\WINDOWS\\System32\\Wbem%s%s",
delim,
BSTR(&oldpath));
SetEnvironmentVariable ("PATH", BSTR(&newpath));
#if 0
status = GetEnvironmentVariable ("PATH", BPTR(&oldpath), (DWORD)BCAP(&oldpath));
if (status > 0)
printf ("PATH: %s\n", BSTR(&oldpath));
#endif
gc_free (&gc);
ret <<= 1;
ASSERT (ret > 0);
}
#endif
return ret;
}
/*
* A printf-like function (that only recognizes a subset of standard printf
* format operators) that prints arguments to an argv list instead
* of a standard string. This is used to build up argv arrays for passing
* to execve.
*/
void
argv_init (struct argv *a)
{
a->capacity = 0;
a->argc = 0;
a->argv = NULL;
}
struct argv
argv_new (void)
{
struct argv ret;
argv_init (&ret);
return ret;
}
void
argv_reset (struct argv *a)
{
size_t i;
for (i = 0; i < a->argc; ++i)
free (a->argv[i]);
free (a->argv);
argv_init (a);
}
static void
argv_extend (struct argv *a, const size_t newcap)
{
if (newcap > a->capacity)
{
char **newargv;
size_t i;
ALLOC_ARRAY_CLEAR (newargv, char *, newcap);
for (i = 0; i < a->argc; ++i)
newargv[i] = a->argv[i];
free (a->argv);
a->argv = newargv;
a->capacity = newcap;
}
}
static void
argv_grow (struct argv *a, const size_t add)
{
const size_t newargc = a->argc + add + 1;
ASSERT (newargc > a->argc);
argv_extend (a, adjust_power_of_2 (newargc));
}
static void
argv_append (struct argv *a, char *str) /* str must have been malloced or be NULL */
{
argv_grow (a, 1);
a->argv[a->argc++] = str;
}
struct argv
argv_clone (const struct argv *a, const size_t headroom)
{
struct argv r;
size_t i;
argv_init (&r);
for (i = 0; i < headroom; ++i)
argv_append (&r, NULL);
if (a)
{
for (i = 0; i < a->argc; ++i)
argv_append (&r, string_alloc (a->argv[i], NULL));
}
return r;
}
struct argv
argv_insert_head (const struct argv *a, const char *head)
{
struct argv r;
r = argv_clone (a, 1);
r.argv[0] = string_alloc (head, NULL);
return r;
}
char *
argv_term (const char **f)
{
const char *p = *f;
const char *term = NULL;
size_t termlen = 0;
if (*p == '\0')
return NULL;
while (true)
{
const int c = *p;
if (c == '\0')
break;
if (term)
{
if (!isspace (c))
++termlen;
else
break;
}
else
{
if (!isspace (c))
{
term = p;
termlen = 1;
}
}
++p;
}
*f = p;
if (term)
{
char *ret;
ASSERT (termlen > 0);
ret = malloc (termlen + 1);
check_malloc_return (ret);
memcpy (ret, term, termlen);
ret[termlen] = '\0';
return ret;
}
else
return NULL;
}
const char *
argv_str (const struct argv *a, struct gc_arena *gc, const unsigned int flags)
{
if (a->argv)
return print_argv ((const char **)a->argv, gc, flags);
else
return "";
}
void
argv_msg (const int msglev, const struct argv *a)
{
struct gc_arena gc = gc_new ();
msg (msglev, "%s", argv_str (a, &gc, 0));
gc_free (&gc);
}
void
argv_msg_prefix (const int msglev, const struct argv *a, const char *prefix)
{
struct gc_arena gc = gc_new ();
msg (msglev, "%s: %s", prefix, argv_str (a, &gc, 0));
gc_free (&gc);
}
void
argv_printf (struct argv *a, const char *format, ...)
{
va_list arglist;
va_start (arglist, format);
argv_printf_arglist (a, format, 0, arglist);
va_end (arglist);
}
void
argv_printf_cat (struct argv *a, const char *format, ...)
{
va_list arglist;
va_start (arglist, format);
argv_printf_arglist (a, format, APA_CAT, arglist);
va_end (arglist);
}
void
argv_printf_arglist (struct argv *a, const char *format, const unsigned int flags, va_list arglist)
{
struct gc_arena gc = gc_new ();
char *term;
const char *f = format;
if (!(flags & APA_CAT))
argv_reset (a);
argv_extend (a, 1); /* ensure trailing NULL */
while ((term = argv_term (&f)) != NULL)
{
if (term[0] == '%')
{
if (!strcmp (term, "%s"))
{
char *s = va_arg (arglist, char *);
if (!s)
s = "";
argv_append (a, string_alloc (s, NULL));
}
else if (!strcmp (term, "%sc"))
{
char *s = va_arg (arglist, char *);
if (s)
{
int nparms;
char *parms[MAX_PARMS+1];
int i;
nparms = parse_line (s, parms, MAX_PARMS, "SCRIPT-ARGV", 0, M_FATAL, &gc);
for (i = 0; i < nparms; ++i)
argv_append (a, string_alloc (parms[i], NULL));
}
else
argv_append (a, string_alloc ("", NULL));
}
else if (!strcmp (term, "%d"))
{
char numstr[64];
openvpn_snprintf (numstr, sizeof (numstr), "%d", va_arg (arglist, int));
argv_append (a, string_alloc (numstr, NULL));
}
else if (!strcmp (term, "%u"))
{
char numstr[64];
openvpn_snprintf (numstr, sizeof (numstr), "%u", va_arg (arglist, unsigned int));
argv_append (a, string_alloc (numstr, NULL));
}
else if (!strcmp (term, "%s/%d"))
{
char numstr[64];
char *s = va_arg (arglist, char *);
if (!s)
s = "";
openvpn_snprintf (numstr, sizeof (numstr), "%d", va_arg (arglist, int));
{
const size_t len = strlen(s) + strlen(numstr) + 2;
char *combined = (char *) malloc (len);
check_malloc_return (combined);
strcpy (combined, s);
strcat (combined, "/");
strcat (combined, numstr);
argv_append (a, combined);
}
}
else if (!strcmp (term, "%s%s"))
{
char *s1 = va_arg (arglist, char *);
char *s2 = va_arg (arglist, char *);
char *combined;
if (!s1) s1 = "";
if (!s2) s2 = "";
combined = (char *) malloc (strlen(s1) + strlen(s2) + 1);
check_malloc_return (combined);
strcpy (combined, s1);
strcat (combined, s2);
argv_append (a, combined);
}
else
ASSERT (0);
free (term);
}
else
{
argv_append (a, term);
}
}
gc_free (&gc);
}
#endif
#ifdef ARGV_TEST
void
argv_test (void)
{
struct gc_arena gc = gc_new ();
char line[512];
const char *s;
struct argv a;
@ -1729,7 +1964,7 @@ argv_test (void)
#endif
argv_msg_prefix (M_INFO, &a, "ARGV");
openvpn_execve_check (&a, NULL, 0, "command failed");
//openvpn_execve_check (&a, NULL, 0, "command failed");
argv_printf (&a, "this is a %s test of int %d unsigned %u", "FOO", -69, 42);
s = argv_str (&a, &gc, PA_BRACKET);
@ -1742,7 +1977,7 @@ argv_test (void)
printf ("%s\n", s);
}
argv_printf (&a, "foo bar %d", 99);
argv_printf (&a, "%sc foo bar %d", "\"multi term\" command following \\\"spaces", 99);
s = argv_str (&a, &gc, PA_BRACKET);
argv_reset (&a);
printf ("%s\n", s);
@ -1752,25 +1987,28 @@ argv_test (void)
printf ("%s\n", s);
argv_printf (&a, "foo bar %d", 99);
argv_printf_cat (&a, "bar %d foo", 42);
argv_printf_cat (&a, "bar %d foo %sc", 42, "nonesuch");
argv_printf_cat (&a, "cool %s %d u %s/%d end", "frood", 4, "hello", 7);
s = argv_str (&a, &gc, PA_BRACKET);
printf ("%s\n", s);
#if 0
while (fgets (line, sizeof(line), stdin) != NULL)
{
char *term;
const char *f = line;
int i = 0;
{
char line[512];
while (fgets (line, sizeof(line), stdin) != NULL)
{
char *term;
const char *f = line;
int i = 0;
while ((term = argv_term (&f)) != NULL)
{
printf ("[%d] '%s'\n", i, term);
++i;
free (term);
}
}
while ((term = argv_term (&f)) != NULL)
{
printf ("[%d] '%s'\n", i, term);
++i;
free (term);
}
}
}
#endif
argv_reset (&a);

36
misc.h
View File

@ -221,9 +221,6 @@ bool delete_file (const char *filename);
/* return true if pathname is absolute */
bool absolute_pathname (const char *pathname);
/* return the next largest power of 2 */
unsigned int adjust_power_of_2 (unsigned int u);
/*
* Get and store a username/password
*/
@ -300,4 +297,37 @@ extern const char *iproute_path;
#define SSEC_PW_ENV 3 /* allow calling of built-in programs and user-defined scripts that may receive a password as an environmental variable */
extern int script_security; /* GLOBAL */
/* return the next largest power of 2 */
size_t adjust_power_of_2 (size_t u);
/*
* A printf-like function (that only recognizes a subset of standard printf
* format operators) that prints arguments to an argv list instead
* of a standard string. This is used to build up argv arrays for passing
* to execve.
*/
void argv_init (struct argv *a);
struct argv argv_new (void);
void argv_reset (struct argv *a);
char *argv_term (const char **f);
const char *argv_str (const struct argv *a, struct gc_arena *gc, const unsigned int flags);
struct argv argv_insert_head (const struct argv *a, const char *head);
void argv_msg (const int msglev, const struct argv *a);
void argv_msg_prefix (const int msglev, const struct argv *a, const char *prefix);
#define APA_CAT (1<<0) /* concatentate onto existing struct argv list */
void argv_printf_arglist (struct argv *a, const char *format, const unsigned int flags, va_list arglist);
void argv_printf (struct argv *a, const char *format, ...)
#ifdef __GNUC__
__attribute__ ((format (printf, 2, 3)))
#endif
;
void argv_printf_cat (struct argv *a, const char *format, ...)
#ifdef __GNUC__
__attribute__ ((format (printf, 2, 3)))
#endif
;
#endif

View File

@ -103,7 +103,7 @@ learn_address_script (const struct multi_context *m,
{
struct argv argv = argv_new ();
setenv_str (es, "script_type", "learn-address");
argv_printf (&argv, "%s %s %s",
argv_printf (&argv, "%sc %s %s",
m->top.options.learn_address_script,
op,
mroute_addr_print (addr, &gc));
@ -473,7 +473,7 @@ multi_client_disconnect_script (struct multi_context *m,
{
struct argv argv = argv_new ();
setenv_str (mi->context.c2.es, "script_type", "client-disconnect");
argv_printf (&argv, "%s", mi->context.options.client_disconnect_script);
argv_printf (&argv, "%sc", mi->context.options.client_disconnect_script);
openvpn_execve_check (&argv, mi->context.c2.es, S_SCRIPT, "client-disconnect command failed");
argv_reset (&argv);
}
@ -1568,7 +1568,7 @@ multi_connection_established (struct multi_context *m, struct multi_instance *mi
delete_file (dc_file);
argv_printf (&argv, "%s %s",
argv_printf (&argv, "%sc %s",
mi->context.options.client_connect_script,
dc_file);

View File

@ -1539,7 +1539,7 @@ ipchange_fmt (const bool include_cmd, struct argv *argv, const struct link_socke
const char *ip = print_sockaddr_ex (&info->lsa->actual.dest, NULL, 0, gc);
const char *port = print_sockaddr_ex (&info->lsa->actual.dest, NULL, PS_DONT_SHOW_ADDR|PS_SHOW_PORT, gc);
if (include_cmd)
argv_printf (argv, "%s %s %s",
argv_printf (argv, "%sc %s %s",
info->ipchange_command,
ip,
port);

4
ssl.c
View File

@ -718,7 +718,7 @@ verify_callback (int preverify_ok, X509_STORE_CTX * ctx)
setenv_str (opt->es, "script_type", "tls-verify");
argv_printf (&argv, "%s %d %s",
argv_printf (&argv, "%sc %d %s",
opt->verify_command,
ctx->error_depth,
subject);
@ -2937,7 +2937,7 @@ verify_user_pass_script (struct tls_session *session, const struct user_pass *up
setenv_untrusted (session);
/* format command line */
argv_printf (&argv, "%s %s", session->opt->auth_user_pass_verify_script, tmp_file);
argv_printf (&argv, "%sc %s", session->opt->auth_user_pass_verify_script, tmp_file);
/* call command */
retval = openvpn_execve (&argv, session->opt->es, S_SCRIPT);