From ab2b17dfc50d57badc3a73888d42e1149d9fc385 Mon Sep 17 00:00:00 2001 From: Gerhard Rieger Date: Fri, 8 Dec 2023 16:19:15 +0100 Subject: [PATCH] Fixed loop of RECVFROM with fork when second address failed --- CHANGES | 5 ++++ test.sh | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ xioclose.c | 10 +++++++ 3 files changed, 97 insertions(+) diff --git a/CHANGES b/CHANGES index 4f2bd96..a440de8 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,11 @@ Corrections: In some situations xioclose() was called nested what could cause hanging of OpenSSL in pthread_rwlock_wrlock() + socat 1.8.0.0 with addresses of type RECVFROM and option fork, where + the second address failed to connect/open in the child process, entered + a fork loop that was only stopped by FD exhaustion caused by FD leak. + Test: RECVFROM_FORK_LOOP + Features: Total inactivity timeout option -T 0 now means 0.0 seconds; up to version 1.8.0.0 it meant no total inactivity timeout. diff --git a/test.sh b/test.sh index 082015b..14c9945 100755 --- a/test.sh +++ b/test.sh @@ -19679,6 +19679,88 @@ esac N=$((N+1)) +# Socat 1.8.0.0 with addresses of type RECVFROM and option fork entered a +# loop that was only stopped by FD exhaustion cause by FD leak, when the +# second address failed to connect/open in the child process +NAME=RECVFROM_FORK_LOOP +case "$TESTS" in +*%$N%*|*%functions%*|*%bugs%*|*%ip4%*|*%udp%*|*%udp4%*|*%fork%*|*%socket%*|*%$NAME%*) +TEST="$NAME: Bug on RECVFROM with fork and child failure" +# Start a Socat process that uses UDP4-RECFROM with fork options, and in the +# second address opens a file in a non existent directory. +# Send a UDP4-packet to the receiver. +# When only one child process is forked off, thus when only one appropriate +# error message is in the log file, the test succeeded. +if ! eval $NUMCOND; then : +elif ! cond=$(checkconds \ + "" \ + "" \ + "" \ + "IP4 UDP STDIO FILE" \ + "UDP4-RECVFROM OPEN STDIO UDP4-SEND" \ + "fork" \ + "udp4" ); then + $PRINTF "test $F_n $TEST... ${YELLOW}$cond${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" + namesCANT="$namesCANT $NAME" +else + tf="$td/test$N.stdout" + te="$td/test$N.stderr" + tdiff="$td/test$N.diff" + da="test$N $(date) $RANDOM" + newport udp4 + CMD0="$TRACE $SOCAT $opts UDP4-RECVFROM:$PORT,fork OPEN:$td/nonexistent/file" + CMD1="$TRACE $SOCAT $opts - UDP4-SENDTO:$LOCALHOST4:$PORT" + printf "test $F_n $TEST... " $N + $CMD0 >/dev/null 2>"${te}0" & + pid0=$! + waitudp4port $PORT 1 + echo "$da" |$CMD1 >"${tf}1" 2>"${te}1" + rc1=$? + kill $pid0 2>/dev/null; wait + if [ "$rc1" -ne 0 ]; then + $PRINTF "$CANT (rc1=$rc1)\n" + echo "$CMD0 &" + cat "${te}0" >&2 + echo "echo \$da\" |$CMD1" + cat "${te}1" >&2 + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" + namesCANT="$namesCANT $NAME" + elif [ $(grep -c " E open(" "${te}0") -eq 0 ]; then + $PRINTF "$CANT (no error)\n" + echo "$CMD0 &" + cat "${te}0" >&2 + echo "echo \$da\" |$CMD1" + cat "${te}1" >&2 + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" + namesCANT="$namesCANT $NAME" + elif [ $(grep -c " E open(" "${te}0") -ge 2 ]; then + $PRINTF "$FAILED (this bug)\n" + echo "$CMD0 &" + head -n 2 "${te}0" >&2 + echo "echo \$da\" |$CMD1" + cat "${te}1" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" + namesFAIL="$namesFAIL $NAME" + else + $PRINTF "$OK\n" + if [ "$VERBOSE" ]; then echo "$CMD0 &"; fi + if [ "$DEBUG" ]; then cat "${te}0" >&2; fi + if [ "$VERBOSE" ]; then echo "$CMD1"; fi + if [ "$DEBUG" ]; then cat "${te}1" >&2; fi + numOK=$((numOK+1)) + listOK="$listOK $N" + fi +fi # NUMCOND + ;; +esac +N=$((N+1)) + + # end of common tests ################################################################################## diff --git a/xioclose.c b/xioclose.c index 12ca7f1..5b9258c 100644 --- a/xioclose.c +++ b/xioclose.c @@ -28,6 +28,16 @@ int xioclose1(struct single *pipe) { } pipe->tag |= XIO_TAG_CLOSED; + if (pipe->dtype & XIOREAD_RECV_ONESHOT) { + if (pipe->triggerfd >= 0) { + char r[1]; + Info("consuming packet to prevent loop in parent"); + Read(pipe->fd, r, sizeof(r)); + Close(pipe->triggerfd); + pipe->triggerfd = -1; + } + } + #if WITH_READLINE if ((pipe->dtype & XIODATA_MASK) == XIODATA_READLINE) { Write_history(pipe->para.readline.history_file);