On Wed, Mar 7, 2012 at 9:10 AM, David Sommerseth
<openvpn.l...@topphemmelig.net> wrote:
[skipped]
> > OpenVPN 2.3alpha1 fails when the argument to "--up" contains more
> > than an execution path. The problem also occurs for the "--down"
> > option and the new "--route-pre-down" option (and presumably any other
> > options that take more than just an execution path).
[skipped]

> Ouch!  I see that check_file_access() needs to strip out any arguments.
> It will now basically 'access("/private/tmp/test/up.sh -x")' ... which is
> a file which doesn't exist ... but if it tested for
> 'access("/private/tmp/test/up.sh")' it should find the file.
>
> However, it isn't as easy to just skip through the string and "terminate
> it" on the first space (0x20) value, as it might have been escaped.
> Which can make quite typical paths like this fail:
>
>   "C:\Program Files\OpenVPN\bin\up-script.bat"


Below is a patch to fix this problem.

It causes the affected options (--up, --down, etc.) to call a new
function for verification: check_cmd_access(). It is called with the
same calling sequence as check_file_access() so as to simplify things,
and it uses check_file_access() to check the file once the path for it
has been isolated. I wrote a new function so that (a) I wouldn't
disturb the existing function, and (b) it could be extended -- for
example, to check for any problems with any arguments that follow the
path to the functions (mismatched quotes, for example).

It's my first proposed patch for OpenVPN, so please examine it _very_
carefully. I've tested it only OS X (and it works fine), but I don't
have a full debugging environment, so it was tested as a "black box"
using msg() calls to verify its behavior. I tried to conform to the
style I saw elsewhere in the source, but may have missed or
misunderstood something.

There are a couple of anomalies in the existing code that I have not dealt with:

1. --iproute also takes a "cmd" argument but the options.c code does
not call check_file_access() for it. (So I have not included it in
this patch),

