Skip to content

Commit 7ef165d

Browse files
committed
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.
1 parent 4a751a2 commit 7ef165d

4 files changed

Lines changed: 175 additions & 74 deletions

File tree

configure.ac

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ AC_INIT([rsync],[ ],[https://rsync.samba.org/bug-tracking.html])
55
AC_C_BIGENDIAN
66
AC_HEADER_DIRENT
77
AC_HEADER_SYS_WAIT
8-
AC_CHECK_HEADERS(sys/fcntl.h sys/select.h fcntl.h sys/time.h sys/unistd.h \
8+
AC_CHECK_HEADERS(poll.h sys/fcntl.h sys/select.h fcntl.h sys/time.h sys/unistd.h \
99
unistd.h utime.h compat.h sys/param.h ctype.h sys/wait.h sys/stat.h \
1010
sys/ioctl.h sys/filio.h string.h stdlib.h sys/socket.h sys/mode.h grp.h \
1111
sys/un.h sys/attr.h arpa/inet.h arpa/nameser.h locale.h sys/types.h \
@@ -909,6 +909,12 @@ AC_HAVE_TYPE([struct stat64], [#include <stdio.h>
909909

910910
# if we can't find strcasecmp, look in -lresolv (for Unixware at least)
911911
#
912+
dnl rsync's I/O readiness loops use poll() rather than select() so that a
913+
dnl file descriptor at or above FD_SETSIZE cannot overflow an fd_set (which
914+
dnl is undefined behaviour and could hang the transfer). poll() is in
915+
dnl POSIX.1-2001; fail early and clearly if this target lacks it.
916+
AC_CHECK_FUNCS([poll], , [AC_MSG_ERROR([rsync requires poll(); please report the platform to the rsync developers])])
917+
912918
AC_CHECK_FUNCS(strcasecmp)
913919
if test x"$ac_cv_func_strcasecmp" = x"no"; then
914920
AC_CHECK_LIB(resolv, strcasecmp)

io.c

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@
3333

3434
#include <poll.h>
3535

36+
/* Readiness bits we act on. poll() can report POLLERR/POLLHUP/POLLNVAL even
37+
* when they were not requested, and POLLPRI stands in for select()'s old
38+
* exception set. */
39+
#define POLL_RD_BITS (POLLIN | POLLPRI | POLLERR | POLLHUP)
40+
#define POLL_WR_BITS (POLLOUT | POLLERR | POLLHUP)
41+
3642
/** If no timeout is specified then use a 60 second I/O timeout */
3743
#define SELECT_TIMEOUT 60
3844

@@ -115,6 +121,19 @@ static xbuf ff_xb = EMPTY_XBUF;
115121
static xbuf iconv_buf = EMPTY_XBUF;
116122
#endif
117123
static int select_timeout = SELECT_TIMEOUT;
124+
125+
/* Turn select_timeout (in seconds) into a poll() millisecond count, keeping it
126+
* positive and bounded. A negative count means "wait forever" to poll(), which
127+
* would bypass our keepalives and timeout enforcement entirely. */
128+
static int poll_timeout_ms(void)
129+
{
130+
int secs = select_timeout;
131+
132+
if (secs <= 0 || secs > SELECT_TIMEOUT)
133+
secs = SELECT_TIMEOUT;
134+
return secs * 1000;
135+
}
136+
118137
static int active_filecnt = 0;
119138
static OFF_T active_bytecnt = 0;
120139
static int first_message = 1;
@@ -251,20 +270,26 @@ static size_t safe_read(int fd, char *buf, size_t len)
251270
/* We use poll() rather than select() so that a high-numbered fd
252271
* (>= FD_SETSIZE) cannot overflow an fd_set bitmap. */
253272
pfd.fd = fd;
254-
pfd.events = POLLIN;
273+
pfd.events = POLLIN | POLLPRI;
255274
pfd.revents = 0;
256275

257-
cnt = poll(&pfd, 1, select_timeout * 1000);
276+
cnt = poll(&pfd, 1, poll_timeout_ms());
258277
if (cnt <= 0) {
259-
if (cnt < 0 && errno == EBADF) {
278+
if (cnt < 0 && errno != EINTR && errno != EAGAIN) {
260279
rsyserr(FERROR, errno, "safe_read poll failed");
261280
exit_cleanup(RERR_FILEIO);
262281
}
263282
check_timeout(1, MSK_ALLOW_FLUSH);
264283
continue;
265284
}
266285

267-
if (pfd.revents) {
286+
/* An invalid fd is reported here rather than via poll()'s return. */
287+
if (pfd.revents & POLLNVAL) {
288+
rsyserr(FERROR, EBADF, "safe_read poll failed");
289+
exit_cleanup(RERR_FILEIO);
290+
}
291+
292+
if (pfd.revents & POLL_RD_BITS) {
268293
ssize_t n = read(fd, buf + got, len - got);
269294
if (DEBUG_GTE(IO, 2)) {
270295
rprintf(FINFO, "[%s] safe_read(%d)=%" SIZE_T_FMT_MOD "d\n",
@@ -337,9 +362,9 @@ static void safe_write(int fd, const char *buf, size_t len)
337362
pfd.events = POLLOUT;
338363
pfd.revents = 0;
339364

340-
cnt = poll(&pfd, 1, select_timeout * 1000);
365+
cnt = poll(&pfd, 1, poll_timeout_ms());
341366
if (cnt <= 0) {
342-
if (cnt < 0 && errno == EBADF) {
367+
if (cnt < 0 && errno != EINTR && errno != EAGAIN) {
343368
rsyserr(FERROR, errno, "safe_write poll failed on %s", what_fd_is(fd));
344369
exit_cleanup(RERR_FILEIO);
345370
}
@@ -348,7 +373,12 @@ static void safe_write(int fd, const char *buf, size_t len)
348373
continue;
349374
}
350375

351-
if (pfd.revents) {
376+
if (pfd.revents & POLLNVAL) {
377+
rsyserr(FERROR, EBADF, "safe_write poll failed on %s", what_fd_is(fd));
378+
exit_cleanup(RERR_FILEIO);
379+
}
380+
381+
if (pfd.revents & POLL_WR_BITS) {
352382
n = write(fd, buf, len);
353383
if (n < 0) {
354384
if (errno == EINTR)
@@ -657,7 +687,7 @@ static char *perform_io(size_t needed, int flags)
657687
if (iobuf.in_fd >= 0 && iobuf.in.size - iobuf.in.len) {
658688
if (!read_batch || batch_fd >= 0) {
659689
pfds[npfds].fd = iobuf.in_fd;
660-
pfds[npfds].events = POLLIN;
690+
pfds[npfds].events = POLLIN | POLLPRI;
661691
pfds[npfds].revents = 0;
662692
in_pollpos = npfds++;
663693
}
@@ -714,10 +744,18 @@ static char *perform_io(size_t needed, int flags)
714744
} else
715745
out = NULL;
716746
if (out) {
717-
pfds[npfds].fd = iobuf.out_fd;
718-
pfds[npfds].events = POLLOUT;
719-
pfds[npfds].revents = 0;
720-
out_pollpos = npfds++;
747+
/* A direct daemon connection uses one fd for both
748+
* directions; give it a single row with both events
749+
* rather than two rows carrying different masks. */
750+
if (in_pollpos >= 0 && iobuf.out_fd == iobuf.in_fd) {
751+
pfds[in_pollpos].events |= POLLOUT;
752+
out_pollpos = in_pollpos;
753+
} else {
754+
pfds[npfds].fd = iobuf.out_fd;
755+
pfds[npfds].events = POLLOUT;
756+
pfds[npfds].revents = 0;
757+
out_pollpos = npfds++;
758+
}
721759
if (iobuf.out_fd > max_fd)
722760
max_fd = iobuf.out_fd;
723761
}
@@ -757,10 +795,10 @@ static char *perform_io(size_t needed, int flags)
757795
poll_timeout = 0;
758796
else {
759797
extra_flist_sending_enabled = False;
760-
poll_timeout = select_timeout * 1000;
798+
poll_timeout = poll_timeout_ms();
761799
}
762800
} else
763-
poll_timeout = select_timeout * 1000;
801+
poll_timeout = poll_timeout_ms();
764802

765803
cnt = poll(pfds, npfds, poll_timeout);
766804

@@ -784,7 +822,20 @@ static char *perform_io(size_t needed, int flags)
784822
pfds[out_pollpos].revents = 0;
785823
}
786824

787-
if (iobuf.in_fd >= 0 && in_pollpos >= 0 && pfds[in_pollpos].revents) {
825+
if (cnt > 0) {
826+
/* poll() reports a bad fd here, not via its return value. */
827+
int p;
828+
for (p = 0; p < npfds; p++) {
829+
if (pfds[p].revents & POLLNVAL) {
830+
msgs2stderr = 1;
831+
rsyserr(FERROR, EBADF, "perform_io: poll reported an invalid fd");
832+
exit_cleanup(RERR_SOCKETIO);
833+
}
834+
}
835+
}
836+
837+
if (iobuf.in_fd >= 0 && in_pollpos >= 0
838+
&& pfds[in_pollpos].revents & POLL_RD_BITS) {
788839
size_t len, pos = iobuf.in.pos + iobuf.in.len;
789840
ssize_t n;
790841
if (pos >= iobuf.in.size) {
@@ -833,7 +884,7 @@ static char *perform_io(size_t needed, int flags)
833884
exit_cleanup(RERR_TIMEOUT);
834885
}
835886

836-
if (out && out_pollpos >= 0 && pfds[out_pollpos].revents) {
887+
if (out && out_pollpos >= 0 && pfds[out_pollpos].revents & POLL_WR_BITS) {
837888
size_t len = iobuf.raw_flushing_ends_before ? iobuf.raw_flushing_ends_before - out->pos : out->len;
838889
ssize_t n;
839890

@@ -894,7 +945,8 @@ static char *perform_io(size_t needed, int flags)
894945
wait_for_receiver(); /* generator only */
895946
}
896947

897-
if (ff_forward_fd >= 0 && ff_pollpos >= 0 && pfds[ff_pollpos].revents) {
948+
if (ff_forward_fd >= 0 && ff_pollpos >= 0
949+
&& pfds[ff_pollpos].revents & POLL_RD_BITS) {
898950
/* This can potentially flush all output and enable
899951
* multiplexed output, so keep this last in the loop
900952
* and be sure to not cache anything that would break
@@ -1153,8 +1205,11 @@ void io_set_sock_fds(int f_in, int f_out)
11531205

11541206
void set_io_timeout(int secs)
11551207
{
1208+
if (secs < 0)
1209+
secs = 0;
11561210
io_timeout = secs;
1157-
allowed_lull = (io_timeout + 1) / 2;
1211+
/* Round up without overflowing when secs is near INT_MAX. */
1212+
allowed_lull = secs / 2 + secs % 2;
11581213

11591214
if (!io_timeout || allowed_lull > SELECT_TIMEOUT)
11601215
select_timeout = SELECT_TIMEOUT;
@@ -1559,7 +1614,10 @@ static void read_a_msg(void)
15591614
goto invalid_msg;
15601615
val = raw_read_int();
15611616
iobuf.in_multiplexed = 1;
1562-
if (!io_timeout || io_timeout > val) {
1617+
/* Ignore a non-positive value: it would otherwise disable our
1618+
* timeout entirely (and a negative one used to be rejected by
1619+
* select(), but would make poll() wait forever). */
1620+
if (val > 0 && (!io_timeout || io_timeout > val)) {
15631621
if (INFO_GTE(MISC, 2))
15641622
rprintf(FINFO, "Setting --timeout=%d to match server\n", val);
15651623
set_io_timeout(val);

socket.c

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
* emulate it using the KAME implementation. */
2626

2727
#include "rsync.h"
28+
#include <poll.h>
2829
#include "itypes.h"
2930
#include "ifuncs.h"
3031
#ifdef HAVE_NETINET_IN_SYSTM_H
@@ -536,8 +537,8 @@ static void sigchld_handler(UNUSED(int val))
536537

537538
void start_accept_loop(int port, int (*fn)(int, int))
538539
{
539-
fd_set deffds;
540-
int *sp, maxfd, i;
540+
struct pollfd *pfds;
541+
int *sp, nsp, i;
541542

542543
#ifdef HAVE_SIGACTION
543544
sigact.sa_flags = SA_NOCLDSTOP;
@@ -549,8 +550,12 @@ void start_accept_loop(int port, int (*fn)(int, int))
549550
exit_cleanup(RERR_SOCKETIO);
550551

551552
/* ready to listen */
552-
FD_ZERO(&deffds);
553-
for (i = 0, maxfd = -1; sp[i] >= 0; i++) {
553+
for (nsp = 0; sp[nsp] >= 0; nsp++) {}
554+
/* poll() rather than select(): a listening fd at or above FD_SETSIZE
555+
* would overflow an fd_set, which is undefined behaviour (issue #231). */
556+
if (!(pfds = new_array(struct pollfd, nsp ? nsp : 1)))
557+
out_of_memory("start_accept_loop");
558+
for (i = 0; sp[i] >= 0; i++) {
554559
if (listen(sp[i], lp_listen_backlog()) < 0) {
555560
rsyserr(FERROR, errno, "listen() on socket failed");
556561
#ifdef INET6
@@ -560,36 +565,32 @@ void start_accept_loop(int port, int (*fn)(int, int))
560565
#endif
561566
exit_cleanup(RERR_SOCKETIO);
562567
}
563-
FD_SET(sp[i], &deffds);
564-
if (maxfd < sp[i])
565-
maxfd = sp[i];
568+
pfds[i].fd = sp[i];
569+
pfds[i].events = POLLIN;
570+
pfds[i].revents = 0;
566571
}
567572

568573
/* now accept incoming connections - forking a new process
569574
* for each incoming connection */
570575
while (1) {
571-
fd_set fds;
572576
pid_t pid;
573577
int fd;
574578
struct sockaddr_storage addr;
575579
socklen_t addrlen = sizeof addr;
576580

577-
/* close log file before the potentially very long select so
581+
/* close log file before the potentially very long wait so the
578582
* file can be trimmed by another process instead of growing
579583
* forever */
580584
logfile_close();
581585

582-
#ifdef FD_COPY
583-
FD_COPY(&deffds, &fds);
584-
#else
585-
fds = deffds;
586-
#endif
586+
for (i = 0; i < nsp; i++)
587+
pfds[i].revents = 0;
587588

588-
if (select(maxfd + 1, &fds, NULL, NULL, NULL) < 1)
589+
if (poll(pfds, nsp, -1) < 1)
589590
continue;
590591

591-
for (i = 0, fd = -1; sp[i] >= 0; i++) {
592-
if (FD_ISSET(sp[i], &fds)) {
592+
for (i = 0, fd = -1; i < nsp; i++) {
593+
if (pfds[i].revents & (POLLIN | POLLERR | POLLHUP)) {
593594
fd = accept(sp[i], (struct sockaddr *)&addr, &addrlen);
594595
break;
595596
}

0 commit comments

Comments
 (0)