Commit Graph
169 Commits
Author SHA1 Message Date
Stuart Inglis bf3a11cf24 io/socket: address review of the poll() conversion
Follow-up to the FD_SETSIZE fix, covering the points raised in review.

Negative/overflowing I/O timeouts.  set_io_timeout() could produce a negative
select_timeout (a peer-supplied MSG_IO_TIMEOUT value was applied unchecked),
and every wait now passes select_timeout * 1000 to poll(), where a negative
millisecond count means "wait forever" -- so a hostile or buggy peer could
stall the other side and bypass keepalives entirely.  select() used to reject
that with EINVAL, which kept the loop and check_timeout() running.  Clamp a
negative argument to 0, compute allowed_lull without overflowing near INT_MAX
(secs / 2 + secs % 2), ignore a non-positive MSG_IO_TIMEOUT value, and funnel
all three waits through poll_timeout_ms(), which keeps the count positive and
bounded.

The daemon accept loop had the same fd_set overflow.  start_accept_loop() still
stored listening sockets in an fd_set, so a daemon started with enough
descriptors already open got listener fds >= FD_SETSIZE and hit the same
undefined behaviour at startup -- verified: with the old code a transfer
through such a daemon yields nothing, with this change it succeeds.  Converted
it to poll() as well.

Readiness testing.  Treating any non-zero revents as ordinary readiness was
wrong: poll() reports POLLERR/POLLHUP/POLLNVAL unrequested, and an invalid fd
shows up as POLLNVAL on a successful poll() rather than -1/EBADF, which left
the EBADF branches dead and let an invalid ff_forward_fd reach
forward_filesfrom_data() (where EBADF reads as EOF).  Use role-specific masks
(POLL_RD_BITS / POLL_WR_BITS), handle POLLNVAL explicitly in all three loops,
and request POLLPRI so select()'s old exception set is not silently dropped.

A bidirectional fd is no longer entered twice.  A direct daemon connection uses
one fd for both directions; it now occupies a single pollfd row with OR-ed
events instead of two rows carrying different masks, which also avoids the
Cygwin < 3.3.6 duplicate-entry readiness bug.

poll() is now a declared requirement: configure.ac checks for poll.h and
poll(), failing with a clear message rather than leaving it implicit.

The test no longer hardcodes FD_SETSIZE (1024 on glibc but 65536 on 64-bit
Solaris, where it would have opened too few fds and passed vacuously); it asks
the C library for the real value via a small compiled probe and skips if that
is unavailable.  Its description now also covers the fortified-libc case, where
the pre-fix result is an abort rather than a hang.

(cherry picked from commit 7ef165dd45)
2026-08-01 20:02:02 +10:00
Andrew Tridgell dcc3c9c51a socket: refuse a meaning-changing first character, and stop refusing aliases
The %H allow-list was wrong in both directions.

Several accepted characters change an argument's MEANING rather than its
text when they lead the value, which quoting cannot prevent because the
word stays intact -- that IS the problem:

  '-' and '+' introduce options to plenty of programs; with
      RSYNC_CONNECT_PROG="prog %H", hosts "-c" and "+x" arrive as
      options, and "sh +x" is as real as "sh -x";
  '~' is tilde-expanded by the nested shell, turning ~root into /root;
  '%' is expanded by a nested fish, where %self becomes its pid.

An empty host has the same shape from the other end: it survives a direct
exec as an empty argument but disappears when a nested shell re-splits the
command, shifting everything after it.  rsync://:873/m/ and ::m/ both
produce one.  None of these can begin a real hostname, so refuse them in
first position only -- mid-word each is literal, which matters because an
IPv6 zone id carries its '%' mid-word.

The other direction: '+' and '~' were refused outright.  They execute
nothing, and RSYNC_CONNECT_PROG exists for custom transports where %H is
often an alias the program resolves itself rather than a name the
resolver sees.  Refusing them mid-word breaks that use case for no gain.

A non-ASCII host stays refused.  That is a policy choice rather than a
free one: a custom connect program never calls getaddrinfo, so a Unicode
alias would otherwise work, and this does exclude it.  A punycode A-label
is unaffected.

What this cannot do is bound what the named program makes of the value.
"host:-rf" arrives intact, and a program that splits on ':' may
reinterpret the tail; that boundary belongs to whoever writes the
command.

The test asserted only that a marker file was absent -- equally true when
rsync failed to parse its arguments, when socketpair_tcp is blocked, or
when touch was missing.  Worse, the marker path was absolute, and a URL
authority ends at the first '/', so the injected `touch` never received
an operand and the check could not fail even with the guard gone.  It now
runs with cwd set to the scratch directory and injects a bare name, so
the marker is genuinely reachable; requires the specific refusal message;
checks the exit status; and checks that ordinary hosts still arrive at
the connect program with their text intact, which is the part an
absence-only test can never show.

