From: Russ Allbery Subject: [PATCH] Handle the rsync v3 -e option for protocol information As of rsync 3, rsync reused the -e option to pass protocol information from the client to the server. We therefore cannot reject all -e options to rsync, only ones not sent with --server or containing something other than protocol information as an argument. Also scan the rsync command line for any --rsh option and reject it as well. This replaces and improves the upstream strategy for rejecting that command-line option, taking advantage of the parsing added to check the -e option. Based on work by Robert Hardy. Debian Bug#471803 Signed-off-by: Russ Allbery --- util.c | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 8 deletions(-) diff --git a/util.c b/util.c index f98d2bc..a257b06 100644 --- a/util.c +++ b/util.c @@ -56,6 +56,7 @@ #ifdef HAVE_LIBGEN_H #include #endif /* HAVE_LIBGEN_H */ +#include /* LOCAL INCLUDES */ #include "pathnames.h" @@ -198,6 +199,73 @@ bool check_command( char *cl, ShellOptions_t *opts, char *cmd, int cmdflag ) /* + * rsync_e_okay() - take the command line passed to rssh and look for an -e + * option. If one is found, make sure --server is provided + * and the option contains only the protocol information. + * Also check for and reject any --rsh option. Returns FALSE + * if the command line should not be allowed, TRUE if it is + * okay. + */ +static int rsync_e_okay( char **vec ) +{ + regex_t re; + int server = FALSE; + int e_found = FALSE; + + /* + * rsync will send -e, followed by either just "." (meaning no special + * protocol) or "N.N" (meaning a pre-release protocol version), + * followed by some number of alphabetic flags indicating various + * supported options. There may be other options between - and the e, + * but -e will always be the last option in the string. A typical + * option passed by the client is "-ltpre.iL". + * + * Note that if --server is given, this should never be parsed as a + * shell, but we'll tightly verify it anyway, just in case. + * + * This regex matches the acceptable flags containing -e, so if it + * does not match, the command line should be rejected. + */ + static const char pattern[] + = "^-[a-df-zA-Z]*e[0-9]*\\.[0-9]*[a-zA-Z]*$"; + + /* + * Only recognize --server if it's the first option. rsync itself + * always passes it that way, and if it's not the first argument, it + * could be hidden from the server as an argument to some other + * option. + */ + if ( vec && vec[0] && vec[1] && strcmp(vec[1], "--server") == 0 ){ + server = TRUE; + } + + /* Check the remaining options for -e or --rsh. */ + if ( regcomp(&re, pattern, REG_EXTENDED | REG_NOSUB) != 0 ){ + return FALSE; + } + while (vec && *vec){ + if ( strcmp(*vec, "--") == 0 ) break; + if ( strcmp(*vec, "--rsh") == 0 + || strncmp(*vec, "--rsh=", strlen("--rsh=")) == 0 ){ + regfree(&re); + return FALSE; + } + if ( strncmp(*vec, "--", 2) != 0 && opt_exist(*vec, 'e') ){ + e_found = TRUE; + if ( regexec(&re, *vec, 0, NULL, 0) != 0 ){ + regfree(&re); + return FALSE; + } + } + vec++; + } + regfree(&re); + if ( e_found && !server ) return FALSE; + return TRUE; +} + + +/* * check_command_line() - take the command line passed to rssh, and verify * that the specified command is one the user is * allowed to run and validate the arguments. Return the @@ -230,14 +298,10 @@ char *check_command_line( char **cl, ShellOptions_t *opts ) if ( check_command(*cl, opts, PATH_RSYNC, RSSH_ALLOW_RSYNC) ){ /* filter -e option */ - if ( opt_filter(cl, 'e') ) return NULL; - while (cl && *cl){ - if ( strstr(*cl, "--rsh" ) ){ - fprintf(stderr, "\ninsecure --rsh= not allowed."); - log_msg("insecure --rsh option in rsync command line!"); - return NULL; - } - cl++; + if ( !rsync_e_okay(cl) ){ + fprintf(stderr, "\ninsecure -e or --rsh option not allowed."); + log_msg("insecure -e or --rsh option in rsync command line!"); + return NULL; } return PATH_RSYNC; } -- tg: (f8b36e2..) fixes/rsync-protocol (depends on: upstream)