]> git.ipfire.org Git - thirdparty/rsync.git/commitdiff
daemon: un-backslash escaped option args (#829)
authorAndrew Tridgell <andrew@tridgell.net>
Thu, 4 Jun 2026 06:19:31 +0000 (16:19 +1000)
committerAndrew Tridgell <andrew@tridgell.net>
Thu, 4 Jun 2026 20:35:12 +0000 (06:35 +1000)
Without --secluded-args, the client's safe_arg() backslash-escapes shell
and wildcard chars in option values before sending them to the server, so
--chown's --usermap=*:user is transmitted as --usermap=\*:user.  Over ssh a
remote shell removes the backslashes before rsync parses the args, but a
daemon has no shell and read_args() stored option args verbatim -- so the
receiver saw the literal "\*", the usermap/groupmap wildcard never matched,
and the module's configured uid/gid won instead.  A regression from the
secluded-args hardening; rsync 3.2.3 (protocol 31) worked.

Un-backslash option args in read_args() on the daemon's first
(non-protected) read, mirroring what the ssh-side shell does.  File args
after the dot are already handled by glob_expand(); the protected (NUL,
already-unescaped) re-read and the server's stdin read pass unescape=0 so
their raw args are left untouched.

Fixes: #829
clientserver.c
io.c
main.c

index 14daba3c0f9bba21d0d3e37adda3b39f23117be9..cc59663ad3aec048fc0a18dcc56b275046fd0f9b 100644 (file)
@@ -1070,7 +1070,7 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
 
        io_printf(f_out, "@RSYNCD: OK\n");
 
-       read_args(f_in, name, line, sizeof line, rl_nulls, &argv, &argc, &request);
+       read_args(f_in, name, line, sizeof line, rl_nulls, 1, &argv, &argc, &request);
        orig_argv = argv;
 
        save_munge_symlinks = munge_symlinks;
@@ -1080,7 +1080,7 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
        if (protect_args && ret) {
                orig_early_argv = orig_argv;
                protect_args = 2;
-               read_args(f_in, name, line, sizeof line, 1, &argv, &argc, &request);
+               read_args(f_in, name, line, sizeof line, 1, 0, &argv, &argc, &request);
                orig_argv = argv;
                ret = parse_arguments(&argc, (const char ***) &argv);
        } else
diff --git a/io.c b/io.c
index 08e7e0aad3d8763e58a4ae75f2207f61e8d503aa..0b96c27095650369cc01d9dd064b94d58d09b8ea 100644 (file)
--- a/io.c
+++ b/io.c
@@ -1292,8 +1292,21 @@ int read_line(int fd, char *buf, size_t bufsiz, int flags)
        return s - buf;
 }
 
+/* Reverse safe_arg()'s backslash escaping of a daemon option arg, the way a
+ * remote shell un-escapes args for the ssh transport.  In place; \X -> X. */
+static void unbackslash_arg(char *s)
+{
+       char *f = s, *t = s;
+       while (*f) {
+               if (*f == '\\' && f[1])
+                       f++;
+               *t++ = *f++;
+       }
+       *t = '\0';
+}
+
 void read_args(int f_in, char *mod_name, char *buf, size_t bufsiz, int rl_nulls,
-              char ***argv_p, int *argc_p, char **request_p)
+              int unescape, char ***argv_p, int *argc_p, char **request_p)
 {
        int maxargs = MAX_ARGS;
        int dot_pos = 0, argc = 0, request_len = 0;
@@ -1335,6 +1348,11 @@ void read_args(int f_in, char *mod_name, char *buf, size_t bufsiz, int rl_nulls,
                                glob_expand(buf, &argv, &argc, &maxargs);
                } else {
                        p = strdup(buf);
+                       /* An option arg the client escaped with safe_arg() (no
+                        * remote shell un-escapes it for a daemon).  File args
+                        * after the dot are handled by glob_expand() below. */
+                       if (unescape)
+                               unbackslash_arg(p);
                        argv[argc++] = p;
                        if (*p == '.' && p[1] == '\0')
                                dot_pos = argc;
diff --git a/main.c b/main.c
index c54fd79bc994e167c7ee723de5817c07ec54e6d3..5aac6cfc6de558497c201c2584f1e092883f583d 100644 (file)
--- a/main.c
+++ b/main.c
@@ -1845,7 +1845,7 @@ int main(int argc,char *argv[])
        if (am_server && protect_args) {
                char buf[MAXPATHLEN];
                protect_args = 2;
-               read_args(STDIN_FILENO, NULL, buf, sizeof buf, 1, &argv, &argc, NULL);
+               read_args(STDIN_FILENO, NULL, buf, sizeof buf, 1, 0, &argv, &argc, NULL);
                if (!parse_arguments(&argc, (const char ***) &argv)) {
                        option_error();
                        exit_cleanup(RERR_SYNTAX);