Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions clientserver.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -932,7 +932,7 @@ AC_FUNC_UTIME_NULL
AC_FUNC_ALLOCA
AC_CHECK_FUNCS(waitpid wait4 getcwd chown chmod lchmod mknod mkfifo \
fchmod fstat ftruncate strchr readlink link utime utimes lutimes strftime \
chflags getattrlist mktime innetgr linkat \
chflags getattrlist mktime innetgr linkat mknodat mkfifoat \
memmove lchown vsnprintf snprintf vasprintf asprintf setsid strpbrk \
strlcat strlcpy stpcpy strtol mallinfo mallinfo2 getgroups setgroups geteuid getegid \
setlocale setmode open64 lseek64 mkstemp64 mtrace va_copy __va_copy \
Expand Down
20 changes: 19 additions & 1 deletion io.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 4 additions & 2 deletions syscall.c
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,9 @@ int do_mknod(const char *pathname, mode_t mode, dev_t dev)
*/
int do_mknod_at(const char *pathname, mode_t mode, dev_t dev)
{
#ifdef AT_FDCWD
/* HAVE_MKNODAT: older Darwin declares AT_FDCWD but not mknodat(), so
* the at-variant won't build there; fall back to do_mknod() (#896). */
#if defined(AT_FDCWD) && defined(HAVE_MKNODAT)
extern int am_daemon, am_chrooted;
char dirpath[MAXPATHLEN];
const char *bname;
Expand Down Expand Up @@ -598,7 +600,7 @@ int do_mknod_at(const char *pathname, mode_t mode, dev_t dev)
return ret;
}

#if !defined MKNOD_CREATES_FIFOS && defined HAVE_MKFIFO
#if !defined MKNOD_CREATES_FIFOS && defined HAVE_MKFIFO && defined HAVE_MKFIFOAT
if (S_ISFIFO(mode))
ret = mkfifoat(dfd, bname, mode);
else
Expand Down
87 changes: 87 additions & 0 deletions testsuite/daemon-groupmap-wild_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
# Regression test for issue #829.
#
# Without --secluded-args the client's safe_arg() backslash-escapes wildcard
# chars in option values, so --chown / --groupmap=*:GROUP is sent to a daemon
# as --groupmap=\*:GROUP. A daemon has no shell to strip the backslash, and
# read_args() used to store option args verbatim, so the receiver saw the
# literal "\*", the wildcard never matched, and the map was ignored (the
# module's configured gid won instead). The fix un-backslashes daemon option
# args.
#
# We run it both ways:
# * default args -- the '*' is safe_arg-escaped and the daemon must
# un-backslash it (the path the fix repairs);
# * --secluded-args -- the '*' is sent raw over the protected channel and
# read with unescape=0, so it must keep working too
# (a guard that the fix didn't disturb that path).
#
# No root needed: a non-root receiver can chgrp(2) to a group the test user
# belongs to, so we map every source group to a second such group and check
# the wildcard took effect.

import os
import subprocess

from rsyncfns import (
SCRATCHDIR, makepath, rmtree, rsync_argv, start_test_daemon,
test_fail, test_skipped, write_daemon_conf,
)

DAEMON_PORT = 12923

# Two distinct groups to map between. As root (the usual CI case) we can
# chgrp(2) to any gid, so take two distinct named groups from the group
# database; a non-root user can only chgrp to groups it belongs to, so use those
# (skip if it is in fewer than two).
if os.geteuid() == 0:
import grp
usable = []
for gr in grp.getgrall():
if gr.gr_gid not in usable:
usable.append(gr.gr_gid)
if len(usable) < 2:
test_skipped("need >=2 groups defined on the system")
else:
usable = []
for g in [os.getgid()] + list(os.getgroups()):
if g not in usable:
usable.append(g)
if len(usable) < 2:
test_skipped("need >=2 groups the test user belongs to")
src_gid, dst_gid = usable[0], usable[1]

moddir = SCRATCHDIR / 'gmod'
srcdir = SCRATCHDIR / 'gsrc'
makepath(moddir)

conf = write_daemon_conf([('gmod', {'path': str(moddir), 'read only': 'no'})])
url = start_test_daemon(conf, DAEMON_PORT) + 'gmod/'


def check(label, *extra_opts):
rmtree(moddir)
rmtree(srcdir)
makepath(moddir)
makepath(srcdir)
f = srcdir / 'f.dat'
f.write_text("hi\n")
os.chown(f, -1, src_gid) # source group differs from the map target

# A --chown-style wildcard map sent to a daemon: the '*' must survive as a
# wildcard so every source group is remapped to dst_gid.
proc = subprocess.run(
rsync_argv('-rg', *extra_opts, f'--groupmap=*:{dst_gid}', str(f), url),
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
if proc.returncode != 0:
print(proc.stdout)
test_fail(f"[{label}] groupmap upload failed (rc={proc.returncode})")

got = os.stat(moddir / 'f.dat').st_gid
if got != dst_gid:
test_fail(f"[{label}] --groupmap='*:{dst_gid}' wildcard ignored over "
f"daemon: got gid {got}, expected {dst_gid} (regression of #829)")


check('default-args')
check('secluded-args', '--secluded-args')
2 changes: 0 additions & 2 deletions util1.c
Original file line number Diff line number Diff line change
Expand Up @@ -1788,8 +1788,6 @@ void *expand_item_list(item_list *lp, size_t item_size, const char *desc, int in
new_ptr == lp->items ? " not" : "");
}

memset((char *)new_ptr + lp->malloced * item_size, 0,
(expand_size - lp->malloced) * item_size);
lp->items = new_ptr;
lp->malloced = expand_size;
}
Expand Down
4 changes: 3 additions & 1 deletion util2.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ void *my_alloc(void *ptr, size_t num, size_t size, const char *file, int line)
who_am_i(), do_big_num(max_alloc, 0, NULL), src_file(file), line);
exit_cleanup(RERR_MALLOC);
}
if (!ptr || ptr == do_calloc)
if (!ptr)
ptr = malloc(num * size);
else if (ptr == do_calloc)
ptr = calloc(num, size);
else
ptr = realloc(ptr, num * size);
Expand Down
Loading