The refused set was built from the characters that obviously execute
something, and missed three that a SECOND shell acts on:
'!' negates in command position. A hook written as an access check --
`pre-xfer exec = sh -c '%RSYNC_USER_NAME% false'` -- becomes
`! false`, reports success, and serves the transfer. An
authenticated user named "!" turns a denial into an approval, which
is precisely the case the fail-closed comment above exists for.
'~' is tilde-expanded, so ~root becomes /root.
'{' and '}' brace-expand in bash and zsh.
None of them execute anything on their own, which is how a set built from
the obvious metacharacters came to miss them. That is also the standing
weakness of the approach: this is a deny-list, and the two rounds of
review it took to find '!' are the argument for eventually inverting it.
The documentation is corrected with it -- it claimed every shell-active
character was refused, which this disproves -- and now lists the set.
Each listed character is pinned by the test, which needed its module
paths to EXIST first: a missing path fails the transfer on its own, so
checking the exit status alone passed whether or not the character was
refused. Removing any single character from the set now fails the test.
Context-aware quoting is only correct for one level of shell parsing. A
hook may re-parse the substituted word in a nested shell:
pre-xfer exec = sh -c 'printf %s %RSYNC_USER_NAME% >out'
The level-1 quotes are removed before the inner shell sees the value, so
an authenticated peer's username still reaches it as syntax however
carefully it was escaped. Escaping cannot fix this; refuse instead.
A %RSYNC_*% value substituted into a shell-executed hook (early exec,
name converter, pre-/post-xfer exec) is now rejected if it holds any
character that can become shell syntax in any context: quote, backtick,
dollar, backslash, semicolon, ampersand, pipe, redirection, parenthesis,
or a control character. Word-splitting and glob characters are left
alone -- they cannot execute anything and paths legitimately contain
them. The refusal is fail-closed and logged: a hook may be an access
check, so silently skipping it is not an option.
Also fix the quote tracker itself, which moved to SHELL_SINGLE_QUOTED on
an apostrophe even inside "...", where it is an ordinary character. That
made a value in `printf %s "it's %RSYNC_USER_NAME%"` escape for the wrong
context. With the refusal above this is defence in depth, and it matters
if the refused set is ever narrowed.
The two existing hook-injection tests asserted that a metacharacter value
was quoted and the transfer still succeeded; both now expect the refusal.
authenticate.c: seed gen_challenge() from /dev/urandom, add an "auth digest" floor
to refuse weak negotiated digests, and fstat the opened --password-file fd rather
than re-stat the pathname; checksum.c carries auth_digest_rank(). socket.c: reject
control bytes in the daemon host before a proxy CONNECT and bind the stunnel server
cert to the requested hostname. clientserver/access: warn when proxy-protocol
fail-closes. loadparm + daemon-parm: only shell-quote %RSYNC_*% for shell-executed
hooks, and add the auth-digest / proxy-protocol-hosts module parameters.
Glibc 2.43 added C23 const-preserving overloads to various string functions,
which change the return type depending on the constness of the argument(s).
Currently this leads to warnings from calls to strtok() or strchr().
Fix this by properly declaring the respective variable types.
Signed-off-by: Holger Hoffstätte <holger@applied-asynchrony.com>
- Use -pedantic-errors with gcc to make an array-init fatal.
- Fix all the extra warnings that gcc outputs due to this option.
- Also add -Wno-pedantic to gcc if we're using the internal popt
code (since it has lots of pedantic issues).
The code now derives all the struct defines, default value assignments,
parser-param defines, and lp_foo() accessor functions from a single list
of daemon parameters.
- All the memory-allocation macros now auto-check for failure and exit
with a failure message that incudes the caller's file and lineno
info. This includes strdup().
- Added the `--max-alloc=SIZE` option to be able to override the memory
allocator's sanity-check limit. It defaults to 1G (as before).
Fixes bugzilla bug 12769.
- Change default_cvsignore char[] into a define.
- Make the DEFAULT_DONT_COMPRESS and DEFAULT_CVSIGNORE defines get set
based on their info in rsync.1.md.
- Add a few more don't-compress suffixes from Simon Matter.
I replaced git-set-file-times with an improved version that I wrote
recently (in python3). A new script uses it to figure out the
last-modified year for each *.[ch] file and updates its copyright.
It also puts the latest year into the latest-year.h file for the
output of --version.
The default value of the skip-compress list actually comes from the
daemon's default lp_dont_compress() value, but a while back the vars
stopped getting default values in a non-daemon run. I added a call to
reset_daemon_vars() so that the "Vars" values get set from "Defaults".
This allows the daemon to run chrooted as any uid+gid you like
(prior to the transfer possibly changing the chroot and/or the
uid+gid further). Based on the patch in #12817.
Initialize both the Globals and Locals back to their default values
when reading the config. This fixes a bug where locals set in the
global section were not getting reset to their default value if the
config item was removed from the file.