UDP-RECVFROM with fork sometimes terminated - handle EINTR on recvmsg()

This commit is contained in:
Gerhard Rieger 2022-08-13 12:04:38 +02:00
parent 9502d092a2
commit 45d87df2fd
3 changed files with 40 additions and 25 deletions

View file

@ -4,6 +4,11 @@ Corrections:
terminating \0 Byte was written behind the last position. terminating \0 Byte was written behind the last position.
Thanks to Martin Liška for sending the address sanitizer report. Thanks to Martin Liška for sending the address sanitizer report.
UDP-RECVFROM with fork sometimes terminated when multiple packets
arrived. This issue was introduced with a bug fix in version 1.7.4.0.
Reason was not handling EAGAIN on recvmsg().
Thanks to Jamie McQuillan for reporting this issue.
Porting: Porting:
OpenSSL, at least 1.1 on Ubuntu, crashed with SIGSEGV under certain OpenSSL, at least 1.1 on Ubuntu, crashed with SIGSEGV under certain
conditions: client connection to server with certificate with empty conditions: client connection to server with certificate with empty

View file

@ -693,6 +693,7 @@ int xioopen_socket_datagram(int argc, const char *argv[], struct opt *opts,
#endif /* WITH_GENERICSOCKET */ #endif /* WITH_GENERICSOCKET */
/* EINTR not handled specially */
int xiogetpacketinfo(int fd) int xiogetpacketinfo(int fd)
{ {
#if defined(MSG_ERRQUEUE) #if defined(MSG_ERRQUEUE)
@ -1245,6 +1246,7 @@ void xiosigaction_hasread(int signum
return; return;
} }
if (pid == xio_waitingfor) { if (pid == xio_waitingfor) {
xio_waitingfor = 0; /* so this child will not set hashappened again */
xio_hashappened = true; xio_hashappened = true;
xio_childstatus = WEXITSTATUS(status); xio_childstatus = WEXITSTATUS(status);
Debug("xiosigaction_hasread() ->"); Debug("xiosigaction_hasread() ->");
@ -1284,6 +1286,7 @@ void xiosigaction_hasread(int signum
PH_INIT, PH_PREBIND, PH_BIND, PH_PASTBIND, PH_EARLY, PH_PREOPEN, PH_FD, PH_INIT, PH_PREBIND, PH_BIND, PH_PASTBIND, PH_EARLY, PH_PREOPEN, PH_FD,
PH_CONNECTED, PH_LATE, PH_LATE2 PH_CONNECTED, PH_LATE, PH_LATE2
OPT_FORK, OPT_SO_TYPE, OPT_SO_PROTOTYPE, cloexec, OPT_RANGE, tcpwrap OPT_FORK, OPT_SO_TYPE, OPT_SO_PROTOTYPE, cloexec, OPT_RANGE, tcpwrap
EINTR is not handled specially.
*/ */
int _xioopen_dgram_recvfrom(struct single *xfd, int xioflags, int _xioopen_dgram_recvfrom(struct single *xfd, int xioflags,
struct sockaddr *us, socklen_t uslen, struct sockaddr *us, socklen_t uslen,
@ -1413,6 +1416,7 @@ int _xioopen_dgram_recvfrom(struct single *xfd, int xioflags,
socklen_t palen = sizeof(_peername); /* peer address size */ socklen_t palen = sizeof(_peername); /* peer address size */
char ctrlbuff[1024]; /* ancillary messages */ char ctrlbuff[1024]; /* ancillary messages */
struct msghdr msgh = {0}; struct msghdr msgh = {0};
int rc;
socket_init(pf, pa); socket_init(pf, pa);
@ -1423,6 +1427,7 @@ int _xioopen_dgram_recvfrom(struct single *xfd, int xioflags,
drop = true; drop = true;
} }
Info("Recvfrom: Checking/waiting for next packet");
/* loop until select()/poll() returns valid */ /* loop until select()/poll() returns valid */
do { do {
struct pollfd readfd; struct pollfd readfd;
@ -1455,15 +1460,15 @@ int _xioopen_dgram_recvfrom(struct single *xfd, int xioflags,
#if HAVE_STRUCT_MSGHDR_MSGCONTROLLEN #if HAVE_STRUCT_MSGHDR_MSGCONTROLLEN
msgh.msg_controllen = sizeof(ctrlbuff); msgh.msg_controllen = sizeof(ctrlbuff);
#endif #endif
if (xiogetpacketsrc(xfd->fd, while ((rc = xiogetpacketsrc(xfd->fd,
&msgh, &msgh,
MSG_PEEK MSG_PEEK
#ifdef MSG_TRUNC #ifdef MSG_TRUNC
|MSG_TRUNC |MSG_TRUNC
#endif #endif
) < 0) { )) < 0 &&
return STAT_RETRYLATER; errno == EINTR) ;
} if (rc < 0) return STAT_RETRYLATER;
palen = msgh.msg_namelen; palen = msgh.msg_namelen;
Notice1("receiving packet from %s"/*"src"*/, Notice1("receiving packet from %s"/*"src"*/,
@ -1534,24 +1539,21 @@ int _xioopen_dgram_recvfrom(struct single *xfd, int xioflags,
break; break;
} }
/* server: continue loop with listen */
xio_waitingfor = pid; xio_waitingfor = pid;
do {
#if HAVE_PSELECT #if HAVE_PSELECT
{ {
struct timespec timeout = { LONG_MAX, 0 }; struct timespec timeout = { LONG_MAX, 0 };
Pselect(0, NULL, NULL, NULL, &timeout, &oldset); Pselect(0, NULL, NULL, NULL, &timeout, &oldset);
Sigprocmask(SIG_SETMASK, &oldset, NULL); Sigprocmask(SIG_SETMASK, &oldset, NULL);
} }
#else /* ! HAVE_PSELECT */ #else /* ! HAVE_PSELECT */
/* now we are ready to handle signals */ /* now we are ready to handle signals */
Sigprocmask(SIG_SETMASK, &oldset, NULL); Sigprocmask(SIG_SETMASK, &oldset, NULL);
Sleep(1); /* any signal speeds up return */
while (!xio_hashappened) {
Sleep(1); /* any signal speeds up return */
}
#endif /* ! HAVE_PSELECT */ #endif /* ! HAVE_PSELECT */
xio_waitingfor = 0; /* so this child will not set hashappened again */ } while (!xio_hashappened) ;
xio_hashappened = false; xio_hashappened = false;
if (xio_childstatus != 0) { if (xio_childstatus != 0) {
@ -1676,9 +1678,12 @@ int retropt_socket_pf(struct opt *opts, int *pf) {
} }
/* this function calls recvmsg(..., MSG_PEEK, ...) to obtain information about /* This function calls recvmsg(..., MSG_PEEK, ...) to obtain information about
the arriving packet. in msgh the msg_name pointer must refer to an (empty) the arriving packet, thus it does not "consume" the packet.
sockaddr storage. */ In msgh the msg_name pointer must refer to an (empty) sockaddr storage.
Returns STAT_OK on success, or STAT_RETRYLATER when an error occurred,
including EINTR.
*/
int xiogetpacketsrc(int fd, struct msghdr *msgh, int flags) { int xiogetpacketsrc(int fd, struct msghdr *msgh, int flags) {
char peekbuff[1]; char peekbuff[1];
#if HAVE_STRUCT_IOVEC #if HAVE_STRUCT_IOVEC

View file

@ -119,6 +119,7 @@ ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) {
socklen_t fromlen = sizeof(from); socklen_t fromlen = sizeof(from);
char infobuff[256]; char infobuff[256];
char ctrlbuff[1024]; /* ancillary messages */ char ctrlbuff[1024]; /* ancillary messages */
int rc;
msgh.msg_name = &from; msgh.msg_name = &from;
msgh.msg_namelen = fromlen; msgh.msg_namelen = fromlen;
@ -128,14 +129,16 @@ ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) {
#if HAVE_STRUCT_MSGHDR_MSGCONTROLLEN #if HAVE_STRUCT_MSGHDR_MSGCONTROLLEN
msgh.msg_controllen = sizeof(ctrlbuff); msgh.msg_controllen = sizeof(ctrlbuff);
#endif #endif
if (xiogetpacketsrc(pipe->fd, &msgh,
while ((rc = xiogetpacketsrc(pipe->fd, &msgh,
MSG_PEEK MSG_PEEK
#ifdef MSG_TRUNC #ifdef MSG_TRUNC
|MSG_TRUNC |MSG_TRUNC
#endif #endif
) < 0) { )) < 0 &&
return -1; errno == EINTR) ;
} if (rc < 0) return -1;
do { do {
bytes = bytes =
Recvfrom(pipe->fd, buff, bufsiz, 0, &from.soa, &fromlen); Recvfrom(pipe->fd, buff, bufsiz, 0, &from.soa, &fromlen);
@ -319,6 +322,7 @@ ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) {
char infobuff[256]; char infobuff[256];
struct msghdr msgh = {0}; struct msghdr msgh = {0};
char ctrlbuff[1024]; /* ancillary messages */ char ctrlbuff[1024]; /* ancillary messages */
int rc;
socket_init(pipe->para.socket.la.soa.sa_family, &from); socket_init(pipe->para.socket.la.soa.sa_family, &from);
/* get source address */ /* get source address */
@ -330,14 +334,15 @@ ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) {
#if HAVE_STRUCT_MSGHDR_MSGCONTROLLEN #if HAVE_STRUCT_MSGHDR_MSGCONTROLLEN
msgh.msg_controllen = sizeof(ctrlbuff); msgh.msg_controllen = sizeof(ctrlbuff);
#endif #endif
if (xiogetpacketsrc(pipe->fd, &msgh, while ((rc = xiogetpacketsrc(pipe->fd, &msgh,
MSG_PEEK MSG_PEEK
#ifdef MSG_TRUNC #ifdef MSG_TRUNC
|MSG_TRUNC |MSG_TRUNC
#endif #endif
) < 0) { )) < 0 &&
return -1; errno == EINTR) ;
} if (rc < 0) return -1;
xiodopacketinfo(&msgh, true, false); xiodopacketinfo(&msgh, true, false);
if (xiocheckpeer(pipe, &from, &pipe->para.socket.la) < 0) { if (xiocheckpeer(pipe, &from, &pipe->para.socket.la) < 0) {
Recvfrom(pipe->fd, buff, bufsiz, 0, &from.soa, &fromlen); /* drop */ Recvfrom(pipe->fd, buff, bufsiz, 0, &from.soa, &fromlen); /* drop */