2. Although the man page [1] says most of the "cmd" arguments can be a
"shell command", that doesn't seem correct. On OS X, for example,
backslashes are discarded (not used to escape the next character) and
double-quotes can only be used at the start and end of an individual
argument in "cmd". The bash shell implements both of these (rendering
     abc"def"ghi.\ txt
as
     abcdefghi.txt
for example. This patch is very restrictive: it allows a "cmd" to be a
path (optionally enclosed in double-quotes), optionally followed by a
space, which may be followed by anything.

I would particularly like input on these lines in the new routine (but
of course all input is welcome):

    ASSERT((path_size <= OPTION_PARM_SIZE) || (path_size > 0));
    msg (M_NOPREFIX|M_OPTERR, "%s fails with '%s': param_size BAD:
stop_char = '%c'; start_ix = %d; "
         "command = " ptr_format "; stop_ptr = " ptr_format ";
path_size = %d; OPTION_PARAM_SIZE = %d",
         opt, command, stop_char, start_ix,
         (ptr_type) command, (ptr_type) stop_ptr, path_size, (int)
OPTION_PARM_SIZE);
    return true;

These lines test to make sure that OPTION_PARM_SIZE is reasonable. I
don't know your conventions, so I included both an assert and an error
message to be output before a failure return ("true" is a failure
indicator).

Let me know of any changes you'd like me to make.

- Jon Bullard

[1] http://openvpn.net/index.php/manuals/523-openvpn-23.html

===============================

--- openvpn/options.c   (revision 1964)
+++ openvpn/options.c   (working copy)
@@ -2656,6 +2656,72 @@
 }

 /*
+ * Check the command that comes after certain script options (e.g., --up).
+ *
+ * The command should consist of a path, which may be enclosed in
double-quotes, and may be
+ * optionally followed by a space which may be followed by arbitrary arguments.
+ *
+ * Once the path has been extracted from the command (if that is
necessary), check_file_access()
+ * is used to do the  sanity checking on it. The type, mode, and opt
arguments to this routine
+ * are the same as the corresponding check_file_access() arguments to
facilitate this.
+ */
+static bool
+check_cmd_access(const int type, const char *command, const int mode,
const char *opt)
+{
+  /* If no command configured, no errors to look for */
+  if (! command)
+    return false;
+
+  /* Test for a quote as the first char of command
+     and for presence of a space in command */
+
+  int   start_ix  = 0;      /* Where the path starts within command
               (0 or 1) */
+  char  stop_char = '\000'; /* Character that terminates the path
within command (' ' or '"') */
+  char *stop_ptr  = NULL;   /* Pointer past end of path       (NULL
or points inside command) */
+
+  if (command[0] == '"')
+  {
+    start_ix  = 1;
+    stop_char = '"';
+    stop_ptr  = strchr(command+1, '"');
+    if (stop_ptr == NULL)
+    {
+      msg (M_NOPREFIX|M_OPTERR, "%s fails with '%s': Unbalanced quote",
+           opt, command);
+      return true;
+    }
+  } else
+  {
+    start_ix  = 0;
+    stop_char = ' ';
+    stop_ptr  = strchr(command, ' ');
+  }
+
+  /* If command doesn't start with a quote and doesn't include any
spaces, just test it directly */
+
+  if ((start_ix == 0) && (stop_ptr == NULL))
+    return check_file_access(type, command, mode, opt);
+
+  /* Extract the path from command into a new string and test that */
+
+  int path_size = stop_ptr - command - start_ix + 1;          /*
(Includes terminating NUL) */
+
+  if ((path_size > OPTION_PARM_SIZE) || (path_size < 1)) {
+    ASSERT((path_size <= OPTION_PARM_SIZE) || (path_size > 0));
+    msg (M_NOPREFIX|M_OPTERR, "%s fails with '%s': param_size BAD:
stop_char = '%c'; start_ix = %d; "
+         "command = " ptr_format "; stop_ptr = " ptr_format ";
path_size = %d; OPTION_PARAM_SIZE = %d",
+         opt, command, stop_char, start_ix,
+         (ptr_type) command, (ptr_type) stop_ptr, path_size, (int)
OPTION_PARM_SIZE);
+    return true;
+  }
+
+  char path[OPTION_PARM_SIZE];
+  memcpy(path, command+start_ix, (path_size-1)*sizeof(char));
+  path[path_size-1] = '\000';
+  return check_file_access(type, path, mode, opt);
+}
+
+/*
  * Sanity check of all file/dir options.  Checks that file/dir
  * is accessible by OpenVPN
  */
@@ -2738,19 +2804,20 @@
                              R_OK|X_OK, "--client-disconnect script");
   errs |= check_file_access (CHKACC_FILE,
options->auth_user_pass_verify_script,
                              R_OK|X_OK, "--auth-user-pass-verify script");
-  errs |= check_file_access (CHKACC_FILE, options->tls_verify,
+  /* ** Script hooks that accept shell commands ** */
+  errs |= check_cmd_access (CHKACC_FILE, options->tls_verify,
                              R_OK|X_OK, "--tls-verify script");
-  errs |= check_file_access (CHKACC_FILE, options->up_script,
+  errs |= check_cmd_access (CHKACC_FILE, options->up_script,
                              R_OK|X_OK, "--up script");
-  errs |= check_file_access (CHKACC_FILE, options->down_script,
+  errs |= check_cmd_access (CHKACC_FILE, options->down_script,
                              R_OK|X_OK, "--down script");
-  errs |= check_file_access (CHKACC_FILE, options->ipchange,
+  errs |= check_cmd_access (CHKACC_FILE, options->ipchange,
                              R_OK|X_OK, "--ipchange script");
-  errs |= check_file_access (CHKACC_FILE, options->route_script,
+  errs |= check_cmd_access (CHKACC_FILE, options->route_script,
                              R_OK|X_OK, "--route-up script");
-  errs |= check_file_access (CHKACC_FILE, options->route_predown_script,
+  errs |= check_cmd_access (CHKACC_FILE, options->route_predown_script,
                              R_OK|X_OK, "--route-pre-down script");
-  errs |= check_file_access (CHKACC_FILE, options->learn_address_script,
+  errs |= check_cmd_access (CHKACC_FILE, options->learn_address_script,
                              R_OK|X_OK, "--learn-address script");
 #endif /* P2MP_SERVER */

Reply via email to