Fault-injected separately: dropping the guard, dropping just the
first-character check, and narrowing the set back each fail the test on
their own, in both the default and --use-tcp transports.
2026-08-01 10:00:05 +10:00
Filipe Casal 78e10e7e32 socket: reject shell-active connect hosts 2026-08-01 10:00:05 +10:00
Andrew TridgellandGreg Kroah-Hartman 0abb4ae6cb testsuite: code-scanner coverage and regression tests + gcov infra
Adds the coverage/regression tests from the code-scanner run and the
gcov plumbing they rely on:

  - scanner-argv-bounds, scanner-batch-flag-mismatch,
    scanner-delete-delay-overread, scanner-daemon-log-checksum:
    regression tests for the argv/-v/--info/--skip-compress bounds, the
    batch metadata-ndx corruption, the read_delay_line off-by-one, and
    the daemon -c/%C checksum-slot leak.
  - daemon-proxy-protocol, daemon-early-exec-nameconv, daemon-auth-group,
    daemon-standalone-detach, misc-coverage, nonroot-restrictive-perms,
    backup-acl-xattr-cache: daemon and path coverage tests.
  - rsyncfns.py: CAP_MKNOD probe in devices_supported().
  - gcov_flush() macro (rsync.h) + calls in the daemon fork/_exit paths
    (clientserver.c, socket.c); no-op without --enable-coverage. Makefile.in
    COVERAGE_EXCLUDE / gcovr / setuid .gcda refinements.
  - CI: list the new TCP/root/ACL tests in the per-platform
    RSYNC_EXPECT_SKIPPED sets.

Co-authored-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2026-07-20 14:05:31 +10:00
Andrew Tridgell d199b43149 daemon: harden authentication, access, socket and config-hook handling
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.
2026-07-20 14:05:31 +10:00
Andrew Tridgell 412cddf6be scan-build: zero-init buffers the analyzer can't prove are written
clang's static analyzer doesn't model SIVAL/SIVAL64/SIVALu or
getpeername/getsockname as initializing their target bytes, so it
reports false "garbage value" reads. Zero-init the affected buffers;
the bytes are always overwritten at runtime, so this only quiets the
analyzer.

io.c:     write_varint/write_varlong b[]
hashtable.c: hash_search buf[]
socket.c: accepted_peer/our_local
2026-06-16 08:55:39 +10:00
Andrew TridgellandClaude Opus 4.7 951bf0a446 socket: enforce socketpair_tcp()'s anti-hijack guarantee
socketpair_tcp() fakes a connected socket pair via a loopback TCP
self-connect (socket -> bind 127.0.0.1:0 -> listen -> connect ->
accept), used by sock_exec() for RSYNC_CONNECT_PROG. Its comment has
long promised that "nobody else can attach to the socket, or if they
do that this function fails", but nothing actually verified it: the
code accept()ed whatever connection arrived first without checking it
was the one our own connect() made.

Between listen() and accept() the ephemeral loopback port is
connectable by any local user. With backlog 1 a same-host attacker who
races a connection in before our connect() lands could have their
socket returned by accept(), handing them one end of the rsync
protocol stream. The exposure is small (loopback only, random
ephemeral port, sub-millisecond window, local users only), but the
promised guarantee was simply not enforced.

Enforce it: after the connection is established, require that the peer
address of the accepted end (fd[0]) equals the local address of our
connecting end (fd[1]), and that both are 127.0.0.1. A hijacked
connection has a different source port and is rejected (errno EPERM,
fail closed). The legitimate self-connect always matches, so there is
no behaviour change for the normal path.

Verified: rebuilds clean with -Wall -W; the full testsuite still
passes in both transports (pipe `make check` 57/3, `runtests.py
--use-tcp` 59/1) -- the pipe transport exercises this code path on
every daemon test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 14:34:52 +10:00
Andrew Tridgell a5fc5ebe7a socket: reject over-long proxy response line
fixes a one byte stack overflow when using RSYNC_PROXY with a
malicious proxy.

Reach: only when RSYNC_PROXY is set and a malicious or MITM'd
proxy returns the pathological response.  The byte written is
always '\0' and the attacker doesn't choose the offset, so impact
is corruption of one adjacent stack byte and possible later
misbehaviour or crash -- no information disclosure beyond the
existing rprintf of buffer contents.

Reported by Aisle Research via Michal Ruprich
2026-05-20 10:01:22 +10:00
Wayne Davison 11eb67eec9 Some memory allocation improvements
- 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.
2020-06-25 20:54:21 -07:00
Wayne Davison e63ff70eae Some indentation fixes. 2020-06-13 19:15:02 -07:00
Wayne Davison b177311aee Use a lock to not fail on a left-over pid file. 2020-06-04 19:08:03 -07:00
Wayne Davison f5446552f3 Silence gcc7.1 warnings about snprintf(). 2020-05-29 14:18:08 -07:00
Wayne Davison 3ba4db7030 Two more spelling fixes and some year updates. 2020-04-16 09:31:02 -07:00
Wayne Davison 3e2e4b5a33 Tweak the copyright year. 2019-03-16 09:15:49 -07:00
Wayne Davison 5df9847f06 Allow some pre-/post-xfer exec shell restrictions.
Support both RSYNC_SHELL & RSYNC_NO_XFER_EXEC environment variables.
2018-03-25 11:02:50 -07:00
Wayne Davison 473108ae6e Tweak copyright date. 2018-01-14 19:55:07 -08:00
Wayne Davison 453914e35b Update the copyright year. 2015-08-08 12:47:03 -07:00
Wayne Davison 23afe20780 Brant Gurganus's autoconf updates.
This improves some obsolete autoconf macros and increases the minimum
autoconf version from 2.60 to 2.69.  Fixes bug 11369.
2015-07-07 10:37:12 -07:00
Wayne Davison dfa5b49110 Bump the year to 2014. 2014-01-26 09:29:15 -08:00
Wayne Davison 7e1a9c4d79 Update copyright year. 2013-01-19 11:05:53 -08:00
Wayne Davison fb0d4403f0 Fix misplaced parens on getnameinfo() call. 2011-08-06 11:21:40 -07:00
Wayne Davison 0a77adee0b Fix Minix build errors. Fixes bug 8313. 2011-07-22 11:17:57 -07:00
Wayne Davison 0a04a80d9f Replace another inet_ntop() call with getnameinfo(). 2011-07-16 16:16:04 -07:00
Wayne Davison 7ae666d2a7 Add more connect debug info, as Carlos suggested. 2011-07-12 16:02:31 -07:00
Wayne Davison fbf4c261f4 Move freeaddrinfo() call after failure-reporting loop. 2011-07-11 18:15:51 -07:00
Wayne Davison 050e5334d8 Added "listen backlog" daemon config paramater. 2011-01-03 19:42:27 -08:00
Wayne Davison 1c99b1d956 Report all socket connection errors if we fail.
Fixes bug 6588.
2011-01-01 14:00:40 -08:00
Wayne Davison 292a5c2b72 Fix a couple socketpair_tcp() issues (see bug 7514). 2010-06-19 09:39:55 -07:00
Matt McCutchen bb499bd7a0 Handle simultaneous arrival of multiple connections. 2009-01-13 09:42:54 -08:00
Wayne Davison b3bf9b9df9 Update the copyright year. 2009-01-03 10:57:14 -08:00
Wayne Davison 9411292489 Fixed a bunch of "warn_unused_result" compiler warnings. 2008-11-09 18:56:21 -08:00
Wayne Davison 5dd14f0c33 Split up the ifuncs.h file into 3 .h files. 2008-09-01 19:11:36 -07:00
Wayne Davison 951e826b75 Added the --info=FLAGS an --debug=FLAGS options, which allows
fine-grained output control (in addition to the coarse -v).
2008-07-13 20:51:08 -07:00
Wayne Davison 1c3e6e8b26 Moved the setting of the socket options before the connect(). 2008-04-15 08:34:17 -07:00
Wayne Davison d3d07a5e86 Include 2008 in the copyright years. 2008-03-01 12:01:41 -08:00
Wayne Davison ba22c9e219 Adding --contimeout=SECONDS option. 2007-11-28 00:28:26 -08:00
Wayne Davison 5621e5105a Moved become_daemon() into clientserver.c. 2007-09-30 06:57:12 +00:00
Wayne Davison 1b42f628f4 Moved inline functions from rsync.h into ifuncs.h. 2007-09-22 16:52:58 +00:00
Wayne Davison 4eff3051a0 Fixed a null-pointer dereference pointed out by Olivier Thauvin. 2007-08-16 01:22:46 +00:00
Wayne Davison e5f1a96ff8 Added support for a %H escape in the value of the RSYNC_CONNECT_PROG
environment variable.
2007-08-11 16:20:56 +00:00
Wayne Davison 8e41b68e8f Tweaking the license text a bit more. 2007-07-10 13:55:49 +00:00
Wayne Davison 4fd842f98d Switching to GPL 3. 2007-07-07 05:33:14 +00:00
Wayne Davison 332cf6df7c The --iconv option has now made it to the trunk. 2007-04-26 05:53:13 +00:00
Wayne Davison ba2133d6ad Further modifications to the copyright comment section. 2007-02-04 14:54:58 +00:00
Wayne Davison 2dc7b8bd0e Got rid of type-casting into isFOO() and toFOO() functions by
using static inline functions that take a signed char pointer.
2006-11-09 02:39:29 +00:00
Wayne Davison 0f5c1c193a Need to include netinet/tcp.h to get TCP_NODELAY defined. 2006-10-31 19:21:57 +00:00
Wayne Davison e7c67065c0 Updated the FSF's address to an even newer one. 2006-04-25 23:51:12 +00:00
Wayne Davison 0f78b81511 - Updated the address for the FSF in the opening comment.
- Standardized the format of the opening comment, including adding a
  brief description of what's in the file for those that lacked it.
- Added some missing copyright lines.
- Some minor whitespace tweaks (in a few of the files).
2006-04-25 20:23:34 +00:00
Wayne Davison 5899b8cf3e Moved the include of netinet/ip.h here along with its apparent
prerequisite, netinet/in_systm.h .
2006-04-11 00:48:28 +00:00
Wayne Davison e2d774cdd7 Improved the bind()-failure message to mention what family failed. 2006-04-02 21:15:37 +00:00